Errors
One envelope, stable codes, a request id on everything.
Every non-2xx response has the same shape:
error.json
{
"error": {
"code": "quota_exceeded",
"message": "Monthly quota exceeded. Upgrade your plan or wait for the reset.",
"request_id": "6f1c2e0a9b4d4c3e8a7f5d2b1c0e9a8f"
}
}Branch on error.code, never on the message text. Validation errors add a details array with the offending field.
| code | HTTP | what happened | what to do |
|---|---|---|---|
| invalid_request | 422 | Body or query failed validation. | Check `details` for the field. |
| invalid_email | 422 | The `email` field isn't a syntactically valid address. | Trim whitespace; ensure one `@`. |
| missing_api_key | 401 | No `Authorization` header. | Send `Authorization: Bearer mxp_live_…`. |
| malformed_api_key | 401 | Token doesn't look like an mxping key. | Keys are `mxp_live_` / `mxp_test_` + 32 chars. |
| invalid_api_key | 401 | Key unknown, revoked, or expired. | Create a new key in the dashboard. |
| insufficient_scope | 403 | Key lacks the scope for this route. | Create a key with `verify:read` / `bulk:write`. |
| email_not_verified | 403 | Free accounts must confirm their email first. | Click the link in the signup email. |
| plan_required | 403 | Feature needs a higher plan (bulk, key limit, deep mode). | Upgrade in the dashboard. |
| quota_exceeded | 429 | Monthly credits used up. | Upgrade, or wait for `quota_reset_at`. |
| rate_limited | 429 | Per-minute plan limit hit. | Back off for `Retry-After` seconds. |
| ip_rate_limited | 429 | Too many requests from one IP. | Spread traffic, or contact support for a raise. |
| auth_locked_out | 429 | 10 failed key attempts from an IP within an hour. | Wait an hour; check the key you're sending. |
| payload_too_large | 413 | Bulk CSV over 25 MB. | Split the file. |
| unsafe_url | 400 | `callback_url` points at a private/internal address. | Use a public HTTPS URL. |
| not_found | 404 | No such job or key. | Check the id belongs to your account. |
| deep_mode_disabled | 503 | Deep mode isn't available yet. | Use fast mode. |
| internal_error | 500 | Our fault. | Retry with backoff; quote `request_id` to support. |
Retrying
Retry 5xx and rate_limited with exponential backoff, honouring Retry-After. Never retry quota_exceededin a loop — it won't clear until the reset. Use an Idempotency-Key so retries are free.