Invoice API Usage Guide
API Version: v1.0 Microservice: XDPeople.Soba.Invoice.WebAPI
Overview
The Invoice API manages the lifecycle of sales documents through a single create endpoint. One call — POST /gateway/invoice/invoices — creates documents whose behaviour is determined by the documentTypeId.
Supported document natures
| Nature | Code | Dedicated guide |
|---|---|---|
| Sales invoice | FT | Invoices Guide |
| Simplified invoice | FS | Invoices Guide |
| Invoice-receipt | FR | Invoice-Receipt Guide |
| Credit note | NC | Credit Notes Guide |
| Debit note | ND | Debit Notes Guide |
| Order | NE | Orders Guide |
| Quote | ORC | Quotes Guide |
| Transport guides | GT, GR, GA, GC, GD | Transport Guides |
Allowed payment mechanisms
Any payments[] entry must use a payment type whose mechanism (paymentMechanism) is in the list below. This restriction applies to all documents, regardless of whether payments are required or optional. Requests send paymentTypeId.
| Code | Description |
|---|---|
NU | Cash |
CC | Credit card |
CD | Debit card |
OU | Other |
Resolve valid identifiers with GET /gateway/payment-types and pick records whose paymentMechanism matches one of the codes above.
The API validates the mechanism of each payments[] entry when creating a document. Payment types with other mechanisms (for example TB, CH, MB, MW) are rejected, even if they are active in the client's database.
Checking-account payment type rules
This API does not accept payment types with SendToCheckingAccount = true. Any attempt to use them returns Payment.CheckingAccount.NotSupported. Document types configured with CheckingAccountFlow != 0 or MoveSetting = CheckingAccount require exactly that kind of payment type — making them incompatible with this API.
When the document type has checking-account requirements, the payment type supplied in payments[] must satisfy them. If it does not, the payment entry is rejected:
| Document type condition | Error |
|---|---|
MoveSetting = CheckingAccount and payment type does not post to checking account | Payment.CheckingAccountType.RequiredForMoveSetting |
Payment mode has IncrementDays > 0 and payment type is not checking-account | Payment.CheckingAccountType.RequiredForDeferredMode |
CheckingAccountFlow != 0 and payment type is not checking-account | Payment.CheckingAccountType.RequiredForDocumentFlow |
Receipt nature RE cannot be created through this API. Use invoice-receipt (FR) when payment is collected on the same document, or consult product documentation for checking-account flows. The Receipts Guide is kept for reference only.
Other natures (proforma, CAS, DIS, etc.) are not available via this API and return DocumentType.NotSupported.
Key features
- End-to-end document creation (validation, totals, payments, persistence)
- Document creation, update, and query (flat and full formats)
- Advanced filters and pagination
- JWT authentication
- Timezone-aware date persistence via
X-Timezoneheader
How document creation works
When you call POST /gateway/invoice/invoices, the API:
- Initial validation — supported nature, country/licence, payment mechanisms, series, customer, issuance rules
- Header preparation — entity resolution, transport defaults, fiscal checks
- Calculations — line and header totals, taxes, discounts
- Payments — split payments, change; for document types with optional payments, at most one entry with no split or change
- Finalization — ATCUD, digital signature, legal observations
- Transactional persistence — header, lines, references, stock, counters
Stock movement follows the document type and item configuration.
Technical information
Base URL
| Environment | Base URL | Documentation |
|---|---|---|
| Production | https://api.xdsoba.com/gateway | Swagger UI |
Request headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer {jwt} |
Content-Type | Yes (POST/PUT) | application/json |
X-Timezone | Recommended | IANA timezone (e.g. Europe/Lisbon). Dates are stored in the local timezone. |
Health checks
GET /gateway/invoice/health
Authentication
POST https://api.xdsoba.com/gateway/auth/combined-login
Content-Type: application/json
{
"tenantKey": "XDPT.30010",
"tenantPassword": "your-tenant-password",
"userKey": "user@example.com",
"userPassword": "your-user-password"
}
Use the token in all requests:
Authorization: Bearer {token}
X-Timezone: Europe/Lisbon
Resolving document type IDs
The numeric documentTypeId is not portable between client databases. Resolve it with:
GET /gateway/document-type
Authorization: Bearer {token}
Use each row's id as documentTypeId. Match description / keyId to the business process (quote, order, invoice, credit note, etc.).
Call GET /gateway/document-type before integration tests. Replace illustrative IDs in the guides with values returned by GET /gateway/document-type.
See Creating document types (API Base) if a nature is missing from your document types.
Creating document types (API Base)
POST /gateway/document-type
Content-Type: application/json
Authorization: Bearer {token}
{
"keyId": "ORC",
"description": "Quote",
"documentType": 0,
"invoiceType": "ORC",
"entityType": 0,
"checkingAccountFlow": 0,
"stockFlow": 0,
"stockBehaviour": 0,
"cashierFlow": 0,
"defaultEntityType": 0,
"printSystemType": 0,
"instances": 1,
"isValued": true,
"voucherExpirationDays": 0,
"voucherDeductibleAfter": 0,
"isAdvancement": false,
"forceAskBatches": false,
"defaultAccountId": 0,
"reportLayout": "",
"htmlLayout": ""
}
Available endpoints
Create document
POST /gateway/invoice/invoices
Content-Type: application/json
Authorization: Bearer {token}
X-Timezone: Europe/Lisbon
Creates a sales document (nature determined by documentTypeId).
Update document
PUT /gateway/invoice/invoices/{id}
Get document by ID
GET /gateway/invoice/invoices/{id}
GET /gateway/invoice/invoices/{id}/flat
/flat— backward-compatible flat format for integrations- Without
/flat— full format for printing (header + bodies)
Get document by type, series, and number
GET /gateway/invoice/invoices/{documentTypeId}/{serieId}/{number}
GET /gateway/invoice/invoices/{documentTypeId}/{serieId}/{number}/flat
List documents
GET /gateway/invoice/invoices?page=1&pageSize=20&status=Open&startDate=2025-01-01&endDate=2025-12-31
Authorization: Bearer {token}
| Parameter | Description |
|---|---|
page | Page number (default: 1) |
pageSize | Page size (default: 20) |
status | Open / Closed |
startDate | Documents created on or after this date |
endDate | Documents created on or before this date |
entityKeyId | Filter by customer/supplier |
documentTypeId | Filter by document type |
sortBy | CreationDate, DocumentNumber, Total, EntityDescription |
sortDescending | Descending order (default: false) |
Data model (create document)
Required fields
{
"serieId": 1,
"documentTypeId": 5,
"currencyId": 1,
"documentBodies": [
{
"itemKeyId": "PROD001",
"quantity": 2,
"retailPrice": 50,
"taxId": 1,
"secondTaxId": 0
}
],
"payments": [
{
"paymentTypeId": 1,
"amount": 123
}
]
}
| Field | Notes |
|---|---|
currencyId | Required in the API contract (> 0). Effective currency is currently taken from terminal/configuration. |
payments | Required when the document type has cashier flow or checking-account flow active. Optional when neither flow applies — omit or supply at most one entry matching the document total. Transport guides (GT…GD), orders (NE), and quotes (ORC) are typically configured without either flow. |
discountPercentage | Optional header discount (0–100). Line-level: documentBodies[].discountPercentage. |
Entity (customer) resolution
entityKeyId is optional. When resolving the entity the API follows this order:
entityKeyIdsupplied — the entity is loaded directly by key. If not found, the request fails immediately withEntity.KeyId.NotFound(404). No fallback is attempted.entityKeyIdomitted,entityVatsupplied — a lookup by VAT is performed. If a match is found, that entity is used. If not, a temporary snapshot is created using the supplied override fields (entityDescription,entityAddress, etc.) with the final-consumer key.- Both omitted — the configured default final consumer is loaded from the database.
Providing an entityKeyId that does not exist is a hard error (404). The entityVat field is ignored in this case — correct or omit entityKeyId first.
Common optional fields
{
"entityDescription": "Example Client, Ltd",
"entityVat": "PT123456789",
"entityAddress": "123 Example Street",
"entityPostalCode": "1000-000",
"entityCity": "Lisbon",
"entityCountry": "Portugal",
"documentDate": "2025-11-28T10:00:00Z",
"dueDate": "2025-12-31",
"paymentModeId": 1,
"obs": "Document notes",
"docReference": "2;1;2026-06-05;FAC;;FAC;",
"extraDocReference": "EXTRA-REF",
"emissionReason": "Return of goods",
"loadPlaceDescription": "Central Warehouse;Lisbon;1000-100;PT",
"unloadPlaceDescription": "Customer Store;Porto;4000-000;PT",
"loadPlaceDate": "2025-11-28T10:00:00",
"unloadPlaceDate": "2025-11-28T15:00:00",
"carrierDescription": "XYZ Carrier",
"taxRegionId": 1,
"numberPersons": 2
}
Load and unload places fields
| Field | Description |
|---|---|
loadPlaceDescription | Origin / load place (max 100 chars) |
unloadPlaceDescription | Destination / unload place (max 100 chars) |
loadPlaceDate | Load date and time (date-time) |
unloadPlaceDate | Unload date and time (date-time) |
carrierDescription | Carrier name or description (max 100 chars) |
Load address (loadPlaceDescription)
When omitted, the engine resolves a default in this order:
- Caller-supplied value — used as-is.
LoadUnloadPlaceassociated with the terminal's default warehouse (structured format).- Licence address (structured format).
- Licence fiscal name / commercial name as fallback.
Unload address (unloadPlaceDescription)
When omitted, the engine resolves in this order:
- Caller-supplied value — used as-is.
- Customer's
EntityAddressmarked asDefaultShipmentAddress = true(structured format).
The expected format is "address;city;postalCode;country" — four semicolon-separated segments. When the field is present in the request, it should follow this same format. When omitted and automatically resolved by the engine, multi-part addresses are formatted the same way.
Dates
When loadPlaceDate or unloadPlaceDate are omitted:
| Date | Default value |
|---|---|
loadPlaceDate | Creation time + 15 minutes (or LoadDateAdditionMinutes when configured) |
unloadPlaceDate | End of the next calendar day at 23:59 |
LoadDateAdditionMinutes is a system configuration value (XConfigMisc). When not configured, 15 minutes is used.
loadPlaceDescription, unloadPlaceDescription, and related load and unload place fields are ignored for simplified invoices (FS), credit notes (NC), and debit notes (ND). Do not rely on them on those natures.
Line fields (reference documents — NC/ND)
On each referenced line, only originBodyGuid, relationType, and quantity are required. The API derives itemKeyId, taxId, retailPrice, and netPrice from the origin line when omitted:
{
"quantity": 1,
"originBodyGuid": "660e8400-e29b-41d4-a716-446655440001",
"relationType": 1
}
- Obtain
originBodyGuidfromGET /gateway/invoice/invoices/{documentTypeId}/{serieId}/{number}/flat(fieldguidon each line). relationType:1= ReferenceOrigin (required for NC/ND).- Quantities are positive; the document type determines credit/debit semantics.
- To override prices (e.g. value correction), supply
retailPrice/netPriceexplicitly. On Angolan documents, prices must matchtotalAmount / quantityfrom the origin — see the dedicated guides for details.
For the full field reference, derived-field rules, and Angola-specific restrictions, see the Credit Notes Guide and Debit Notes Guide.
Parent-child lines (guid / parentGuid)
Menus and composite articles are different catalogue concepts (see below). When you send child lines, both use the same linking fields in the payload:
| Field | Where | Description |
|---|---|---|
guid | Parent line only | Client-generated UUID that identifies the parent line in the request |
parentGuid | Child lines | Must equal the parent line's guid |
Shared rules:
- One level only — child lines cannot have their own children.
- Child lines linked with
parentGuidare treated as informative and are excluded from the fiscal total; the parent line carries the sale value. - Generate a new UUID for each parent line in every request.
Menu
A menu is a catalogue article of type Menu. The customer or operator selects options from the article's menu composition (e.g. starter, main course, side dish). Each selected option is sent as a child line.
The parent itemKeyId must be the menu article. Child itemKeyId values are the chosen options.
Example: Document with menu.
Composite article
A composite article is a catalogue article of type Composite — a kit or finished product with components defined in composite detail in the catalogue.
Unlike a menu, you do not need to send the component articles in documentBodies. A single line with the composite itemKeyId is enough — the same shape as a normal article line (no guid / parentGuid required).
Optionally, you may add child lines with parentGuid if you want the components listed explicitly on the document (quantities and prices follow the composite article setup).
Example: Document with composite article.
Usage examples
Simple invoice with payment
POST /gateway/invoice/invoices
Content-Type: application/json
Authorization: Bearer {token}
X-Timezone: Europe/Lisbon
{
"serieId": 1,
"documentTypeId": 5,
"entityKeyId": "CLI001",
"currencyId": 1,
"entityDescription": "Example Client, Ltd",
"entityVat": "PT123456789",
"paymentModeId": 1,
"payments": [
{
"paymentTypeId": 1,
"amount": 123
}
],
"documentBodies": [
{
"itemKeyId": "PROD001",
"itemDescription": "Product 1",
"quantity": 2,
"retailPrice": 50,
"taxId": 1,
"secondTaxId": 0
}
]
}
Response (201 Created) includes documentNumber, total, totalTaxes, change (when applicable), and guid.
Split payment
{
"payments": [
{
"paymentTypeId": 1,
"amount": 50
},
{
"paymentTypeId": 3,
"amount": 73
}
]
}
The sum of amount values must match the delivered total. Change is calculated automatically when the payment type allows it.
Document with menu
Use when selling a menu article: parent line with guid (the menu) and child lines with parentGuid (selected options). Item keys, prices, and payment total are illustrative — resolve real values via the API.
POST /gateway/invoice/invoices
Content-Type: application/json
Authorization: Bearer {token}
X-Timezone: Europe/Lisbon
{
"serieId": 1,
"documentTypeId": 1,
"entityKeyId": "1",
"entityVat": "555666777",
"currencyId": 1,
"obs": "Document notes.",
"saleZoneAreaObjectId": 1,
"payments": [
{
"paymentTypeId": 1,
"amount": 1140
}
],
"documentBodies": [
{
"guid": "06a646ac-1101-4252-8fd9-20c7173ded78",
"itemKeyId": "353",
"quantity": 1,
"retailPrice": 10,
"taxId": 1
},
{
"parentGuid": "06a646ac-1101-4252-8fd9-20c7173ded78",
"itemKeyId": "366",
"quantity": 1,
"retailPrice": 0,
"taxId": 1
},
{
"parentGuid": "06a646ac-1101-4252-8fd9-20c7173ded78",
"itemKeyId": "354",
"quantity": 1,
"retailPrice": 0,
"taxId": 1
}
]
}
Item 353 is the menu article; 366 and 354 are the selected options. The sum of payments[].amount must match the total calculated by the API.
Document with composite article
Use when selling a composite article. Send only the composite line — component articles from composite detail do not need to be included in the request.
POST /gateway/invoice/invoices
Content-Type: application/json
Authorization: Bearer {token}
X-Timezone: Europe/Lisbon
{
"serieId": 1,
"documentTypeId": 5,
"entityKeyId": "CLI001",
"currencyId": 1,
"payments": [
{
"paymentTypeId": 1,
"amount": 49.9
}
],
"documentBodies": [
{
"itemKeyId": "KIT001",
"quantity": 1,
"retailPrice": 49.9,
"taxId": 1
}
]
}
Item KIT001 must be a catalogue article of type Composite. The API resolves components from the article's composite detail — you do not send COMP-A, COMP-B, or similar unless you explicitly want them as informative child lines on the printed document.
List open documents
GET /gateway/invoice/invoices?status=Open&page=1&pageSize=20
Authorization: Bearer {token}
Error handling
| Code | When |
|---|---|
201 Created | Document created |
200 OK | Update or query success |
400 Bad Request | Validation failed |
401 Unauthorized | Missing/invalid token |
404 Not Found | Document not found |
409 Conflict | Concurrency conflict |
500 Internal Server Error | Unexpected error |
Common validation codes: DocumentType.NotSupported, Payment.InsufficientValue, SaleDocumentRules.NCND.*, DocumentReferenceBalance.*.
Best practices
- Always send
X-Timezonefor correct local dates - Resolve
documentTypeIdwithGET /gateway/document-type— never hard-code guide examples - Use
payments[]at header level for documents that require payment - For NC/ND: set
emissionReason; use per-line GUID refs for in-app origins (PT/AO) - Menus: send selected options as child lines with
guid/parentGuid— see menu example - Composite articles: a single line with the composite
itemKeyIdis enough — see composite example
Specialized guides
| Guide | Topic |
|---|---|
| Quotes | ORC |
| Orders | NE |
| Invoices | FT, FS |
| Invoice-Receipt | FR |
| Credit Notes | NC |
| Debit Notes | ND |
| Transport Guides | GT, GR, GA, GC, GD |
| Receipts | RE (reference — not available via API) |
Last Updated: July 7, 2026