Skip to main content
When a call fails, read its retry field to learn whether to send it again. Read outcome to learn whether your work started.

Read the Error Response

Every HTTP error has this shape. These values come from a request to a path that does not exist.

Read the Retry Field

retry has four values. The HTTP status never decides a retry: a 409 can be safe or after_fix. Treat a retry value you do not know as never. Error codes lists each code’s value.

Let the Client Retry for You

The SDKs and the CLI resend retry: safe errors twice, with the same idempotency key, and never any other value. Pass max_retries or maxRetries to change the count; 0 turns it off. This not_found raises at once because its retry is not safe:
The wait is retry_after_seconds, or 0.5 seconds doubling to 8 when it is null; a longer wait is raised, not slept. The CLI always uses two. Over plain HTTP, wait and send the same request with the same Idempotency-Key.

Decide by Outcome, Not by Status

The HTTP status does not say whether the work happened. A 202 is a success status, not a completion: the work is still pending. A 4xx or 5xx can carry any outcome, including unknown and completed.
A failed call carries outcome. not_started: nothing ran anywhere; resend when retry is safe, otherwise follow the fix. unknown: the provider may have acted; don't send it again yet, inspect the operation. partial: part of the change happened; do only what is missing. completed: the change happened; don't send it again, read the resource.A failed call carries outcome. not_started: nothing ran anywhere; resend when retry is safe, otherwise follow the fix. unknown: the provider may have acted; don't send it again yet, inspect the operation. partial: part of the change happened; do only what is missing. completed: the change happened; don't send it again, read the resource.A failed call carries outcome. not_started: nothing ran anywhere; resend when retry is safe, otherwise follow the fix. unknown: the provider may have acted; don't send it again yet, inspect the operation. partial: part of the change happened; do only what is missing. completed: the change happened; don't send it again, read the resource.A failed call carries outcome. not_started: nothing ran anywhere; resend when retry is safe, otherwise follow the fix. unknown: the provider may have acted; don't send it again yet, inspect the operation. partial: part of the change happened; do only what is missing. completed: the change happened; don't send it again, read the resource.

Check the operation before you act on unknown or partial.

Check Whether Your Work Started

An unknown or partial outcome always carries retry: check_outcome, whatever the code’s usual value. An unknown 504 timeout is left for a command whose answer BoxRouter lost. Its operation reads completed with settled_by: wait_timeout, which means BoxRouter stopped waiting, not that the command ended. A process-start failure after dispatch is partial or unknown, and a process wait_for_port ended is partial. Check the sandbox and any effects before starting another command. A failed process removal can also report not_started after it began removing retained output; check what remains before retrying. Two cases have no outcome or retry field:
  • A command that exits nonzero or reaches timeout_seconds is a result, not an error. exec returns its exit_code, timed_out, stdout and stderr; check=True raises CommandFailed for either.
  • A transport failure has no BoxRouter response. If the client never connected, its message says the request was not sent: treat that as not_started. If the connection dropped after sending, treat it as unknown.

Inspect an Uncertain Operation

An error with retry: check_outcome can include an operation_id. Reading it never repeats the work. In the SDKs, operations.wait reads it until the outcome is known and raises a timeout error if it stays unknown. The CLI and HTTP examples read it once. Inspect the resource and its effects before retrying if no ID is available. Keep the request ID and idempotency key. The CLI and HTTP examples need jq; the HTTP example also needs curl. For real work, inspect effects before deleting an uncertain sandbox. In this example the sandbox does not exist, so the call fails before anything runs and reports not_started. Its retry is check_outcome, but there is no operation_id, so nothing is read.
If the operation still reads unknown, look at the resource as well: the sandbox’s state, or the files the command writes. A missing log line does not prove nothing happened. If it stays unknown, contact support with the operation_id and the idempotency_key. Do not create a new idempotency key to get around an unresolved operation.

Recover a Lost Creation Response

Send the create request again with the same body and Idempotency-Key. You choose this key to identify one particular call. BoxRouter looks it up in its records:
  • If the first request completed, you get the same sandbox and nothing new is created.
  • If it is still unresolved, you get 409 with its operation_id, and nothing is sent to a provider.
  • If BoxRouter never received it, this request creates the sandbox.
  • If the body differs, you get 409 conflict and nothing happens.
The SDKs and CLI generate a new key for each call unless you pass one, so pass your own to be able to resend. You can also find the sandbox by a label you set, without the key:
Once operation history is removed, the same key returns 409 conflict and creates nothing. See the retention policy. Search by label, or ask support if you are unsure.

Fix It by Symptom

Handle a code you do not know by its retry value, because new codes can be added.

Keep the Diagnostic Identifiers

Every HTTP error has a request_id. Keep the operation_id when the error includes it, and save the idempotency key you sent with the request. Provider failures can include provider, native_status and native_code; a failed create can list attempted routes in details.attempts (route result). When available, the CLI prints these on stderr. Python’s boxrouter.api_error.APIError exposes them in snake case (operation_id), TypeScript’s APIError in camel case (operationId), and both keep the whole error body in error. Send them when you ask for help, and keep API keys, provider credentials, and workload data out of the report.

When Cleanup Fails

Leaving a Python with block or TypeScript finally requests destruction; it does not confirm it. If the sandbox reads destroying, BoxRouter finishes the destroy without you; to see the result, inspect the operation before retrying. With the ID, check its state. If it is destroying, check again. If it is running or paused and no deletion is pending, follow the destroy instructions. In Python, the cleanup error carries the sandbox ID. If your code also failed, a BaseExceptionGroup holds both errors. In TypeScript, a finally error replaces the earlier one, so catch them separately when both matter. Pressing Ctrl-C during boxrouter run requests destruction and prints the boxrouter get command that confirms it.

Next Steps

Error Codes

Every code, whether work started, and what to do.

Check Your Limits

Which limits apply and who can change them.

Create a Sandbox

Destroy what you create so a failure leaves nothing behind.