Portugal Adapter Architecture
Internal reference for the Portugal fiscalization adapter covering the AT API flow, certificate management, and monthly SAF-T batch architecture.
AT API flow
Portugal's Autoridade Tributária (AT) requires two integration points:
1. Per-transaction (local, synchronous)
Each transaction is processed locally without an AT API call:
POST /v1/transactions
→ PortugalAdapter.ProcessTransaction()
→ SequenceStore.NextSequence(nif+series) // gap-free counter
→ FormatATCUD(validationCode, seqNum) // "ABCD1234-42"
→ SHA-256 hash (NIF;DocID;ATCUD;Amount) // 4-char extract
→ GenerateQRContent(QRParams) // Portaria 195/2020
→ Return Result{fiscal_id, qr_code_url}
- Latency: ~10ms (no network call)
- Fiscal ID format:
PT-{Series}-{SeqNum}(e.g.,PT-A 2026/-1) - QR content:
A:NIF*B:BuyerNIF*C:PT*D:FT*E:N*F:YYYYMMDD*G:DocID*H:ATCUD*I1:PT*...
2. Monthly batch (SAF-T submission)
At month end, the adapter generates and submits SAF-T PT v1.04_01 XML:
CronJob / Manual trigger
→ Collect all transactions for period
→ Build AuditFile XML (Header + MasterFiles + SourceDocuments)
→ Submit to AT /saft/submit endpoint
→ Store submission receipt
Certificate management
Portugal requires two types of certificates:
Document signing certificate (RSA-4096)
Used to compute the SHA-256 document hash that appears in the QR code (field Q). The signing chain links documents in sequence:
Hash(N) = SHA-256(NIF + ";" + DocID + ";" + ATCUD + ";" + Amount)
→ first 4 hex chars stored as hash extract
Currently implemented as a simplified hash (no RSA signing of the chain). Full RSA signing chain is planned for production AT certification.
AT API authentication certificate (mTLS)
For SAF-T submission, AT requires mutual TLS authentication:
- Certificate issued by AT or Portuguese PKI
- Stored via the
/v1/certificatesendpoint - Referenced by
at_certificate_idin the adapterConfig - Used for mTLS handshake when submitting SAF-T files
Certificate lifecycle
- Merchant obtains certificates from AT portal
- Uploads PKCS#12 bundle via
POST /v1/certificates - FiscalAPI stores encrypted, tracks expiry
- Certificate expiry webhook fires 30 days before expiration
- Merchant rotates certificate, updates location config
ATCUD implementation
Series registration flow
1. Merchant registers each document series with AT → receives ValidationCode per series
2. ValidationCodes stored in location country_config.series map (keyed by doc type)
3. Adapter resolves doc type from transaction (sale→FT/FS, refund→NC, adjustment→ND)
4. Each transaction: ATCUD = series[docType].atcud_code + "-" + SequentialNumber
5. Sequence is gap-free per (NIF, docType) key
Sequence store
The SequenceStore interface provides atomic gap-free counters:
type SequenceStore interface {
NextSequence(ctx context.Context, key string) (int64, error)
}
- Key format:
{NIF}:{Series}(e.g.,501442600:A 2026/) - PostgreSQL advisory locks ensure gap-free sequencing under concurrent access
- Same pattern as the France NF525 adapter's per-register sequencing
SAF-T PT v1.04_01 structure
The SAF-T file follows the Portuguese standard (Portaria n.º 302/2016):
<AuditFile xmlns="urn:OECD:StandardAuditFile-Tax:PT_1.04_01">
<Header>
<AuditFileVersion>1.04_01</AuditFileVersion>
<CompanyID>{NIF}</CompanyID>
<TaxRegistrationNumber>{NIF}</TaxRegistrationNumber>
<CompanyName>{LegalName}</CompanyName>
<FiscalYear>{YYYY}</FiscalYear>
<StartDate>{YYYY-MM-01}</StartDate>
<EndDate>{YYYY-MM-DD}</EndDate>
<CurrencyCode>EUR</CurrencyCode>
<SoftwareCertificateNumber>{CertNum}</SoftwareCertificateNumber>
<ProductID>FiscalAPI</ProductID>
</Header>
<MasterFiles>
<Customer>...</Customer>
<Product>...</Product>
<TaxTable>
<TaxTableEntry>
<TaxType>IVA</TaxType>
<TaxCountryRegion>PT</TaxCountryRegion>
<TaxPercentage>23</TaxPercentage>
</TaxTableEntry>
</TaxTable>
</MasterFiles>
<SourceDocuments>
<SalesInvoices>
<NumberOfEntries>{count}</NumberOfEntries>
<TotalDebit>{sum}</TotalDebit>
<TotalCredit>{sum}</TotalCredit>
<Invoice>
<InvoiceNo>{DocID}</InvoiceNo>
<ATCUD>{ATCUD}</ATCUD>
<InvoiceStatus>N</InvoiceStatus>
<InvoiceDate>{YYYY-MM-DD}</InvoiceDate>
<InvoiceType>FT</InvoiceType>
<Line>...</Line>
<DocumentTotals>
<TaxPayable>{tax}</TaxPayable>
<NetTotal>{net}</NetTotal>
<GrossTotal>{gross}</GrossTotal>
</DocumentTotals>
</Invoice>
</SalesInvoices>
</SourceDocuments>
</AuditFile>
Document types in SAF-T
| InvoiceType | Description | Transaction type mapping |
|---|---|---|
| FT | Fatura (standard invoice) | sale |
| FS | Fatura Simplificada (< €100, final consumer) | sale (simplified) |
| FR | Fatura-Recibo (invoice-receipt) | sale (with payment) |
| NC | Nota de Crédito (credit note) | credit |
| ND | Nota de Débito (debit note) | adjustment |
Invoice statuses
| Status | Code | Description |
|---|---|---|
| Normal | N | Active document |
| Cancelled | A | Anulado (voided) |
| Self-billing | S | Auto-faturação |
| Summary | R | Resumo (summarized) |
AT error codes
The adapter defines error codes matching AT responses:
| Code | Category | Description | Retriable |
|---|---|---|---|
| AT-001 | auth | Authentication failure | No |
| AT-002 | auth | Certificate expired | No |
| AT-003 | auth | Certificate revoked | No |
| AT-100 | validation | Invalid NIF | No |
| AT-101 | validation | Invalid document date | No |
| AT-102 | validation | Invalid ATCUD format | No |
| AT-200 | atcud | Series not registered | No |
| AT-201 | atcud | Duplicate sequential number | No |
| AT-202 | atcud | Invalid validation code | No |
| AT-300 | signature | Invalid document hash | No |
| AT-301 | signature | Signing chain broken | No |
| AT-800 | rate_limit | Rate limit exceeded | Yes |
| AT-900 | server | Internal AT server error | Yes |
| AT-901 | server | AT service unavailable | Yes |
| AT-902 | server | Request timeout | Yes |
Mock AT service
For testing, the adapter includes MockATServer with endpoints:
POST /saft/submit-- SAF-T file submissionPOST /atcud/register-series-- Series registrationPOST /atcud/validate-- ATCUD validation
The mock supports error injection via request headers for testing all error paths (auth failure, validation errors, rate limiting, timeouts, duplicate detection).
Key source files
| File | Purpose |
|---|---|
internal/adapters/portugal/adapter.go | Main adapter, ProcessTransaction flow |
internal/adapters/portugal/config.go | Config struct, NIF validation (mod-11) |
internal/adapters/portugal/atcud.go | ATCUD generator, formatting |
internal/adapters/portugal/qr.go | QR content generation (Portaria 195/2020) |
internal/adapters/portugal/saft.go | SAF-T PT v1.04_01 XML types |
internal/adapters/portugal/errors.go | AT error codes, retriability |
internal/adapters/portugal/mockat.go | Mock AT server for testing |
internal/locations/model.go | PortugalConfig (location-level config) |