API 사용법
터미널, 스크립트, CI, 자동화 에이전트에서 JPUShare를 쓸 때는 사용자 API 키(jpk_로 시작)로 API를 호출합니다. 사람이 직접 부르든 에이전트가 부르든 키와 호출 방법은 같습니다.
API 기본 주소는 https://share.jedutools.io입니다. 먼저 시작하기에서 로그인과 연구실 소속을 확인합니다.
이 문서의 예제는 운영 사이트에서 실제로 실행해 확인한 것입니다(2026년 9월 27일).
1. 인증 방식 구분하기
| 구분 | 인증 값 | 쓰는 곳 |
|---|---|---|
| 로그인 토큰(JWT) | 브라우저 로그인 때 로그인 서버가 발급 | 웹 화면. 키 관리·관리자·연구실 가입 경로는 이 토큰만 받습니다. |
| 사용자 API 키 | jpk_로 시작하는 키. 내 정보/설정 → API 키 발급에서 직접 발급 | 터미널·스크립트·에이전트. 허용 범위 안의 경로만 호출합니다. |
요청 헤더는 둘 다 Authorization: Bearer 인증값입니다. 브라우저 로그인만으로 터미널에 토큰이 설정되지는 않으며, 비밀번호로 토큰을 받는 CLI 절차도 없습니다. 터미널에서는 API 키를 씁니다.
API 키는 발급한 사용자 본인의 자격 증명입니다. 키를 가진 프로그램은 그 사용자로 인증되며, 키의 허용 범위(scope)는 그 사용자의 권한을 좁힐 뿐 넓히지 않습니다. 키는 비밀번호처럼 보관하고, 소스 코드·프롬프트·로그·공유 문서·오류 문의에 원문을 넣지 마세요.
2. API 키 발급과 허용 범위
키 발급 절차는 에이전트 연결하기 1절을 참고하세요. 키 발급·목록·폐기(/v1/api-keys)는 브라우저에 로그인한 상태에서만 할 수 있습니다.
활성 키는 사용자당 최대 5개이고, 유효 기간은 1~90일(기본 30일)입니다. 키를 잃어버렸거나 노출했다면 내 정보/설정에서 폐기하고 새로 발급합니다. 권한은 요청마다 다시 평가되므로 폐기·만료·계정 비활성화·연구실 역할 변경은 다음 요청부터 바로 적용됩니다.
허용 범위와 호출할 수 있는 경로
범위는 할 일에 필요한 것만 고릅니다. 범위는 발급 후 바꿀 수 없으므로 부족하면 새로 발급합니다.
아래 표의 경로만 API 키로 호출할 수 있습니다. 표에 없는 경로(관리자·토론·키 관리·연구실 가입 관련 등)를 키로 부르면 403 JWT_REQUIRED가 반환됩니다.
| 범위 | 할 수 있는 일 | 경로 |
|---|---|---|
account:read | 내 계정·소속 정보 조회 | GET /v1/me, GET /v1/auth/me |
platform:read | GPU 서버·실행 환경·플랫폼 통계 조회 | GET /v1/tiers, GET /v1/workers, GET /v1/workers/{tier}/gpu/history, GET /v1/workspace-profiles, GET /v1/platform/stats, GET /v1/platform/queue-snapshot |
jobs:read | 내 작업 목록·상세·로그·GPU 사용량·결과 조회 | GET /v1/jobs, GET /v1/jobs/{id}, GET /v1/jobs/{id}/gpu, GET /v1/jobs/{id}/gpu/history, GET /v1/jobs/{id}/log, GET /v1/jobs/{id}/results, GET /v1/jobs/{id}/results-zip |
jobs:submit | 작업 제출 | POST /v1/jobs |
jobs:cancel | 내 작업 취소 | POST /v1/jobs/{id}/cancel |
queue:read | 내 대기열 조회 | GET /v1/queue |
queue:reorder | 내 대기열 순서 변경 | PUT /v1/queue/reorder |
storage:read | 파일 목록 조회·다운로드 주소 발급 | GET /v1/storage, GET /v1/storage/objects, GET /v1/storage/objects/{key}/url |
storage:write | 파일 업로드·폴더 생성·복사 | POST /v1/storage/upload-url, POST /v1/storage/folders, POST /v1/storage/copy, POST /v1/storage/multipart/init·part·complete·abort |
storage:organize | 파일·폴더 이동·이름 변경 | POST /v1/storage/move, POST /v1/storage/rename 등 |
storage:delete | 파일·폴더 삭제 | DELETE /v1/storage/objects/{key}, POST /v1/storage/folders/delete 등 |
lab:read | 소속 연구실·구성원·연구실 작업·사용량 조회 | GET /v1/labs/me, GET /v1/labs/me/members, GET /v1/labs/me/jobs, GET /v1/labs/me/usage |
인증 없이 호출할 수 있는 공개 경로는 GET /v1/auth/config와 GET /v1/maintenance/status뿐입니다.
3. 키로 호출하기
발급받은 키를 환경 변수에 넣고 모든 요청에 Bearer로 붙입니다. 이 문서의 예제는 Bash 기준이며, JPUSHARE_API_KEY와 JOB_ID는 본인의 값으로 설정한 환경 변수라고 가정합니다.
export JPUSHARE_API_KEY='jpk_...'
curl --fail-with-body 'https://share.jedutools.io/v1/me' \-H "Authorization: Bearer $JPUSHARE_API_KEY"
GET /v1/me로 키가 유효한지 확인합니다. 응답의 username과 lab으로 키가 누구의 것인지 알 수 있습니다. actions는 이 키로 할 수 있는 동작이며, 계정 권한과 키 범위의 교집합입니다.
4. 저장 공간 위치 지정 규칙
저장 공간 API는 실제 저장소 이름 대신 scope와 folder 두 값의 조합으로 위치를 지정합니다. 조합은 네 가지뿐입니다.
scope | folder | 화면 이름 | 용도 |
|---|---|---|---|
shared | data | 연구실 공유 / data | 연구실 공유 데이터(작업 입력용) |
shared | result | 연구실 공유 / result | 연구실 공유 결과(results/작업 ID 폴더) |
private | data | 내 폴더 / data | 나만 보는 데이터 |
private | result | 내 폴더 / result | 나만 보는 결과(작업 ID 폴더) |
표 밖의 조합은 400 INVALID_LOCATION입니다. 파일 키(key, prefix)는 항상 이 위치 기준의 상대 경로입니다.
| 메서드와 경로 | 주요 입력 | 응답 |
|---|---|---|
GET /v1/storage | 없음 | quota_gb, used_gb, 자리별 사용량 |
GET /v1/storage/objects | 필수 scope·folder, 선택 prefix, limit, cursor | items, next_cursor |
# 연구실 공유 / data 아래 train/ 폴더의 파일 목록curl --fail-with-body --get 'https://share.jedutools.io/v1/storage/objects' \-H "Authorization: Bearer $JPUSHARE_API_KEY" \--data-urlencode 'scope=shared' \--data-urlencode 'folder=data' \--data-urlencode 'prefix=train/'
items의 각 항목은 key, size, last_modified, is_prefix를 가집니다. is_prefix=true이면 폴더 항목입니다. 객체 size는 바이트, 저장 공간 용량의 _gb 필드는 GiB(1024³바이트) 기준입니다.
5. 파일 업로드와 다운로드
화면에서 올리는 방법은 데이터 관리를 참고하세요.
작은 파일 — 단일 PUT (storage:write)
업로드는 두 단계입니다. 먼저 업로드용 임시 주소를 발급받고, 그 주소로 파일 본문을 PUT 합니다. content_length는 올릴 파일의 정확한 바이트 수입니다(예: stat -c %s sample.csv).
# 1) 업로드 주소 발급 — 응답을 upload.json 에 저장curl --fail-with-body 'https://share.jedutools.io/v1/storage/upload-url' \-H "Authorization: Bearer $JPUSHARE_API_KEY" \-H 'Content-Type: application/json' \-d '{"scope":"shared","folder":"data","key":"train/sample.csv","content_length":1234}' \-o upload.jsonUPLOAD_URL=$(jq -r .url upload.json)UPLOAD_TAGGING=$(jq -r '.headers["x-amz-tagging"]' upload.json)
응답은 url, headers, expires_in입니다. headers의 모든 항목을 PUT 요청에 그대로 붙여야 합니다 — 지금은 x-amz-tagging 하나이며, 파일의 소유자 표시가 여기에 실립니다.
# 2) 발급받은 주소로 PUT — headers 의 항목을 빠짐없이 -H 로curl --fail-with-body -X PUT "$UPLOAD_URL" \-H "x-amz-tagging: $UPLOAD_TAGGING" \--data-binary @sample.csv
headers를 빠뜨리면 서명이 맞지 않아 저장소가 403으로 거절합니다. XML 본문의 <Code>는 보통 SignatureDoesNotMatch이고, 요청을 받은 저장소 노드에 따라 AccessDenied로 나오기도 합니다. 이 403은 JPUShare API가 아니라 저장소의 응답이므로 code 봉투가 없습니다.
# 잘못된 예 — x-amz-tagging 을 빠뜨린 PUT 은 403 (SignatureDoesNotMatch 또는 AccessDenied)curl -sS -X PUT "$UPLOAD_URL" --data-binary @sample.csv
content_length가 단일 PUT 상한(256 MiB)을 넘으면 400 MUST_USE_MULTIPART가 반환됩니다. 그때는 아래 멀티파트 업로드를 씁니다.
큰 파일 — 멀티파트 업로드 (storage:write)
멀티파트는 개시(init) → 파트 주소 발급(part) → 파트 PUT → 완료(complete) 순서입니다. 파트 크기는 개시 응답의 part_size(256 MiB)이며, 전체 크기가 그 이하면 개시가 400 USE_SINGLE_PUT으로 거절됩니다.
| 경로 | 요청 본문 | 응답 |
|---|---|---|
POST /v1/storage/multipart/init | {"scope","folder","key","content_length"} — 전체 바이트 수 | {"upload_id","part_size"} |
POST /v1/storage/multipart/part | {"upload_id","part_numbers":[1,2,…]} — 1~10000 | {"parts":[{"part_number","url"}]} |
POST /v1/storage/multipart/complete | {"upload_id","parts":[{"part_number","etag"}]} | {"etag"} |
POST /v1/storage/multipart/abort | {"upload_id"} | 빈 200 |
# 1) 개시 — 300 MiB 파일(314572800 바이트)curl --fail-with-body 'https://share.jedutools.io/v1/storage/multipart/init' \-H "Authorization: Bearer $JPUSHARE_API_KEY" \-H 'Content-Type: application/json' \-d '{"scope":"shared","folder":"data","key":"uploads/big.bin","content_length":314572800}' \-o init.jsonUPLOAD_ID=$(jq -r .upload_id init.json)# 2) 파트 주소 발급 — ceil(314572800 / part_size) = 2 파트curl --fail-with-body 'https://share.jedutools.io/v1/storage/multipart/part' \-H "Authorization: Bearer $JPUSHARE_API_KEY" \-H 'Content-Type: application/json' \-d '{"upload_id":"'"$UPLOAD_ID"'","part_numbers":[1,2]}' \-o parts.json# 3) (아래 블록) 파트마다 PUT 하고 ETag 를 모아 complete.json 을 만든 뒤 완료curl --fail-with-body 'https://share.jedutools.io/v1/storage/multipart/complete' \-H "Authorization: Bearer $JPUSHARE_API_KEY" \-H 'Content-Type: application/json' \-d @complete.json
파트 PUT에는 추가 헤더가 필요 없습니다. 응답 헤더의 ETag 값을 따옴표까지 그대로 모읍니다.
split -b 256M -d -a 1 big.bin part- # part-0, part-1for n in 1 2; dourl=$(jq -r ".parts[] | select(.part_number==$n) | .url" parts.json)etag=$(curl -sS -X PUT "$url" --data-binary @part-$((n-1)) -D - -o /dev/null \| tr -d '\r' | awk 'tolower($1)=="etag:" {print $2}')echo "{\"part_number\":$n,\"etag\":$(jq -Rn --arg e "$etag" '$e')}"done | jq -s --arg id "$UPLOAD_ID" '{upload_id:$id, parts:.}' > complete.json
중간에 그만두면 POST /v1/storage/multipart/abort에 {"upload_id": "..."}를 보내 올린 파트를 정리합니다. 완료·중단했거나 정리되어 없어진 upload_id는 404 UPLOAD_NOT_FOUND이며, 그때는 개시부터 다시 올립니다.
다운로드 (storage:read)
curl --fail-with-body --get "https://share.jedutools.io/v1/storage/objects/train/sample.csv/url" \-H "Authorization: Bearer $JPUSHARE_API_KEY" \--data-urlencode 'scope=shared' \--data-urlencode 'folder=data'
응답의 url은 만료되는 임시 다운로드 주소입니다. expires_at이 지나면 다시 발급받으세요.
6. 작업 제출·확인·취소
화면에서 제출하는 방법은 학습 작업 실행을 참고하세요.
실행 환경 고르기 (platform:read)
작업에는 실행 환경의 스냅샷 ID(workspace_snapshot_id)를 지정합니다. 값은 실행 환경 목록의 current_snapshot_id입니다.
curl --fail-with-body 'https://share.jedutools.io/v1/workspace-profiles' \-H "Authorization: Bearer $JPUSHARE_API_KEY"
각 항목은 id, name, current_snapshot_id, supported_compute_caps(이미지가 지원하는 GPU 세대, 예: ["8.0","8.6","9.0"])를 가집니다. GPU 서버의 세대는 GET /v1/tiers 항목의 compute_capability(예: "8.6")입니다.
호환 규칙: supported_compute_caps 중 점 앞 숫자가 GPU와 같고 점 뒤 숫자가 GPU 이하인 값이 하나라도 있으면 호환입니다. 둘 중 하나라도 null이면 서버가 판정하지 않고 받아 줍니다. 호환되지 않는 조합으로 제출하면 400 VALIDATION_FAILED의 errors[].code가 WORKSPACE_GPU_INCOMPATIBLE입니다.
제출 (jobs:submit)
POST /v1/jobs는 multipart/form-data입니다. 코드 원본은 ZIP 파일(artifact) 또는 Git 주소(git_url) 중 하나만 지정합니다. 아래 예제의 code.zip은 학습 작업 실행의 main.py 예제를 압축한 것입니다.
curl --fail-with-body 'https://share.jedutools.io/v1/jobs' \-H "Authorization: Bearer $JPUSHARE_API_KEY" \-F 'artifact=@code.zip' \-F 'command=python' \-F 'args=["main.py"]' \-F 'name=API 제출 예제' \-F 'work_type=training' \-F 'description=API 사용법 예제' \-F 'tier=jpu-60' \-F "workspace_snapshot_id=$SNAPSHOT_ID" \-F 'timeout_seconds=3600' \-F 'result_location=private' \-F 'channels=[{"name":"train","scope":"shared","folder":"data","prefix":"train/","size_gb":1}]' \-o job.jsonJOB_ID=$(jq -r .job_id job.json)
description(작업 목적, 500자 이내)은 모든 GPU 서버에서 필수입니다. 비었거나 공백뿐이면 400DESCRIPTION_REQUIRED가 반환됩니다.args와channels는 JSON 문자열로 전달합니다.channels의 각 항목은name(코드에서JOB_CHANNEL_<이름>환경 변수로 접근),scope,folder,prefix,size_gb를 가집니다.result_location은 결과를 둘 자리로shared(연구실 공유) 또는private(내 폴더)입니다. 생략하면 입력 채널을 따라갑니다 —private채널이 하나라도 있으면private, 아니면shared.tier는GET /v1/tiers로 현재 선택 가능한 값을 확인합니다. GPU 수는 GPU 서버(tier)가 정하며num_gpus는 무시됩니다.timeout_seconds는 60~259200초(최대 72시간)입니다. 화면은 시간 단위 1~72로만 받습니다.- 응답은
job_id,state,priority,tier,queue_position,warnings를 포함합니다. - 연구실 소속이 없으면 본문 검사 전에 403
NO_LAB이 반환됩니다.
내 작업 목록 (jobs:read)
curl --fail-with-body --get 'https://share.jedutools.io/v1/jobs' \-H "Authorization: Bearer $JPUSHARE_API_KEY" \--data-urlencode 'limit=20'
응답 예시의 ID와 내용은 설명용입니다.
{"items": [{"job_id": "00000000-0000-4000-8000-000000000001","state": "RUNNING","priority": "NORMAL","name": "학습 실험","work_type": "training","description": "작은 데이터로 학습 확인"}],"next_cursor": null}
GET /v1/jobs는 자신의 작업을 반환합니다. limit은 1~100이며 기본값은 20입니다. state=RUNNING처럼 상태 코드로 필터링할 수 있습니다. next_cursor가 문자열이면 다음 요청의 cursor에 그대로 전달하고, null이면 마지막 페이지입니다.
상태 확인과 종료 판정 (jobs:read)
curl --fail-with-body "https://share.jedutools.io/v1/jobs/$JOB_ID" \-H "Authorization: Bearer $JPUSHARE_API_KEY"
상세 응답에는 job_id, state, command, args, timeout_seconds, tier, attempts, events 등이 포함됩니다. 작업이 끝나기를 기다릴 때는 이 요청을 5~10초 간격으로 반복하며 state를 봅니다.
| 구분 | state 값 |
|---|---|
| 진행 중 | USER_QUEUE_WAIT, GLOBAL_QUEUE_WAIT, ESCALATION_WAIT(화면의 대기 중), ASSIGNED(배정됨), RUNNING, CANCEL_REQUESTED(취소 중) |
| 종료 상태 | SUCCEEDED, FAILED_RUNTIME, FAILED_ENV, FAILED_OOM_CONFIRMED, FAILED_OOM_SUSPECT, TIMED_OUT, CANCELLED, KILLED_BY_ADMIN, FAILED_SUBMISSION, UPLOAD_FAILED |
종료 상태 10개 중 하나가 되면 더 바뀌지 않습니다. SUCCEEDED 외의 종료 상태면 상세 응답의 last_error_code(예: RUNTIME_EXIT_NONZERO)와 last_error_message(화면의 실패 사유 한 줄과 같은 문장)를 보고, 로그의 stderr를 확인합니다. 두 필드는 성공한 작업에서 null입니다.
로그와 결과 (jobs:read)
# 오류 출력(stderr) — 표준 출력은 stream=stdoutcurl --fail-with-body "https://share.jedutools.io/v1/jobs/$JOB_ID/log?stream=stderr" \-H "Authorization: Bearer $JPUSHARE_API_KEY"
응답은 stream, state, finished, lines이며 lines는 문자열 배열입니다. 쿼리에는 stream 외에 선택 값 lines를 줄 수 있습니다. 실행 중인 작업을 따라가려면 follow=1을 붙여 SSE(text/event-stream)로 받습니다(한 연결 최대 15분). 아직 시작하지 않은 작업은 404 NO_ATTEMPT입니다.
# 결과 파일 목록 — 각 항목의 url 이 임시 다운로드 주소curl --fail-with-body "https://share.jedutools.io/v1/jobs/$JOB_ID/results" \-H "Authorization: Bearer $JPUSHARE_API_KEY"
- 응답의
objects각 항목은key,size,last_modified,url,expires_at입니다. 목록이 길면 응답의next_cursor를 다음 요청의cursor에 넣습니다.url은 만료되는 임시 주소이므로 만료되면 목록을 다시 받습니다. - 프로그램이 실행된 뒤 실패한 작업도
logs/stdout.log,logs/stderr.log,exit_code.txt가 남습니다. 준비 단계에서 실패한 작업(FAILED_ENV등)은 결과가 비어 있을 수 있으니last_error_message를 봅니다. - 전체를 한 파일로 받으려면
GET /v1/jobs/{id}/results-zip(완료된 작업, 2 GiB까지)을 씁니다. - 결과는 결과 위치와 관계없이 작업이 끝나고 30일이 지나면 정리됩니다.
취소 (jobs:cancel)
curl --fail-with-body -X POST "https://share.jedutools.io/v1/jobs/$JOB_ID/cancel" \-H "Authorization: Bearer $JPUSHARE_API_KEY"
7. 오류 코드와 재시도
일반 오류 응답은 code, error, message와 경우에 따라 request_id를 포함하고, 작업 제출 검증 오류는 status: "VALIDATION_FAILED"와 errors 배열(code, field, message, detail)을 사용합니다. HTTP 상태와 code를 함께 보고 분기하세요.
| 상태·코드 | 의미와 대응 |
|---|---|
| 401 | 키가 없거나 만료·폐기됨. Authorization: Bearer jpk_... 형식과 키 상태를 확인합니다. |
403 JWT_REQUIRED | 그 경로는 API 키로 호출할 수 없습니다(2절 표 밖). 브라우저 로그인 토큰이 필요합니다. |
403 API_KEY_SCOPE_REQUIRED | 키에 그 경로의 범위가 없습니다. 필요한 범위를 포함해 새 키를 발급받습니다. |
403 API_KEY_ROUTE_FORBIDDEN | 분류되지 않은 경로입니다. 호출 경로와 메서드를 확인합니다. |
| 403 (그 밖) | 해당 작업·저장 공간에 접근할 권한이 있는지 확인합니다. |
403 NO_LAB | 연구실 소속이 없어 작업 제출이 거절되었습니다. 계정의 연구실 참가 상태를 확인합니다. |
403 SignatureDoesNotMatch / AccessDenied (XML) | 업로드 주소로 PUT 할 때 발급 응답의 headers를 빠뜨렸습니다. |
400 INVALID_LOCATION | scope/folder 조합이 잘못되었습니다. 네 가지 조합만 허용됩니다. |
400 MUST_USE_MULTIPART / USE_SINGLE_PUT | 파일 크기에 맞지 않는 업로드 방식입니다. 256 MiB 초과는 멀티파트, 이하는 단일 PUT. |
400 VALIDATION_FAILED / WORKSPACE_GPU_INCOMPATIBLE | 실행 환경이 그 GPU 서버(tier)의 GPU 세대를 지원하지 않습니다. 호환 규칙(6절)에 맞는 환경이나 GPU 서버를 고릅니다. |
400 VALIDATION_FAILED / WORKER_ACCOUNT_MISSING | 그 GPU 서버에 실행 계정이 아직 없습니다(가입 직후 준비 일부 실패). 다른 GPU 서버로 내거나 잠시 뒤 재시도하고, 계속되면 담당자에게 알립니다. |
| 400·422 (그 밖) | 응답의 code, errors, detail을 보고 요청 값을 고칩니다. |
| 429 | 요청이 너무 잦습니다. 전역 제한은 평문 본문 + Retry-After 헤더이고(JSON으로 파싱하지 마세요), JOIN_RATE_LIMITED·LOG_STREAM_LIMIT은 JSON 봉투입니다. 표시된 시간만큼 쉬고 재시도합니다. |
503 SERVICE_MAINTENANCE | 점검 중입니다. GET /v1/maintenance/status(공개)로 상태를 확인하고 종료 후 재시도합니다. |
범위가 모자란 키로 부르면 이렇게 거절됩니다.
# account:read 만 가진 키로 제출 → 403 API_KEY_SCOPE_REQUIREDcurl -sS 'https://share.jedutools.io/v1/jobs' \-H "Authorization: Bearer $READONLY_API_KEY" \-F 'command=python' \-F 'tier=jpu-60'
키 관리 경로는 범위와 관계없이 키로 부를 수 없습니다.
# 키 목록은 브라우저 로그인 토큰 전용 → 403 JWT_REQUIREDcurl -sS 'https://share.jedutools.io/v1/api-keys' \-H "Authorization: Bearer $JPUSHARE_API_KEY"
8. 주의할 것
- LLM 기능은 현재 제공하지 않습니다.
/v1/llm/*경로는 503LLM_DISABLED입니다. - 파괴적 동작(삭제·취소·순서 변경)은 사람의 확인을 받은 뒤 실행하세요.
/docs·/redoc·/openapi.json은 API 문서 공개가 설정된 환경에서만 열립니다. 이 주소의 404만으로 서비스 장애라고 판단하지 마세요.- 만료를 미리 확인하세요. 키는 최대 90일까지 유효하며 만료되면 401이 반환됩니다. 오래 도는 자동화는 만료 전 재발급 절차를 운영자와 맞춰 두세요.
결과 파일이 보이지 않거나 다운로드가 안 되면 문제 해결을 참고하세요.