Skip to main content
Every error response uses the same envelope. Switch on code (machine-readable, stable), not on message (human-readable, may change).

Response shape

{
  "code": "precondition_failed",
  "message": "Resource version mismatch. Expected 1, current is 2.",
  "retryable": true
}
FieldTypeNotes
codestringStable machine-readable identifier. See the catalog below.
messagestringShort human-readable label. Safe to log, not locale-stable, not a stable contract.
retryablebooleanWhether retrying the same request can succeed. 5xx, 429, and 412 are retryable.
detailsobjectOptional structured context. precondition_failed (412) responses include expected_resource_version and current_resource_version here so clients can react without parsing message. Other endpoints may add their own structured payload (for example, export_dsfinvk_build_pending returns unbuilt_session_ids). Always check whether the field is present before reading it.
The trace identifier is returned only in the X-Request-Id response header (e.g. X-Request-Id: req_a1b2c3d4e5f60718293a). It is not embedded in the error body. Persist it in your client logs and attach it to support cases.

Retry guidance

ClassRetry?How
5xx, rate_limit_exceeded, precondition_failedYesResend the request. For precondition_failed, re-read the resource to refresh ETag first. For rate_limit_exceeded, honor Retry-After.
All other 4xxNoFix the request before resending.
The retryable field on each response reflects this rule: it is true for any 5xx, 429, or 412, and false otherwise.

Standard error codes

These codes map directly to HTTP status codes and apply to every endpoint across the Fiscalization, Reporting, and Hosted Receipts APIs unless noted.
HTTPcodeRetryableWhen
400invalid_requestNoRequest body, query, or path is malformed or fails schema validation. Includes missing required fields, wrong types, decimal vs. integer mismatches, invalid ISO 3166-1 alpha-3 country codes (DEU not DE), unknown discriminator values (e.g. unknown operation type), and a malformed If-Match header value (must be a quoted positive integer, e.g. "1").
401unauthorizedNoMissing or malformed Authorization header, invalid API key, or API key environment mismatch (sandbox key against production or vice versa).
403forbiddenNoAuthenticated caller is not allowed to access the requested tenant, country, or merchant. Most commonly: API key country does not match the merchant or location country.
404not_foundNoResource does not exist or is not visible to the caller.
409conflictNoGeneric resource conflict. Specific 409 subtypes are listed below — code distinguishes the cause.
412precondition_failedYesStale If-Match on a mutation. Both the expected and current resource versions appear in details (expected_resource_version, current_resource_version) and in message. Re-read the resource and retry with the current ETag.
422validation_errorNoRequest shape is valid but a field value is semantically wrong (amount sign mismatch, missing register reference, invalid timestamp range, etc.). See Validation errors for the specific conditions.
428precondition_requiredNoIf-Match header is missing on a mutation that requires it (POST /operations/{id}/complete, POST /operations/{id}/void).
429rate_limit_exceededYesPer-API-key rate budget exceeded. Honor Retry-After and reduce concurrency.
500internal_errorYesUnhandled server error. Retry the request; escalate with X-Request-Id if sustained.
501not_implementedYesThe requested capability is registered in the schema but no handler is wired up. Today this only happens if a request lands for an export type with no matching exporter.

Conflict (409) variants

All 409 responses share the envelope. The code field distinguishes the cause.
codeWhereWhen
location_has_registersDELETE /locations/{id}Location still has registers attached. Delete or reassign the registers first.
register_has_dependenciesDELETE /registers/{id}Register has operations recorded against it, or the register is fiscalized. Decommission first if applicable; otherwise the register cannot be removed.
register_already_fiscalizedPOST /registers/{id}/fiscalizeRegister is already fiscalized, or a fiscalization is in progress.
register_invalid_fiscal_statePOST /registers/{id}/decommissionRegister is already decommissioned, or has not been fiscalized yet. Other decommission preconditions (non-KassenSichV regime, missing TSS/client metadata) surface as validation_error with 422.
register_no_open_sessionPOS operation endpointsRegister has no open session. Open a session first via POST /operations with type: SESSION_OPEN.
session_invalid_stateSession lifecycle endpointsSession-level state violation: a session is already open on the register, session counts or amounts are invalid, or the session cannot be closed in its current state.
operation_invalid_statePOST /operations/{id}/complete, POST /operations/{id}/voidOperation is not in OPEN state. You cannot complete an already-completed or voided operation, and you cannot void what is not open.

