Errors and pagination
Problem documents, machine-readable codes, and cursor pagination.
Errors
Every error is an RFC 9457 problem document:
{
"type": "https://docs.nexuscrm.com/api/problems/validation_failed",
"title": "The request was invalid.",
"status": 422,
"detail": "The phone number is not a valid Indian mobile number.",
"code": "validation_failed",
"request_id": "01KYM97KCJK416C465Y2A8AN3N",
"errors": { "phone": ["The phone number is not a valid Indian mobile number."] }
}Branch on code, never on title or detail. The human-readable strings may be reworded at any time; the code is a contract.
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated | Missing, malformed or revoked token |
| 403 | forbidden | Authenticated, but the token lacks the ability |
| 404 | not_found | No such record — or it is outside your data scope |
| 409 | conflict | A duplicate, or a state that forbids the change |
| 422 | validation_failed | See errors for per-field detail |
| 429 | rate_limited | Back off; see Retry-After |
| 503 | service_unavailable | Retry with backoff |
Responses never contain a stack trace, a SQL fragment or an internal database id. Quote request_id to support and we can find the exact request in our logs.
Pagination
Cursor-based throughout. There are no page numbers.
GET /api/v1/leads?page[size]=50{
"data": [],
"links": { "next": "/api/v1/leads?page[size]=50&page[after]=01H8XGJ..." },
"meta": { "has_more": true }
}Follow links.next until it is null:
let url = '/api/v1/leads?page[size]=100';
while (url) {
const page = await get(url);
process(page.data);
url = page.links.next;
}Why there is no page=2
OFFSET 40000 makes the database walk forty thousand rows in order to discard them, so deep pages get progressively slower until an export times out. A cursor is a WHERE on an indexed column — page 500 costs the same as page 1.
It is also stable. With offset pagination, a row inserted while you iterate shifts everything down by one and you read the same record twice. A cursor cannot do that, because it remembers a position in the data rather than a count.
Stuck on a response you did not expect? Send us the request_id from the error body and we can trace the exact call — contact support.