Rate Limits
The Keeper Health API enforces limits at three levels: per request (hard input caps), per second (rate limiting), and per account (quota and concurrency). Staying under them is simple — honor Retry-After when you see a 429 or 503 and don't poll more aggressively than the recommended cadence.
Default limits
Default limits are listed below. Contact company@keeperhealth.com to discuss a higher tier for your account.
| Limit | Default |
|---|---|
| Requests per second | 5 |
| Concurrent in-flight jobs | 10 |
| NPIs per request | 1000 (hard limit) |
| Billing codes per request | 100 (hard limit) |
| Fee schedules per request | 20 (hard limit) |
| Download URL validity | ~1 hour |
| Job result retention | 30 days |
Requests that exceed the per-request hard limits are rejected with 400 validation_error before any work is enqueued — see Create Search → Request Body for field limits and Errors for the envelope shape.
Requests that exceed the per-second rate limit receive 429 rate_limited with a Retry-After header — see Errors for the response body.
Polling cadence
Recommended: poll every 15–30 seconds. More frequent polling does not speed up your job and is the most common reason customers hit the rate limiter. Code Examples shows correctly-paced polling loops in cURL, Python, JavaScript, TypeScript, and Go.
- Typical completion time for small requests (≤ 10 NPIs, 1 fee schedule) is 30–60 seconds.
- Larger requests scale proportionally with the number of NPIs, billing codes, and fee schedules.
- If you see
status: "processing"for more than 5 minutes, something is likely wrong — contact company@keeperhealth.com with yourjob_id. See Get Search Status for the full state machine. - There is no webhook in v1. Polling is the only notification mechanism.
Handling 429 and 503
Both 429 rate_limited and 503 responses (enqueue_failed, service_unavailable) include a Retry-After header with the number of seconds to wait before retrying. Honor it — don't invent your own schedule.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{
"error": {
"type": "rate_limited",
"message": "Rate limit exceeded. Retry after 30 seconds."
}
}If you don't see a Retry-After header (e.g. on a transient network failure), fall back to exponential backoff:
attempt 1: wait 5s
attempt 2: wait 10s
attempt 3: wait 20s
attempt 4: wait 40s
Use an Idempotency-Key on the retried POST /v1/searches so you don't accidentally create duplicate jobs when a retry races with a slow-but-successful original request.
If you consistently hit rate limits after honoring Retry-After, contact company@keeperhealth.com to discuss a higher tier.
See also
- Create Search — per-request limits enforced at the field level
- Get Search Status — what you're polling, and when to stop
- Code Examples — polling loops paced to these limits
- Errors — response envelope for
429and503