| 400 | body.domain required | POST body had no domain field. | Add it. |
| 400 | sandbox: only [...] supported | Live domain with a test key. | Use a ki_test_ key for sandbox, ki_live_ key for real. |
| 400 | test domains require a ki_test_ key | Test domain with a live key. | Same — match domain to key prefix. |
| 400 | invalid_cursor | History pagination cursor malformed. | Drop the cursor (start over) or use the next_cursor we returned. |
| 400 | window must be one of ['7d','30d','90d'] | Bad ?window= value on /v1/movers. | Use a supported value. |
| 401 | unauthorized | Missing or invalid X-API-Key. | Check the header is set and the key is active. Allow up to 5 min for revocations to propagate. |
| 403 | free_tier_sandbox_only | Live key on Free tier attempted a real-domain score. The Free trial is sandbox-only. | Use a ki_test_ key against the 4 canned test domains, or upgrade to Starter. |
| 403 | forbidden | Job belongs to a different account. | Job IDs are bound to the user that started them — any of that user’s keys can poll, but keys from another account cannot. |
| 404 | no score available for this domain | GET /v1/score/{domain} against a never-scored domain. | Trigger one with POST /v1/score. |
| 404 | no_history | Same shape, history endpoint. | Trigger an initial score. |
| 404 | job not found | Unknown or expired (>30d) job ID. | Re-run POST /v1/score to start a fresh cold pipeline. |
| 429 | cold_budget_exhausted (reason: monthly_cap) | Account exhausted its tier’s monthly cold-call budget. v1.0 is flat-only — no overage. | Upgrade to the next tier, or wait for the next monthly reset (Retry-After header gives seconds remaining). v1.5 will add opt-in metered overage. |
| 429 | rate limited (reason: per_key_cold_1h) | This key hit 200 cold calls in 1 hour. | Wait — Retry-After header gives seconds until reset. |
| 429 | rate limited (reason: account_cold_24h) | Account hit 2× tier monthly cold cap inside 24h. | Wait, or email support if legitimate spike. |