> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.modbox.run/errors/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.modbox.run/_mcp/server. # Errors Every failing request returns the same JSON shape, with an HTTP status that matches. Branch on `error.code` — it is a stable part of the API contract. Never parse `error.message`, which is written for humans and may change. ```json { "error": { "code": "image_disabled", "message": "Sandbox image 'Desktop Browser' is disabled.", "hint": "Enable the image with PUT /sandbox-images/{id} or pick an enabled one.", "docs_url": "https://docs.modbox.run/errors#image_disabled", "retryable": false, "request_id": "req_3f8a1c9e42b7", "details": null }, "detail": "Sandbox image 'Desktop Browser' is disabled." } ``` | Field | Meaning | | ------------ | -------------------------------------------------------------------------------- | | `code` | Stable identifier. Branch on this. | | `message` | Human-readable explanation. Do not parse. | | `hint` | Concrete next step that usually resolves the error. | | `docs_url` | Link to the section for this code. | | `retryable` | `true` when retrying the identical request may succeed. | | `request_id` | Correlation id, also in the `X-Request-Id` header. Quote it in support requests. | | `details` | Field-level problems, present on validation errors. | > **Note** > > `detail` is deprecated. It mirrors `error.message` so older clients keep > working, and will be removed in a future release. ## Retry behaviour `retryable` tells you whether to back off and try again, or fix the request first. Retrying a non-retryable error will fail identically every time. ```python resp = client.post("/sandboxes/provision", json=payload) if resp.status_code >= 400: err = resp.json()["error"] if err["retryable"]: time.sleep(backoff) # safe to retry as-is else: raise RuntimeError(err["hint"] or err["message"]) ``` On `429` (`rate_limited`), wait the number of seconds in the `Retry-After` header before retrying. ## Validation errors A `422` lists exactly which fields are wrong, so you can correct them without guessing: ```json { "error": { "code": "validation_error", "message": "The request body or parameters failed validation.", "hint": "Fix the fields listed in 'error.details' and resend the request.", "retryable": false, "details": [ { "field": "body.task_id", "issue": "Field required" }, { "field": "body.ttl_seconds", "issue": "Input should be less than or equal to 86400" } ] } } ``` ## Error codes ### Authentication and authorization | Code | Status | What to do | | --------------------- | ------ | ---------------------------------------------------------- | | `unauthenticated` | 401 | Send `Authorization: Bearer `. | | `invalid_token` | 401 | Token expired or revoked — refresh it or create a new one. | | `invalid_credentials` | 401 | Wrong username or password. Do not retry unchanged. | | `email_not_verified` | 403 | Open the verification link before logging in. | | `forbidden` | 403 | The credential is valid but lacks access to this resource. | | `insufficient_role` | 403 | Ask an owner or admin to raise your role. | ### Workspaces | Code | Status | What to do | | --------------------- | ------ | -------------------------------------------------------- | | `workspace_required` | 400 | Send the `X-Workspace-Id` header. | | `workspace_forbidden` | 403 | You are not a member. List yours with `GET /workspaces`. | | `workspace_not_found` | 404 | The workspace does not exist. | | `member_not_found` | 404 | No such member in this workspace. | ### Sandboxes | Code | Status | What to do | | ----------------------- | ------ | ------------------------------------------------------------------------ | | `sandbox_not_found` | 404 | No sandbox with that id or name that you can access. | | `sandbox_name_conflict` | 409 | Pick another name or destroy the existing sandbox. | | `sandbox_not_ready` | 409 | Still provisioning — poll or call `POST /sandboxes/wait`. **Retryable.** | | `provisioning_failed` | 502 | Check the image and quota, then retry. **Retryable.** | > **Note** > > A sandbox you can't access reports `sandbox_not_found`, not `forbidden`. > Confirming that someone else's sandbox exists would leak information, so the > two cases are deliberately indistinguishable. ### Images and registries | Code | Status | What to do | | -------------------- | ------ | ------------------------------------------------------- | | `image_not_found` | 404 | List images with `GET /sandbox-images`. | | `image_disabled` | 400 | Enable it, or choose an enabled image. | | `image_immutable` | 403 | Global default images are read-only — copy one instead. | | `registry_not_found` | 404 | List credentials with `GET /registries`. | ### API tokens | Code | Status | What to do | | ------------------- | ------ | ---------------------------------------- | | `api_key_not_found` | 404 | List your tokens with `GET /api-tokens`. | ### Generic | Code | Status | What to do | | ------------------ | ------ | --------------------------------------------------------------------- | | `validation_error` | 422 | Fix the fields in `error.details`. | | `not_found` | 404 | The resource does not exist. | | `conflict` | 409 | The resource already exists or is in the wrong state. | | `rate_limited` | 429 | Wait for `Retry-After` seconds. **Retryable.** | | `internal_error` | 500 | Retry with backoff; contact support with `request_id`. **Retryable.** | | `upstream_error` | 502 | An infrastructure dependency failed. **Retryable.** | > One envelope, stable codes, and a hint telling you what to do next