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

# Errors

> HTTP status codes the Synthpop API returns, what causes each, and the JSON error bodies to expect.

## How errors are reported

The API signals failures with standard HTTP status codes. Most errors carry a JSON
body. Simple errors use the conventional `{"detail": "..."}` shape; two cases —
frozen Tasks and request validation — return a structured, typed body documented
below.

Always branch on the status code first, then read the body for detail.

## Status codes at a glance

| Status | Where it happens                                            | Cause                                                                                                                             |
| ------ | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `POST /auth/get_token`                                      | The `api_secret` is malformed (wrong shape).                                                                                      |
| `400`  | `POST /task/create`                                         | The `task_type` is not in your org's list of allowed task types.                                                                  |
| `400`  | `GET /task/get`                                             | You supplied neither `uuid` nor `external_id`, or you supplied both. Exactly one is required.                                     |
| `400`  | `GET /task/{task_uuid}/data_item/{data_item_uuid}/get_link` | The Data Item exists but is not a file, so it has no download link.                                                               |
| `403`  | `POST /auth/get_token`                                      | The `api_secret` is invalid or obsolete (revoked/rotated).                                                                        |
| `404`  | `GET /task/get`                                             | No Task matches the identifier you supplied.                                                                                      |
| `404`  | `get_link`                                                  | No such Data Item ID for the specified Task.                                                                                      |
| `409`  | `POST /task/{uuid}/upload`                                  | The Task is frozen — body is a `TaskFrozenError`.                                                                                 |
| `409`  | `POST /task/{uuid}/upload`                                  | The Task is not accepting uploads, or every uploaded file is a duplicate — body is `{"detail": "..."}` (not a `TaskFrozenError`). |
| `422`  | Any endpoint                                                | Request validation failed (missing/ill-typed parameter or field). Body is an `HTTPValidationError`.                               |
| `429`  | `POST /auth/get_token`                                      | Too many token-exchange attempts. Response carries a `Retry-After` header; wait that long before retrying.                        |
| `429`  | `POST /task/create`                                         | Your org's deferred-queue cap is full. Back off and retry later.                                                                  |

## 400 — Bad request

A `400` means the request was understood but is not acceptable as sent. The
specific causes differ by endpoint (see the table above). The body is the
conventional detail shape:

<Accordion title="400 example bodies">
  Unknown `task_type` on `POST /task/create`:

  ```json theme={null}
  {
    "detail": "Task type 'X' not in list of tasks for your organization: [...]."
  }
  ```

  Neither/both identifiers on `GET /task/get`:

  ```json theme={null}
  {
    "detail": "Either 'uuid' or 'external_id' have to be set."
  }
  ```

  The `uuid` query parameter is what this message calls `task_id` — pass exactly one of `uuid` or `external_id`.

  Requesting a link for a non-file Data Item on `get_link`:

  ```json theme={null}
  {
    "detail": "The Data Item exists, but is not a file."
  }
  ```
</Accordion>

<Note>
  `task_type` values are provisioned for your organization during onboarding —
  there is no discovery endpoint. If you get "Task type not in list of allowed
  tasks," confirm the exact value with your Synthpop contact.
</Note>

## 403 — Forbidden

Returned by `POST /auth/get_token` when the `api_secret` is well-formed but
invalid or obsolete — for example after it has been rotated or revoked. Mint a
fresh secret from the Synthpop UI and exchange it again.

<Accordion title="403 example body">
  ```json theme={null}
  {
    "detail": "Invalid API secret."
  }
  ```
</Accordion>

<Warning>
  Distinguish `400` from `403` on token exchange: `400` means the secret is the
  wrong **shape** (fix the string you send); `403` means the shape is right but the
  secret is not **valid** (rotate it). Retrying the same call will not fix either.
</Warning>

## 404 — Not found

The identifier is well-formed but nothing matches it. On `GET /task/get` this
means no Task with that `uuid`/`external_id` exists for your org; on `get_link` it
means the Task has no Data Item with that ID.

<Accordion title="404 example body">
  ```json theme={null}
  {
    "detail": "Task not found."
  }
  ```
