Errors
Every error response uses the same envelope:Error codes
| Code | HTTP status | Description |
|---|---|---|
validation_error | 400 | Request body or parameters are invalid |
unauthorized | 401 | Missing or invalid API key |
forbidden | 403 | API key does not have access to this resource |
not_found | 404 | The requested resource does not exist |
idempotency_conflict | 409 | A request with this Idempotency-Key already succeeded with a different body |
caller_id_not_verified | 422 | The caller ID is not verified for your account |
internal_error | 500 | An unexpected server error occurred |
fields map contains a per-field explanation so you can surface the right error next to the right input.
Idempotency
EveryPOST, PUT, PATCH, and DELETE endpoint supports the Idempotency-Key header. Set it to a unique value (a UUID is recommended) on your first request, and retry the same request with the same header to safely replay.
Semantics
- First request with a key. We run the handler and persist the response under the key.
- Repeat with the same key and same body. We replay the saved response verbatim, including status code. The replay carries an
Idempotency-Replay: trueresponse header so you can tell. - Repeat with the same key but a different body. We return
409 idempotency_conflictwith the original key’s request id infields.conflicts_with.
TTL
Idempotency records live for 24 hours after the first successful response. After the window, reusing the key is treated as a fresh request.Best practices
- Use a new key for each new logical operation. Reusing keys across different calls drops traffic.
- The key is scoped to the API key it was submitted under. Rotating keys resets the idempotency window.
- Idempotency only protects the RevDesk side. Don’t rely on it for cross-process transactions (for example, a call placed plus a Stripe charge).
When to set one
- Retrying a request after a network timeout — critical.
- Background jobs that may re-run after a worker crash — critical.
- Interactive UIs with a double-click-vulnerable button — nice to have.
Pagination
Every list endpoint returns the same envelope:?cursor=<meta.cursor> on the next request to walk to the next page. meta.cursor is omitted on the last page. Cursors are opaque to clients — treat the value as a bag of bytes, do not parse it.
Example
Limits
limitdefault is 20, max 100.- Cursors are valid for 24 hours from issuance. Requesting an expired cursor returns
400 validation_errorwithfields.cursor. - Ordering is by creation time, newest first for every list endpoint. Use
?order=ascwhere supported to reverse.