Tabba.
IntegrationerSådan virker TabbaFeatures

© 2026 Tabba. Alle rettigheder forbeholdes.

Udviklere
Udviklere
Udviklere
Getting startedQuickstartAuthenticationAccounting platformsexternal_id and idempotencyWork registrationIssuance and auto-sendWebhooksPaginationErrors

Andre spor

AI clientsAPI reference

Errors

The error envelope, every code, what to retry, rate limits, and the versioning policy.

The API answers with real HTTP status codes, so res.ok and ordinary error handling work. Every failure has the same body:

{
  "error": {
    "type": "invalid_request_error",
    "code": "VALIDATION_FAILED",
    "message": "The customer failed validation.",
    "field_errors": {
      "email": ["EMAIL_REQUIRED_FOR_EMAIL_SEND"],
      "vat_zone_ref": ["VAT_ZONE_REQUIRED"]
    },
    "request_id": "req_…"
  }
}
FieldMeaning
typeThe family: authentication_error, permission_error, invalid_request_error, rate_limit_error or api_error
codeThe stable code to branch on
messageEnglish, for a log or a developer. Not for an end user
field_errorsPer field of the request, the codes of what to fix. Present where a field is at fault
refusalWhy the accounting system refused, where it did
record_idOn CREATE_UNFINISHED only: the id of the record the request did create
request_idThe id of the request

Branch on code, never on message. Codes are added and never renamed. An unknown code should fall through to a generic handler. A success is the bare resource, with an object field that names its kind, and has no envelope around it.

Status codes

StatusMeaningCodes
400The request is malformedBAD_REQUEST
401The key is missing, unknown or revokedUNAUTHORIZED, INVALID_API_KEY
403The key lacks the scope the endpoint needsSCOPE_INSUFFICIENT
404The company holds no such recordNOT_FOUND and the resource's own, below
409The request runs into the state of the recordsee below
422The request failed validation, or the accounting system refused itVALIDATION_FAILED and the refusals below
429Over a rate limitRATE_LIMITED
500An error on Tabba's sideINTERNAL_ERROR, DB_ERROR, MIRROR_WRITE_FAILED, REGISTRATION_FAILED
502The accounting system did not answer, or did not finishPLATFORM_API_ERROR, CREATE_UNFINISHED

Every code

The key and the request

CodeStatusMeaning
UNAUTHORIZED401The request carries no Authorization: Bearer header
INVALID_API_KEY401The key is malformed, unknown or revoked
SCOPE_INSUFFICIENT403A read key on an endpoint that needs read_write
RATE_LIMITED429Over a rate limit. Retry after Retry-After seconds
BAD_REQUEST400The body is not JSON, or a reference is not in one of its forms
VALIDATION_FAILED422The body or the query failed validation. See field_errors
NOT_FOUND404A path names a record the company does not hold

Customers, products and people

CodeStatusMeaning
DUPLICATE_EXTERNAL_ID409Another record of the same kind carries the external_id
DUPLICATE_PRODUCT_NUMBER409A product with the number exists under another external_id
DELETE_BLOCKED_BY_REFERENCES409An invoice, a recurring series or a credit note of the company names the record. The accounting system was not asked, and nothing was deleted
DELETE_BLOCKED_BY_BOOKINGS409The accounting system holds bookings against the record. Nothing was deleted
RECORD_ARCHIVED409The external_id names a record the accounting system has switched off. It is neither updated nor created again until it is offered once more
CREATE_IN_PROGRESS409Another request under the same external_id is still creating the record. Carries Retry-After
CREATE_UNFINISHED502The accounting system made the record and the create did not finish. record_id names it

Invoices

