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

# Errors & limits

> The shared error model, idempotency, rate limits, polling conventions, and data region notes.

Every Migration API endpoint shares one error model, one idempotency model, and
one rate limit.

## Status codes

| Status | Meaning                                                                                                                                                                                                         |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Malformed request — a required field is missing or a value is invalid (e.g. an unknown template slug, a bad `external_id`, an invalid run `kind`).                                                              |
| `401`  | API key missing, malformed, or revoked.                                                                                                                                                                         |
| `404`  | The referenced migration, run, source, or template isn't in your account.                                                                                                                                       |
| `409`  | A conflict — a reused `external_id`, an ambiguous source name, a run already in progress, or a state precondition (e.g. asking for the [session log](/migration-api/session-log) before the migration settles). |
| `410`  | The resource expired and its data was deleted — an [extraction](/migration-api/extract-a-document) or [export job](/migration-api/export-jobs) past its retention window. Start a new one.                      |
| `412`  | A guarded write lost a race. Only from [knowledge](/migration-api/knowledge) writes sent with `if_unmodified_since` — re-read, merge, and write again.                                                          |
| `413`  | The uploaded file is too large. [Extraction](/migration-api/extract-a-document) caps a single document at 100 MB.                                                                                               |
| `429`  | Rate limit hit — back off and retry.                                                                                                                                                                            |
| `500`  | Server error — safe to retry idempotent requests.                                                                                                                                                               |
| `502`  | An upstream worker couldn't be reached — document parsing or the export executor. Retry.                                                                                                                        |
| `503`  | The feature isn't available in your account's data region.                                                                                                                                                      |

Run-level failures are different from request errors: a `POST /runs` request can
return `201` (accepted) and then resolve to a `failed` run. Read the run's
`error` field for the reason. See [Poll a run](/migration-api/poll-a-run).

## Error body

Every error response is a JSON object with a single human-readable `error`
string:

```json theme={null}
{ "error": "external_id must be alphanumeric (hyphens/underscores allowed), max 255 chars" }
```

There is no machine-readable error code, field pointer, or request-id header
today — branch on the HTTP status and surface the `error` message for debugging.

## Rate limits

Each endpoint allows roughly **100 requests per minute** per API key; over that
you get a `429` with `{ "error": "Too many requests" }`. There's no `Retry-After`
header — back off (a second or two) and retry. Space your run polling out so a
fleet of concurrent migrations doesn't burn the budget on status checks.

## Idempotency

Migration **creation** is idempotent on the `external_id` you choose. Re-sending
[`POST /migrations`](/migration-api/create-a-migration) with the same `external_id`
(and the same source) returns the **existing migration** — with a `200` instead
of a `201` — rather than creating a second one. This makes create retries safe
and doubles as recovery: if you lose a migration ID, re-POST with the same
`external_id` to get it back.

Runs are **not** keyed by an idempotency token. Instead, only **one run runs at a
time per migration** — starting a second while one is in flight returns `409` with
the active `run_id`, which is how you recover a run you lost track of. See
[Start a run](/migration-api/start-a-run).

## Polling conventions

* Poll runs on an interval of ≈2s, backing off as the run ages.
* Treat `awaiting_approval`, `completed`, `failed`, and `canceled` as terminal
  for a given run; stop polling.
* Make your handling idempotent — the same terminal state can be read more than
  once.
* Prefer the [thread stream](/migration-api/stream-the-thread) (SSE) over tight
  polling when you want live progress.

## Data region

Migrations are created in your **account's region**. If you need data to land in
a specific region for residency reasons, talk to us at
[roupen@vern.so](mailto:roupen@vern.so) — additional regions are enabled per account.

## Next

* [Poll a run](/migration-api/poll-a-run)
* [Start a run](/migration-api/start-a-run)
