Skip to main content

Portugal Fiscalization Guide

What is Portuguese fiscalization?

Portugal requires all invoicing software to do three things:

  1. Generate a unique code (ATCUD) on every invoice -- a tamper-evident identifier that proves the document is authentic
  2. Include a QR code on every invoice -- scannable by tax inspectors to verify the document
  3. Submit a monthly summary file (SAF-T) to the tax authority -- a structured XML file containing all invoices issued that month

Portugal does not require real-time reporting of each transaction to a government server. Instead, compliance is enforced through local document integrity (each invoice gets an ATCUD code and QR code) and monthly batch reporting (the SAF-T file).

In plain English: Every invoice your merchant issues gets a unique code and a QR code baked into it. At the end of each month, a summary file of all invoices is sent to Portugal's tax authority (the AT -- Autoridade Tributária e Aduaneira). The tax authority can verify individual invoices by scanning the QR code, or review the full month's activity via the SAF-T file.

FiscalAPI handles ATCUD generation, QR code content, document sequencing, and SAF-T file preparation automatically.

How it works

Portugal's flow is synchronous and local per transaction, with monthly batch submission:

  1. Your app creates a transaction via POST /v1/transactions
  2. FiscalAPI assigns a gap-free sequential number for the document series
  3. An ATCUD code is generated using the AT-assigned validation code and sequence number
  4. A QR code content string is built per the government specification
  5. A 4-character document hash is computed
  6. Your transaction completes immediately with a fiscal_id, ATCUD, and QR content
  7. At month end, FiscalAPI generates and submits the SAF-T PT billing file to AT

There is no per-transaction government API call. Transactions complete in ~10ms.

Example: When your POS submits a EUR 50 sale at a store in Lisbon (6 pastéis de nata at EUR 1.50 each + a bottle of vinho verde at EUR 12.00), FiscalAPI assigns the next sequential number in the series, generates the ATCUD code (e.g., ABCD1234-42), builds the QR code content string, and returns the transaction immediately. Your receipt printer renders the QR code from the content string.

You don't need to worry about this

FiscalAPI handles ATCUD code generation, QR code content, sequential numbering, document hashing, and monthly SAF-T file generation and submission. You just create transactions and print receipts with the QR code.

ATCUD codes

What is an ATCUD?

ATCUD stands for Código Único de Documento -- "Unique Document Code." Every invoice issued in Portugal must carry one. It uniquely identifies the document and proves it was issued by authorized software using an AT-registered document series.

An ATCUD has two parts:

ATCUD: {ValidationCode}-{SequentialNumber}
  • ValidationCode: An alphanumeric code (8+ characters) that you get from the AT when you register a document series. Think of it as a "license plate" for a series of documents.
  • SequentialNumber: A gap-free counter within that series (1, 2, 3, ...).

Example: ABCD1234-42 means validation code ABCD1234, document number 42 in that series.

Series registration

Before a merchant can issue invoices, each document series must be registered with the AT to obtain a validation code. FiscalAPI manages this process. You provide the validation codes in your location's country_config.

You don't need to worry about this

FiscalAPI tracks sequential numbering per series automatically. You never need to maintain counters or worry about gaps.

QR code

What goes in the QR code?

Every Portuguese invoice must include a QR code containing structured data that tax inspectors can scan to verify the document. The QR content is a text string with fields separated by *, each prefixed with an identifier:

FieldIDWhat it containsExample
Issuer NIFASeller's tax number123456789
Buyer NIFBBuyer's tax number (use 999999990 for walk-in consumers)999999990
Buyer countryCBuyer's country codePT
Document typeDFT, FS, FR, NC, or ND (see document types below)FT
Document statusEN = normal, A = cancelledN
Document dateFDate in YYYYMMDD format20260315
Document IDGSeries prefix + numberFT A/1
ATCUDHFull ATCUD codeABCD1234-1
Tax breakdownI1-I8Country, base amounts, and tax amounts per rate
Tax totalNTotal tax amount23.00
Gross totalOTotal including tax123.00
HashQFirst 4 characters of the document hasha1b2
CertificateRSoftware certificate number1234
You don't need to worry about this

