Error format RFC 9457, idempotency, pagination¶
Context and Problem Statement¶
Clients work offline with an outbox and repeat requests (04-sync-protocol.md, section 13). They must tell errors apart by machine (retry, step-up, discard, reload) and load long lists page by page, without permission filters revealing quantities. Which error format, which idempotency mechanism and which pagination apply to all operations?
Decision Drivers¶
- One format for REST, gateway errors and tools; a standard instead of a custom design.
- Every write request can be repeated without duplicates (04, goal 2).
- A retry after
401 step_up_requiredor429must work with the same ID. - Lists reveal nothing about invisible objects (R-AZ-01, P2).
- Generators of all four languages must be able to type errors and pages.
Considered Options¶
- Errors: RFC 9457 Problem Details with a
typeof its own per code. - Errors: custom format
{"error": {"code", "message"}}. - Idempotency: client ID in the body for messages and interactions,
Idempotency-Keyfor all other POST and PATCH. - Idempotency: only
Idempotency-Keyfor everything. - Pagination: opaque cursor without a total count.
- Pagination: offset and limit with a total count.
Decision Outcome¶
Chosen options: "RFC 9457", "Client ID plus Idempotency-Key", "Cursor without a total count", because they follow standards, map the rules from 04 without exception and reveal no quantities.
Rulings (docs/spec/06-api.md, sections 5 to 8):
application/problem+jsonwithtypeas a relative URI/api/v1/problems/{code},code,correlation_id; extensionserrors[],current,reason,hint. Every instance delivers the description of a code under this path.400for schema and rule violations,422only foridempotency_key_reuse,409for state conflicts,412for a failedIf-Match.- Messages:
client_msg_id; interactions:client_interaction_id; other POST and PATCH:Idempotency-Key(UUIDv7). Only final responses are stored, not401,429and5xx. - Cursors are bound to filter and sort order, keyset-based, without a total count. Thread history
via
thread_seq, events viaseq.
Consequences¶
- Good, because HTTP libraries and proxies know Problem Details and clients branch on
code. - Good, because the outbox can repeat every request blindly.
- Good, because keyset pages are stable and cheap under load.
- Bad, because relative
typeURIs deviate from the recommendation "absolute URIs" in RFC 9457; a later switch would be a breaking change. - Bad, because without a total count the interface shows no "Page 3 of 12"; this is intended.
- Bad, because two idempotency paths exist; the reason is the permanent unique index for messages (04, 5.1), which has no expiry date.
Confirmation¶
Lint (G1): every operation documents 4xx responses from components.responses with
application/problem+json; examples are checked against schemas. Schemathesis (G7) checks status,
media type and schema of every response and custom rules: problem format on every error,
Correlation-Id in every response, the same 404 for foreign and missing IDs. Integration tests
check retries with the same ID after a timeout, step-up and 429.
Pros and Cons of the Options¶
RFC 9457¶
- Good, because a standard with a media type and extension rules (ignore unknown fields).
- Neutral, because
titleis English; clients translate viacode.
Custom error format¶
- Good, because shorter.
- Bad, because every tool must learn it anew; no media type to tell it apart.
Client ID plus Idempotency-Key¶
- Good, because messages stay unique across days and reinstallations (04, 5.1).
- Bad, because two mechanisms in the docs.
Only Idempotency-Key¶
- Good, because one mechanism.
- Bad, because a key with an expiry (
sync.idempotency_key_ttl) does not protect outbox entries that are days old from duplicates.
Cursor without a total count¶
- Good, because stable with new rows and without a second query.
- Bad, because no jump to an arbitrary page.
Offset and limit with a total count¶
- Good, because familiar.
- Bad, because shifts with new rows; total counts over filtered sets are expensive and reveal sizes.
More Information¶
- RFC 9457: https://www.rfc-editor.org/rfc/rfc9457.html (retrieved 2026-10-10) [verified].
- draft-ietf-httpapi-idempotency-key-header, draft -07 expired: https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/ (retrieved 2026-10-10) [verified].
- RFC 9110, section 13.1.1 (If-Match, 412): https://www.rfc-editor.org/rfc/rfc9110.html (retrieved 2026-10-10) [verified].