</Accordion>

## 409 — Conflict (frozen Task, or Task not accepting uploads)

`POST /task/{uuid}/upload` returns `409` in two distinct cases, each with a
different body shape:

* The Task has been **frozen** by a newer, patient-matched Task. The body is
  a `TaskFrozenError` naming the Task to use instead.
* The Task is not accepting uploads (it is not in `waiting`), or **every**
  file in the upload request is a duplicate of an existing Data Item. The
  body is the conventional `{"detail": "..."}` shape — for example
  `"All uploaded files are duplicates of existing data items."` — **not** a
  `TaskFrozenError`.

<Accordion title="409 TaskFrozenError body">
  ```json theme={null}
  {
    "message": "This task has been superseded and is frozen; upload to the current task instead.",
    "current_task_uuid": "3f6b2c1a-9d4e-4b7a-8c21-0a1b2c3d4e5f"
  }
  ```

  | Field               | Type   | Description                                                        |
  | ------------------- | ------ | ------------------------------------------------------------------ |
  | `message`           | string | Human-readable description of the error.                           |
  | `current_task_uuid` | UUID   | The current, non-frozen Task that uploads should be redirected to. |
</Accordion>

<Accordion title="409 example body (not accepting uploads / all duplicates)">
  ```json theme={null}
  {
    "detail": "All uploaded files are duplicates of existing data items."
  }
  ```
</Accordion>

<Warning>
  If the body is a `TaskFrozenError`, do not retry the same Task — redirect your
  upload to `current_task_uuid`. If the body is a plain `{"detail": ...}` (the
  Task is not accepting uploads, or every file was a duplicate), retrying the
  same bytes will not help; fix the request instead. See
  [Responding to waiting Tasks](/guides/responding-to-waiting-tasks#frozen-and-non-waiting-tasks).
</Warning>

## 422 — Validation error

FastAPI returns `422` when a request fails schema validation — a missing required
field, a wrong type, or an out-of-range value (for example `length=500` on
`GET /task/list`, which caps at 200). The body is an `HTTPValidationError`: a
`detail` array where each entry pinpoints one problem.

<Accordion title="422 HTTPValidationError body">
  ```json theme={null}
  {
    "detail": [
      {
        "loc": ["query", "length"],
        "msg": "Input should be less than or equal to 200",
        "type": "less_than_equal"
      }
    ]
  }
  ```

  | Field  | Type   | Description                                                                            |
  | ------ | ------ | -------------------------------------------------------------------------------------- |
  | `loc`  | array  | Path to the offending input — e.g. `["query", "length"]` or `["body", "external_id"]`. |
  | `msg`  | string | Human-readable explanation.                                                            |
  | `type` | string | Machine-readable error type.                                                           |
</Accordion>

## 429 — Too many requests

Two **distinct** endpoints return `429`:

* **`POST /task/create`** returns `429` when too much deferrable work is
  already parked server-side awaiting capacity (it reports as `pending`, not
  as a separate observable status). Back off and retry the create later;
  in-flight Tasks are unaffected. See
  [load backpressure](/guides/async-results#load-backpressure).
* **`POST /auth/get_token`** returns `429` when there have been too many
  token-exchange attempts. This response carries a `Retry-After` header telling
  you how many seconds to wait before trying again. A token is valid for 365
  days, so exchange once and reuse it rather than minting a fresh token per
  request.

<Note>
  The `429` on `POST /task/create` is part of the API contract but is **not yet
  present in the committed OpenAPI snapshot** that powers the
  [API reference](/api-reference/introduction). Handle it anyway; the committed
  reference will include it once the schema-regeneration job lands.
</Note>

## Related

<CardGroup cols={2}>
  <Card title="Responding to waiting Tasks" icon="reply" href="/guides/responding-to-waiting-tasks">
    Recover from a `409` and resume a paused Task.
  </Card>

  <Card title="Working with async results" icon="rotate" href="/guides/async-results">
    Polling, callbacks, and load backpressure (the `429` queue cap).
  </Card>
</CardGroup>
