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 |
403 Forbidden | Valid credentials, but insufficient permissions or scope |
404 Not Found | Resource does not exist (or doesn't belong to your organisation) |
409 Conflict | Duplicate resource - most commonly a duplicate email address |
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"
}That's the whole envelope - there's no machine-readable error code alongside it. 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.