Skip to main content

France Fiscalization Guide

France requires NF525-certified point-of-sale systems to maintain tamper-evident signature chains, immutable audit logs, and perpetual grand totals. Unlike Spain and Italy, there is no government API submission -- all compliance is enforced locally through cryptographic integrity controls.

FiscalAPI handles signing, chaining, audit logging, and receipt generation automatically.

How it works

France's NF525 flow is synchronous and local:

  1. You create a transaction via POST /v1/transactions
  2. FiscalAPI signs the transaction data with ECDSA P-256 and chains it to the previous signature
  3. A sequential number is assigned (gap-free per register)
  4. The transaction is logged in the Technical Event Log (JET)
  5. Grand totals (perpetual, daily, monthly, yearly) are updated
  6. Your transaction completes immediately with a fiscal_id and signature extract

There is no async processing or external submission -- transactions complete in ~10ms.

Signature chain

Each transaction is digitally signed using ECDSA P-256 with SHA-256. Signatures form a tamper-evident chain where each signature incorporates the previous one:

Signature(N) = ECDSA( payload(N) + "," + Signature(N-1) )

The first transaction in a chain signs payload + "," (empty previous signature).

Signing payload

The payload is 7 comma-separated fields:

PositionFieldExample
1Total TTC per tax rate (pipe-separated)120.00
2Total TTC120.00
3Timestamp (YYYYMMDDHHMMSS)20260315143022
4Register IDCAISSE-01
5Sequential number (zero-padded 6 digits)000042
6Transaction typeSALE
7First transaction flagN

Example: 120.00,120.00,20260315143022,CAISSE-01,000042,SALE,N

Signature extract

NF525 requires a 4-character extract of the signature to be printed on receipts. FiscalAPI returns this as signature_extract in the transaction response. The extract consists of the 3rd, 7th, 13th, and 19th characters (1-indexed) of the Base64url-encoded signature.

Fiscal ID format

The fiscal_id for French transactions is {register_id}/{sequence_number} -- for example, CAISSE-01/000042.

Technical Event Log (JET)

The JET (Journal des Événements Techniques) is an append-only audit log required by NF525. Every fiscally significant operation is recorded and cannot be modified or deleted.

Event types

EventDescription
TICKETIndividual sale transaction
DUPLICATEReceipt reprint
GRANDTOTALGrand total calculation
ARCHIVEArchive file creation
CART_EMPTYCart emptying / line deletion
PAYMENT_CHANGEPayment method change
LOGINUser login
LOGOUTUser logout
CONFIG_CHANGEConfiguration modification

FiscalAPI automatically logs TICKET events for every transaction. Each JET entry includes the register ID, operator ID, timestamp, amount, tax breakdown, and a digital signature.

Grand totals

NF525 requires perpetual counters that track cumulative transaction amounts. FiscalAPI maintains four levels of counters per register:

CounterResetFormula
Perpetual totalNever (lifetime of register)sales - |returns|
Perpetual total (absolute)Neversales + |returns|
Daily totalMidnightRunning total for current day
Monthly totalMonth boundaryAccumulated for current month
Yearly totalYear boundaryAccumulated for current year

For refunds, the perpetual total decreases while the perpetual absolute total increases. This ensures both net and gross volumes are tracked independently.

Country config fields

The country_config object for French locations:

FieldTypeRequiredDescription
siretstringYes14-digit establishment identifier (validated with Luhn check)
sirenstringNo9-digit company identifier (derived from SIRET if not provided)
tva_intracomstringYesFrench VAT number: FR + 2 check digits + SIREN
naf_codestringYesActivity code: 4 digits + 1 uppercase letter (e.g., 6201Z)
legal_namestringYesBusiness legal name
register_idstringYesUnique POS register identifier (e.g., CAISSE-01)
certificate_refstringNoNF525 certificate reference (e.g., B 525/0498-5)
software_versionstringNoCertified software version
signing_key_idstringNoReference to signing key (auto-generated if empty)

