Errors
The API returns conventional HTTP status codes plus a structured JSON body so clients can render field-specific errors.
Status codes
| Status | Meaning |
|---|---|
200 |
Success |
201 |
Created (POST) |
204 |
No content (typically DELETE) |
400 |
Validation failed — body has field-level errors |
401 |
Missing or invalid auth token |
403 |
Authenticated but not authorized for this action or record |
404 |
Record does not exist, or exists in another tenant (we return 404 rather than 403 to avoid leaking existence across orgs) |
409 |
Conflict — e.g. duplicate unique key |
422 |
Semantically invalid (e.g. illegal state transition) |
429 |
Rate limit exceeded |
5xx |
Server error |
Validation error shape
{
"error": true,
"errors": {
"email": "Enter a valid email address.",
"custom_fields": {
"bant_score": "must be a number",
"industry": "must be one of ['saas', 'manufacturing']"
}
}
}
- Top-level keys are field names.
- For nested objects (
custom_fields), the value is itself a{ key: message }map. - A single field can have a list of messages if multiple validators fire.
Auth error shape
{
"detail": "Authentication credentials were not provided."
}
Or for an expired access token:
{
"detail": "Token expired.",
"code": "token_not_valid"
}
When you see code: token_not_valid, call POST /api/auth/refresh/ and retry the original request.
Rate limiting
{
"detail": "Request was throttled. Expected available in 12 seconds.",
"retry_after": 12
}
The Retry-After HTTP header is also set. Default limits: 1000 requests/hour per token for read endpoints, 200 for write endpoints. Self-hosted deployments can change this via REST_FRAMEWORK['DEFAULT_THROTTLE_RATES'] in settings.py.