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)