Status codes
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.
Error body
Every error response is a JSON object with a single human-readableerror
string:
error message for debugging.
Rate limits
Each endpoint allows roughly 100 requests per minute per API key; over that you get a429 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 theexternal_id you choose. Re-sending
POST /migrations 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.
Polling conventions
- Poll runs on an interval of ≈2s, backing off as the run ages.
- Treat
awaiting_approval,completed,failed, andcanceledas 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 (SSE) over tight polling when you want live progress.