Appearance
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.
Structure of the security object
json
{
"security": {
"fiscalization": {
"type": "Tse",
"...": "type-specific fields, see below"
},
"extension:anybill": {
"required": true,
"failure": false
}
}
}securityis always mandatory, even when no fiscalization data exists for the receipt.fiscalizationholds the fiscal data. Itstypeselects the country-specific structure.extension:anybill.requiredstates whether the country requires fiscalization at all. Defaulttrue.extension:anybill.failurestates whether the fiscal unit failed for this transaction. Defaultfalse.
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 |
| No fiscalization obligation (e.g. CH, NL, BE, LU) | required: false | omit | Nothing is rendered |
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.
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:
- Set
security.extension:anybill.failuretotruefor every receipt whose transaction could not be signed. The flag describes a single transaction, not the state of the till. - Omit the signature data. Optionally send a
fiscalizationobject that contains onlytypeandadditionalLegalTextto print the details of the failure (start of the outage, reason, serial number of the unit) on the receipt. - As soon as the fiscal unit is available again, send receipts with
failure: false(or without the flag) and a completefiscalizationobject. - 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
typeis omitted, the object is treated asTse. - If
typecarries an unknown value (including a wrong casing such assecurityInformation), the object is also treated asTse. The API then reports missing TSE fields, which is misleading. Check the spelling oftypefirst 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, 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 |
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 |
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.
| 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 |
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) |
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 |
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 |
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 |
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 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:anybillfor a store without fiscalization obligation; the API then requires a fullfiscalizationobject. - Misspelling
type(wrong casing or a lowercase value). The object falls back toTseand the API reports missing TSE fields. - Swapping
processTypeandprocessData:processTypeis the short process name (Kassenbeleg-V1),processDatais theBeleg^...string. - Sending time stamps without a time zone offset, or a placeholder date before 1900-01-01.
- Sending the TSE
serialNumberin a non-hexadecimal representation (e.g. Base64). - Sending
additionalTseDataoradditionalDataentries with an emptydisplayNameorvalue. - Sending both
tseandfiscalization, or bothtseFailureandfailure. - Sending placeholder or partial signature data together with
failure: true; it is neither validated nor rendered. Send onlytypeandadditionalLegalText. - Repeating the failure notice in a footer text for German or Austrian stores; anybill adds it.
- Setting
failure: truepermanently instead of per affected transaction, or keepingrequired: falseafter a temporary outage.