Client SDK
English
The half of the SDK that runs on your own machine. The other half is Worker SDK.
Read out of the installed source (runpod 1.11.0) and exercised against a live endpoint. Outputs shown are real.
Authentication
import runpod
runpod.api_key = os.environ["RUNPOD_API_KEY"]
Calling an endpoint
ep = runpod.Endpoint("ku3jultxavac2v")
ep.run_sync({"name": "SDK"}, timeout=180)
# {'greeting': 'Hello, SDK!', 'worker_id': 'rrlm8trcnfn5of'}
- Input is auto-wrapped.
{"name": "SDK"}becomes{"input": {"name": "SDK"}}. Passing{"input": {...}}yourself also works. run_syncfalls back to polling./runsynccan returnIN_PROGRESSrather than a finished job; the SDK notices and waits. Over raw HTTP you write that loop yourself.
run_sync’s default timeout is 86,400 seconds — a full day. A wedged job blocks your script until tomorrow. Pass a realistic value.
Async form returns a Job:
job = ep.run({"name": "async"})
job.status() # 'IN_QUEUE'
job.output(timeout=180) # {'greeting': 'Hello, async!', ...}
job.status() # 'COMPLETED'
| Method | Behaviour |
|---|---|
job.status() |
Current state, cached once final |
job.output(timeout=0) |
0 returns what exists now; above 0 polls every second, raises TimeoutError |
job.stream() |
Yields partials from a generator handler |
job.cancel() |
Cancels the job |
Seeing what the endpoint is doing
ep.health()
{'jobs': {'completed': 7, 'failed': 5, 'inProgress': 0, 'inQueue': 0, 'retried': 5},
'workers': {'idle': 2, 'initializing': 0, 'ready': 2, 'running': 0,
'throttled': 0, 'unhealthy': 0}}
That failed: 5, retried: 5 is a real record of the {"input": {}} requests that hung during these labs — the platform retried and failed them, which the HTTP call itself never reported. purge_queue() drops anything still queued.
Job states
FINAL_STATES = ["COMPLETED", "FAILED", "TIMED_OUT"]
is_completed(s) -> s in ["COMPLETED", "FAILED", "TIMED_OUT", "CANCELLED"]
The two disagree on CANCELLED. run_sync checks the list without it; job.output() polls on the function that has it. If cancellation is possible, prefer output().
Managing infrastructure
| Area | Functions |
|---|---|
| Pods | create_pod, get_pods, get_pod, stop_pod, resume_pod, terminate_pod |
| Endpoints | create_endpoint, get_endpoints, update_endpoint_template |
| Templates | create_template |
| Hardware | get_gpus, get_gpu |
| Account | get_user, update_user_settings |
| Registries | create_container_registry_auth, update_…, delete_… |
get_pods() is the habit worth keeping — it lists everything currently costing money.
The SDK reports which AI agent is driving it
Every client call carries a User-Agent, and the SDK inspects the environment to see whether a coding agent is at the keyboard. Run from this repository, under Claude Code:
>>> from runpod import agent, user_agent
>>> agent.detect()
'claude-code'
>>> user_agent.USER_AGENT
'RunPod-Python-SDK/1.11.0 (Darwin 25.6.0; arm64) Language/Python 3.11.14 (via claude-code)'
CLAUDECODE=1 was the trigger. The registry covers 21 harnesses — Claude Code, Codex, Cursor, Gemini CLI, Copilot, Cline, Zed, Replit and others — and mirrors Hugging Face’s public agent-harnesses list so identifiers match across tools. Any tool can self-identify with AI_AGENT; the value is sanitised and capped at 64 characters so it cannot forge a header. A bare AGENT is deliberately ignored — too common in CI to mean anything.
Client-side only. The worker’s own HTTP path does not use it, so traffic from inside a running worker is untagged. It is the calls from your machine that get attributed.
The bundled runpod CLI
Installing the SDK also installs a runpod command. This is not runpodctl, which is a separate Go binary from a Homebrew tap.
runpod config runpod pod runpod exec runpod ssh runpod project
runpod project is the least advertised and most interesting: new scaffolds a worker (default and llama2 templates), start brings up a dev Pod from runpod.toml, deploy ships it. A hot-reload loop against real GPUs — a different workflow from the image-build cycle these labs use.
한국어
SDK 중 내 컴퓨터에서 도는 절반입니다. 나머지 절반은 Worker SDK 에 있습니다.
설치된 소스(runpod 1.11.0)를 읽고 실제 엔드포인트에 실행해 확인했습니다. 아래 출력은 실제 결과입니다.
인증
import runpod
runpod.api_key = os.environ["RUNPOD_API_KEY"]
엔드포인트 호출
ep = runpod.Endpoint("ku3jultxavac2v")
ep.run_sync({"name": "SDK"}, timeout=180)
# {'greeting': 'Hello, SDK!', 'worker_id': 'rrlm8trcnfn5of'}
- 입력이 자동으로 감싸집니다.
{"name": "SDK"}가{"input": {"name": "SDK"}}로 바뀝니다. 직접{"input": {...}}를 넘겨도 동작합니다. run_sync가 폴링으로 전환합니다./runsync는 완료된 작업 대신IN_PROGRESS를 반환할 수 있는데, SDK 가 이를 감지해 기다립니다. 순수 HTTP 로는 이 루프를 직접 짜야 합니다.
run_sync 의 기본 타임아웃은 86,400초, 하루입니다. 멈춘 작업 하나가 스크립트를 내일까지 붙잡습니다. 현실적인 값을 넘기세요.
비동기 형태는 Job 을 반환합니다.
job = ep.run({"name": "async"})
job.status() # 'IN_QUEUE'
job.output(timeout=180) # {'greeting': 'Hello, async!', ...}
job.status() # 'COMPLETED'
| 메서드 | 동작 |
|---|---|
job.status() |
현재 상태. 최종 상태가 되면 캐시 |
job.output(timeout=0) |
0 이면 현재 값 즉시 반환, 초과면 1초 간격 폴링 후 TimeoutError |
job.stream() |
제너레이터 핸들러의 조각을 순차 반환 |
job.cancel() |
작업 취소 |
엔드포인트 상태 확인
ep.health()
{'jobs': {'completed': 7, 'failed': 5, 'inProgress': 0, 'inQueue': 0, 'retried': 5},
'workers': {'idle': 2, 'initializing': 0, 'ready': 2, 'running': 0,
'throttled': 0, 'unhealthy': 0}}
여기 failed: 5, retried: 5 는 이 실습 중 멈췄던 {"input": {}} 요청들의 실제 기록입니다. 플랫폼이 재시도하고 실패시켰지만 HTTP 호출 자체로는 알 수 없던 사실입니다. purge_queue() 는 대기 중인 작업을 비웁니다.
작업 상태
FINAL_STATES = ["COMPLETED", "FAILED", "TIMED_OUT"]
is_completed(s) -> s in ["COMPLETED", "FAILED", "TIMED_OUT", "CANCELLED"]
둘이 CANCELLED 취급을 다르게 합니다. run_sync 는 CANCELLED 가 빠진 목록을 확인하고, job.output() 은 포함된 함수로 폴링합니다. 취소 가능성이 있으면 output() 을 쓰세요.
인프라 관리
| 영역 | 함수 |
|---|---|
| Pod | create_pod, get_pods, get_pod, stop_pod, resume_pod, terminate_pod |
| 엔드포인트 | create_endpoint, get_endpoints, update_endpoint_template |
| 템플릿 | create_template |
| 하드웨어 | get_gpus, get_gpu |
| 계정 | get_user, update_user_settings |
| 레지스트리 | create_container_registry_auth, update_…, delete_… |
get_pods() 는 습관으로 삼을 만합니다. 지금 비용이 나가고 있는 모든 것을 나열해 줍니다.
SDK 는 자기를 구동하는 AI 에이전트를 보고합니다
모든 클라이언트 호출에는 User-Agent 가 실리고, SDK 가 환경을 검사해 코딩 에이전트가 키보드를 잡고 있는지 확인합니다. 이 저장소에서 Claude Code 로 실행한 결과입니다.
>>> from runpod import agent, user_agent
>>> agent.detect()
'claude-code'
>>> user_agent.USER_AGENT
'RunPod-Python-SDK/1.11.0 (Darwin 25.6.0; arm64) Language/Python 3.11.14 (via claude-code)'
트리거는 CLAUDECODE=1 이었습니다. 레지스트리는 21개 하니스를 다루며 — Claude Code, Codex, Cursor, Gemini CLI, Copilot, Cline, Zed, Replit 등 — 도구 간 식별자를 맞추기 위해 Hugging Face 의 공개 agent-harnesses 목록을 따릅니다. 어떤 도구든 AI_AGENT 로 스스로를 식별할 수 있고, 값은 정제 후 64자로 잘려 헤더를 위조할 수 없습니다. 밋밋한 AGENT 는 일부러 무시합니다. CI 에서 너무 흔해 의미가 없기 때문입니다.
클라이언트 쪽에만 적용됩니다. 워커 자체의 HTTP 경로는 이를 쓰지 않으므로 실행 중인 워커 내부 트래픽에는 태그가 붙지 않습니다. 집계되는 것은 내 컴퓨터에서 하는 호출입니다.
함께 설치되는 runpod CLI
SDK 를 설치하면 runpod 명령도 함께 설치됩니다. runpodctl 과 다릅니다 — 그쪽은 Homebrew tap 으로 설치하는 별도의 Go 바이너리입니다.
runpod config runpod pod runpod exec runpod ssh runpod project
runpod project 가 가장 덜 알려져 있으면서 흥미롭습니다. new 는 워커를 스캐폴딩하고(default, llama2 템플릿), start 는 runpod.toml 기반 개발용 Pod 를 띄우고, deploy 는 배포합니다. 실제 GPU 를 상대로 하는 핫 리로드 루프이며, 이 실습들의 이미지 빌드 주기와는 다른 워크플로입니다.