Validation errors (422)

422 responses currently emit code: "validation_error" for every case. The message field describes the specific violation.

Operation shape

ConditionOperation types
ONLINE operations must not specify a register_id.SALE, RETURN, EXCHANGE
POS operations must specify a register_id.SALE, RETURN, EXCHANGE
SALE operations must not specify external_related_operation or related_operation_id.SALE
RETURN and EXCHANGE operations must specify exactly one of related_operation_id or external_related_operation.RETURN, EXCHANGE

Amount and payment validation

ConditionOperation types
SALE total_amount must be ≥ 0; RETURN total_amount must be ≤ 0.SALE, RETURN
Each line_items[].total_amount must follow the same sign rule as the parent operation.SALE, RETURN
Each payments[].amount must follow the same sign rule as the parent operation.All goods-movement types
Sum of payments[].amount must match the operation total_amount.All goods-movement types on complete

Decimal precision

HTTPcodeWhen
422tax_amount_precision_invalidA line_items[].taxes[].tax_amount doesn’t match total_amount × rate_i / (1 + Σ rate_j) at 8 dp. See Decimal precision.

Fiscalization preconditions

ConditionEndpoint
Register is not fiscalized. Complete fiscalization before completing operations.POST /operations/{id}/complete
Register is missing active TSS or Client. Re-fiscalize the register.POST /operations/{id}/complete
Operation has no goods-movement details; cannot complete.POST /operations/{id}/complete
POS operation is missing a register reference.POST /operations/{id}/complete
Merchant is missing a German fiscal identity (DEU).POST /registers/{id}/fiscalize
Fiscalization is only supported for locations in Germany (DEU).POST /registers/{id}/fiscalize
Decommission is only supported for KassenSichV registers.POST /registers/{id}/decommission
Register has no active TSS or client to decommission.POST /registers/{id}/decommission
Active TSS is missing an admin PIN; cannot authenticate to decommission.POST /registers/{id}/decommission
TSE signing failed (phase 1) — TSS unreachable or rejected the request before the operation reached the device.POST /operations/{id}/complete
TSE signing failed (phase 2) — TSS rejected the operation after start. The operation is left in a recoverable state.POST /operations/{id}/complete

Reporting API

ConditionEndpoint
from and to must be valid ISO-8601 timestamps.Reporting export endpoints (returns 400 invalid_request).
from must be strictly before to.Reporting export endpoints.

Not-yet-implemented endpoints

Today the only path that can return HTTP 501 Not Implemented is the export endpoint, when a request lands for an export type that has no exporter registered. The body carries code: "not_implemented" and retryable: true. Because the public wire enum currently exposes only dsfinvk, this path is unreachable through normal use.

Reserved codes (not yet emitted)

The OpenAPI contract advertises these codes on relevant endpoints, but the runtime does not emit them yet. They are kept here so client switch statements can include them ahead of enforcement landing.
codeHTTPStatus
idempotency_key_conflict409Documented on every mutating operation endpoint. Idempotency-Key replay is not enforced server-side today; reusing a key currently re-runs the request. Treat the documented behavior as forward-looking.
regime_validation_failed422Documented for regime-specific rule failures (KassenSichV / RKSV / Fattura). Current handlers throw UnprocessableEntityException without a custom code, so these responses surface as validation_error.

Idempotency and concurrency

Idempotency keys, retention windows, and concurrency rules (If-Match / ETag) are documented on the Limits page. The errors those rules emit (precondition_required, precondition_failed) are in the standard table above; idempotency_key_conflict is forward-looking — see Reserved codes.

Request tracing

Every response includes an X-Request-Id header. Persist it in your client logs and attach it to every support case — it is the fastest path to a root-cause answer.

Next steps