runpodctl is the cli tool to manage gpu pods, serverless endpoints, and more on runpod.io.
note: all pods automatically come with runpodctl installed with a pod-scoped api key.
wget -qO- cli.runpod.net | sudo bashbrew install runpod/runpodctl/runpodctlwget https://github.com/runpod/runpodctl/releases/latest/download/runpodctl-windows-amd64.exe -O runpodctl.exerunpodctl is available on conda-forge.
# conda
conda install conda-forge::runpodctl
# mamba
mamba install conda-forge::runpodctl
# pixi (user-global install)
pixi global install runpodctl# configure api key
runpodctl config --apiKey=your_api_key
# list all pods
runpodctl pod list
# get a specific pod
runpodctl pod get pod_id
# create a pod
runpodctl pod create --image=runpod/pytorch:2.8.0-py3.11-cuda12.8.1-cudnn-devel-ubuntu22.04 --gpu-id=NVIDIA_A100
# start/stop/delete a pod
runpodctl pod start pod_id
runpodctl pod stop pod_id
runpodctl pod delete pod_idcommands follow noun-verb pattern: runpodctl <resource> <action>
runpodctl pod list # list all pods
runpodctl pod get <id> # get pod details
runpodctl pod create --image=<img> # create a pod
runpodctl pod update <id> # update a pod
runpodctl pod start <id> # start a stopped pod
runpodctl pod stop <id> # stop a running pod
runpodctl pod delete <id> # delete a podrunpodctl serverless list # list endpoints (alias: sls)
runpodctl serverless get <id> # get endpoint details
runpodctl serverless create # create endpoint
runpodctl serverless update <id> # update endpoint
runpodctl serverless delete <id> # delete endpointother resources: template (alias: tpl), volume (alias: vol), registry (alias: reg)
send and receive files without api key using croc:
# send a file
runpodctl send data.txt
# output: code is: 8338-galileo-collect-fidel
# receive on another computer
runpodctl receive 8338-galileo-collect-fideldefault output is json (optimized for agents). use --output flag for alternatives:
runpodctl pod list # json (default)
runpodctl pod list --output=table # human-readable table
runpodctl pod list --output=yaml # yaml formatdata goes to stdout; errors go to stderr as a single flat json object, and the
exit code is non-zero. branch on code, never on the message text:
| field | notes |
|---|---|
error |
human-readable message, unwrapped (never a nested json blob) |
code |
stable, lowercase. present on every error from the resource commands (see the caveat below) |
status |
http status, only when the failure came back from a rest call |
status is deliberately absent when the api answered 200 with an empty result
(graphql reports a missing resource that way), so code is the field to branch
on — if status == 404 misses every graphql not-found.
codes the cli generates:
| code | meaning |
|---|---|
usage_error |
your invocation was wrong (unknown command/flag, bad or missing args, missing required flags). usage text is printed after the json |
not_found |
the resource does not exist |
bad_request unauthorized forbidden conflict rate_limited server_error api_error |
derived from the rest status |
graphql_error |
graphql returned an errors array (http 200) |
no_credentials |
no api key configured — run runpodctl doctor or set RUNPOD_API_KEY |
network_error |
the api could not be reached at all — dns, refused, tls, timeout. the only code that means "transient, retry" |
cli_error |
anything else local: validation, config, bad input (including a malformed RUNPOD_API_URL) |
the api may also return its own code, which is passed through lowercased, so treat the list as the set the cli generates rather than an exhaustive one.
known gaps. the json error shape covers pod, serverless, template,
volume, registry, gpu, datacenter, billing, user, model, ssh,
send, receive, hub and update. these still print plaintext on stderr and
carry no code:
| surface | shape |
|---|---|
legacy get/create/remove/start/stop pod, create/remove pods, get cloud |
Error: <msg> via cobra, exit 1 |
exec |
plaintext, exit 1 |
project |
prints to stdout and exits 0 (bug, tracked as CON-816) |
so a parser should tolerate a non-json line on stderr from those, and must not
rely on the exit code for project until CON-816 lands.
| variable | default | what it sets |
|---|---|---|
RUNPOD_API_KEY |
— | api key. also settable via runpodctl doctor or ~/.runpod/config.toml |
RUNPOD_API_URL |
https://rest.runpod.io/v1 |
rest control plane (config key restApiUrl) |
RUNPOD_GRAPHQL_URL |
https://api.runpod.io/graphql |
graphql control plane (config key apiUrl) |
RUNPOD_INVOKE_URL |
https://api.runpod.ai/v2 |
base for the serverless invoke urls reported by serverless create/get/list/update (config key invokeUrl) |
invoke is a separate service from the control plane: pointing RUNPOD_API_URL
or RUNPOD_GRAPHQL_URL at a non-prod host does not move the invoke urls.
override RUNPOD_INVOKE_URL explicitly when you need that.
legacy commands are still supported but deprecated. please update your scripts:
get pod, create pod, remove pod, start pod, stop pod
releases are fully automated by goreleaser via the release github action (.github/workflows/release.yml). to cut a release:
-
make sure
mainis green and holds everything you want to ship. -
create and push a
v*semver tag on the commit to release:git tag v2.7.0 git push origin v2.7.0
-
the tag push triggers the
releaseworkflow, which runsgoreleaser release --cleanand:- builds binaries for darwin/linux/windows (amd64/arm64), incl. raw
runpodctl-{os}-{arch}binaries for the legacy self-update command and a upx-compressed linux/amd64 build. - creates the github release with archives + checksums (prereleases are auto-detected from the tag).
- opens pull requests on runpod/homebrew-runpodctl for the homebrew formula and cask.
- builds binaries for darwin/linux/windows (amd64/arm64), incl. raw
-
merge the homebrew pull requests. the tap lives in a separate repo, so the release is not complete for
brew installusers until those prs are merged. (they cannot be combined into this repo.)
notes:
- the workflow can also be run manually via
workflow_dispatch(re-runs goreleaser against the current ref). - the homebrew prs are authored by a github app; tap auth uses the
RELEASE_APP_ID/RELEASE_APP_PRIVATE_KEYsecrets, and goreleaser pushes via the generatedHOMEBREW_TAP_TOKEN. - conda-forge is updated separately by the conda-forge feedstock bot, not by this workflow.
{"error":"failed to get endpoint: endpoint not found","code":"not_found","status":404}