Errors always use the same format: a machine-readable code in error, a human-readable message in message and, where needed, details.
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Language: en
{
"error": "validation_error",
"message": "The request is invalid. See details for the affected fields.",
"details": [
{ "path": "lines.0.quantity", "message": "Too small: expected number to be >0" }
]
}
- Evaluate
error, not message – the code is part of the stable API contract, the text may change. message is written in English or German depending on the Accept-Language request header (en or de). Without a supported language, the API v1 responds in English.- For
validation_error, details contains a list with path (dot notation) and message per field. - Retrying is worthwhile for
429, 409 idempotency_in_progress and 5xx – after a pause and with the same Idempotency-Key. Other 4xx errors will not change if you retry.
HTTP status codes
| Status | Meaning |
|---|
| 400 | invalid request |
| 401 | not authenticated, or key/token invalid |
| 402 | quota used up or feature not included in the subscription plan (e.g. letters) |
| 403 | no permission |
| 404 | not found |
| 409 | current state does not allow the action (conflict) |
| 410 | resource no longer exists (e.g. import file) |
| 413 / 415 | file too large, or file type not allowed |
| 422 | cannot be processed for business reasons (e.g. required details missing) |
| 429 | too many requests |
| 500 | error at DebtFlow |
| 501 | not available |
| 502 | an external service (email, Stripe, letter dispatch) reported an error |
| 503 | temporarily unavailable |
General error codes
| Status | error | Meaning |
|---|
| 400 | validation_error | path, query or body parameters invalid; details lists the fields |
| 401 | unauthorized | no Bearer token, or token cannot be read |
| 401 | invalid_api_key | API key unknown, revoked or expired |
| 401 | invalid_token | OAuth access token unknown, expired or connection removed |
| 403 | forbidden | action not allowed for this account |
| 403 | insufficient_scope | key or app lacks the required permission; see details.missing |
| 403 | session_required | only possible in the dashboard (account management) |
| 403 | account_not_approved | account has not been approved yet |
| 403 | account_disabled | account is blocked |
| 403 | mfa_required | dashboard session without a completed second factor |
| 403 | demo_unavailable | blocked in the public demo account (sending, costs, AI, uploads, credentials) |
| 403 | plan_limit_reached | subscription plan limit reached (e.g. number of contacts) |
| 404 | not_found | resource does not exist or belongs to another account |
| 409 | conflict | current state does not allow the action |
| 409 | idempotency_in_progress | a request with the same Idempotency-Key is still running |
| 409, 422 | idempotency_conflict | Idempotency-Key was already used for a different request |
| 409 | limit_reached | maximum reached (e.g. webhook endpoints, API keys) |
| 413 | payload_too_large | file or request too large |
| 415 | unsupported_media_type | file type not allowed |
| 429 | rate_limited | too many requests; respect the Retry-After header |
| 500 | internal_error | error at DebtFlow; try again later |
| 501 | not_implemented | feature is not available (yet) |
| 503 | not_ready | service is starting or temporarily not ready; try again later |
Domain-specific error codes
This list is checked against the source code (automated test). If in doubt, react to the HTTP status: new, more specific codes may be added without the status changing.
Invoices, credit notes, quotes
| Status | error | Meaning |
|---|
| 409 | document_locked | document is finalised and immutable (GoBD, German bookkeeping rules); correct it with a cancellation invoice or credit note |
| 409 | number_conflict | document number already assigned – check the pattern of the number range |
| 409 | number_range_in_use | numbers have already been assigned in this period; the start number can no longer be changed |
| 409 | invoice_not_finalized | action only possible for finalised invoices |
| 409 | offer_not_finalized | action only possible for finalised quotes |
| 409 | has_credit_notes | cancellation not possible because credit notes exist – correct the remainder with a credit note |
| 409 | already_deducted | progress invoice has already been deducted in a final invoice |
| 409 | already_handed_over | invoice has already been handed over to dunning |
| 409 | already_paid | already paid in full |
| 409 | not_payable | document can no longer be paid |
| 409 | einvoice_not_available | no e-invoice exists for this document |
| 422 | invoice_incomplete | mandatory invoice details (§ 14 UStG, German VAT Act) missing; details lists them |
| 422 | overpayment | payment exceeds the outstanding amount |
| 422 | einvoice_invalid | e-invoice violates rules (XRechnung/ZUGFeRD); details lists them |
| 422 | einvoice_unreadable | file is not a readable e-invoice |
| 422 | einvoice_not_found | PDF contains no embedded e-invoice (ZUGFeRD/Factur-X) |
| 500 | pdf_fonts_missing | PDF could not be generated (fonts missing on the server) |
| 502 | email_failed | sending the email failed |
Debts, payments, letters
| Status | error | Meaning |
|---|
| 402 | quota_exceeded | quota (e.g. letters per month) used up |
| 402 | letters_not_available | letter dispatch not included in the plan or not set up |
| 409 | target_not_open | item is no longer open |
| 409 | amount_exceeds_open | amount exceeds the outstanding amount |
| 409 | payment_processing | payment is currently being processed |
| 409 | payment_unavailable | online payment not possible for this item |
| 502 | provider_failed | external service (e.g. letter provider) reported an error |
| 400 | invalid_token | Briefdiener token rejected (only /integrations/briefdiener) |
| 503 | unreachable | Briefdiener is temporarily unreachable |
Research and creditor AI
| Status | error | Meaning |
|---|
| 409 | inquiry_in_progress | a residents’ registration office request is already running for this contact (no double charges) |
| 422 | missing_contact_data | date of birth or known address of the contact is missing |
| 429 | ai_rate_limited | too many AI requests in a short time; respect details.retryAfter |
| 429 | ai_quota_exhausted | daily AI quota used up |
| 502 | ai_failed | AI service did not return usable text; try again later |
| 503 | ai_unavailable | creditor AI is not set up |
Bank
| Status | error | Meaning |
|---|
| 400 | invalid_iban | IBAN invalid |
| 409 | duplicate_iban | a bank account with this IBAN already exists |
| 409 | iban_mismatch | import belongs to a different account |
| 409 | account_archived | bank account is archived |
| 409 | account_has_transactions | bank account has transactions and cannot be deleted |
| 409 | already_matched | transaction is already matched |
| 409 | not_matched | transaction is not matched to any item |
| 409 | transaction_matched | a matched transaction cannot be changed |
| 409 | transaction_ignored | transaction is marked as ignored |
| 409 | direction_mismatch | match incoming payments only to open items and outgoing payments only to receipts |
| 409 | amount_exceeds_transaction | matches exceed the transaction amount |
| 409 | possible_duplicate | import probably contains transactions that were already imported |
| 409 | import_completed | import has already been completed |
| 409 | import_has_matches | import contains matched transactions and cannot be reverted |
| 409 | already_reverted | import has already been reverted |
| 410 | import_file_missing | import file no longer exists – upload it again |
| 422 | mapping_required | column mapping for the CSV file is missing |
| 422 | unprocessable_file | file could not be processed |
Receipts and imports
| Status | error | Meaning |
|---|
| 409 | already_assigned | receipt is already assigned |
| 409 | in_use | entry is still in use |
| 422 | file_missing | file missing |
| 422 | ocr_unreadable | OCR could not read the file |
| 422 | ocr_unsupported | file type not supported by OCR |
| 422 | ocr_file_too_large | file is too large for OCR |
| 400 | invalid_payload | incoming data (e.g. inbound email) invalid |
| 400 | invalid_signature | signature of an incoming call invalid |
Accounting
| Status | error | Meaning |
|---|
| 409 | period_closed | period is closed (locked) |
| 409 | invalid_period | period invalid or overlapping |
| 409 | open_issues | open issues prevent closing; details lists them |
| 409 | closing_in_progress | closing is already in progress |
| 409 | chart_locked | chart of accounts can no longer be changed after the first journal entries |
| 409 | generated_entry | automatically generated journal entry – change the source instead of the entry |
| 409 | datev_numbers_missing | DATEV consultant/client number missing for the DATEV export |
Projects and time tracking
| Status | error | Meaning |
|---|
| 409 | project_has_entries | project has time entries |
| 409 | task_has_entries | task has time entries |
| 409 | time_entry_invoiced | time entry has already been invoiced |
| 409 | time_entries_not_billable | selected time entries are not billable |
| 409 | timer_not_running | no timer is running |
| 409 | invoice_active | time entries belong to an invoice that has not been cancelled and cannot be released |
| 422 | nothing_to_invoice | no billable time entries in the period |
Stripe Connect and online payments
| Status | error | Meaning |
|---|
| 400 | stripe_oauth_failed | connecting to Stripe failed |
| 400 | invalid_state | callback does not match the connection that was started |
| 409 | stripe_not_connected | no Stripe account connected |
| 409 | stripe_account_in_use | Stripe account is connected to another DebtFlow account |
| 409 | already_connected | already connected |
| 409 | not_connected | not connected |
| 409 | managed_in_stripe | setting is managed in Stripe |
| 502 | stripe_checkout_failed | Stripe did not create the payment page |
| 503 | online_payments_unavailable | online payments are not configured |
Security (dashboard)
| Status | error | Meaning |
|---|
| 400 | challenge_expired | passkey request expired – start again |
| 400 | passkey_invalid | passkey response invalid |
| 400 | invalid_second_factor | code or passkey incorrect |
| 400 | setup_expired | setup expired – start again |
| 409 | passkey_exists | passkey is already registered |
| 409 | no_factor | no second factor set up |
| 409 | no_passkey | no passkey set up |
| 409 | last_factor | the last second factor cannot be removed |
| 403 | reauthentication_required | please sign in again to complete the setup |
| 429 | mfa_locked | too many failed attempts – temporarily locked |
The OAuth token endpoint deviates from this and responds in OAuth format with error_description; see Authentication.