Skip to content

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, and you can open the record (or the endpoint), but your role does not allow this action
404 Record does not exist, exists in another tenant, or is in your org but you are not allowed to open it
409 Conflict, e.g. duplicate unique key
422 Semantically invalid (e.g. illegal state transition)
429 Rate limit exceeded
5xx Server error

Not found versus forbidden

From django-crm 1.11.0 a record in your own org that you are not allowed to open answers 404 on every method, with the same body as an id that does not exist. That covers the records themselves, sales goals, and the comments and attachments on tickets, tasks and invoices: a comment or file on a record you cannot open is the same 404. It used to answer 403 on some endpoints, which told a caller that the id was real. A 403 now means you can see the record but may not do what you asked: a member deleting a deal they can read, or editing a goal they can see, for example, or anyone but an admin calling an admin-only endpoint.

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-token/ 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.