CodeStatusMeaning
INVOICE_NOT_FOUND404A body names an invoice the company does not hold
INVOICE_ALREADY_ISSUED409The invoice is issued and frozen. Raise a credit note
INVOICE_NOT_ISSUED409A credit note was asked for an invoice that is not issued
INVOICE_IS_OCCURRENCE409The invoice is an occurrence of a recurring series, and is written through its series
ISSUE_IN_PROGRESS409Another attempt holds the invoice. Carries Retry-After
HOURLY_INCOMPLETE409An hourly line is not fully complete. Nothing was issued
NOTHING_TO_BILL409The invoice has no hours to bill. Nothing was issued
CUSTOMER_GONE409The accounting system no longer holds the customer
PRODUCT_GONE409The accounting system no longer holds a product of the invoice
MISSING_LINES422The invoice has no lines to issue
INVALID_SEND_METHOD422The customer cannot be sent to this way. Nothing was issued
MISSING_EINVOICE_DETAILS422The customer lacks e-invoice details. Nothing was issued
ISSUE_REFUSED422The accounting system refused to book the invoice. See refusal

Credit notes

CodeStatusMeaning
INVOICE_ALREADY_CREDITED409A full credit was asked for an invoice that already carries a credit
NOTHING_TO_CREDIT409Nothing the invoice billed is left to credit
CREDIT_IN_PROGRESS409Another credit of the invoice is running. Carries Retry-After
CREDIT_ATTEMPT_UNRESOLVED409An earlier credit of the invoice got no definite answer. Send the request that made it again first
CREDIT_REFUSED422The accounting system refused to book the credit note. See refusal

Recurring series and occurrences

CodeStatusMeaning
SERIES_NOT_FOUND404A body names a recurring series the company does not hold
SERIES_LINE_NOT_FOUND404The line is not on that occurrence of the series
OCCURRENCE_OUT_OF_RANGE404The date starts no billing period of the series
SERIES_NOT_ACTIVE409The series is a draft, and nothing is invoiced from it
SERIES_SETUP_LOCKED409The series has issued an invoice: its customer, currency, schedule and title are locked
OCCURRENCE_ISSUED409The occurrence is issued and its lines are frozen
OCCURRENCE_SKIPPED409An admin skipped the occurrence
DELETE_EMPTIES_OCCURRENCE409Taking the line away would leave an occurrence that is an invoice with no lines
SCHEDULE_DROPS_REGISTERED_WORK409The change leaves out occurrences with registered work. Send acknowledge_dropped_work: true to go ahead
END_HAS_PASSED409The end date of the series has passed and cannot be moved
END_CREDITED409A credit note has settled the end of the series, and it cannot be moved
END_BLOCKED_BY_CREDIT409An invoice of the series carries a credit that is not finished
SERIES_END_CONFLICT409The series changed while its end was being set. Nothing was written
OCCURRENCE_HAS_NO_LINES422The series states no line for the occurrence
END_BEFORE_START422The end date lies before the start of the series

Work registrations

CodeStatusMeaning
LINE_NOT_FOUND404The invoice or the occurrence has no such line
NOT_ASSIGNED409The person holds no place on the line
HOURS_BILLED_LOCKED409The hours were billed when the invoice was issued, and are locked
REGISTRATION_FAILED500The registration could not be saved

The accounting system, and Tabba's own side

CodeStatusMeaning
PLATFORM_REFUSED422The accounting system refused the write. Nothing was written. See refusal
PLATFORM_NOT_CONFIGURED409The company's connection is missing a setting the write needs. An admin completes it in the workspace
PLATFORM_UNAVAILABLE409The company has no accounting system to write to
PLATFORM_API_ERROR502The accounting system could not be reached, or did not answer. It may or may not hold the change
MIRROR_WRITE_FAILED500The accounting system took the write, and Tabba could not record it
DB_ERROR500A read or a write on Tabba's side failed
INTERNAL_ERROR500An error on Tabba's side

No code names an accounting system: see Accounting platforms.

Field errors

field_errors holds, per field of the request, the codes of what to fix. A field is named by its dotted path as the request spells it, such as lines.0.agreed_value, and _ stands for the value as a whole. Where a rule has a code of its own the entry is that code, such as VAT_ZONE_REQUIRED. Otherwise it is a sentence that says what the field should have been.

