Skip to main content

Rate limits

Requests are limited per API key. The limit protects the workflow that everything else depends on: an integration polling in a loop must never be able to slow down a judge signing an urgent warrant.

The unauthenticated public link endpoints are limited too, by client address rather than by key, and report the same headers.

The headers

Every response carries the current state of your budget:

HeaderMeaning
RateLimit-LimitRequests permitted in the current window
RateLimit-RemainingRequests left in the window
RateLimit-ResetSeconds until the window resets

Read them on successful responses, not only on failures. A client that watches RateLimit-Remaining and slows down never sees a 429.

When you exceed it

HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
"error": {
"message": "Rate limit exceeded. Retry after 30 seconds.",
"type": "rate_limit_error",
"code": "rate_limit_exceeded",
"param": null,
"request_id": "req_01HZY5R2K3Q9"
}
}

Wait for Retry-After seconds, then retry. Retrying immediately makes the situation worse for everyone using that key, including the people waiting on a warrant.

Backing off

# Example client: honour Retry-After, then exponential backoff with jitter.
def request_with_retry(send, attempts=5):
delay = 1.0
for attempt in range(attempts):
response = send()
if response.status_code != 429:
return response
wait = float(response.headers.get("Retry-After", delay))
sleep(wait + random.uniform(0, 0.3 * wait))
delay = min(delay * 2, 60)
raise RateLimited()
  • Honour Retry-After when it is present. It is not a suggestion.
  • Add jitter. Several workers retrying on the same schedule reconverge into the same spike.
  • Cap the backoff and give up rather than retrying forever.

Staying under the limit

  • Do not poll tightly. A review queue refreshed every 30 seconds is responsive to a person; every second is a hundred wasted calls a minute.
  • Read the list, not each item. GET /v1/warrants carries enough to render a queue without fetching every application.
  • Cache what does not move. Templates, roles, locations, and document styles change rarely. Refetch them on a schedule, not per request.
  • Use one key per integration. Separate keys have separate budgets, so a bulk export cannot starve the workflow that people are waiting on.
  • Do not use the audit trail as a change feed. It is history, and polling it is the most expensive way to learn something the resource itself would have told you.

Bulk work

Backfills and exports should run at a deliberate pace, off-hours where possible, with a concurrency of one or two rather than a fan-out. If an integration needs a rate above the standard limit, talk to eCourtDate rather than working around it with more keys.