Getting started · Schema v2.1
The unified schema
Every transaction — whether it originated on Stripe, Klarna, Swish, or Shopify — is normalized to the same shape before it ever reaches your code. One immutable document per transaction, all field names in camelCase, all monetary amounts as integer minor units (öre, cents). Refunds and credit notes never mutate the original — they create new linked documents.
Three layers, one shape
Country differences never change the document shape. The schema is built on three layers — the same pattern Peppol uses for international invoicing.
01
Core
Fields that exist everywhere — vendor, items, tax, totals, payment, source. Structurally optional fields may be conditionally required by a rule pack.
02
Country rule packs
Per-country validation keyed by vendor.country + documentTier. A pack may only restrict the core — require fields, constrain values. It never adds fields.
03
Extensions
Country-specific additions live in a namespace — e.g. extensions.se.controlCode. Only extensions add fields, so the core never grows per country.
Every document declares which rule pack it satisfies via profileId.
If Sweden requires extensions.se.controlCode,
it's required when the profile is an se-* profile and absent everywhere else.
Profiles
A profile = (country, document tier, version). Together with documentTier (receipt today;
invoice tiers reserved), it tells you exactly which validation rules a document passed.
se-receipt-1 Active Sweden, receipt tier. Validated by the write API — kassaregister fields, SEK, SE_MOMS rates.
core-receipt-1 Active The minimum core. Applied as fallback when no country pack exists for the business country.
no / dk / fi / uk / us-receipt-1 Active Live — validated by the write API against each country's VAT/format/rounding rules.
*-simplified_invoice-* / *-full_invoice-* Reserved Invoice tiers land later as rule packs — no schema changes required.
Canonical shape
A Swedish café sale in May 2026 — food at the temporary 6% rate, merchandise at 25%, paid cash
(so totals.rounding is present).
This is the exact shape returned by every read endpoint.
Transaction object
{ "id": "txn_a1b2c3d4", "schemaVersion": "2.1", "profileId": "se-receipt-1", "documentTier": "receipt", "type": "sale", "settled": true, "receiptNumber": "R-2026-004521", "timestamp": "2026-05-12T14:32:15+02:00", "createdAt": "2026-05-12T12:32:18Z", "currency": "SEK", "vendor": { "id": "vendor_abc123", "name": "Kaffekoppen Södermalm", "legalName": "Kaffekoppen AB", "orgNumber": "556789-1234", "vatNumber": "SE556789123401", "country": "SE", "address": { "line1": "Götgatan 45", "city": "Stockholm", "postalCode": "118 26", "country": "SE" }, "locationName": "Kaffekoppen Södermalm" }, "customer": null, "items": [ { "name": "Cappuccino", "quantity": 2, "unitPrice": 4900, "amount": 9800, "taxRate": 6, "taxCategory": "S", "taxAmount": 555, "category": "food_beverage" }, { "name": "Kanelbulle", "quantity": 1, "unitPrice": 3500, "amount": 3500, "taxRate": 6, "taxCategory": "S", "taxAmount": 198, "category": "food_beverage" }, { "name": "Tygkasse med logga", "quantity": 1, "unitPrice": 14900, "amount": 14900, "taxRate": 25, "taxCategory": "S", "taxAmount": 2980, "category": "merchandise" } ], "tax": { "scheme": "SE_MOMS", "pricesIncludeTax": true, "summary": [ { "rate": 6, "category": "S", "label": "Moms 6%", "taxableAmount": 12547, "taxAmount": 753 }, { "rate": 25, "category": "S", "label": "Moms 25%", "taxableAmount": 11920, "taxAmount": 2980 } ] }, "totals": { "subtotal": 28200, "discount": 0, "tax": 3733, "rounding": 0, "total": 28200 }, "payment": { "method": "cash" }, "extensions": { "se": { "controlUnitId": "CU-SE-20241234", "controlCode": "ABCD 1234 EFGH 5678", "sequenceNumber": 4521 } }, "source": { "platform": "izettle", "externalId": "iz_pay_9f8e7d6c", "syncedAt": "2026-05-12T12:32:18Z" }, "locale": "sv-SE" }
Field groups
Fields are organized into four semantic groups.
Identity
id, schemaVersion, profileId, documentTier, type, settled, receiptNumber, timestamp, createdAtParties
vendor (snapshot of the business), customer (when known)Money
items[], tax, totals, payment, currencyProvenance
source, extensions, linkedTransaction, locale, metadataTransaction types
There is no mutable status field. A document has a type,
a settled boolean
(has the money changed hands?), and — for everything except plain sales — a linkedTransaction pointing at the original.
sale Completed purchase or conversion. totals.total is non-negative; 0 represents a free or fully discounted sale.
refund Full or partial return. totals.total = amount going back to the customer (positive). Links to the original sale.
void Cancellation before settlement (same day). totals.total = 0, settled = false. Links to the voided sale.
credit_note Formal price reduction without returning goods. Common in B2B. Links to the original document.
exchange Return + new purchase in one transaction. items mixes negative and positive amounts; totals.total is the net.
Tax
tax.scheme requiredtax.pricesIncludeTax requiredtax.reverseCharge optionaltax.summary requiredtax.jurisdictions optionalTax categories
Every line item carries a taxCategory,
and every summary entry a category.
A bare taxRate: 0 is ambiguous
— the code carries the legal distinction. Codes follow UN/CEFACT UNCL5305.
S Standard/reduced rate
Any positive rate from the scheme’s table.
Z Zero-rated
0% with right of deduction — exports, intra-EU supplies, UK zero-rated food.
E Exempt
No VAT, no deduction right — healthcare, education, financial services.
AE Reverse charge
Buyer accounts for VAT. Requires tax.reverseCharge: true.
O Out of scope
Not subject to the tax — US non-taxable lines, non-business.
Payment & cash rounding
The payment object is the legal minimum
— receipt law in SE/NO/DK requires the tender type — never processor event data.
Authorization codes, settlement batches, and acquirer references don't belong here.
payment.method requiredpayment.last4 optionaltotals.rounding optionalExtensions
Country-specific additions are namespaced under extensions so they can never collide with the core or each other. Namespaces are lowercase country codes.
This replaces the old top-level fiscal object from schema v2.0.
extensions.se — Sweden's kassaregister fields, required by se-receipt-1:
extensions.se.controlUnitId requiredextensions.se.controlCode requiredextensions.se.sequenceNumber requiredReserved namespaces: no (chained
fiscal signatures), dk, fi, uk, us.