Validation rules

FieldRuleExample
siret14 digits, Luhn check80365813700036
siren9 digits, Luhn check, auto-derived from SIRET[:9]803658137
tva_intracomFR + 2 check digits + 9-digit SIRENFR83404833048
naf_code4 digits + 1 uppercase letter (dot optional)6201Z
legal_nameRequired, non-emptyBoulangerie Dupont SAS
register_idRequired, non-emptyCAISSE-01
La Poste exception

SIRET numbers starting with 356000000 (La Poste) use an alternative checksum (digit sum % 5 == 0) instead of the standard Luhn algorithm.

Configuration example

curl -X POST https://api.fiscalapi.com/v1/locations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer fsk_test_abc123def456..." \
-d '{
"name": "Paris Boulangerie",
"country": "FR",
"address": "12 Rue de Rivoli, 75001 Paris",
"tax_id": "80365813700036",
"country_config": {
"siret": "80365813700036",
"tva_intracom": "FR83803658137",
"naf_code": "1071C",
"legal_name": "Boulangerie Dupont SAS",
"register_id": "CAISSE-01",
"certificate_ref": "B 525/0498-5",
"software_version": "1.0.0"
}
}'

Creating a transaction

curl -X POST https://api.fiscalapi.com/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer fsk_test_abc123def456..." \
-d '{
"location_id": "loc_abc123",
"type": "sale",
"amount": 12000,
"currency": "EUR",
"line_items": [
{
"description": "Baguette tradition",
"quantity": 2,
"unit_price": 130,
"vat_rate": 550
},
{
"description": "Croissant au beurre",
"quantity": 3,
"unit_price": 150,
"vat_rate": 550
},
{
"description": "Tarte aux pommes",
"quantity": 1,
"unit_price": 2800,
"vat_rate": 2000
}
]
}'

Response

{
"id": "txn_abc123",
"status": "success",
"fiscal_id": "CAISSE-01/000042",
"signature_extract": "cgmp",
"created_at": "2026-03-15T14:30:22Z"
}

The signature_extract (4 characters) must appear on the printed receipt for NF525 compliance.

NF525-compliant receipts

FiscalAPI generates PDF receipts that include all mandatory NF525 fields:

  • Store name, address, SIRET, TVA
  • NAF code
  • Date and time (DD/MM/YYYY HH:MM:SS)
  • Register ID ("Caisse")
  • Sequential number (zero-padded 6 digits)
  • Transaction type (SALE / RETURN)
  • Itemized lines with quantity, unit price, tax rate, and totals
  • Tax breakdown per rate (Taux, Base HT, TVA, TTC)
  • 4-character signature extract
  • NF525 certificate reference
  • Software version
  • Footer: "Ticket de caisse certifié NF525"

Concurrency and data integrity

FiscalAPI uses PostgreSQL advisory locks to serialize writes per register. This ensures:

  • Gap-free sequencing: Sequential numbers are never skipped or duplicated
  • Chain integrity: Each signature correctly references the previous one
  • Atomic updates: Grand totals, chain state, and JET entries are updated in a single transaction

Multiple registers can process transactions concurrently without contention.

Error examples

Missing SIRET:

{
"error": "siret is required"
}

Invalid SIRET (Luhn check failure):

{
"error": "siret failed Luhn check"
}

Invalid NAF code:

{
"error": "naf_code must be 4 digits followed by 1 uppercase letter"
}

Sandbox testing

Use test_mode: true on your transactions to test NF525 compliance without affecting production chains. Sandbox transactions use separate chain state and sequential numbering.

curl -X POST https://api.fiscalapi.com/v1/transactions \
-H "Authorization: Bearer fsk_test_abc123def456..." \
-d '{
"location_id": "loc_abc123",
"type": "sale",
"test_mode": true,
"amount": 1200,
"currency": "EUR",
"line_items": [
{
"description": "Test item",
"quantity": 1,
"unit_price": 1000,
"vat_rate": 2000
}
]
}'