Skip to content
Runtime

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).

Get a key into a variable

Make a key at API 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:

Terminalread -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

Terminal# 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

Terminal# 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/<plural>/{id}
Actions POST /v1/<plural>/{id}:<verb>, 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:

Terminalcurl -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.

Facts on this page were checked on 26 October 2026.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card