A customer, a product or a person that a body names and the company does not hold is reported here, as CUSTOMER_NOT_FOUND, PRODUCT_NOT_FOUND or ASSIGNEE_NOT_FOUND under the field.

Refusals by the accounting system

A refusal is data, not an outage. Where the accounting system said no, the error carries what it said:

{
  "error": {
    "type": "invalid_request_error",
    "code": "ISSUE_REFUSED",
    "message": "The platform refused the invoice. Nothing was booked.",
    "refusal": {
      "reason": "…",
      "params": {},
      "platform_reason": "…"
    },
    "request_id": "req_…"
  }
}

reason is a code of Tabba's own, params are its parameters, and platform_reason is the accounting system's own words, or null where it gave none. Show platform_reason to a person as the accounting system's words. An outage is PLATFORM_API_ERROR, never a refusal.

What to retry

AnswerWhat to do
429Wait Retry-After seconds, then send the request again
409 with CREATE_IN_PROGRESS, ISSUE_IN_PROGRESS or CREDIT_IN_PROGRESSWait Retry-After seconds, then send the same request again. It finds what the other request came to
502 with PLATFORM_API_ERRORRetry with backoff. A create sent under an external_id is safe to send again unchanged within the hour. A create sent without one is not: it can make a second record
502 with CREATE_UNFINISHEDSend the same request again under its external_id, or a PATCH by record_id
500Retry with backoff, and quote the request_id if it persists
400, 401, 403, 404, 422, and every other 409Do not retry unchanged. The same request is refused again

Issuing an invoice and creating a credit note are safe to send again whatever the first answer was: an issued invoice and a booked credit note are answered as they stand.

request_id

Every response carries the id of its request in the X-Request-Id header, and every error repeats it in the body as request_id. It is req_ and a random string. Log it beside your own trace id, and quote it when you ask about a call. Every call under a key is in the account's activity log under the same id.

Rate limits

Limits are counted per key, in a sliding window of 60 seconds.

BucketLimitCounted for
default300 requests per 60 seconds, per keyEvery endpoint
platform_write30 requests per 60 seconds, per keyOn top of default, the endpoints that make the accounting system book something inside the request: POST /invoices/{ref}/issue, POST /recurring-series/{ref}/occurrences/{period}/issue and POST /credit-notes
unauthenticated60 requests per 60 seconds, per client addressA request with no key, or with a key that is not a live key of Tabba's

Every metered response carries three headers. They state the bucket with the least room left:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1782892860

X-RateLimit-Reset is when the window has room again, in seconds since the epoch. Past a limit the answer is 429 with RATE_LIMITED, and one header more:

Retry-After: 12

The separate platform_write bucket means that a retry loop on an issue spends its own budget and leaves your reads alone. A request refused for its scope spends nothing.

A request that carries no key, or a key that is unknown or revoked, is counted by the address it came from. Past that limit it is answered 429 in place of the 401. A request under your live key is not counted there, whatever else the same address sends.

Versioning

Changes within /api/v1 are additive. Within it, Tabba may:

  • Add endpoints.
  • Add optional fields to a request body.
  • Add fields to a response body.
  • Add event types to webhooks.
  • Add error codes.

Within /api/v1, an endpoint, a field and an error code are not renamed, and a code is not moved to another status. Write your client so that an unknown field is ignored and an unknown code reaches a generic handler.

Pagination

Cursors, the order of a list, and following a list with updated_since.

På denne side

Status codesEvery codeThe key and the requestCustomers, products and peopleInvoicesCredit notesRecurring series and occurrencesWork registrationsThe accounting system, and Tabba's own sideField errorsRefusals by the accounting systemWhat to retryrequest_idRate limitsVersioning

Denne side

Vis som MarkdownÅbn i Claude