Operational documentation for synchronous Moloni document issuance.
All endpoints are synchronous: each request returns either a final issuance result or a final error.
| Document | Path | Use case |
|---|---|---|
| Invoice receipt (FR) | POST /api/v1/invoices/ | Immediate payment; existing backward-compatible endpoint. |
| Invoice (FT) | POST /api/v1/documents/invoices/ | Issue invoice now, payment later. |
| Receipt (RC) | POST /api/v1/documents/receipts/ | Register payment against a previously issued invoice. |
The credentials below are for demonstration only and do not grant real access.
X-Client-Id: airtable_ops_prod X-Api-Key: ak_demo_9f83b7d1a5c40c22 X-Api-Secret: as_demo_5baf4f0f613949f0863b83de8f4c4f90
Idempotency-Key is a required request header generated by the integrator. It protects document issuance from accidental duplicates when a request is retried because of a timeout, network failure, or uncertain response.
Use a stable unique value from the source system when possible, prefixed by the operation type. A UUID is also valid. Keep the same key only for retries of the exact same operation and exact same JSON body.
FR immediate payment: Idempotency-Key: fr-order-74291 FT invoice issue: Idempotency-Key: ft-school-2026-0001 RC receipt issue: Idempotency-Key: rc-school-2026-0001 UUID example: Idempotency-Key: 2878e76a-6654-4e52-a11b-dcc525eef985
| Scenario | Correct key behavior | Result |
|---|---|---|
| Retry the same FT after a timeout | Reuse ft-school-2026-0001 with the same body | Returns the original FT response. |
| Create the RC for that FT | Use a new key, for example rc-school-2026-0001 | Creates a separate receipt operation. |
| Send same key with changed amount/customer/items | Do not do this | Returns 409 IDEMPOTENCY_CONFLICT. |
The sales payload below is used by FR and FT. Optional fields can be omitted. If VAT number is missing, the system applies final-consumer VAT number internally.
{
"reference": "TI-2026-05-18-0007",
"customer": {
"name": "Eduardo Costa",
"vat_number": "509999999",
"email": "[email protected]",
"address": "Rua do Campo Grande 45, Lisboa"
},
"items": [
{
"product": "mensalidade TheInventors",
"gross_price": 10.00,
"vat_rate": 23
}
]
}
| Field | Type | Behavior |
|---|---|---|
| customer.vat_number | string | Customer matching uses this value first. FT requires a valid PT VAT number. FR may fall back to the final-consumer customer when the supplied VAT number is invalid. |
| customer.address | string | Used when creating a new customer. Existing customers are reused without updating their profile. |
| customer.email | string | If present, validated as email and forwarded to customer flow in Moloni. |
Use this when the customer pays immediately and the issued document must include payment.
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="https://billing.theinventors.io"
PATH_URL="/api/v1/invoices/"
IDEMP="fr-escola-2026-0001"
CLIENT_ID="airtable_ops_prod"
API_KEY="ak_demo_9f83b7d1a5c40c22"
API_SECRET="as_demo_5baf4f0f613949f0863b83de8f4c4f90"
BODY='{
"reference": "FR-ESCOLA-2026-0001",
"customer": {
"name": "Escola Exemplo, Lda",
"vat_number": "509999999",
"email": "[email protected]",
"address": "Rua Exemplo 1, Lisboa"
},
"items": [
{
"product": "Mensalidade Julho",
"gross_price": 500.00,
"vat_rate": 23
},
{
"product": "Kit aluno",
"gross_price": 120.00,
"vat_rate": 23
}
]
}'
curl -sS -X POST "${BASE_URL}${PATH_URL}" \
-H "Content-Type: application/json" \
-H "X-Client-Id: ${CLIENT_ID}" \
-H "X-Api-Key: ${API_KEY}" \
-H "X-Api-Secret: ${API_SECRET}" \
-H "Idempotency-Key: ${IDEMP}" \
--data "$BODY"
{
"success": true,
"request_id": "req_4ad9b2ec1d9f3a71",
"reference": "FR-ESCOLA-2026-0001",
"ref_interna": "FR-ESCOLA-2026-0001",
"document": {
"type": "invoice_receipt",
"moloni_document_id": 974989624,
"number": "FR M/32",
"series": "M",
"total_gross": 620,
"currency": "EUR",
"pdf_url": "https://...",
"created_at": "2026-07-28T08:15:00Z"
},
"documento": {
"tipo": "fatura_recibo",
"moloni_document_id": 974989624,
"numero": "FR M/32",
"serie": "M",
"total_com_iva": 620,
"moeda": "EUR",
"pdf_url": "https://...",
"created_at": "2026-07-28T08:15:00Z"
},
"email": {
"status": "sent",
"error": null
}
}
When FR uses the final-consumer fallback, the success response also includes:
"customer_resolution": {
"fallback": "final_consumer_invalid_vat"
}
Use this when the customer will pay later. This issues an invoice without payment attached.
customer.alternate_address_id is an optional positive JSON integer identifying an existing Moloni alternate address. Omit it to retain the current main-address behavior. Strings, decimal numbers, null, zero, negative values and booleans are rejected.
"customer": {
"name": "Escola Exemplo",
"vat_number": "500766460",
"alternate_address_id": 2051743
}
With this field, a valid VAT number and an existing, uniquely matched customer are required. The API resolves the customer by VAT and queries customerAlternateAddresses/getAll in the selected Moloni environment, checking both address and customer IDs before invoices/insert. It never creates or updates a customer or address in this flow. IDs are environment-specific: a PROD ID must not be reused in STG.
Malformed, unavailable or other-customer addresses return 422 INVALID_ALTERNATE_ADDRESS, with error.details.field = customer.alternate_address_id, without issuing an invoice. An upstream validation failure returns 503 MOLONI_UNAVAILABLE (or 504 MOLONI_TIMEOUT), not an invalid-address result. Ambiguous VAT matching keeps 422 CUSTOMER_AMBIGUOUS; invalid VAT keeps the existing validation error.
The field is included in the original body hash. Changing it with the same Idempotency-Key returns 409 IDEMPOTENCY_CONFLICT. Identical-body retries return the stored response, including stored errors. FR and RC are unchanged; this option is supported only on FT. The legacy alias cliente.alternate_address_id is also accepted; do not combine both customer object aliases when requesting an alternate address.
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="https://billing.theinventors.io"
PATH_URL="/api/v1/documents/invoices/"
IDEMP="ft-escola-2026-0001"
CLIENT_ID="airtable_ops_prod"
API_KEY="ak_demo_9f83b7d1a5c40c22"
API_SECRET="as_demo_5baf4f0f613949f0863b83de8f4c4f90"
BODY='{
"reference": "FT-ESCOLA-2026-0001",
"customer": {
"name": "Escola Exemplo, Lda",
"vat_number": "509999999",
"email": "[email protected]",
"address": "Rua Exemplo 1, Lisboa"
},
"items": [
{
"product": "Mensalidade Julho",
"gross_price": 500.00,
"vat_rate": 23
},
{
"product": "Kit aluno",
"gross_price": 120.00,
"vat_rate": 23
}
]
}'
curl -sS -X POST "${BASE_URL}${PATH_URL}" \
-H "Content-Type: application/json" \
-H "X-Client-Id: ${CLIENT_ID}" \
-H "X-Api-Key: ${API_KEY}" \
-H "X-Api-Secret: ${API_SECRET}" \
-H "Idempotency-Key: ${IDEMP}" \
--data "$BODY"
{
"success": true,
"request_id": "req_9f0b4c7e2d1a6b35",
"reference": "FT-ESCOLA-2026-0001",
"ref_interna": "FT-ESCOLA-2026-0001",
"document": {
"type": "invoice",
"moloni_document_id": 998320760,
"number": "FT ESC/123",
"series": "ESC",
"total_gross": 620,
"currency": "EUR",
"pdf_url": "https://...",
"created_at": "2026-07-28T08:20:00Z"
},
"documento": {
"tipo": "fatura",
"moloni_document_id": 998320760,
"numero": "FT ESC/123",
"serie": "ESC",
"total_com_iva": 620,
"moeda": "EUR",
"pdf_url": "https://...",
"created_at": "2026-07-28T08:20:00Z"
}
}
Use this when the invoice has been paid. If the FT was created by this API, send its moloni_document_id as invoice_document_id. The system reuses the stored customer and invoice total unless an explicit payment.amount is provided.
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="https://billing.theinventors.io"
PATH_URL="/api/v1/documents/receipts/"
IDEMP="rc-escola-2026-0001"
CLIENT_ID="airtable_ops_prod"
API_KEY="ak_demo_9f83b7d1a5c40c22"
API_SECRET="as_demo_5baf4f0f613949f0863b83de8f4c4f90"
BODY='{
"reference": "RC-ESCOLA-2026-0001",
"invoice_document_id": 998320760,
"payment": {
"date": "2026-07-28",
"amount": 620.00,
"method": "bank_transfer"
}
}'
curl -sS -X POST "${BASE_URL}${PATH_URL}" \
-H "Content-Type: application/json" \
-H "X-Client-Id: ${CLIENT_ID}" \
-H "X-Api-Key: ${API_KEY}" \
-H "X-Api-Secret: ${API_SECRET}" \
-H "Idempotency-Key: ${IDEMP}" \
--data "$BODY"
{
"reference": "RC-ESCOLA-2026-0001",
"invoice_document_id": 998320760,
"payment": {
"date": "2026-07-28",
"amount": 620.00,
"method": "bank_transfer"
}
}
{
"success": true,
"request_id": "req_a2c7d1e9f0b3c4d5",
"reference": "RC-ESCOLA-2026-0001",
"ref_interna": "RC-ESCOLA-2026-0001",
"document": {
"type": "receipt",
"moloni_document_id": 998421111,
"number": "RC ESC/45",
"series": "ESC",
"total_gross": 620,
"currency": "EUR",
"pdf_url": "https://...",
"created_at": "2026-07-28T08:30:00Z",
"associated_invoice_document_id": 998320760
},
"documento": {
"tipo": "recibo",
"moloni_document_id": 998421111,
"numero": "RC ESC/45",
"serie": "ESC",
"total_com_iva": 620,
"moeda": "EUR",
"pdf_url": "https://...",
"created_at": "2026-07-28T08:30:00Z",
"fatura_associada_moloni_document_id": 998320760
}
}
If the invoice was not created by this API, include customer and payment.amount, because the system cannot reuse stored customer/total data.
When the invoice exists in this API, the receipt reuses the Moloni environment recorded on that invoice. School FT and RC documents use the dedicated ESC series in PROD and the registered M2026 series in STG. The FR configuration remains independent.
All endpoints return the same error envelope.
{
"success": false,
"request_id": "req_62d03a9dabc7e8f4",
"error": {
"code": "VALIDATION_ERROR",
"message": "items[0].vat_rate is invalid.",
"details": null
}
}
| HTTP | Code | When it happens |
|---|---|---|
| 201 | - | Document issued successfully. |
| 400 | VALIDATION_ERROR | Invalid payload or missing Idempotency-Key. |
| 401 | AUTH_ERROR | Missing or invalid API client credentials. |
| 403 | IP_NOT_ALLOWED | Source IP is outside the key whitelist. |
| 409 | IDEMPOTENCY_CONFLICT | Same Idempotency-Key used with a different body. |
| 422 | MOLONI_BUSINESS_ERROR | Business rule rejected by Moloni API. |
| 422 | CUSTOMER_AMBIGUOUS | Ambiguous customer during resolution process. |
| 422 | INVALID_ALTERNATE_ADDRESS | FT-only: invalid alternate_address_id, missing VAT/existing customer, nonexistent or other-customer address. No invoice issued. |
| 502 | MOLONI_BAD_GATEWAY | Invalid/intermediary response from Moloni. |
| 503 | MOLONI_UNAVAILABLE | Moloni service unavailable. |
| 504 | MOLONI_TIMEOUT | Timeout while communicating with Moloni. |
The Type of Request column identifies the requested fiscal document and, when issuance succeeded, includes its Moloni number:
If issuance fails before Moloni assigns a number, the column displays only Invoice Receipt, Invoice, or Receipt. The failure reason remains in the separate Error column.
Backoffice: https://billing.theinventors.io/admin/