Developer essentials
Errors
Every refused request answers with the rule it broke, a message for a person, and a JSON pointer into the request when one applies. Extra keys carry context.
The error object
{ "rule": "ORIGIN_DUPLICATE", "message": "An origin with externalReference 2026-000117 already exists",
"path": "/externalReference", "existingId": "org_06g6…" }
| rule | string | Stable, machine-readable. Branch on it. |
| message | string | For a person. May change; do not parse it. |
| path | string | JSON pointer into the request body, when the problem is one field |
| … | Context named by the rule: existingId, differs, scope, status, issues[] |
The status code says who has to act. 4xx is yours: the request, the key, the state of the object. 5xx is ours: retry with the same idempotency key.
Rules by status
| Status | Rule | |
|---|---|---|
| 400 | VALIDATION_FAILED | Body or query did not match the schema. issues[] lists every problem with its path. |
| 400 | ACTOR_REQUIRED · ACTOR_INVALID | See actors |
| 400 | IDEMPOTENCY_KEY_REQUIRED | Every POST needs one |
| 400 | AMOUNT_NOT_INTEGER | Money is integer minor units |
| 400 | PARTY_UNRESOLVED | No identifier to resolve or create a party from |
| 401 | UNAUTHENTICATED | No key, unknown key, revoked key |
| 403 | SCOPE_MISSING | The grant lacks the scope; scope names it |
| 403 | COMPANY_NOT_SANDBOX | A sandbox endpoint on a live company |
| 404 | COMPANY_NOT_FOUND | No grant on the company, or no such company |
| 404 | ORIGIN_NOT_FOUND · PARTY_NOT_FOUND · POSTING_NOT_FOUND · ATTACHMENT_NOT_FOUND · EXTERNAL_ACCOUNT_NOT_FOUND · FISCAL_YEAR_NOT_FOUND · NOT_FOUND | |
| 409 | ORIGIN_DUPLICATE | Same content or same external reference already received; existingId names it |
| 409 | IDEMPOTENCY_CONFLICT · IDEMPOTENCY_IN_PROGRESS | differs says whether the path or the body changed. See Idempotency. |
| 413 | ATTACHMENT_TOO_LARGE | 10 MB decoded |
| 422 | ENTRY_MUST_BALANCE · POSTING_PARTY_REQUIRED | Ledger invariants; see what Vexless refuses |
| 422 | ACCOUNT_NOT_FOUND · ACCOUNT_ARCHIVED · TAX_CODE_NOT_FOUND | |
| 422 | DIMENSION_REQUIRED · DIMENSION_DUPLICATE_CATEGORY | |
| 422 | DATE_TOO_FAR_AHEAD | More than 60 days ahead; manual entries are exempt |
| 422 | PERIOD_LOCKED · FISCAL_YEAR_CLOSED | An existing entry on or before the lock date cannot change |
| 422 | ORIGIN_INVALID_STATE · PAYMENT_INVALID_STATE | The object is not in a state that allows this action; status says which |
| 422 | ORIGIN_NOT_REVERSIBLE | Only POSTED or SETTLED origins reverse, and never an invoice with payments allocated |
| 422 | PAYMENT_NO_PAYEE_IBAN | Your company has not supplied an IBAN for the seller; add one with a party PATCH |
| 422 | SETTLEMENT_INVALID · SETTLEMENT_ACCOUNT_MISMATCH · ALLOCATION_EXCEEDS_REMAINING · POSTING_NOT_OPEN_ITEM | See allocation rules |
| 422 | FISCAL_YEAR_NOT_READY · LOCK_BLOCKED | Origins in the year are still uninterpreted; the lock only moves forward |
| 500 | INTERNAL | Retry with the same idempotency key |
Validation errors
{ "rule": "VALIDATION_FAILED", "message": "Invalid input: expected string, received undefined", "path": "/lines/0/description",
"issues": [ { "path": "/lines/0/description", "message": "Invalid input: expected string, received undefined" },
{ "path": "/dueDate", "message": "ISO date YYYY-MM-DD" } ] }
The schema is the OpenAPI document; the same schemas that produce these errors are published in it, so a client generated from it never sends a request that fails here.
Handling
- Branch on
rule, never onmessage. - Show
messageandpathto the person who can fix the input, when there is one. - Treat
ORIGIN_DUPLICATEas success withexistingId. - Treat 5xx and timeouts as retryable with the same idempotency key.