> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.modbox.run/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 <token>`.                      |
| `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.**                   |