Runtime

How to retry sandbox API calls safely with idempotency keys

Send the same Idempotency-Key with the same body, and for 24 hours Runtime answers the first result again instead of acting twice.

On Runtime every write already carries a key, and both SDKs retry with it, so a retried create never makes a second sandbox and a retried command never runs twice. That matters for money as well as correctness: a duplicate 2 vCPU, 4 GiB sandbox left waiting costs $0.03125 an hour, about $22.50 over 30 days, at the rates checked on 25 September 2026 (pricing). You only choose a key yourself when a retry has to survive a process restart.

What the SDKs already do

A write is any change: a create, an exec, a pause, a file write. The SDK makes a key for each call and keeps it through its own retries.

Failure What the SDK does
Timeout or dropped connection Retries with the same key and a growing delay
429 rate_limited Waits retryAfterMs, then retries with the same key
502, 503, 504 Retries with the same key
trial_busy, no_capacity A create waits for room, up to two minutes by default
spending_limit_reached Not retried: a person has to raise the limit
Any other 4xx Thrown at once as a typed error with a code and a hint

The typed error keeps the key it sent, as error.idempotencyKey in JavaScript and error.idempotency_key in Python, so you can log it with the requestId (errors and retries).

Choose your own key when the process can die

A worker that crashes after sending a create but before saving the sandbox id cannot tell whether the create happened. Derive the key from the job, not from the attempt, and the worker that picks the job up again gets the same sandbox.

TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();const job = "1842";const input = { labels: { job } };const options = { idempotencyKey: `job-${job}-create` };const first = await runtime.sandboxes.create(input, options);// A restarted worker sends the same call again:const again = await runtime.sandboxes.create(input, options);console.log(first.id === again.id); // true: one sandbox, answered twiceawait again.stop();
Pythonfrom withruntime import Runtimeruntime = Runtime()job = "1842"first = runtime.sandboxes.create(idempotency_key=f"job-{job}-create", labels={"job": job})again = runtime.sandboxes.create(idempotency_key=f"job-{job}-create", labels={"job": job})print(first.id == again.id)  # Trueagain.stop()

Over HTTP, send the header yourself:

Terminalcurl -sS https://api.withruntime.com/v1/sandboxes \  -H "Authorization: Bearer ${RUNTIME_API_KEY}" \  -H "Idempotency-Key: job-1842-create" \  -H "Content-Type: application/json" \  -d '{"labels": {"job": "1842"}}'

What the server answers

You send Runtime answers
Same key, same body The first result again, with "replayed": true and the header Idempotency-Replayed: true
Same key, different body 422 idempotency_key_reused
A new key New work
Any key after 24 hours New work: the server has forgotten it

A replay is an answer, not a second action. Nothing is created, run or charged again (retries and idempotency).

Mistakes that cause duplicates

  • A fresh key on every attempt. A retry loop that makes a new UUID each time turns a lost reply into two sandboxes. Make the key once, before the first attempt, and reuse it.
  • Changing the body on a retry. Adding a label or raising timeoutSeconds and sending the old key is refused with 422. That is a new request: give it a new key.
  • Reading silence as failure. A missing response does not prove the call failed. Retry the identical input with the same key, and inspect files and processes before repeating a side effect whose outcome is still unknown (the request timed out).
  • Retrying a failed fork with its old key. A fork that failed is over; the same key answers the same error. Fork again with a new key, and stop any copies the error names in details.startedSandboxIds (forking).
  • Omitting the key in a direct MCP call. A key the server made for a field you left out cannot be recovered from a lost reply. Choose idempotencyKey before the first call to a tool that accepts it (MCP).

Start

Terminalnpx withruntime sandbox run --trial -- python3 -c 'print(6 * 7)'

New accounts get 50 free sandbox hours, no card. The first run prints a link to approve in your browser.

Facts on this page were checked on 25 September 2026.