Rate limits

600 requests per minute per key, RateLimit-* headers on every response to an authenticated request, a 429 with Retry-After when exceeded, and the unauthenticated health probe.

View this page as Markdown

Each API key may make 600 requests per 60-second window. The window is fixed (it starts on the minute boundary the first request falls in), the count is per key, and every plan has the same limit — create separate keys for separate integrations and each gets its own window.

Limit Value
Requests per key 600 per 60 seconds (a fixed window)
GET /v1/health 60 per 60 seconds per client address (unauthenticated)
Headers on every authenticated response RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset (seconds), RateLimit-Policy
When exceeded 429 rate_limited with Retry-After (seconds)

Headers

Every response to a request that passed authentication carries the IETF RateLimit header fields — a success, a refusal, a 429:

RateLimit-Limit: 600
RateLimit-Remaining: 412
RateLimit-Reset: 23
RateLimit-Policy: 600;w=60

RateLimit-Reset is the number of seconds until the window rolls over (never 0).

A response given before a key is accepted carries none of them: a 401 (a missing, unknown or revoked key), and a 405 method_not_allowed (a verb the path does not support) or a 404 not_found for a path no route matches. Those last two are answered before authentication is attempted, by design: they never count toward the window and are not logged. The health probe is unauthenticated and carries none of them either.

When the limit is hit

The request is refused with 429 rate_limited and a Retry-After header in seconds — the same number as RateLimit-Reset:

HTTP/1.1 429 Too Many Requests
Retry-After: 23
RateLimit-Limit: 600
RateLimit-Remaining: 0
RateLimit-Reset: 23
Content-Type: application/problem+json
{
  "type": "https://developers.processcart.com/errors#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "This key is limited to 600 requests per 60 seconds.",
  "code": "rate_limited",
  "requestId": "req_…"
}

A 429 is never stored for idempotent replay: retry the same request with the same Idempotency-Key after Retry-After and it executes. Back off on 429 (sleep Retry-After, then resume) rather than spreading requests across keys — a refused request still counts toward the window.

Reducing requests

  • GET /v1/runs returns full Runs (fields and tasks included) — one request per 100 runs, no per-run follow-ups.
  • updatedSince fetches only what changed (Incremental sync).
  • Webhooks deliver the full Run with every run event (a lite header on a workflow-trash cascade) (Webhooks) — no polling at all.

Health

GET /v1/health is unauthenticated and answers { "status": "ok" } with 200 when the database responds, or { "status": "degraded" } with 503. It is limited per client address (60 per minute), cached for ten seconds on each server, and never logged. The public status page at status.processcart.com checks it continuously — see Status & uptime.