API Docs/Rate Limits

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.

LimitDefault
Requests per second5
Concurrent in-flight jobs10
NPIs per request1000 (hard limit)
Billing codes per request100 (hard limit)
Fee schedules per request20 (hard limit)
Download URL validity~1 hour
Job result retention30 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 your job_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.

cURL
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

Keeper Health API v1 · Questions? company@keeperhealth.com