Skip to main content

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/certificates endpoint
  • Referenced by at_certificate_id in the adapter Config
  • Used for mTLS handshake when submitting SAF-T files

Certificate lifecycle

  1. Merchant obtains certificates from AT portal
  2. Uploads PKCS#12 bundle via POST /v1/certificates
  3. FiscalAPI stores encrypted, tracks expiry
  4. Certificate expiry webhook fires 30 days before expiration
  5. 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

InvoiceTypeDescriptionTransaction type mapping
FTFatura (standard invoice)sale
FSFatura Simplificada (< €100, final consumer)sale (simplified)
FRFatura-Recibo (invoice-receipt)sale (with payment)
NCNota de Crédito (credit note)credit
NDNota de Débito (debit note)adjustment

Invoice statuses

StatusCodeDescription
NormalNActive document
CancelledAAnulado (voided)
Self-billingSAuto-faturação
SummaryRResumo (summarized)

AT error codes

The adapter defines error codes matching AT responses:

CodeCategoryDescriptionRetriable
AT-001authAuthentication failureNo
AT-002authCertificate expiredNo
AT-003authCertificate revokedNo
AT-100validationInvalid NIFNo
AT-101validationInvalid document dateNo
AT-102validationInvalid ATCUD formatNo
AT-200atcudSeries not registeredNo
AT-201atcudDuplicate sequential numberNo
AT-202atcudInvalid validation codeNo
AT-300signatureInvalid document hashNo
AT-301signatureSigning chain brokenNo
AT-800rate_limitRate limit exceededYes
AT-900serverInternal AT server errorYes
AT-901serverAT service unavailableYes
AT-902serverRequest timeoutYes

Mock AT service

For testing, the adapter includes MockATServer with endpoints:

  • POST /saft/submit -- SAF-T file submission
  • POST /atcud/register-series -- Series registration
  • POST /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

FilePurpose
internal/adapters/portugal/adapter.goMain adapter, ProcessTransaction flow
internal/adapters/portugal/config.goConfig struct, NIF validation (mod-11)
internal/adapters/portugal/atcud.goATCUD generator, formatting
internal/adapters/portugal/qr.goQR content generation (Portaria 195/2020)
internal/adapters/portugal/saft.goSAF-T PT v1.04_01 XML types
internal/adapters/portugal/errors.goAT error codes, retriability
internal/adapters/portugal/mockat.goMock AT server for testing
internal/locations/model.goPortugalConfig (location-level config)