Developers

Errors

Envelope, typed codes, rate limits, and versioning policy.

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

StatusFamilyTypical code
400Bad requestMalformed JSON
401AuthUNAUTHORIZED / INVALID_API_KEY
403Scope / platformSCOPE_INSUFFICIENT / PLATFORM_UNSUPPORTED
404Not foundRESOURCE_NOT_FOUND / LINE_NOT_FOUND
409Business conflictsee below
422ValidationVALIDATION_FAILED (with field_errors)
429Rate limitRATE_LIMITED (with Retry-After header)
500InternalINTERNAL_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_ENDED — the series' end date is final (it has passed, or a credit note already reversed part of it) and cannot be moved.
  • 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 the accounting platform has entries against them.
  • PLATFORM_NOT_CONFIGURED — the connection is missing a choice its platform requires of every record (an e-conomic agreement whose document defaults were never confirmed). Nothing about your request is wrong; an admin finishes the Configure step in the workspace. See Accounting platforms.
  • DELETE_BLOCKED_BY_REFERENCES — a customer or product delete blocked because Invoia still holds the record: invoices, recurring series or credit notes for a customer; invoice lines, series lines or one-time lines for a product. The message names which. Nothing was deleted on either side — the reference is permanent, so retrying will not help.

Platform-independent by contract

Codes do not vary by accounting platform. PLATFORM_UNSUPPORTED (403) is the one refusal a platform difference produces, and DINERO_API_ERROR (502) is the frozen name for "the accounting platform behind your integration failed" whichever platform that is. Accounting platforms has the full list.

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 writes that stay inside Invoia. A generous default.
  • Platform-writes — a separate, stricter bucket for the endpoints that write to the accounting platform (issue, credit-note), whichever platform your key is on. 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 code should 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.