Skip to main content
Didit enforces multiple layers of rate limiting to keep the API stable while still allowing healthy bursts of traffic. Limits are applied per identifier — your x-api-key or, if no API key is sent, your client IP. Each scope keeps an independent counter with a 60-second sliding window.

Identifier and exemptions

The middleware picks a rate-limit identifier in this order in this order:
  1. x-api-key header → key-scoped counter.
  2. X-Forwarded-For first hop → IP-scoped counter.
  3. REMOTE_ADDR → IP-scoped counter.
The following calls bypass rate limiting entirely:
  • The internal DIDIT_WEBAPP_API_KEY (used only by the Didit console).
  • Endpoints protected by the IsValidSession permission (the embedded/SDK session traffic).
  • GET /system/healthcheck (served by HealthMiddleware before this stack).
  • Provider webhooks: Stripe, AML watcher, face verification callbacks, KYC list mutation.

Global limits

Every authenticated request is also bucketed by HTTP method into a generic scope, so any new route inherits a sane default: Endpoint-specific limits (next section) apply in addition to the global limit. The first scope to exceed its counter is the one that returns 429.

Endpoint-specific limits

The middleware defines stricter scopes for the highest-impact routes:
GET /v3/session/<id>/decision/ is served by SessionV3RetrieveView and is not in the session-decision scope. It is governed only by the generic-get 600/min ceiling. The 100/min decision throttle applies to the v1 and v2 decision paths.
The codebase also defines FREE_SESSION_RATE_LIMIT = 10 and PAID_SESSION_RATE_LIMIT = 600. These constants are not currently applied to POST /v3/session/ — that endpoint uses the single session-v2-create limit of 600/min for every application. Do not plan capacity around a per-workflow free-tier throttle until it is documented as live.

429 response shape

When you exceed any scope you get an HTTP 429 with this body and headers:
X-RateLimit-* headers are only emitted on the 429 response — successful responses do not include them today. Use the Retry-After value (or X-RateLimit-Reset - now) as your delay before the next attempt.

Client guidance

  • Respect Retry-After as the floor for your backoff. On top of it, layer exponential backoff with jitter (5s → 10s → 20s → 40s ± random) so two clients that hit 429 at the same time do not synchronise.
  • Stop retrying after a small ceiling (e.g. 5 attempts). Surface the error to your caller and alert your team — sustained 429s usually mean a runaway loop, not a transient spike.
  • Use one identifier per workload. Sharing an x-api-key across many backends collapses them into the same counter, which can cause one noisy neighbour to starve the rest. Issue a separate application (and therefore a separate api_key) per environment, brand, or customer — see Programmatic registration.
  • Cache decision reads. The /decision/ endpoints are not designed for tight polling loops; if you need real-time updates, use Webhooks and only call GET /v3/session/<id>/decision/ for back-fill or audit.
  • Generate PDFs offline. PDF rendering is CPU-bound (50/min cap). Trigger it from a queue after the session reaches a terminal status, not on every page view.

Sample 429-aware client

Need more headroom?

If 600 sessions per minute is not enough — for example, a marketing launch or a backfill — email support@didit.me with your application UUID and the expected sustained and burst rates. Overrides are configured per application.