JEduTools Docs

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:readGPU 서버·실행 환경·플랫폼 통계 조회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 두 값의 조합으로 위치를 지정합니다. 조합은 네 가지뿐입니다.

scopefolder화면 이름용도
shareddata연구실 공유 / data연구실 공유 데이터(작업 입력용)
sharedresult연구실 공유 / result연구실 공유 결과(results/작업 ID 폴더)
privatedata내 폴더 / data나만 보는 데이터
privateresult내 폴더 / result나만 보는 결과(작업 ID 폴더)

표 밖의 조합은 400 INVALID_LOCATION입니다. 파일 키(key, prefix)는 항상 이 위치 기준의 상대 경로입니다.

메서드와 경로주요 입력응답
GET /v1/storage없음quota_gb, used_gb, 자리별 사용량
GET /v1/storage/objects필수 scope·folder, 선택 prefix, limit, cursoritems, 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.json
UPLOAD_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.json
UPLOAD_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-1
for n in 1 2; do
url=$(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.json
JOB_ID=$(jq -r .job_id job.json)
  • description(작업 목적, 500자 이내)은 모든 GPU 서버에서 필수입니다. 비었거나 공백뿐이면 400 DESCRIPTION_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=stdout
curl --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_LOCATIONscope/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_REQUIRED
curl -sS 'https://share.jedutools.io/v1/jobs' \
-H "Authorization: Bearer $READONLY_API_KEY" \
-F 'command=python' \
-F 'tier=jpu-60'

키 관리 경로는 범위와 관계없이 키로 부를 수 없습니다.

# 키 목록은 브라우저 로그인 토큰 전용 → 403 JWT_REQUIRED
curl -sS 'https://share.jedutools.io/v1/api-keys' \
-H "Authorization: Bearer $JPUSHARE_API_KEY"

8. 주의할 것

  • LLM 기능은 현재 제공하지 않습니다. /v1/llm/* 경로는 503 LLM_DISABLED입니다.
  • 파괴적 동작(삭제·취소·순서 변경)은 사람의 확인을 받은 뒤 실행하세요.
  • /docs·/redoc·/openapi.json은 API 문서 공개가 설정된 환경에서만 열립니다. 이 주소의 404만으로 서비스 장애라고 판단하지 마세요.
  • 만료를 미리 확인하세요. 키는 최대 90일까지 유효하며 만료되면 401이 반환됩니다. 오래 도는 자동화는 만료 전 재발급 절차를 운영자와 맞춰 두세요.

결과 파일이 보이지 않거나 다운로드가 안 되면 문제 해결을 참고하세요.

JPUShare 안내로 돌아가기