FiscalAPI generates the entire QR content string automatically and returns it in the qr_code_url field. You just need to render it as a QR image on your receipts using any standard QR code library.

info

The qr_code_url field contains the QR code content string, not an image URL. Use any QR code library to render the string as a scannable QR image on your receipts.

Document types

Portugal requires different document types depending on the transaction. The adapter selects the correct type automatically:

CodeNameWhen UsedDescription
FTFatura (Invoice)B2B sales, or B2C sales over EUR 100Standard full tax invoice. Required when the buyer provides a tax ID (NIF) or the sale exceeds EUR 100.
FSFatura Simplificada (Simplified invoice)B2C sales under EUR 100Simplified invoice for small retail sales to walk-in consumers. No buyer identification needed.
FRFatura-Recibo (Invoice-receipt)Immediate payment + receiptCombined invoice and payment receipt. Used when payment happens at point of sale and the customer needs both documents in one.
NCNota de Crédito (Credit note)Refunds, returns, correctionsIssued when refunding a customer or correcting a previous invoice (full or partial).
NDNota de Débito (Debit note)Adjustments, surchargesIssued when adjusting an amount upward (e.g., correcting an undercharge on a previous transaction).

You don't need to specify the document type -- the adapter determines it from your transaction:

  • Submit a saleFT or FS (based on amount and buyer info)
  • Submit a refundNC automatically
  • Submit an adjustmentND automatically
  • Submit a void → cancels the original document (status A / Anulado)

Example: When your POS submits a EUR 50 sale to a walk-in consumer (no buyer tax ID), FiscalAPI automatically uses the FS (simplified invoice) type. If the same sale is EUR 150, it uses FT (full invoice) instead.

VAT rates

Portugal has three VAT rate tiers, with reduced rates for the autonomous regions (Azores and Madeira):

Rate tierMainlandAzoresMadeira
Reduced6%4%5%
Intermediate13%9%12%
Normal (standard)23%16%22%
You don't need to worry about this

FiscalAPI automatically groups line items by rate tier for the QR code tax breakdown and SAF-T generation. You just provide the VAT rate on each line item.

SAF-T PT monthly submission

What is SAF-T?

SAF-T (Standard Audit File for Tax Purposes) is an XML file format used across Europe for tax reporting. Portugal's version (SAF-T PT, version 1.04_01) is submitted monthly to the AT and contains:

  • Header: Company identification, fiscal year, software certification
  • Master files: Customer list, product list, tax rate table
  • Source documents: Every invoice issued in the reporting period, with full line-item detail

In plain English: At the end of each month, a comprehensive XML file summarizing all invoices is sent to Portugal's tax authority. It's like a detailed monthly sales report in a standardized format.

You don't need to worry about this

FiscalAPI generates the SAF-T file automatically from your transactions and submits it to the AT via their web service. You can also trigger manual SAF-T generation via the batch endpoint.

Country config fields

The country_config object for Portuguese locations uses a series map. Portugal's tax authority (AT) tracks document sequences per series, and each series has its own ATCUD validation code issued by AT when you register the series. A single location typically processes multiple document types (invoices, simplified invoices, credit notes), so the config maps each document type to its own series prefix and ATCUD code.

The adapter automatically determines which document type to use for each transaction (see Document types above) and looks up the corresponding series config.

FieldTypeRequiredDescription
seriesobjectYesMap of document type codes (FT, FS, FR, NC, ND) to series config
series[TYPE].prefixstringYesSeries prefix including year (e.g., "FT 2026/")
series[TYPE].atcud_codestringYesAT-assigned ATCUD validation code for this series (8+ alphanumeric characters, obtained when registering the series with AT)

