# How to call the Runtime REST API with curl Send `Authorization: Bearer $RUNTIME_API_KEY` to `https://api.withruntime.com/v1/...`. `POST /v1/sandboxes` with `{}` creates a sandbox. **On Runtime, every product sits behind one HTTPS API. curl can reach everything that the SDKs, the CLI and the MCP server can do.** Money is an integer number of microdollars. Every error has one shape, with a hint. A create that you retry with the same `Idempotency-Key` never makes two sandboxes. API version 0.2.0, checked 26 October 2026 ([API reference](/docs/api)). ## Get a key into a variable Make a key at [API keys](https://withruntime.com/account/keys). Or, from a terminal, run `npx withruntime keys create --name curl`. Keep the key in your secret store. Load it into the shell without typing it on the command line: ```bash no-run read -rs RUNTIME_API_KEY && export RUNTIME_API_KEY ``` Never add `-v` or `--trace` to a curl command that carries the key. Both print the `Authorization` header. ## Create, run, stop ```bash no-run # Create, and wait up to 60 seconds for it to be running. ID=$(curl -sS https://api.withruntime.com/v1/sandboxes \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: build-1842" \ -H "Prefer: wait=60" \ -d '{"timeoutSeconds": 600}' | jq -r .id) # Run a command. The answer is {exitCode, stdout, stderr, timedOut}. curl -sS "https://api.withruntime.com/v1/sandboxes/${ID}:exec" \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"command": "python3 -c \"print(sum([125, 250, 375]))\""}' # Stop it. curl -sS -X POST "https://api.withruntime.com/v1/sandboxes/${ID}:stop" \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" ``` Write `"${ID}:exec"` with braces. zsh, the default shell on macOS, reads `$ID:e` as a modifier, and sends `/xec`. ## Files and background processes ```bash no-run # Upload a file: the raw body becomes the file; parents are made. curl -sS -X PUT --data-binary @main.py \ "https://api.withruntime.com/v1/sandboxes/${ID}/files/content?path=/workspace/main.py" \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" \ -H "Content-Type: application/octet-stream" # Start a server in the background, then read its output. PID=$(curl -sS "https://api.withruntime.com/v1/sandboxes/${ID}/processes" \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"command": "python3 -m http.server 8000"}' | jq -r .id) curl -sS "https://api.withruntime.com/v1/sandboxes/${ID}/processes/${PID}/output?waitMs=5000" \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" # Download a file. curl -sS -o result.txt \ "https://api.withruntime.com/v1/sandboxes/${ID}/files/content?path=/workspace/result.txt" \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" # A whole folder, each way, as a tar archive (up to 1 MiB in one PUT). curl -sS -X PUT --data-binary @project.tar.gz \ "https://api.withruntime.com/v1/sandboxes/${ID}/files/archive?path=/workspace/project" \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" curl -sS -o out.tar.gz \ "https://api.withruntime.com/v1/sandboxes/${ID}/files/archive?path=/workspace/project&gzip=true" \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" ``` ## How the API is shaped | Convention | Rule | | ----------- | ----------------------------------------------------------------------- | | Resources | `/v1//{id}` | | Actions | `POST /v1//{id}:`, such as `:stop`, `:pause`, `:wake` | | Field names | camelCase; unknown fields are refused, and every wrong one is named | | Times | ISO 8601 in UTC | | Money | Integer microdollars: 1,000,000 is one US dollar | | Waiting | `Prefer: wait=N` (at most 120), or `?waitFor=running&timeoutSeconds=60` | | Lists | `{"data": [...], "nextCursor": "..."}`; pass it back as `cursor` | | Retries | `Idempotency-Key`, remembered for 24 hours | | Rate | About 50 requests a second per key, bursts of 200 | | Request id | `x-request-id` on every response | ## Read errors Every error has the shape `{"error": {"code", "status", "message", "hint", "requestId"}}`. To print the code and the hint, use jq: ```bash no-run curl -sS https://api.withruntime.com/v1/sandboxes/not-an-id \ -H "Authorization: Bearer ${RUNTIME_API_KEY}" | jq -r '.error | "\(.code): \(.hint)"' ``` | Status | Typical code | What to do | | ------ | ---------------------------------------------- | ----------------------------------------------- | | 401 | `unauthorized` | Check the key | | 402 | `included_usage_used_up`, `insufficient_funds` | Add credit, or read `GET /v1/limits` | | 409 | `sandbox_paused` | Resolve the state, then retry | | 422 | `idempotency_key_reused` | Same key needs the same body; new work, new key | | 429 | `rate_limited`, `no_credit_running_limit` | Wait `Retry-After`, retry with the same key | | 503 | `no_capacity`, `busy`, `host_unavailable` | Retry after `Retry-After` with the same key | ## Mistakes and how Runtime handles them - **Running a command before the sandbox is up.** Without `Prefer: wait=60`, a create can return `starting`. Add the header, or read the sandbox with `?waitFor=running`. - **Retrying a create after a timeout with no key.** The first request may have worked. Send the same `Idempotency-Key` and body again. The server returns the first result with `"replayed": true`. Nothing happens twice. - **Reusing a key for different work.** A changed body under the same key is refused with 422. It is not run. - **Reading a large output from `:exec`.** The JSON response holds 64 KiB of each stream, for a command that runs up to 60 seconds. Send `"stream": true` for NDJSON with no output limit, or write to a file and download it. - **Calling a 0.1.0 route.** Routes under `/v1/resources` return 410 `upgrade_required`, and `details.replacement` names the new route. The SDKs do these retries for you. Use curl in a shell script, in a language without an SDK, or for a quick check from a CI step. ## Related - [How to use the OpenAPI spec](/how-to/use-the-openapi-spec) to generate a typed client - [How to run sandboxes from GitHub Actions](/how-to/run-sandboxes-from-github-actions) - [How to pause and resume a sandbox](/how-to/pause-and-resume-a-sandbox) - [Troubleshooting](/docs/troubleshooting) Facts on this page were checked on 26 October 2026.