

# Fiscalization

Every receipt sent to `POST /v3/bill` carries a `security` object. It transports the
data that the fiscal regulations of the store's country require on the receipt: the
TSE signature in Germany, the RKSV signature in Austria, the TicketBAI QR code in
Spain, and so on. This page explains how the object is validated and which fields
are mandatory for each fiscalization type. The legal background is described under
[Legal Requirements at the POS](https://developer.anybill.de/security_and_compliance/#legal-requirements-at-the-pos).

## Structure of the security object

```json
{
  "security": {
    "fiscalization": {
      "type": "Tse",
      "...": "type-specific fields, see below"
    },
    "extension:anybill": {
      "required": true,
      "failure": false
    }
  }
}
```

- `security` is always mandatory, even when no fiscalization data exists for the receipt.
- `fiscalization` holds the fiscal data. Its `type` selects the country-specific structure.
- `extension:anybill.required` states whether the country requires fiscalization at all. Default `true`.
- `extension:anybill.failure` states whether the fiscal unit failed for this transaction. Default `false`.

The extension also accepts `publicKey` and `signedData`. Both are only relevant for EV
charging receipts and are not validated.

## Which case applies?

Whether `fiscalization` is mandatory is decided solely by the two flags in the extension:

| Case | `extension:anybill` | `fiscalization` | Effect on the receipt |
| --- | --- | --- | --- |
| Regular receipt | omitted, or `required: true` and `failure: false` | **mandatory**, validated according to its `type` | Fiscal data is rendered |
| Fiscal unit unavailable | `failure: true` | optional, not validated | A failure notice is rendered instead of the fiscal data; see [Handling a fiscal unit failure](#handling-a-fiscal-unit-failure) |
| No fiscalization obligation (e.g. CH, NL, BE, LU) | `required: false` | omit | Nothing is rendered |

::: tip Omitting the extension
Omitting `extension:anybill` is identical to sending `required: true` and `failure: false`.
Integrations that leave out the extension therefore always have to send a complete
`fiscalization` object, which is the most common cause of unexpected TSE validation errors.
:::

::: warning Data sent with `failure: true` or `required: false` is not validated
As soon as one of the two flags disables the requirement, the `fiscalization` object is
skipped by the validation entirely, including a partially filled one. Signature data sent
in these cases is neither checked nor rendered. With `required: false` omit the object;
with `failure: true` send at most `type` and `additionalLegalText` as described below.
:::

## Handling a fiscal unit failure

A failure of the TSE, the signature creation unit or any other fiscal unit does not
suspend the obligation to issue a receipt. The till keeps sending every receipt to
`POST /v3/bill`; it only marks the affected transactions:

1. Set `security.extension:anybill.failure` to `true` for every receipt whose transaction
   could not be signed. The flag describes a single transaction, not the state of the till.
2. Omit the signature data. Optionally send a `fiscalization` object that contains only
   `type` and `additionalLegalText` to print the details of the failure (start of the
   outage, reason, serial number of the unit) on the receipt.
3. As soon as the fiscal unit is available again, send receipts with `failure: false` (or
   without the flag) and a complete `fiscalization` object.
4. Do not resend or correct receipts that were issued with `failure: true`. The outage
   log required by the regulations (e.g. DSFinV-K in Germany) is kept by the till, not by
   anybill.

Whether and how the failure is shown on the receipt depends on the seller's country in
`bill.head.seller.address.countryCode` and on the `fiscalization` object that is sent:

| Seller country | `fiscalization` sent | Rendered on the receipt |
| --- | --- | --- |
| Germany | omitted | `TSE Fehlfunktion!` |
| Germany | `type: Tse` with `extension:anybill.additionalLegalText` | `TSE Fehlfunktion!` followed by the additional legal text |
| Austria | omitted | `**SICHERHEITSEINRICHTUNG AUSGEFALLEN**` |
| Austria | `type: Rksv` with `additionalLegalText` | `**SICHERHEITSEINRICHTUNG AUSGEFALLEN**` followed by the additional legal text |
| Any other country | omitted | Nothing; add your own notice via `additionalLegalText` of a `Tse` or `Rksv` object or via a footer text |

The notice texts for Germany and Austria are added by anybill. Do not repeat them in a
footer text. The failure notice is only generated for the `Tse` and `Rksv` types; for the
other types `failure` has no effect on the rendering.

Recommended payload for a German store during a TSE outage:

```json
{
  "security": {
    "fiscalization": {
      "type": "Tse",
      "extension:anybill": {
        "additionalLegalText": "TSE ausgefallen seit 29.11.2021 14:52 Uhr, Seriennummer 623323B6...BA60B"
      }
    },
    "extension:anybill": {
      "failure": true
    }
  }
}
```

## The type discriminator

`fiscalization.type` selects the structure and the validation rules. The value is
**case-sensitive** and must be spelled exactly as listed:

| `type` | Country / regulation | Mandatory fields |
| --- | --- | --- |
| `Tse` | Germany, KassenSichV | `serialNumber`, `logTimeFormat`, `certificate`, `timestampStart`, `timestampEnd`, `transactionNumber`, `signatureNumber`, `signature` |
| `Rksv` | Austria, RKSV | `posInspectionQrData` |
| `BoiTva` | France, BOI-TVA | `posInspectionQrData` |
| `TBai` | Spain (Basque Country), TicketBAI | `qrCodeData`, `link`, `number`, `id` |
| `Sweden` | Sweden, control unit | `paymentType`, `fiscalNumber`, `controlCode` |
| `Portugal` | Portugal, ATCUD | `atcud`, `qrCodeData` |
| `SecurityInformation` | Generic, any country without a dedicated type | `title`; per `data[]` entry `displayName`, `value`, `displayed` |

Two rules follow from the implementation:

- If `type` is **omitted**, the object is treated as `Tse`.
- If `type` carries an **unknown value** (including a wrong casing such as `securityInformation`),
  the object is also treated as `Tse`. The API then reports missing TSE fields, which is
  misleading. Check the spelling of `type` first whenever TSE errors appear for a non-German receipt.

For `bill.countryCode: "FRA"` the API applies additional French rules and expects the
`BoiTva` type. When using the [POS SDK](https://developer.anybill.de/vendor_api_sdk/overview), `type` is derived from
the concrete class and does not need to be set.

## Germany – TSE (`Tse`)

The values originate from the TSE transaction of the receipt: `startTransaction()`
provides the start time stamp, `finishTransaction()` provides the end time stamp, the
signature counter and the signature.

| Field | Required | Constraints | Content |
| --- | --- | --- | --- |
| `serialNumber` | yes | hexadecimal string, max. 64 chars | Serial number of the TSE |
| `logTimeFormat` | yes | max. 100 chars | Time format used by the TSE, e.g. `unixTime` |
| `certificate` | yes | max. 10,000 chars | Certificate chain of the TSE in PEM format |
| `timestampStart` | yes | ISO 8601 with offset, after 1900-01-01 | Time stamp returned by `startTransaction()` |
| `timestampEnd` | yes | ISO 8601 with offset, after 1900-01-01 | Time stamp returned by `finishTransaction()` |
| `transactionNumber` | yes | integer ≥ 1 | Transaction number assigned by the TSE (`0` is rejected as empty) |
| `signatureNumber` | yes | integer ≥ 1 | Signature counter returned by `finishTransaction()` (`0` is rejected as empty) |
| `signature` | yes | – | Signature returned by `finishTransaction()` |
| `signatureAlgorithm` | no | max. 64 chars | e.g. `ecdsa-plain-SHA256` |
| `firstOrder` | no | ISO 8601 with offset, after 1900-01-01 | Start time stamp of the first order transaction when a receipt closes several orders (e.g. hospitality) |
| `processType` | no | max. 100 chars | Process type passed to `finishTransaction()`, e.g. `Kassenbeleg-V1` |
| `processData` | no | max. 250 chars | Process data passed to `finishTransaction()`, e.g. `Beleg^42.31_16.26_0.00_0.00_0.00^58.57:Bar` |
| `extension:anybill.posInspectionQrData` | no | max. 1,024 chars | Content of the QR code for the Kassennachschau |
| `extension:anybill.additionalLegalText` | no | max. 5,120 chars | Additional legal text printed with the fiscal data |
| `extension:anybill.additionalTseData` | no | map of entries; each `displayName` (max. 60 chars) and `value` (max. 512 chars) is mandatory | Further key/value pairs, e.g. the cash register id |

::: details Example TSE
```json
{
  "type": "Tse",
  "serialNumber": "623323B6C170DF2200...8F3A78E5BA7C4BA60B",
  "signatureAlgorithm": "ecdsa-plain-SHA384",
  "logTimeFormat": "unixTime",
  "certificate": "LS0tLS...S0tCg==",
  "timestampStart": "2021-11-29T15:00:57+01:00",
  "timestampEnd": "2021-11-29T15:00:59+01:00",
  "transactionNumber": 50,
  "signatureNumber": 121,
  "processType": "Kassenbeleg-V1",
  "processData": "Beleg^42.31_16.26_0.00_0.00_0.00^58.57:Bar",
  "signature": "AjEGS...dhRitb",
  "extension:anybill": {
    "posInspectionQrData": "V0;KASSE-01;Kassenbeleg-V1;Beleg^42.31_16.26_0.00_0.00_0.00^58.57:Bar;50;121;2021-11-29T14:00:57.000Z;2021-11-29T14:00:59.000Z;ecdsa-plain-SHA384;unixTime;AjEGS...dhRitb;BFsx...Qw==",
    "additionalTseData": {
      "cashRegisterId": {
        "displayName": "Kassen-ID",
        "value": "KASSE-01"
      }
    }
  }
}
```
:::

## Austria – RKSV (`Rksv`)

| Field | Required | Constraints | Content |
| --- | --- | --- | --- |
| `posInspectionQrData` | yes | max. 1,024 chars | Machine-readable code of the receipt (`_R1-AT1_...`) |
| `serialNumber` | no | max. 128 chars | Serial number of the signature creation unit |
| `transactionNumber` | no | integer ≥ 0 | Receipt number of the cash register |
| `signatureNumber` | no | integer ≥ 0 | Signature counter |
| `signature` | no | – | Signature value |
| `additionalLegalText` | no | max. 5,120 chars | Additional legal text |
| `additionalData` | no | map of entries; each `displayName` (max. 60 chars) and `value` (max. 512 chars) is mandatory | Further key/value pairs |

::: details Example RKSV
```json
{
  "type": "Rksv",
  "posInspectionQrData": "_R1-AT1_4690F01D01_68_2021-10-04T13:54:19_4,00_0,00_0,00_0,00_0,00_jVWJIf9wOCDo544VtmI7dQ==_15BE7FD2_eUwHgkS8AGE=_GkWvZVamt1RKsUBSbGACKgcfhX7iNiRq4iQgZ8E4CUPHStNAnC6k7IPA5cG4OFlyAh0GsbSRanKBUfy0PsyVqg==",
  "serialNumber": "Tpzx/mesZGSu/XO6ZaKZO/bk87HKejMaU2VCDoAgmp4=",
  "transactionNumber": 1459,
  "signatureNumber": 3833,
  "signature": "AjEGS...dhRitb"
}
```
:::

## France – BOI-TVA (`BoiTva`)

Requires `bill.countryCode: "FRA"`. France additionally expects `cashRegister.version`
and the country-specific attributes (SIRET, NAF) in the misc extension; see the
[Bill Data Model](https://developer.anybill.de/vendor_api/bill_data_model).

| Field | Required | Constraints | Content |
| --- | --- | --- | --- |
| `posInspectionQrData` | yes | max. 1,024 chars | QR code content for the inspection |
| `additionalData[]` | no | list | Additional lines rendered before or after the QR code |
| `additionalData[].type` | yes | `Text` or `KeyValue` | Selects the structure of the entry |
| `additionalData[].sequenceNumber` | no | integer | Order of the entries |
| `additionalData[].position` | no | `Before` or `After` | Position relative to the QR code |
| `additionalData[].value` | yes | – | Text (`Text`) or value (`KeyValue`) |
| `additionalData[].alignment` | no | `Left`, `Right`, `Center` | Alignment; `Text` only |
| `additionalData[].displayName` | yes for `KeyValue` | – | Label of the value |

::: details Example BOI-TVA
```json
{
  "type": "BoiTva",
  "posInspectionQrData": "FR;12345678900012;2024-03-01T10:15:00;58.57;...",
  "additionalData": [
    {
      "type": "KeyValue",
      "sequenceNumber": 1,
      "position": "Before",
      "displayName": "Logiciel",
      "value": "POS 4.2 – certifié NF525"
    },
    {
      "type": "Text",
      "sequenceNumber": 2,
      "position": "After",
      "alignment": "Center",
      "value": "Ticket sécurisé"
    }
  ]
}
```
:::

## Spain – TicketBAI (`TBai`)

| Field | Required | Constraints | Content |
| --- | --- | --- | --- |
| `qrCodeData` | yes | max. 4,096 chars | Content of the TicketBAI QR code |
| `link` | yes | max. 1,024 chars | TicketBAI verification URL |
| `id` | yes | max. 1,024 chars | TicketBAI identifier (`TBAI-...`) |
| `number` | yes | integer ≥ 1 | TicketBAI invoice number (`0` is rejected as empty) |

::: details Example TicketBAI
```json
{
  "type": "TBai",
  "qrCodeData": "https://tbai.example.eus/qr/?id=TBAI-00000006Y-251019-btFpwP8dcLGAF-237&s=T&nf=27174&i=5.00&cr=007",
  "link": "https://tbai.example.eus/qr/?id=TBAI-00000006Y-251019-btFpwP8dcLGAF-237",
  "id": "TBAI-00000006Y-251019-btFpwP8dcLGAF-237",
  "number": 27174
}
```
:::

## Sweden (`Sweden`)

| Field | Required | Constraints | Content |
| --- | --- | --- | --- |
| `paymentType` | yes | max. 1,024 chars | Payment types of the transaction, comma-separated |
| `fiscalNumber` | yes | max. 1,024 chars | Combination of storage location, cash register and transaction number |
| `controlCode` | yes | max. 2,048 chars | Control code (digital signature) of the control unit |
| `text` | no | – | Additional information |

::: details Example Sweden
```json
{
  "type": "Sweden",
  "paymentType": "Card",
  "fiscalNumber": "STO-01-000123-4567",
  "controlCode": "MTIzNDU2Nzg5MDEyMzQ1Ng=="
}
```
:::

## Portugal (`Portugal`)

| Field | Required | Constraints | Content |
| --- | --- | --- | --- |
| `atcud` | yes | – | Unique document code (ATCUD: validation code and sequential number) |
| `qrCodeData` | yes | max. 1,024 chars | Content of the QR code required on Portuguese receipts |

::: details Example Portugal
```json
{
  "type": "Portugal",
  "atcud": "JFF8K2VD-1234",
  "qrCodeData": "A:123456789*B:999999990*C:PT*D:FR*E:N*F:20240301*G:FR 2024/1234*H:JFF8K2VD-1234*..."
}
```
:::

## Generic security information (`SecurityInformation`)

Use this type for countries or regimes without a dedicated structure. The entries are
rendered as a titled block of key/value pairs and QR codes.

| Field | Required | Constraints | Content |
| --- | --- | --- | --- |
| `title` | yes | max. 64 chars | Heading of the block |
| `data[]` | no | list | Entries of the block |
| `data[].displayName` | yes | max. 64 chars | Label of the entry |
| `data[].value` | yes | – | Value, or the QR code content for `type: Qr` |
| `data[].displayed` | yes | boolean | Whether the entry is rendered on the receipt |
| `data[].type` | no | `Text` or `Qr` | Rendering of the entry; set it explicitly |
| `data[].key` | no | – | Technical key of the entry |
| `data[].sequenceNumber` | no | integer | Order of the entries |

::: details Example SecurityInformation
```json
{
  "type": "SecurityInformation",
  "title": "Fiscal data",
  "data": [
    {
      "type": "Text",
      "key": "deviceId",
      "displayName": "Device",
      "value": "FD-4711",
      "displayed": true,
      "sequenceNumber": 1
    },
    {
      "type": "Qr",
      "key": "verification",
      "displayName": "Verification",
      "value": "https://verify.example.org/?doc=4711",
      "displayed": true,
      "sequenceNumber": 2
    }
  ]
}
```
:::

## Receipts without fiscalization data

Fiscal unit unavailable – minimal form, see [Handling a fiscal unit failure](#handling-a-fiscal-unit-failure)
for the variant with an additional legal text:

```json
{
  "security": {
    "extension:anybill": {
      "failure": true
    }
  }
}
```

No fiscalization obligation in the country of the store:

```json
{
  "security": {
    "extension:anybill": {
      "required": false
    }
  }
}
```

## Deprecated field names

The following names are aliases of the current fields. They behave identically and remain
accepted for existing integrations, but must not be used in new code and must not be
combined with the current names – if both are sent, the last one in the payload wins.

| Deprecated | Current |
| --- | --- |
| `security.tse` | `security.fiscalization` |
| `security.extension:anybill.tseFailure` | `security.extension:anybill.failure` |
| `security.extension:anybill.tseRequired` | `security.extension:anybill.required` |

The deprecated `tse` property is marked read-only in the OpenAPI specification and is therefore
not offered as a request field in the API reference.

## Common mistakes

- Omitting `extension:anybill` for a store without fiscalization obligation; the API then requires a full `fiscalization` object.
- Misspelling `type` (wrong casing or a lowercase value). The object falls back to `Tse` and the API reports missing TSE fields.
- Swapping `processType` and `processData`: `processType` is the short process name (`Kassenbeleg-V1`), `processData` is the `Beleg^...` string.
- Sending time stamps without a time zone offset, or a placeholder date before 1900-01-01.
- Sending the TSE `serialNumber` in a non-hexadecimal representation (e.g. Base64).
- Sending `additionalTseData` or `additionalData` entries with an empty `displayName` or `value`.
- Sending both `tse` and `fiscalization`, or both `tseFailure` and `failure`.
- Sending placeholder or partial signature data together with `failure: true`; it is neither validated nor rendered. Send only `type` and `additionalLegalText`.
- Repeating the failure notice in a footer text for German or Austrian stores; anybill adds it.
- Setting `failure: true` permanently instead of per affected transaction, or keeping `required: false` after a temporary outage.
