Part 5 · 2 chapters · ~12 min
Errors, Pagination and Filtering
One error format with RFC 9457 problem details, validation errors per field, cursor pagination over keyset queries, filtering, sorting and sparse fieldsets, consistent list envelopes, and limits on page sizes.
11
Errors
code
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{ "type": "https://api.bank.example/errors/insufficient-funds", "title": "Insufficient funds", "status": 422,
"detail": "Available balance is ₦4,200.00; this transfer needs ₦5,025.00.",
"code": "insufficient_funds", "request_id": "req_91af", "retryable": false }ONE ERROR FORMAT: RFC 9457 PROBLEM DETAILS
machine-readable, human-readable, the same everywhere
swipe the figure sideways, or tap expand for full screen
1/4
stable type
The type URI (or a code) is the stable, documented identifier clients branch on. Titles and details can change wording; types never do.
clients branch on a stable type or codenever on the message text
12
Pagination, filtering and sorting
code
GET /v1/transfers?account_id=ac_1&state=completed&created_after=2026-09-01T00:00:00Z&limit=50
200 { "data": [ ... 50 items ... ], "next_cursor": "eyJ0IjoiMjAyNi0wOS0yOVQxMDowMDowMFoiLCJpZCI6InRyXzkxIn0", "has_more": true }
-- the cursor encodes the last (created_at, id); the query is keyset, so deep pages stay fast
SELECT * FROM transfers WHERE account_id = $1 AND (created_at, id) < ($2, $3) ORDER BY created_at DESC, id DESC LIMIT 51;| rule | why |
|---|---|
| opaque cursors, not page numbers | stable under inserts; keyset queries; free to change internally |
| a maximum limit (e.g. 100) | protects the database |
| sort only by indexed, documented fields | arbitrary sorts become full scans |
| filters as explicit query parameters | a generic query language invites expensive queries |
sparse fieldsets (?fields=id,state) if payloads are large | bandwidth for mobile clients |