Errors & Rate Limiting
HTTP status codes
| Code | Meaning |
|---|---|
200 OK | Request succeeded |
201 Created | Resource created |
202 Accepted | Request accepted but not yet complete (e.g. registration pending email verification) |
400 Bad Request | Invalid request body, bad query parameter, or missing required field |
401 Unauthorized | Missing, malformed, revoked, or expired API key / session |
402 Payment Required | The organisation is read-only because its trial or subscription has ended: reads still work, writes are refused with code: "read_only" until a plan is chosen |
403 Forbidden | Valid credentials, but insufficient permissions or scope. code: "PLAN_LIMIT_REACHED" can only occur for an organisation with no plan, which is already read-only; no paid plan limits open offboardings |
404 Not Found | Resource does not exist (or doesn't belong to your organisation) |
409 Conflict | A duplicate (an open offboarding for the same email, with its id in the body) or a stage gate that blocks a status change (code: "stage_gate") |
429 Too Many Requests | Rate limit exceeded |
500 Internal Server Error | Server-side error |
Error response format
All error responses follow this shape:
{
"error": "A human-readable description of the error"
}Some errors add a code so a client can act on them without parsing the text: stage_gate, PLAN_LIMIT_REACHED, manager_email_is_employee, unknown_apps and read_only. Where a code exists, the endpoint's page names it; everything else has only the message. In non-production environments the error message is often the raw exception message rather than a generic one, which is useful for debugging but shouldn't be pattern-matched against in client code.
Rate limiting
PurpletGo layers a few different rate limits depending on which endpoints you're calling:
| Endpoint group | Limit |
|---|---|
POST /api/auth/login | 10 requests / 15 minutes per IP |
POST /api/auth/register | 5 requests / hour per IP |
/api/v1/* (per API key) | 120 requests / 60 seconds per key |
Everything else under /api | 300 requests / 60 seconds per user (or per IP if unauthenticated) |
When the limit is exceeded, the API returns 429 with the standard error envelope:
{
"error": "Too many requests. Please slow down."
}Limits enforced by the shared Express rate limiter also send a Retry-After header with the number of seconds to wait. The per-API-key limit on /api/v1 is checked separately in the auth middleware and doesn't set that header - back off and retry after a few seconds if you hit it.