Errors and rate limits
Failures always use the same envelope. Branch on the code, never on the message text or on substring matching.
Error envelope
{
"error": {
"code": "payment_past_due",
"message": "API access is suspended because a payment is past due."
}
}code is contract and stable. message is prose written for a human reading a terminal and may be reworded at any time.
Code table
| Status | Code | What it means | What to do |
|---|---|---|---|
| 400 | invalid_request | A query parameter failed validation: an out of range limit, an unparsable cursor, or a malformed updated_since. | Fix the request. Retrying unchanged will fail identically. |
| 401 | invalid_key | The key is missing, malformed, unknown, revoked, or past its expiry. The four cases are intentionally indistinguishable. | Check the Authorization header, then check the key's status on the API keys page. Do not retry in a loop. |
| 402 | payment_past_due | The organization has an unpaid invoice. API access is suspended while the console remains usable. | An organization admin resolves billing. The same key resumes working automatically, with no reissue. |
| 402 | subscription_inactive | The subscription is canceled or restricted, or a trial ended without a payment method. | Reactivate the subscription in the console. The same key resumes working automatically. |
| 403 | insufficient_scope | The key is valid but was not granted a scope this endpoint requires. The message names the missing scope. | Create a key with the required scope and migrate to it. Scopes on an existing key cannot be widened. |
| 403 | api_disabled | API access has been switched off for this organization by EnrollPilot support, independently of billing. | Contact support. Retrying will not help. |
| 404 | not_found | No such record, or the id belongs to another organization. Both answer the same way so the API cannot be used to probe for ids. | Verify the id came from a list response on this same key. |
| 405 | method_not_allowed | Version 1 is read only. Anything other than GET is refused, and the response carries an Allow: GET header. | Use GET. |
| 429 | rate_limited | A per key, per organization, or unauthenticated per address limit was exceeded. | Wait for the number of seconds in the Retry-After header, then retry with backoff. |
| 500 | internal_error | Something failed on our side. The message is deliberately generic; details stay in our logs. | Retry with exponential backoff. If it persists, contact support with the approximate time of the request. |
| 503 | api_unavailable | The public API is switched off globally, which happens only during an incident. | Retry later with backoff. |
| 503 | api_temporarily_unavailable | The API cannot currently enforce its rate limits and refuses traffic rather than serve it unthrottled. | Retry shortly with backoff. |
Understanding 402
The API is stricter about billing than the console is, on purpose. An organization whose payment is past due can keep working in the browser while automated data extraction is suspended. That is why payment_past_due exists as its own code rather than being folded into subscription_inactive.
| Situation | Code | Recovery |
|---|---|---|
| Invoice unpaid, subscription otherwise intact | payment_past_due | Pay the invoice. Access returns automatically. |
| Subscription canceled or restricted | subscription_inactive | Reactivate the subscription. |
| Trial ended with no payment method on file | subscription_inactive | Add a payment method and choose a plan. |
Never treat 402 as a reason to reissue keys
Rate limits
| Limit | Applies to | Ceiling |
|---|---|---|
| Per key | Each individual API key | 120 requests per minute |
| Per organization | All of an organization's keys combined | 600 requests per minute |
| Unauthenticated | Requests with no valid key, by source address | 60 attempts per minute |
The per organization ceiling is the real limit. Adding keys does not add capacity, so splitting a job across several keys will not raise your throughput.
Handling 429
- Read the
Retry-Afterheader and sleep for at least that many seconds. - Back off exponentially with jitter on repeated limiting. Do not retry in a tight loop.
- Prefer larger pages over more requests. One call at
limit=200costs the same as one call atlimit=10.
Retry policy at a glance
| Status | Retry? |
|---|---|
| 400, 401, 403, 404, 405 | No. Fix the request, the key, or the scope. |
| 402 | No. Resolve billing first, then resume on the same key. |
| 429 | Yes, after Retry-After, with backoff. |
| 500, 503 | Yes, with exponential backoff and jitter. |
Caching
Every response, success and failure alike, is sent with Cache-Control: private, no-store, max-age=0. Do not place a shared cache in front of the API.