Okouro throttles requests to keep the API responsive for everyone. Limits use a fixed time window and are enforced before most other processing.
How limits are applied
Requests are counted in a rolling window. There are three tiers:
| Tier | Applies to | Counted per | Default limit |
|---|---|---|---|
| Authenticated | all protected endpoints | the user (falls back to IP if the token is invalid) | 300 requests / 60s |
| Public | public routes and the MCP endpoint | client IP | 300 requests / 60s |
| Credential | login, signup, complete-signup, refresh, accept-invitation, redeem-password-change | client IP | 10 requests / 60s |
The credential tier is deliberately tight to slow down brute-force attempts. GET /health is exempt from all limits.
Limits are advisory safety valves, not quotas; the exact numbers are set by server configuration and may differ from the defaults above. Read them from the response headers rather than hard-coding them.
Response headers
Every allowed response includes your current budget:
x-ratelimit-limit: 300
x-ratelimit-remaining: 297
x-ratelimit-reset: 42
x-ratelimit-limit- the ceiling for the window.x-ratelimit-remaining- requests left in the current window.x-ratelimit-reset- seconds until the window resets.
When you are limited
Exceeding a limit returns 429 Too Many Requests with a Retry-After header:
HTTP/1.1 429 Too Many Requests
retry-after: 12
x-ratelimit-remaining: 0
{ "error": "rate_limited", "message": "Too many requests; please slow down." }
Wait the number of seconds in Retry-After, then retry. Note the body here has an extra message field compared to the standard error shape.
The credential tier is the exception: to avoid revealing the brute-force threshold, a throttled credential request answers with a generic body and only a Retry-After header (no x-ratelimit-* headers):
HTTP/1.1 429 Too Many Requests
retry-after: 12
{ "error": "too many attempts; please try again later" }
Account lockout
The credential endpoints (/auth/login, /auth/register, /auth/refresh, invitation and password-change redemption) are additionally protected by a per-account lockout, independent of IP. After a small number of consecutive failed logins on one account, that account is temporarily locked and returns 429 Too Many Requests with a Retry-After header, even from different IP addresses. A successful login clears it. This complements the per-IP throttling described above; it does not replace it.
The body is the same generic “too many attempts” message as the credential-tier throttle above, so a locked account is not distinguishable from a throttled IP.
Staying under the limit
- Batch reads with pagination. Pull larger pages (
per_pageup to 100) instead of many small pages. See Conventions. - Cache what rarely changes. Reference data such as clients and consultants does not need to be refetched on every operation.
- Respect
Retry-Afteron429. Hammering a limited endpoint keeps you limited; backing off clears it. - Authenticate. Authenticated traffic is keyed per user, so one busy integration does not exhaust another’s budget, and it avoids the shared public IP bucket.
Fail-open behaviour
If the rate limiter’s backing store is unavailable, requests to ordinary API routes are allowed through rather than blocked. The credential endpoints fall back to an in-process limiter in that case, so brute-force protection on login is not fully lost during an outage. Either way, treat the limits as best-effort protection, not a guarantee, and still design your client to behave well under 429.