Docs / API reference

Errors, limits and pagination

The Invoa API error format and codes, the 600 requests a minute rate limit and its headers, offset pagination, and safe retries with idempotency keys.

Every Invoa API error has the same JSON shape and a standard HTTP status code. This page lists the codes, the rate limit, how list endpoints page, and how to retry safely.

Error format

json
{
  "error": {
    "code": "validation",
    "message": "Invalid request body.",
    "details": [
      { "path": "lines.0.unitPrice", "message": "Invalid input: expected number, received string" }
    ]
  }
}
FieldTypeDescription
error.codestringMachine-readable code from the table below. Branch on this, not on message.
error.messagestringHuman-readable explanation. May change wording.
error.detailsarray or objectPresent on some errors. For validation errors, a list of { path, message } pointing at each invalid field.

Status codes and error codes

StatuscodeWhen it happens
400invalid_jsonThe body is not valid JSON.
400validationA field is missing or invalid. details lists each problem. Also returned by send when the contact has no email and you did not pass to.
401unauthenticatedNo API key in Authorization or x-api-key.
401invalid_api_keyThe key is unknown, malformed or revoked.
403insufficient_scopeThe key lacks the scope this endpoint needs. details.required_scope names it.
403plan_limitThe company is on the Free plan; the API is part of Core and Pro. details has plan, requiredPlan and upgradeUrl.
403forbiddenThe company may not use this option, for example a PDF template not on its plan.
404not_foundThe ID does not exist in this company.
409conflictThe record clashes with an existing one, such as an item sku already in use, or a concurrent change got there first. Fetch the latest state and try again.
409invalid_stateOnly from GET /documents/{id}/pdf when the document is still a draft.
422invalid_stateThe action does not fit the record's current state, for example issuing a document that is not a draft, sending a draft, voiding an accepted or converted quotation, recording a payment on a void or fully paid invoice, triggering a milestone that is already invoiced, or a date inside a period locked by your accountant.
422plan_limitThe plan does not allow this: the monthly invoice allowance is used up (quotations, proformas, delivery orders and receipts are not counted; details has used, limit and plan), or the feature needs Pro (details has feature, requiredPlan, trialAvailable and upgradeUrl).
422einvoice_window_closedVoiding a validated e-invoice more than 72 hours after validation. Issue a credit note instead.
429rate_limitedToo many requests. See Rate limits.
500internal_errorSomething failed on our side. Retry later, and quote the x-request-id if you contact support.

Successful requests return 200, 201 (created) or 202 (accepted for background processing). DELETE returns 204 with no body.

Rate limits

Each API key can make 600 requests per minute. The window resets at the start of each minute.

Every authenticated response carries these headers:

HeaderMeaning
x-ratelimit-limitRequests allowed per minute (600).
x-ratelimit-remainingRequests left in the current minute.
x-ratelimit-resetWhen the current window ends, as a Unix timestamp in seconds.

When you go over the limit, Invoa answers 429 rate_limited with details.reset:

json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests; slow down.",
    "details": { "limit": 600, "reset": 1760494860 }
  }
}

Wait until the reset time before sending more requests.

Pagination

List endpoints use offset pagination.

ParameterTypeDescription
limitintegerItems per page, 1 to 200. Default 50 (100 for GET /items).
offsetintegerHow many items to skip. Default 0.
bash
curl "https://app.invoa.io/api/v1/documents?type=invoice&limit=50&offset=50" \
  -H "Authorization: Bearer $INVOA_API_KEY"
json
{
  "data": [],
  "pagination": { "total": 123, "limit": 50, "offset": 50, "has_more": true }
}
FieldDescription
pagination.totalTotal items matching your filters.
pagination.limitThe limit applied.
pagination.offsetThe offset applied.
pagination.has_moretrue if there are more items after this page. Request the next page with offset + limit.

GET /webhooks is the one list that is not paginated.

Idempotency and safe retries

Invoa does not read an Idempotency-Key header. Retry safety depends on the endpoint:

RequestSafe to retry?
Any GETYes.
POST /documents with idempotencyKeyYes. A retry with the same key returns the document already created for that key, instead of creating a second draft. Keys are unique per company and never expire.
POST /documents/{id}/issue, /voidYes in effect. A repeat fails with 422 invalid_state because the document is already issued or void. Fetch the document to confirm the outcome.
POST /schedules/items/{id}/triggerYes in effect. Each milestone creates one invoice; a repeat fails with 422 invalid_state.
POST /einvoice/{documentId}/submitYes in effect. A repeat fails with 422 invalid_state once the submission is queued.
POST /paymentsNo. A retry records a second payment. Before retrying, check GET /payments?documentId=….
POST /documents/{id}/sendNo. A retry emails the customer again.
POST /items with a skuYes in effect. A repeat fails with 409 conflict because the SKU is taken.
POST /contacts, POST /items without a sku, POST /schedules, POST /webhooksNo. A retry creates a duplicate. Search first.

Use an idempotencyKey that comes from your own system, such as shop-order-8812 or booking-1001-deposit. It must be 8 to 128 characters.

For 429 and 5xx responses, retry with exponential backoff, but only for requests that are safe to repeat.

Something unclear or out of date? Tell us at hello@invoa.io and we'll fix the page.