> ## 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. executing with no ready preview). |
| `429`  | Rate limit hit — back off and retry.                                                                                                                      |
| `500`  | Server error — safe to retry idempotent requests.                                                                                                         |

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
[vish@vern.so](mailto:vish@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)
