Conventions
Common conventions across all v1 endpoints — request IDs, idempotency, cursor pagination, versioning, and transport rules.
Request IDs
Each request gets an ID in ULID format prefixed with req_. This ID is returned in 2 headers (aliased):
X-Request-ID: req_01J9X8Z3F4K2M5N7P9Q1R3S5T7Capturia-Request-ID: req_01J9X8Z3F4K2M5N7P9Q1R3S5T7
Provide this ID to support to retrieve the incident in Sentry/logs.
Idempotency
Mutating routes (POST / PUT / DELETE) accept an optional Idempotency-Key header to ensure a retry doesn't execute the operation twice.
Idempotency-Key: a1b2c3d4-1234-4567-89ab-cdef01234567Accepted format:
[a-zA-Z0-9_-]{1,64}(typically a UUID v4)Storage TTL: 24h in Vercel KV
Scope: per API key (two different keys can reuse the same Idempotency-Key without conflict)
Behaviors
| Scenario | Outcome |
|---|---|
| First execution | Endpoint executed normally, response cached 24h |
| Replay (same payload) | Original response replayed verbatim, same status, |
| Conflict (different payload) |
|
| Concurrent request (lock active) |
|
Pagination
All GET collection endpoints use opaque cursor-based pagination:
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
limit | int | 20 | 100 | Page size (max 100, > 100 → |
cursor | string | — | — | Opaque base64url cursor returned in the previous response's |
{
"data": [...],
"pagination": {
"next_cursor": "eyJ2IjoxLCJmIjoiY3JlYXRlZF9hdCIsInMiOiJkZXNjIiwidmFsIjoiMjAyNi0wNS0wNFQxMDowMDowMFoiLCJpZCI6ImN0X2FiYyJ9",
"has_more": true,
"limit": 20
}
}Error format
All errors follow a unified envelope. See Error catalog for the exhaustive list of codes.
Versioning and deprecation
URL versioning (/v1/, future /v2/). A major version is bumped only for breaking changes — backwards-compatible additions stay in the current version.
Minimum 12-month delay between deprecation announcement and removal
Headers emitted on deprecated endpoints:
Deprecation(RFC 9745) +Sunset(RFC 8594) +Link: <url>; rel="deprecation"Public changelog page:
/developers/api/v1/changelog
Dry-run mode
The ?dry_run=true query param is parsed by the route wrapper but support varies per endpoint. Check each endpoint's reference to know whether dry-run is implemented.
Content-Type
Strict application/json (with or without charset=utf-8). Refused: multipart/form-data, application/x-www-form-urlencoded, etc → 415 invalid_content_type.
Body size
Limit: 1 MB per request. Over → 413 payload_too_large. For large batches (>100 contacts), use multiple smaller requests with distinct Idempotency-Key.
Encoding
Strict UTF-8. Any other charset rejected → 400 invalid_encoding.
Compression
Handled automatically by Vercel: Accept-Encoding: gzip, br supported. The Vary: Accept-Encoding header is emitted for intermediate caches.