Rate limits
SenseCrypt rate-limit policy for the protocol endpoints you integrate against — the per-action windows and ceilings you need to design retries, the 429 response shape with Retry-After, how the client IP is determined, and how to handle limits.
SenseCrypt applies per-action, fixed-window rate limiting to protect against brute force, enumeration, and abuse. Limits are shared across every instance — there is no per-instance in-memory state, so a limit is a limit no matter which node serves the request.
This page documents the limits on the protocol endpoints you call so you can design sensible retries. The limits on the sign-in ceremony itself (email entry, registration, CIBA dispatch, and the like) are strict and tuned to stop abuse; they aren't published, because normal user traffic stays well under them and only automated abuse trips them.
The response when you're limited
Exceeding any scope returns:
- HTTP
429 Too Many Requests - A
Retry-Afterheader with the number of seconds until the window rolls over. - A JSON body:
{
"error": "rate_limited",
"message": "Too many attempts. Please wait and try again",
"retry_after_seconds": 42
}The retry_after_seconds value appears in the body and as the Retry-After header. On the SCIM endpoints the same 429 is re-rendered as a SCIM error envelope, with the Retry-After header preserved. A browser that trips a limit on a page it was sent to — the sign-in ceremony, /authorize, /logout or SAML /sso — sees a branded rate-limited card instead of this body, decided on its Accept header; a machine caller always gets the JSON (see error codes). Honor Retry-After — retrying before it elapses just burns another attempt.
How limits are scoped
Each action is limited on one or more scopes. The scopes that apply to the protocol endpoints are:
ip— the caller's IP address (see Determining the client IP).client_id— the OAuth client / M2M app. Each action keeps its own per-client counter.
A request must pass every scope for its action. A window is a fixed clock-aligned bucket: for a 60-second window, all requests in the same wall-clock minute share a counter.
The per-IP counter is shared by the OAuth and OIDC endpoints. Every call from one IP address in the same minute to the token, authorization, PAR, UserInfo, introspection, revocation and logout endpoints, the CIBA backchannel endpoint and in-app sign-in adds to one counter. Each endpoint then compares that shared count with its own ip maximum in the table below. For example, 50 token calls and 50 UserInfo calls from one address in the same minute make 100: the token endpoint (maximum 60) refuses for the rest of that minute, while UserInfo (maximum 120) still answers. Spreading a burst over several endpoints therefore buys nothing. The count is not per tenant either, so calls for different tenants from one address add to the same counter. Two actions keep a per-IP counter of their own: the protected resource (oidc_resource) and SAML SSO (saml_sso).
Protocol endpoint limits
These are the limits on the OAuth / OIDC and SAML protocol endpoints. Columns are the scope, its window (seconds), and the maximum requests allowed in that window.
| Action | Scope | Window (s) | Max |
|---|---|---|---|
oidc_token | ip / client_id | 60 / 60 | 60 / 120 |
oidc_authorize | ip / client_id | 60 / 60 | 60 / 120 |
oidc_par | ip / client_id | 60 / 60 | 60 / 120 |
oidc_revoke | ip / client_id | 60 / 60 | 60 / 120 |
oidc_introspect | ip / client_id | 60 / 60 | 120 / 240 |
oidc_userinfo | ip | 60 | 120 |
oidc_logout | ip | 60 | 120 |
oidc_resource | ip | 60 | 120 |
ciba_bc_authorize | ip / client_id | 60 / 60 | 30 / 60 |
saml_sso | ip | 60 | 60 |
oidc_authorize_challenge | ip | 60 | 60 |
oidc_authorize_challenge_start | tenant | 60 | 600 |
oidc_authorize_challenge_target | client_id + login_hint | 3600 | 30 |
The three oidc_authorize_challenge* rows are in-app sign-in, which a tenant's own mobile app runs through the Mobile SDK. The per-IP row counts every call; the per-tenant and per-address rows count only the start of a sign-in, so a sign-in already under way is never stalled by a flood of starts.
Introspection is deliberately looser (resource servers introspect on a hot path and should cache). UserInfo, logout and the protected resource take no client credential, so they are limited per IP only, and the limit is checked before any token signature is verified. CIBA's backchannel start is tighter than the token endpoint because each call may dispatch a sign-in notification.
The sign-in ceremony (email entry, device registration, PIN and verification handling) and the admin console carry their own strict, abuse-tuned limits on top of these. They are intentionally not documented here: legitimate end-user and console traffic never approaches them, so you don't need to design around them — but an automated load test will hit them.
Determining the client IP
For IP-scoped limits, the client IP is taken from the X-Forwarded-For header, counting from the right:
- The IP is read
TRUSTED_PROXY_HOPSpositions from the right of the header (default 1, i.e. a single reverse proxy / ALB). Set it to2for CDN → ALB, or0to ignore the header and use the socket peer.
If you self-host, set TRUSTED_PROXY_HOPS to match your proxy topology — too high and clients can spoof their IP; too low and every client can appear to share one IP (which would trip IP-scoped limits for everyone). See Self-hosting.
Handling limits in your integration
- Cache introspection results. Don't introspect the same token on every request — verify the JWT locally against the JWKS and reserve introspection for revocation checks.
- Back off on
429. ReadRetry-Afterand wait; don't hammer. - Don't share one egress IP across unrelated tenants at high volume without accounting for the IP-scoped ceilings on the protocol endpoints.
- Expect a tight sign-in funnel. The sign-in and registration limits are strict by design (they're anti-abuse). Normal user traffic stays well under them; automated load tests will not.
Related
- Error codes — the full error taxonomy, including the
429shape. - Self-hosting — proxy configuration and the
TRUSTED_PROXY_HOPSknob. - Troubleshooting — what to do when you hit a limit unexpectedly.
Error codes
The complete SenseCrypt error taxonomy — OAuth/OIDC, SAML, SCIM, and device-plane error codes, their HTTP statuses, the exact envelopes, and what each one means.
CORS
How cross-origin browser access to SenseCrypt works — the backend registers no CORS middleware of its own, so CORS is terminated at the operator's reverse proxy, and confidential apps should prefer server-side token exchange.