> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuneplane.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> One error shape, and what each status code actually means here.

Every failure returns a JSON body with a `detail` field and an HTTP status that says what kind of
failure it was.

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{ "detail": "Insufficient quota: requested 8 GPUs, 2 available" }
```

`detail` is a string in almost every case. Request validation is the exception: FastAPI returns an
array, one entry per field it rejected.

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
  "detail": [
    {
      "loc": ["body", "spec", "resources", "pools"],
      "msg": "List should have at least 1 item after validation",
      "type": "too_short"
    }
  ]
}
```

A client that renders errors should handle both. `loc` is the path to the offending field, joined
with dots, and `msg` is the message worth showing.

## Status codes

| Code | What it means here | What to do |
| - | - | - |
| `400` | The request was understood and refused. The most common failure by far: a malformed JobSpec, a bad reference, a value outside its declared range | Read `detail`; it names the specific thing |
| `401` | No token, a malformed one, or an expired one | [Re-authenticate](https://docs.tuneplane.ai/en/api-reference/authentication.md). Never retry the same token |
| `403` | Authenticated, but not allowed. An admin-only endpoint, a dataset you may not read, a deployment you do not own | Not retryable. Ask an administrator |
| `404` | No such job, run, dataset, model, or deployment — or one you are not permitted to know exists | Check the id |
| `409` | A state conflict: promoting a revision that is not ready, pushing a dataset version that already exists, stopping a job that already stopped | Re-read the current state and decide again |
| `411` | A length-required upload arrived without `Content-Length` | Set the header |
| `413` | The upload exceeds the deployment's limit, or a submitted archive expands past it | Shrink the package. `TUNEPLANE_MAX_UPLOAD_MB` is the limit |
| `422` | Request body failed schema validation | Fix the fields `detail[].loc` names |
| `429` | Rate limited — login attempts or device-code polls | Back off. Poll the device flow no faster than every five seconds |
| `500` | A defect in the control plane | Retryable once; then report it with the response and the timestamp |
| `501` | The deployment is not configured for this feature | An administrator has to enable it |
| `502` | A backend the control plane depends on failed: the executor, the object store, an OIDC provider | Usually transient. Retry with backoff |
| `503` | The control plane is up but deliberately not serving: maintenance mode, or draining for an upgrade | Retry later. `GET /api/version` still answers |

## Reading a rejected submission

`POST /api/jobs` refuses before anything is scheduled, and the `detail` names the gate that refused
it. These are the ones worth recognising:

> **Recipe handshake failure**
>
> The `recipe.lock.json` in your submission does not match the catalog on this server — the name,
> version, digest, or `framework.runtime_id` differs. The platform published a new recipe version
> and your experiment is still pinned to the old one.
>
> `tp recipe status <exp>` shows the difference; `tp recipe upgrade <exp>` applies it.

> **Insufficient quota**
>
> The requested GPU count exceeds what your account may hold at once. **Waiting in the queue is not
> this error** — a queued job was admitted. This is a refusal at the door, and only an administrator
> can change it.

> **Image registry not allowlisted**
>
> `--image` points at a registry host the deployment does not permit. The allowlist is server-side
> on purpose: it is what stops a submission from running arbitrary code from anywhere.

> **Entrypoint override rejected**
>
> `spec.source.entrypoint` was non-empty. The recipe owns the entrypoint; a job cannot replace it.
> Use a `custom/custom` recipe if you need to run your own command.

> **Sensitive file in the package**
>
> The packager found `.env`, `*.pem`, `id_rsa*`, or similar and refused to build the archive — this
> fails on your machine, before any request is sent. Secrets reach a job through the platform's
> injection channel, never through the working directory.

> [!NOTE]
>
> Some error strings raised by older code paths are still in Chinese. They are being translated as
> the surrounding code is touched. If you hit one, the status code and the endpoint are the reliable
> parts to quote in a bug report.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.