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…" }
rulestringStable, machine-readable. Branch on it.
messagestringFor a person. May change; do not parse it.
pathstringJSON 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

StatusRule
400VALIDATION_FAILEDBody or query did not match the schema. issues[] lists every problem with its path.
400ACTOR_REQUIRED · ACTOR_INVALIDSee actors
400IDEMPOTENCY_KEY_REQUIREDEvery POST needs one
400AMOUNT_NOT_INTEGERMoney is integer minor units
400PARTY_UNRESOLVEDNo identifier to resolve or create a party from
401UNAUTHENTICATEDNo key, unknown key, revoked key
403SCOPE_MISSINGThe grant lacks the scope; scope names it
403COMPANY_NOT_SANDBOXA sandbox endpoint on a live company
404COMPANY_NOT_FOUNDNo grant on the company, or no such company
404ORIGIN_NOT_FOUND · PARTY_NOT_FOUND · POSTING_NOT_FOUND · ATTACHMENT_NOT_FOUND · EXTERNAL_ACCOUNT_NOT_FOUND · FISCAL_YEAR_NOT_FOUND · NOT_FOUND
409ORIGIN_DUPLICATESame content or same external reference already received; existingId names it
409IDEMPOTENCY_CONFLICT · IDEMPOTENCY_IN_PROGRESSdiffers says whether the path or the body changed. See Idempotency.
413ATTACHMENT_TOO_LARGE10 MB decoded
422ENTRY_MUST_BALANCE · POSTING_PARTY_REQUIREDLedger invariants; see what Vexless refuses
422ACCOUNT_NOT_FOUND · ACCOUNT_ARCHIVED · TAX_CODE_NOT_FOUND
422DIMENSION_REQUIRED · DIMENSION_DUPLICATE_CATEGORY
422DATE_TOO_FAR_AHEADMore than 60 days ahead; manual entries are exempt
422PERIOD_LOCKED · FISCAL_YEAR_CLOSEDAn existing entry on or before the lock date cannot change
422ORIGIN_INVALID_STATE · PAYMENT_INVALID_STATEThe object is not in a state that allows this action; status says which
422ORIGIN_NOT_REVERSIBLEOnly POSTED or SETTLED origins reverse, and never an invoice with payments allocated
422PAYMENT_NO_PAYEE_IBANYour company has not supplied an IBAN for the seller; add one with a party PATCH
422SETTLEMENT_INVALID · SETTLEMENT_ACCOUNT_MISMATCH · ALLOCATION_EXCEEDS_REMAINING · POSTING_NOT_OPEN_ITEMSee allocation rules
422FISCAL_YEAR_NOT_READY · LOCK_BLOCKEDOrigins in the year are still uninterpreted; the lock only moves forward
500INTERNALRetry 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 on message.
  • Show message and path to the person who can fix the input, when there is one.
  • Treat ORIGIN_DUPLICATE as success with existingId.
  • Treat 5xx and timeouts as retryable with the same idempotency key.