Troubleshooting & Rate Limits
Common errors, what they mean, and the rate limits that protect the service.
Error responses
- OAuth endpoints (
/oauth2/*) return standard OAuth2 errors (RFC 6749): a JSON body with anerrorcode (and oftenerror_description). - Admin/API endpoints (
/api/*) return a JSON body{ "error": "<code>", "message": "…" }with an appropriate HTTP status.
OAuth errors
error | Typical cause | Fix |
|---|---|---|
invalid_request | Missing/invalid parameter, or a redirect_uri that isn't registered. | Check the request; register the exact redirect URI. |
invalid_grant | Expired/invalid authorization code, or a reused/expired refresh token. | Restart the flow; use the newest refresh token (they rotate — see Token Lifecycle). |
invalid_client | Wrong client ID or missing/invalid API key on a confidential client. | Verify the client ID and API key. |
invalid_token | The presented token is expired, malformed, or revoked. | Refresh the access token or re-authenticate. |
unsupported_grant_type | A grant_type Curupira doesn't support. | Use authorization_code or refresh_token. |
API errors
| HTTP | error | Meaning |
|---|---|---|
| 400 | bad_request / validation_error | Malformed or invalid input (e.g. a redirect URI without http(s)://). |
| 401 | unauthorized | No or invalid session/credential. |
| 403 | forbidden | Authenticated, but lacking the required permission (or out of tenant scope). |
| 404 | not_found | The resource doesn't exist (or isn't visible to your tenant). |
| 409 | conflict / last_admin | State conflict — e.g. removing the last admin of a tenant. |
| 429 | too_many_requests | Rate-limited (see below) — honor the Retry-After header. |
| 500 | internal_server_error | Unexpected server error; retry, and contact support if it persists. |
Rate limits & lockout
Curupira protects authentication endpoints two ways:
Login brute-force protection
Repeated failed logins trigger a temporary lockout with exponential backoff — after a small
threshold of failures (≈5) the base lock is ~60 seconds and roughly doubles with each further failure,
capped at ~15 minutes. Blocked attempts return 429 with a Retry-After header. Wait for
the Retry-After interval, then retry — a correct login after the window succeeds. (The thresholds
are deployment-tunable.)
IP rate limiting
Endpoints are rate-limited per client IP (tunable via the deployment's RATE_LIMIT_* settings; the
built-in defaults are deliberately tight — e.g. ~60 requests/minute on login). Exceeding a limit
returns 429 too_many_requests. Clients should respect Retry-After and back off rather than
hammering the endpoint.
:::tip Design for 429s
Treat 429 as a normal, recoverable condition: read Retry-After, wait, and retry with exponential
backoff. Don't retry immediately in a tight loop — that only prolongs the limit.
:::
Common issues
- "redirect_uri mismatch" /
invalid_requestat login — theredirect_urimust match a registered URI exactly (scheme, host, path, no trailing-slash differences). See Tenant Onboarding. - Refresh suddenly fails with
invalid_grant— you reused an old refresh token. Store and use the new one returned by each refresh (Token Lifecycle). 403on an admin action — your admin account lacks that permission, or the resource is in another tenant. Check your role assignment.- SSO login refused for a missing/unverified email — the provider didn't return a verified email; Curupira requires one to provision or link an account. See SSO Provider Setup.
Getting help
- The OpenID discovery document lists the live endpoints and capabilities.
- Admins can inspect the Logs views (login attempts, events, audit) in the dashboard to see what happened server-side.