Errors
Every error the API returns, as RFC 9457 problem details.
Every failure is an RFC 9457 problem document (application/problem+json) whose
type URI ends in one of the slugs below, with a machine-readable error property
carrying the same code. This page is a build gate: the server cannot ship an error
code that is not documented here.
{
"type": "https://api.accountflow.com/errors/resource-not-found",
"title": "Resource Not Found",
"status": 404,
"detail": "Resource not found",
"error": "resource_not_found",
"requestId": "6f1c2d3e-…"
}
| Code | Status | Meaning | What to do |
|---|---|---|---|
payload_too_large |
413 | An import request carries more lines than one request may hold (10 000 — the limit extension says so), or the request body is larger than 8 MiB (refused before it is parsed). |
Split the data into several requests and send them one after another: nothing queues, and a second import of the same kind for the same company while one is running is 409 conflict with reason: job_in_flight. |
resource_not_found |
404 | The id doesn't exist within your reach. Deliberately indistinguishable from "exists but isn't yours" and "deleted". | Check the id; check you're using the right client for the right organization. |
duplicate_resource |
409 | The thing you're creating already exists (same company number in the org, same policy name, same assignment, duplicate rule). | Fetch the existing resource instead, or change the distinguishing field. |
conflict |
409 | Valid request, wrong state: an in-flight job for the same scope (jobId names it; reason: job_in_flight), a company not bound to the Api accounting system (reason: company_not_api_managed), an expired/incomplete upload slot, an archived policy, a GL line already reconciled this year, a missing GL mapping, an object in use (reason: *_in_use, inUse lists them), settings that changed since the version you named (currentVersion is the one now), an account template that changed since its preview, part of an account already on another intercompany link (reason: intercompany_account_owned, intercompany_account_shared, intercompany_account_split_differently, intercompany_slice_owned; definitionId and active name that link). |
Read reason and the extensions; resolve the state (poll the job, finish the upload, unmatch first, remove the dependent work; for settings, read them again and re-apply your change) and retry. |
deleted_by_organization_cascade |
409 | The company was deleted as part of its organization's deletion — restoring it alone is impossible. | Restore the organization instead. |
validation_failed |
400 | The request is malformed: a required field is missing or blank, a value is out of range (pageSize outside 1–200, period outside 1–12), a parameter cannot be parsed. |
Fix the request; detail names the field, and the errors / parameter extensions say which. |
unprocessable |
422 | The request is well-formed but semantically wrong: unknown role, foreign company on a rule, size over the type's cap, declared-vs-actual mismatch (an upload whose content is recognisably another type is deleted), an invalid organization number, an account code missing from the year's chart (reason: unknown_account_codes), a line id containing a forbidden character, an account template apply with warnings not confirmed (reason: confirmation_required, warnings lists them). |
The detail explains the rule that failed; the errors extension lists the offending lines; for confirmation_required, have a person confirm the warnings and send them as confirmedWarnings. |
malware_detected |
422 | The uploaded file failed the virus scan at complete_document_upload. The file is deleted and no document is created. |
Do not retry the same file. A clean file can be uploaded to the same slot while its URL is valid, or through a new upload. |
unauthorized |
401 | Missing, expired, or invalid token. WWW-Authenticate distinguishes only no token (a bare Bearer challenge) from a token that was rejected (error="invalid_token") — it does not say whether the rejected token was expired, mis-audienced or malformed. |
Fetch a fresh token; check audience and issuer. |
forbidden |
403 | Authenticated, but this action is denied. | Check the client's provisioning in Accountflow. |
capability_denied |
403 | This kind of principal can never do this (e.g. an MCP sign-in calling /v1; an interactive session writing). |
Use an API client (client credentials) for /v1. |
insufficient_scope |
403 | The token lacks a required scope — WWW-Authenticate names it (scope="..."), as does the requiredScope extension. |
Request the scope (interactive) or have it granted (machine clients). |
rate_limited |
429 | Plan quota exceeded. Retry-After (and retryAfterSeconds) say when to come back. |
Back off per Retry-After; consider the premium plan for sustained volume. |
idempotency_key_missing |
400 | Every mutation requires an Idempotency-Key header. |
Send a unique key per logical operation; retries reuse the same key. |
idempotency_key_reused |
422 | The key was used before with a different request body (within the 24-hour replay window, for the same operation). | Never reuse keys across different operations; generate a UUID per logical action. |
idempotency_in_flight |
409 | The same key's first request is still executing. Retry-After is set. |
Wait and retry with the same key — you'll get the stored response. |
invalid_cursor |
400 | The cursor is expired, tampered with, from another client, or from a request with different filters. | Restart from the first page. Never modify or share cursors. |
method_not_allowed |
405 | The path exists; the verb doesn't. | Check the reference. |
internal_error |
500 | Our bug. The response's requestId lets support find it immediately. |
Retry with the same Idempotency-Key (safe); report the requestId if it persists. |
not_implemented |
501 | Reserved. No operation returns it today; the slug stays in the catalog so that the code can never surface it undocumented. | If you ever see it, report the requestId. |
service_unavailable |
503 | A dependency (e.g. the authorization backend, the import engine, the semantic layer, the virus scanner at upload completion — the uploaded file is kept) is unavailable. Fails closed — never wrong data. Retry-After is set. |
Retry with backoff; mutations are idempotent-safe to retry with the same key. |
unsupported_media_type |
415 | The declared content type isn't accepted (document uploads validate against the allowlist; the supported extension lists it). |
GET the reference for accepted types and size caps. |
Problem extension fields
Beyond the RFC 9457 members (type, title, status, detail, instance) every
problem document carries error (the slug above) and requestId (the request's
correlation id — the same value as the X-Request-Id response header). The spec
declares these extensions on the operations that can produce them; each is present
only when it applies:
| Extension | Type | Present on | Meaning |
|---|---|---|---|
reason |
string, snake_case |
conflict, unprocessable |
The specific rule that fired, e.g. job_in_flight, company_not_api_managed, unknown_account_codes, lines_in_use, account_in_use, confirmation_required, intercompany_slice_owned. Branch on error first, then on reason. |
currentVersion |
string | conflict on a settings write |
The settings' version now; read them again before re-applying your change. |
warnings |
array of strings | unprocessable with reason: confirmation_required |
The account-template warnings still to be confirmed (TEMPLATE_CHANGED, DOCUMENTATION_REQUIREMENT_REMOVED, AUTO_APPROVE_ENABLED). |
definitionId, active, counterparty |
UUID, boolean, string | conflict with reason: intercompany_* |
The intercompany link that already has that part of the account, whether it is active, and its counterparty. |
inUse |
array of {id, dependencies} |
conflict with reason: *_in_use |
The objects (by your id or key — never an internal id) that user work depends on, and what depends on them. |
accountCodes |
array of strings | unprocessable with reason: unknown_account_codes |
The codes missing from the year's chart of accounts. |
errors |
array of error items (below) | validation_failed, unprocessable |
Field-level detail, up to 100 items. |
limit |
integer | payload_too_large |
The cap that was exceeded (lines per request). |
jobId |
UUID | conflict with reason: job_in_flight, idempotency_in_flight |
The job or request that is blocking this one — poll it. |
retryAfterSeconds |
integer | rate_limited, service_unavailable, idempotency_in_flight |
The Retry-After header as a number. |
requiredScope |
string, resource:action |
insufficient_scope |
The scope the token lacks. |
parameter |
string | validation_failed, invalid_cursor |
The query or path parameter at fault (pageSize, cursor, year, …). |
supported |
array of strings | unsupported_media_type, validation_failed |
The accepted values when the request named one outside them (content types, filter values). |
The errors item — one shape everywhere
{ "field": "lines[12].amount", "message": "amount is required", "index": 12 }
field— the field at fault (empty when a whole line is malformed);message— human-readable, not for branching;index— present when the error addresses one element of a request array, 0-based;allowedValues— present when the field is an enumeration and the value was outside it.
Every validation_failed and unprocessable response uses this one item shape; there
is no per-endpoint variant.