SenseCrypt Docs
Reference

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-After header 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. Honor Retry-After — retrying before it elapses just burns another attempt.

How limits are scoped

Each action is limited independently on one or more scopes. The scopes that apply to the protocol endpoints are:

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.

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.

ActionScopeWindow (s)Max
oidc_tokenip / client_id60 / 6060 / 120
oidc_authorizeip / client_id60 / 6060 / 120
oidc_parip / client_id60 / 6060 / 120
oidc_revokeip / client_id60 / 6060 / 120
oidc_introspectip / client_id60 / 60120 / 240
ciba_bc_authorizeip / client_id60 / 6030 / 60
saml_ssoip6060

Introspection is deliberately looser (resource servers introspect on a hot path and should cache). 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_HOPS positions from the right of the header (default 1, i.e. a single reverse proxy / ALB). Set it to 2 for CDN → ALB, or 0 to 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. Read Retry-After and 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.
  • Error codes — the full error taxonomy, including the 429 shape.
  • Self-hosting — proxy configuration and the TRUSTED_PROXY_HOPS knob.
  • Troubleshooting — what to do when you hit a limit unexpectedly.

On this page