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

# Forge errors

> The error contract on forge.alakazam.gg: status codes, exact semantics, and what to do about each.

<div className="forge-kicker">◆ FORGE · errors</div>

Errors return a JSON body with a `detail` message:

```json theme={null}
{ "detail": "campaign not found" }
```

`detail` is a string on every hand-raised error. The one exception is `422`
(request-body validation), where FastAPI returns `detail` as an **array** of
`{loc, msg, type}` records naming exactly which field failed.

## Status codes

| Code  | Meaning              | What to do                                                                                                                                                                                                                                           |
| ----- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | Bad request          | Malformed input: an invalid campaign/job id, a pair record that doesn't round-trip from `/partners`, an empty sample, a verdict other than `confirm`/`overrule`, a run whose plan has nothing runnable. The message names the field. Fix and resend. |
| `401` | Unauthorized         | Missing, invalid, or expired bearer token. Get a fresh session token and retry. Never returned for an auth *outage*: that's `503`.                                                                                                                   |
| `402` | Insufficient credits | Deliver only. `detail` starts with `insufficient_credits:need=N` and appends where to buy more, nothing was billed, nothing was exported. Top up and retry the same call.                                                                            |
| `403` | Forbidden            | The direct job API on an auth-enforcing deployment, a campaign-job artifact outside the review allowlist, or publishing to a namespace not enabled on the server. Use the campaign routes.                                                           |
| `404` | Not found            | Unknown id, or someone else's. Foreign campaigns, jobs, and episode names all read as `404`. Also: downloading `export.tar.gz` before a delivery has run.                                                                                            |
| `409` | Conflict             | The resource isn't in a state that allows this yet: ungraded review, health mid-recompute, pruned artifacts, a campaign run on the raw export route, an unfinished job. See below, each has a specific next move.                                    |
| `413` | Payload too large    | The transform plan exceeds its size bound. Trim it.                                                                                                                                                                                                  |
| `422` | Validation failed    | The request body doesn't match the endpoint's schema. `detail` lists the offending fields.                                                                                                                                                           |
| `503` | Backend unavailable  | Fail-closed: the billing or auth backend couldn't be reached, or a dataset read is temporarily unavailable. Nothing changed. Retry shortly.                                                                                                          |

## 401: authentication

Every forge API call takes `Authorization: Bearer <access token>` (your
Supabase session token). Missing or unverifiable tokens get `401` with
`authentication required`, `invalid or expired session`, or `invalid token`, re-authenticate and retry. When the auth *backend* is down or rate-limited,
you get `503 auth check failed` instead: that's a retry condition, not a
"sign in again."

## 402: insufficient credits

Only [deliver](/forge-billing) can return it:

```json theme={null}
{ "detail": "insufficient_credits:need=N:checkout=/api/billing/credits/checkout:packs=/api/billing/credits/packs" }
```

`N` is the number of not-yet-billed kept episodes entering this delivery. The
debit is atomic and refused whole, **nothing changed**: no credits moved, no
export started, the kept set is untouched. Top up and retry the exact same
call; it will bill the same set once.

The colon-separated fields come **after** the token, never before it, so
matching on the `insufficient_credits` prefix keeps working. The two pointers
are there for headless callers with no UI to fall back on: `packs` lists what
you can buy, `checkout` opens the purchase.

## 403: forbidden surfaces

Three doors are closed by design, not by accident:

* **The direct job API** (`POST /api/aug/datasets`, `POST /api/aug/jobs`) is a
  dev/studio surface. On auth-enforcing deployments (forge.alakazam.gg) it
  would run real renders and export them with no billing, so it answers
  `403 the direct job API is disabled on this deployment`. Campaigns are the
  door.
* **Campaign job artifacts** outside the review allowlist. The files route on
  a campaign's run serves what review needs, clips, gate series, stills.
  Everything else under the job dir *is* the deliverable, so it answers
  `403 this artifact ships in the campaign's delivered export`. Deliver, then
  download the archive.
* **Publishing** to a Hugging Face namespace not on the server's allowlist.
  Export locally instead, or ask us to enable your namespace.

## 404: tenancy reads as not-found

`404` deliberately covers two cases you can't tell apart: an id that doesn't
exist, and an id that belongs to another account. Campaigns, jobs, and episode
names are all owner-scoped this way. If a request that worked yesterday now
404s, check which account's token you're sending before assuming the resource
is gone.

## 409: right call, wrong moment

Each `409` names a specific state; each has one move:

| You called                                       | Why it refused                                                                                                     | Next move                                      |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- |
| `review` on an ungraded episode                  | verdicts exist only for machine `accept`/`reject`: a verdict parked on a pending render could flip to "kept" later | wait for verification to grade it, then review |
| `runs` during a target-class switch              | health is recomputing; a run must reflect the settled target, never a stale one                                    | poll `health_status`, run when `ready`         |
| `deliver` after artifacts were pruned            | the kept episodes' renders are no longer on disk; billing them would charge for phantoms                           | re-run, re-review, then deliver                |
| `/api/aug/jobs/{id}/export` on a campaign run    | campaign runs exit only through the campaign's billed Deliver: the raw route would be a free side door             | `POST …/campaigns/{id}/deliver`                |
| `/api/aug/jobs/{id}/export` on an unfinished job | the accept set isn't final until the job settles                                                                   | poll until `succeeded`, then export            |

## 503: fail closed

The forge refuses rather than guesses. If the billing backend is unreachable
at deliver time, delivery refuses (`billing check failed, try again`) instead
of exporting unledgered data. If the auth backend is unreachable, calls refuse
instead of pooling you into someone else's tenant. If a dataset read is
temporarily unavailable, listings say so instead of returning an empty page.
In every case nothing changed on your campaign, wait a moment and retry the
same call.

## Additions found in adversarial review

* **`502`**: dataset registration can fail upstream (opening a sample or
  creating a campaign while the dataset host is unreachable). Retryable.
* **`500`**: `POST …/proposals` surfaces a mining crash as `mining failed`.
  Report it; re-running after a health recompute usually clears it.
* **More `409`s**: switching target class before health has computed; a chunk
  upload whose sha256 doesn't match its manifest entry; completing an upload
  twice; completing an upload with missing chunks (this last one is the single
  place `detail` is an object (`{error, missing}`) so tooling can list the
  gaps).
* **A fourth `403` door**: `/api/workbench/admin/*` (usage, comp grants) is
  operator-only, `admin only` for everyone else.
