Log in Create workspace
Skip to content

Platform

API and login rate limits

Keep integrations responsive by pacing requests from the response headers and honoring retry-after on HTTP 429. Machine traffic uses node-local Hammer counters; password login has a separate PostgreSQL-backed throttle shared by application nodes.

API docs
On this page

Start here

Rate limits protect APIs and sign-in from bursts. Identify the bucket, pace traffic, and distinguish a temporary request limit from a monthly quota.

Before you begin

  • A captured status code and response headers from the limited request
  • The caller type, without its secret
  • A client capable of delayed retries

Put it into practice

  1. Watch every response

    Read x-ratelimit-remaining and x-ratelimit-reset on machine API responses that provide them. Spread bursts before remaining reaches zero rather than waiting for a rejection.

  2. Handle 429 explicitly

    Read type before retrying. For problems/rate-limited, honor retry-after, add jitter, and preserve any idempotency identifier. For problems/quota-exceeded, pause the work and resolve usage or plan entitlement instead of retrying.

  3. Find the correct bucket

    Determine whether the request was keyed by credential, user, partner, engagement, or client address. Login protection and partner failed-auth throttles are separate from the normal machine API counter.

What success looks like

Steady traffic stays below the limit, bursts recover automatically without a retry storm, and operators can tell a short rate window from a quota that requires usage or plan action.

01 Default limits

details

1000 requests per 60 seconds

API_RATE_LIMIT controls the default ceiling (1000). Authenticated machine requests are keyed by credential ID; unauthenticated bootstrap requests are keyed by client IP, where IPv6 clients share one bucket per /64 prefix (the same rule applies to every IP-keyed throttle on the platform: failed-auth, sign-up, password reset and login). The 60-second period is fixed in runtime configuration. ETS counters are local to each application node.

02 Password login protection

details

10 failures per identity and IP

Ten failed passwords for the same normalized email and client IP within 15 minutes start a 30-minute cooldown. The tenth failure returns HTTP 429 with retry-after; a successful login clears this identity/IP bucket.

50 unreturned attempts per client IP

A broader 15-minute IP ceiling limits email rotation and Argon2 CPU abuse. Every submission reserves one slot and a verified login returns its own slot, so the budget is spent by failures and shared NAT egress traffic is not punished for succeeding. IPv6 clients are counted per /64 prefix. Login counters are durable in PostgreSQL and shared across application nodes.

20 sign-ups per client IP per hour

Self-serve registration (POST /api/v1/register and /users/register) is throttled per client IP (IPv6 addresses share a /64 bucket). Beyond 20 registrations in an hour the API returns HTTP 429 problems/rate-limited with a retry-after header and extra.retry_after_seconds, and the sign-up page shows a retry-later flash. The limit comes from config :agent_ops, :registration_throttle (ip_limit, window_ms), not an environment variable. Password-reset requests have a similar fixed budget (10 per IP and 5 per email per 15 minutes).

03 Invitation email throttle

details

5 per recipient per hour, 100 per workspace per day

Invitation create and resend share one budget: at most 5 emails to the same recipient in a workspace per hour and 100 invitation emails per workspace per 24 hours. Exceeding either returns 429 problems/rate-limited with retry-after and extra.retry_after_seconds on POST /api/v1/operator/workspaces/:workspace_id/invitations and PATCH …/invitations/:id/resend (nothing is created), or a flash in the members screen.

04 Trusted client IPs

details

Forwarded headers are fail-closed

The app reads x-forwarded-for only when the direct peer is listed in TRUSTED_PROXY_IPS. Forwarded headers from every other peer are ignored; keep the application origin firewalled from direct traffic.

05 Response headers

details

x-ratelimit-remaining

Requests remaining in the current window. Present on successful responses that pass through the API rate-limit plug, including machine, operator session API, SCIM, partner, and engagement routes. Browser pages, /health, /ready, /metrics, and /webhooks/nowpayments do not run that plug.

x-ratelimit-reset

ISO 8601 estimate computed as response time plus the configured period, not the exact fixed-window boundary. Use Retry-After to schedule a retry after HTTP 429.

06 When rate limited (HTTP 429)

example

Response format

Request-rate limits return retry-after plus the shared RFC 9457 problem body, including on operator, SCIM, partner, and engagement routes. Retry-After governs when a rate-limited client should retry; x-ratelimit-reset is only an estimate. A quota-exceeded 429 is a different problem type and does not promise retry-after. Password-login limits return retry-after with a minimal plain-text response.

http
HTTP/1.1 429 Too Many Requests
retry-after: 42
content-type: application/json

{
  "type": "https://robotscenter.net/problems/rate-limited",
  "title": "Rate limited",
  "status": 429,
  "detail": "Rate limit exceeded. Please retry later.",
  "request_id": "req-abc123",
  "extra": { "retry_after_seconds": 42 }
}

07 How the machine API is keyed

details

Credential, SCIM token, user, engagement, partner, or IP

Authenticated machine requests use credential ID, SCIM uses token ID, and browser-authenticated operator API calls use user ID. Partner routes key partner:{id}, engagement ingest keys engagement:{id}, and unauthenticated rate-limited routes fall back to client IP.

Surfaces without this Hammer bucket

Browser pages, GET /health, GET /ready, GET /metrics, and POST /webhooks/nowpayments do not run this plug. Password login uses its separate durable throttle. The operator session API and SCIM /scim/v2 do run the Hammer limiter.

08 Failed-auth throttle on token-authenticated planes

details

30 unauthenticated HTTP 401s per client address per 60 seconds

Every token-authenticated API plane runs AuthFailureThrottle before credential lookup: the machine /api/v1 routes (agk_ keys and access tokens), /api/v1/otlp, partner, engagement, and SCIM. Each credential plane keeps its own bucket per client address (IPv6 counted per /64): agent credentials (machine /api/v1 and /api/v1/otlp together), partner keys, engagement tokens, and SCIM tokens. After 30 unauthenticated 401 responses on one plane in 60 seconds the next attempt on that plane is HTTP 429 problem JSON with retry-after; the other planes are unaffected. Valid credentials, an authenticated 403, and a 401 for a credential that verified but was refused for its state (a closed or expired engagement, a revoked token, a suspended partner) do not increment the counter, but a valid credential can still be rejected before credential lookup while its plane's failure bucket for that address is exhausted.

09 Configuration

details

Environment variables

Set API_RATE_LIMIT to change the per-node machine API ceiling. Set TRUSTED_PROXY_IPS to the comma-separated direct reverse-proxy addresses allowed to supply x-forwarded-for. Login, registration and password-reset thresholds are fixed production safety limits (registration and password reset are configurable only via config :agent_ops, :registration_throttle and :password_reset_throttle). Set OUTBOUND_PRIVATE_NETWORK_ALLOWLIST to the comma-separated CIDRs that production connectors, webhooks, SMTP relays and alert destinations may reach on private address space; the app refuses to boot on an invalid CIDR.

Troubleshooting

No rate-limit headers appear
Operator and SCIM APIs do use the Hammer plug. Browser pages, health and readiness probes, metrics, and the NOWPayments webhook do not; confirm the route before assuming a proxy removed headers.
Limits differ across nodes
Machine counters are node-local. Treat the documented ceiling as a per-node implementation limit and keep client-side pacing conservative across a load-balanced deployment.

Where to go next

see also