Note: The Portuguese NIF (Número de Identificação Fiscal -- a 9-digit tax number, similar to a US TIN) goes in the location's tax_id field, not in country_config.

You only need to configure series for the document types your location will issue. At minimum, most locations need FT (standard invoices). Add FS if you process small B2C sales, and NC if you handle refunds.

Validation rules

FieldRuleExample
tax_id (NIF)9 digits, mod-11 check digit, cannot start with 0 or 4 (set on location)501442600
Series keysMust be one of: FT, FS, FR, NC, ND"FT"
prefixNon-empty string"FT 2026/"
atcud_codeNon-empty alphanumeric string from AT (8+ chars)"ABCD1234"
NIF validation

Portuguese NIFs use a weighted mod-11 checksum. The last digit is the check digit, calculated from the first 8 digits with weights 9, 8, 7, 6, 5, 4, 3, 2. Valid first digits: 1-3 (individual), 5 (company), 6 (public entity), 7 (other entity), 8 (sole trader), 9 (irregular/temporary).

Configuration example

curl -X POST https://api.zyntem.dev/v1/locations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"name": "Lisbon Store",
"country": "PT",
"address": "Rua Augusta 100, 1100-053 Lisboa",
"tax_id": "501442600",
"country_config": {
"series": {
"FT": { "prefix": "FT 2026/", "atcud_code": "ABCD1234" },
"FS": { "prefix": "FS 2026/", "atcud_code": "EFGH5678" },
"NC": { "prefix": "NC 2026/", "atcud_code": "IJKL9012" }
}
}
}'

Creating a transaction

Example: A store in Lisbon sells 6 pastéis de nata (EUR 1.50 each, 6% VAT) and a bottle of vinho verde (EUR 12.00, 23% VAT).

curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"location_id": "loc_abc123",
"type": "sale",
"amount": 12300,
"currency": "EUR",
"line_items": [
{
"description": "Pastel de nata",
"quantity": 6,
"unit_price": 150,
"vat_rate": 600
},
{
"description": "Vinho verde",
"quantity": 1,
"unit_price": 1200,
"vat_rate": 2300
}
]
}'

Specifying a buyer (B2B transactions)

For B2B transactions, include the buyer's NIF and country. This also causes FiscalAPI to use the FT (full invoice) document type instead of FS (simplified).

{
"metadata": {
"buyer_nif": "500100144",
"buyer_country": "PT"
}
}

If omitted, the buyer defaults to a final consumer (NIF 999999990, country PT).

Response

{
"id": "txn_abc123",
"status": "success",
"fiscal_id": "PT-A 2026/-1",
"qr_code_url": "A:501442600*B:999999990*C:PT*D:FT*E:N*F:20260315*G:FT A 2026//1*H:ABCD1234-1*I1:PT*I3:7.36*I4:0.54*I5:9.76*I6:2.76*N:3.30*O:123.00*Q:a1b2",
"created_at": "2026-03-15T10:30:00Z"
}

The qr_code_url contains the full QR content string. Render it as a QR image on the receipt. The fiscal_id format is PT-{Series}-{SequenceNumber}.

Error examples

Missing NIF:

{
"error": "nif is required"
}

Invalid NIF (mod-11 check digit failure):

{
"error": "nif is invalid: \"123456780\""
}

Missing series map:

{
"error": "Portugal country_config.series is required (map of document type to series config)"
}

Missing ATCUD code for a series:

{
"error": "Portugal country_config.series[FT].atcud_code is required"
}

Sandbox testing

Sandbox routing is automatic when using a test API key (zyn_test_...). Test transactions hit sandbox endpoints without affecting production data.

curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"location_id": "loc_abc123",
"type": "sale",
"amount": 1500,
"currency": "EUR",
"line_items": [
{
"description": "Test item",
"quantity": 1,
"unit_price": 1220,
"vat_rate": 2300
}
]
}'