We provide a complete mechanism for SAFT-PT compliance based on the Portuguese Tax Authority's (Autoridade Tributária e Aduaneira - AT) regulatory requirements.
It is required by law for all companies operating in Portugal to maintain SAFT-PT compliant records of all invoices, credit notes, and other fiscal documents. Space Invoices automatically generates SAFT-PT compliant data, document hashes, QR codes, and ATCUD codes.
WARNING: While we take great care to ensure SAFT-PT compliance and that our technical solution fits all requirements where applicable, we urge users to do their own research on the matter. Space Invoices can only ensure the technical aspect of the process, the final responsibility of compliance setup and operation is on the side of the user / provider of the data / the issuer of the invoices ie. the end user and not Space Invoices.
Organization Requirements
The organization must be configured with all required fields for SAFT-PT compliance. These are validated when creating or updating a Portuguese organization.
| Field | Format | Notes |
|---|---|---|
countryAlpha2Code |
PT |
Must be Portugal |
taxNumber |
9 digits | Valid Portuguese NIF, cannot be 123456789 |
companyNumber |
string | Commercial registry number, or NIF if no commercial registration |
phone |
international format | e.g. +351 123456789 |
email |
valid email | Organization contact email |
address |
string | Latin characters only |
city |
string | Latin characters only |
zip |
XXXX-XXX |
Portuguese postal code format (e.g. 1000-001) |
state |
string | Region name (used to determine fiscal region) |
startingCapital |
number | Company starting capital amount |
The fiscalRegionCode is automatically determined from the state field:
- Azores →
PT-AC - Madeira →
PT-MD - All others →
PT
NOTE: Once documents have been created, taxNumber and countryAlpha2Code cannot be changed.
Account Requirements
The following fields are required for Portuguese document validation. They are resolved in this order:
- From
_at.accountFirstname,_at.accountLastname,_at.accountTaxNumberon the document (if provided) - Fallback to
firstname,lastname,taxNumberon the Account making the request
The resolved values are saved to the document's _at object automatically on each save.
| Account field | _at override |
Format | Notes |
|---|---|---|---|
firstname |
_at.accountFirstname |
string | Latin characters only |
lastname |
_at.accountLastname |
string | Latin characters only |
taxNumber |
_at.accountTaxNumber |
string | Personal tax identification number |
Set account-level defaults via the Account API, or pass per-document values in _at when creating the document.
// Option A: set on the account (applies to all documents)
const response = await fetch('https://api.spaceinvoices.com/v1/accounts/{id}', {
method: 'PATCH',
headers: {
"Authorization": "ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"firstname": "Jo\u00e3o",
"lastname": "Silva",
"taxNumber": "123456789"
})
});
// Option B: pass per-document via _at (overrides account values)
const document = await fetch('https://api.spaceinvoices.com/v1/organizations/{id}/documents', {
method: 'POST',
headers: {
"Authorization": "ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"type": "invoice",
"_at": {
"accountFirstname": "Jo\u00e3o",
"accountLastname": "Silva",
"accountTaxNumber": "123456789"
},
// ... other document fields
})
});
Login to auto-populate your access token.
Your access token is displayed in examples.
# Option A: set on the account (applies to all documents)
curl -X PATCH 'https://api.spaceinvoices.com/v1/accounts/{id}' \
-H 'Authorization: ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"firstname": "Jo\u00e3o",
"lastname": "Silva",
"taxNumber": "123456789"
}'
# Option B: pass per-document via _at (overrides account values)
curl -X POST 'https://api.spaceinvoices.com/v1/organizations/{id}/documents' \
-H 'Authorization: ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"type": "invoice",
"_at": {
"accountFirstname": "Jo\u00e3o",
"accountLastname": "Silva",
"accountTaxNumber": "123456789"
}
}'
Login to auto-insert your own access token.
Your access token displayed in examples.
ATCUD Series
Before issuing documents, you must register ATCUD series obtained from the Portuguese Tax Authority (AT).
| Field | Format | Notes |
|---|---|---|
validationCode |
string (min 8 chars) | Code from AT, alphanumeric without vowels, 0, or 1 |
code |
string | Series identifier (e.g. MYSERIES) |
documentType |
enum | invoice, credit-note, or estimate |
startDate |
date | When the series becomes valid |
firstNumber |
string | Starting sequential number |
Supported Document Types
| Type | SAFT-PT Code | Category |
|---|---|---|
invoice |
FT (Fatura) | Sales Invoice |
advance |
FT (Fatura) | Sales Invoice |
credit-note |
NC (Nota de Crédito) | Sales Invoice |
estimate |
OR (Working Document) | Working Document |
Document numbers are automatically generated in the format: {TYPE} {SERIES}/{SEQUENCE}
Examples: FT MYSERIES/000000001, NC MYSERIES/000000042
Document Item Classification (required)
Each non-separator document item must include a classification field. This field is required for Portuguese organizations. Valid values:
| Value | SAFT-PT ProductType | Use for |
|---|---|---|
product |
P | Physical goods |
service |
S | Services |
advance |
O | Advance payments |
convenience_fee |
I | Convenience fees |
late_fee |
I | Late payment fees |
handling_fee |
I | Handling fees |
NOTE: Use the API value (e.g. service), not the SAFT-PT code (e.g. S).
NOTE: For advance documents, all items must have classification set to advance.
// Example document item
{
"name": "Consulting service",
"quantity": 1,
"priceGross": 100.00,
"classification": "service",
"_documentItemTaxes": [{ "rate": 23.0 }]
}
Document Client Requirements
The _documentClient object has conditional requirements based on the isEndCustomer flag:
End customer (isEndCustomer: true)
| Field | Required | Notes |
|---|---|---|
isEndCustomer |
YES | Must be true |
name |
NO | Latin characters only |
taxNumber |
NO | Not required for end customers |
Business customer (isEndCustomer: false or omitted)
| Field | Required | Format |
|---|---|---|
isEndCustomer |
YES | Must be false |
address |
YES | Latin characters only |
city |
YES | Latin characters only |
zip |
YES | XXXX-XXX (Portuguese postal code) |
taxNumber |
YES | 9 digits (valid Portuguese NIF) |
Tax Requirements
Taxes are matched by rate in _documentItemTaxes. For Portuguese documents, a legal 0% line must resolve to explicit exemption metadata. A missing tax is not the same as a valid Portuguese zero-rate tax.
The API resolves Portuguese zero-rate metadata in this order:
_documentItems[*]._documentItemTaxes[*].taxExemptionCodeandtaxExemptionReason- the referenced
Taxrecord'staxExemptionCodeandtaxExemptionReason
If a Portuguese line resolves to 0% and no exemption metadata can be resolved, the API rejects the document.
The export mapping for the most common Portuguese cases is:
| Case | Input | SAFT-PT output |
|---|---|---|
| Standard VAT | positive VAT rate | NOR, RED, or INT depending on rate/classification |
| Margin scheme | rate: 0 + M13 |
TaxType=IVA, TaxCode=OUT |
| Intra-community delivery | rate: 0 + M16 |
TaxType=IVA, TaxCode=ISE |
| Export delivery | rate: 0 + M02 |
TaxType=IVA, TaxCode=ISE |
| Not subject to VAT | rate: 0 + M99 |
TaxType=NS, TaxCode=NS |
Tax exemption codes: When a tax rate is 0 or exempt, a taxExemptionCode should be provided. Valid Portuguese codes include M01, M02, M04, M05, M06, M07, M09, M10, M11, M12, M13, M14, M15, M16, M19, M20, M21, M25, M26, M30, M31, M32, M33, M34, M40, M41, M42, M43, M44, M45, M46, and M99.
The special code M99 indicates "Not Subject to VAT".
PT 0% Tax Metadata
Use an explicit 0% tax whenever a Portuguese document line is legally non-taxable or exempt. Do not leave the line without tax and expect the export to infer the legal reason later.
You can implement this in two supported ways:
- Create dedicated
Taxrecords withrate: 0,taxExemptionCode, andtaxExemptionReason, then reference them bytaxId. - Pass line-level
_documentItemTaxeswithrate: 0,taxExemptionCode, andtaxExemptionReasonwhen the meaning is document-specific.
Line-level exemption metadata overrides the referenced tax.
Recommended dedicated Portuguese zero-rate taxes:
M13for margin scheme / second-hand goodsM16for intra-community deliveryM02for export deliveryM99only when the transaction is not subject to VAT
Important: A generic "NoTax" tax should not be reused for multiple Portuguese legal cases. Create one zero-rate tax per legal meaning, or pass the exemption data on the document line.
Typical validation errors are:
Portuguese 0% taxes require taxExemptionCode metadata.Portuguese 0% taxes require taxExemptionReason metadata.PT 0% tax metadata missing for line "...". No tax is attached; provide taxExemptionCode/taxExemptionReason on the document line or referenced tax.
// Option A: create a dedicated PT 0% tax and reuse it
await fetch('https://api.spaceinvoices.com/v1/Organizations/{id}/taxes', {
method: 'POST',
headers: {
Authorization: 'ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Margin scheme',
recoverable: true,
compound: false,
taxExemptionCode: 'M13',
taxExemptionReason: 'Regime da margem de lucro - Bens em segunda mão',
_taxRates: [{
rate: 0,
dateValidFrom: '1970-01-01T00:00:00.000Z'
}]
})
});
// Option B: pass the PT 0% meaning directly on the document line
await fetch('https://api.spaceinvoices.com/v1/Organizations/{id}/documents', {
method: 'POST',
headers: {
Authorization: 'ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'invoice',
_documentItems: [{
name: 'Used bicycle',
classification: 'product',
quantity: 1,
priceGross: 100,
_documentItemTaxes: [{
rate: 0,
taxExemptionCode: 'M13',
taxExemptionReason: 'Regime da margem de lucro - Bens em segunda mão'
}]
}]
})
});
Validation Rules
| Rule | Details |
|---|---|
| Document date | Must be today's date (unless manual or canceled) |
| Custom totals | Only allowed for manual documents (_at.isManual: true) |
| Cancellation | Requires _at.cancellationReason |
| Negative quantities | Only allowed on credit notes |
| Negative prices | Not allowed |
| Crypto currencies | Not supported for Portuguese documents |
| Character encoding | Organization and client text fields must use Latin characters only (Latin Extended-A) |
SAFT-PT Export
The SAFT-PT XML export can be generated for a given date range and includes all non-draft documents (invoices, credit notes, advances, estimates). The export conforms to SAFT-PT version 1.04_01.
The XML is generated with windows-1252 encoding. Portuguese characters such as ã are supported.