Errors
The API uses HTTP-native status codes so resp.ok and standard
handling work correctly. Failure bodies always follow one shape:
{
"error": {
"type": "validation_failed",
"code": "VALIDATION_FAILED",
"message": "Body failed schema validation.",
"field_errors": {
"name": ["Required"],
"email": ["Invalid email address"]
},
"request_id": "req_01H…"
}
}
Branch on code, not on message — code is stable; message is
free-form and may improve over time.
Status code map
| Status | Family | Typical code |
|---|---|---|
400 | Bad request | Malformed JSON |
401 | Auth | UNAUTHENTICATED |
403 | Scope | INSUFFICIENT_SCOPE |
404 | Not found | RESOURCE_NOT_FOUND / LINE_NOT_FOUND |
409 | Business conflict | see below |
422 | Validation | VALIDATION_FAILED (with field_errors) |
429 | Rate limit | RATE_LIMITED (with Retry-After header) |
500 | Internal | INTERNAL_ERROR |
Typed 409 codes
Codes you can branch on to distinguish business-rule conflicts:
INVOICE_ALREADY_ISSUED— you tried to edit a frozen invoice; raise a credit note.INVOICE_NOT_ISSUED— you tried to credit-note an unissued invoice.SERIES_CANCELLED— a write against a cancelled series.OCCURRENCE_NOT_FOUND— no current occurrence (past horizon, paused).OCCURRENCE_SKIPPED— the occurrence was explicitly skipped.DELETE_BLOCKED_BY_BOOKINGS— a customer delete blocked because Dinero has bookings against them.
request_id
Every response — success or failure — includes a request_id. Log it
alongside your integration's own trace id. If you open a support
ticket, the request_id lets us find your exact call in seconds.
Rate limiting
Every response carries three rate-limit headers:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1717000000
On 429, the response adds:
Retry-After: 12
Two buckets:
- General — reads and non-Dinero writes. A generous default.
- Dinero-writes — a separate, stricter bucket for endpoints that
hit Dinero (
issue,credit-note). This protects the expensive operations without throttling your reads.
Retry with exponential backoff on Retry-After. Do not retry on
400/401/403/404/422 — those will not succeed on retry with the same
payload.
Versioning
The URL major (/v1) is additive-only. Within a major, we may:
- Add new endpoints.
- Add new optional fields on request bodies.
- Add new fields on response bodies.
- Add new event types to webhooks.
- Add new error codes (branch defensively: an unknown
codeshould fall through to a generic handler).
We will NOT, within /v1:
- Remove endpoints.
- Remove or rename fields.
- Change the meaning of an existing
code. - Tighten validation on an existing endpoint in a way that would reject payloads that used to succeed.
A breaking change ships as /v2. Both majors are supported in parallel
during a deprecation window announced in the changelog.