> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openfiskal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Complete catalog of error codes the API can emit, with HTTP status, retry guidance, and the conditions that trigger each one.

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

## Response shape

```json theme={null}
{
  "code": "precondition_failed",
  "message": "Resource version mismatch. Expected 1, current is 2.",
  "retryable": true
}
```

| Field       | Type    | Notes                                                                                                                                                                                                                                                                                                                                                                                       |
| ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`      | string  | Stable machine-readable identifier. See the catalog below.                                                                                                                                                                                                                                                                                                                                  |
| `message`   | string  | Short human-readable label. Safe to log, not locale-stable, not a stable contract.                                                                                                                                                                                                                                                                                                          |
| `retryable` | boolean | Whether retrying the same request can succeed. `5xx`, `429`, and `412` are retryable.                                                                                                                                                                                                                                                                                                       |
| `details`   | object  | Optional 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

| Class                                               | Retry? | How                                                                                                                                          |
| --------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `5xx`, `rate_limit_exceeded`, `precondition_failed` | Yes    | Resend the request. For `precondition_failed`, re-read the resource to refresh `ETag` first. For `rate_limit_exceeded`, honor `Retry-After`. |
| All other `4xx`                                     | No     | Fix 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.

| HTTP | `code`                  | Retryable | When                                                                                                                                                                                                                                                                                                                                                                   |
| ---- | ----------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `invalid_request`       | No        | Request 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"`). |
| 401  | `unauthorized`          | No        | Missing or malformed `Authorization` header, invalid API key, or API key environment mismatch (sandbox key against production or vice versa).                                                                                                                                                                                                                          |
| 403  | `forbidden`             | No        | Authenticated 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.                                                                                                                                                                                              |
| 404  | `not_found`             | No        | Resource does not exist or is not visible to the caller.                                                                                                                                                                                                                                                                                                               |
| 409  | `conflict`              | No        | Generic resource conflict. Specific 409 subtypes are listed below — `code` distinguishes the cause.                                                                                                                                                                                                                                                                    |
| 412  | `precondition_failed`   | Yes       | Stale `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`.                                                                                                                                |
| 422  | `validation_error`      | No        | Request shape is valid but a field value is semantically wrong (amount sign mismatch, missing register reference, invalid timestamp range, etc.). See [Validation errors](#validation-errors-422) for the specific conditions.                                                                                                                                         |
| 428  | `precondition_required` | No        | `If-Match` header is missing on a mutation that requires it (`POST /operations/{id}/complete`, `POST /operations/{id}/void`).                                                                                                                                                                                                                                          |
| 429  | `rate_limit_exceeded`   | Yes       | Per-API-key rate budget exceeded. Honor `Retry-After` and reduce concurrency.                                                                                                                                                                                                                                                                                          |
| 500  | `internal_error`        | Yes       | Unhandled server error. Retry the request; escalate with `X-Request-Id` if sustained.                                                                                                                                                                                                                                                                                  |
| 501  | `not_implemented`       | Yes       | The 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.

| `code`                          | Where                                                          | When                                                                                                                                                                                                 |
| ------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `location_has_registers`        | `DELETE /locations/{id}`                                       | Location still has registers attached. Delete or reassign the registers first.                                                                                                                       |
| `register_has_dependencies`     | `DELETE /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_fiscalized`   | `POST /registers/{id}/fiscalize`                               | Register is already fiscalized, or a fiscalization is in progress.                                                                                                                                   |
| `register_invalid_fiscal_state` | `POST /registers/{id}/decommission`                            | Register 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_session`      | POS operation endpoints                                        | Register has no open session. Open a session first via `POST /operations` with `type: SESSION_OPEN`.                                                                                                 |
| `session_invalid_state`         | Session lifecycle endpoints                                    | Session-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_state`       | `POST /operations/{id}/complete`, `POST /operations/{id}/void` | Operation 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

| Condition                                                                                                              | Operation 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

| Condition                                                                                | Operation 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

| HTTP | `code`                         | When                                                                                                                                             |
| ---- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| 422  | `tax_amount_precision_invalid` | A `line_items[].taxes[].tax_amount` doesn't match `total_amount × rate_i / (1 + Σ rate_j)` at 8 dp. See [Decimal precision](/decimal-precision). |

### Fiscalization preconditions

| Condition                                                                                                            | Endpoint                            |
| -------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| 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

| Condition                                          | Endpoint                                                    |
| -------------------------------------------------- | ----------------------------------------------------------- |
| `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.

| `code`                     | HTTP | Status                                                                                                                                                                                                        |
| -------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idempotency_key_conflict` | 409  | Documented 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_failed` | 422  | Documented 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`](/errors-and-limits#concurrency-rules)) are documented on the [Limits](/errors-and-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](#reserved-codes-not-yet-emitted).

## 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

* [Limits and platform guarantees](/errors-and-limits)
* [Authentication](/auth)
* [Transaction lifecycle](/pos-operation-ingestion)
