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, so curl reaches everything the SDKs, the CLI and the MCP server do. Money is integer microdollars, every error has one shape with a hint, and a create you retry with the same Idempotency-Key never makes two sandboxes. API version 0.2.0, checked 25 September 2026 (API reference).

Get a key into a variable

Make a key at API keys, or from a terminal with npx withruntime keys create --name curl, and keep it 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 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 '{"funding": "trial", "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(6 * 7)\""}'# 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 macOS default shell, 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}"

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, every wrong one named at once
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 is {"error": {"code", "status", "message", "hint", "requestId"}}. Print the code and the hint with 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 trial_exhausted, insufficient_funds Add credit, or read GET /v1/limits
409 trial_busy, sandbox_paused Resolve the state, then retry
422 idempotency_key_reused Same key needs the same body; new work, new key
429 rate_limited Wait Retry-After, retry with the same key
503 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 answer 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, and the server answers 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, not run.
  • Reading a huge output from :exec. The JSON answer holds 64 KiB of each stream for a command 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 answer 410 upgrade_required, and details.replacement names the new one.

The SDKs do these retries for you. Reach for curl in a shell script, a language without an SDK, or a quick check from a CI step.

Facts on this page were checked on 25 September 2026.