Invoices
Validate Invoice
/v1/app/invoices/validate
Checks an invoice payload against the FIRS schema to confirm it's ready for submission, without submitting it.
| Field | Type | Required | Description |
|---|---|---|---|
| business_id | uuid | Yes | FIRS business ID — must be a valid UUID |
| irn | string | Yes | Invoice Reference Number |
| issue_date | date | Yes | |
| due_date | date | Yes | Must be on or after issue_date |
| invoice_type_code | string | Yes | FIRS invoice type code |
| tax_point_date | date | Yes | Date VAT becomes due |
| document_currency_code / tax_currency_code | string | Yes | 3-letter currency codes — both required here |
| order_reference | string | No | |
| accounting_supplier_party | object | Yes | party_name, tin, email, telephone (all required, no format check); postal_address {street_name, city_name, country (2-letter)} required, postal_zone optional |
| accounting_customer_party | object | No | Same shape as supplier party, but telephone is optional here |
| payment_means | array | Yes | payment_means_code, payment_due_date — min 1 |
| tax_total | array | Yes | tax_amount, tax_subtotal[] {taxable_amount, tax_amount, tax_category {id, percent}} — min 1 |
| legal_monetary_total | object | Yes | line_extension_amount, tax_exclusive_amount, tax_inclusive_amount, payable_amount |
| invoice_line | array | Yes | hsn_code, product_category, invoiced_quantity, line_extension_amount, item {name}, price {price_amount, base_quantity, price_unit (max 3 chars, must exist in Invoice Quantity Codes)} — min 1 |
Returns 200 on a valid payload, or 400 with the validation failure message.
Submit Invoice
/v1/app/invoices/submit
Validates, signs, and submits an invoice to FIRS in one call. Internally this runs through several FIRS steps
(IRN validation, data validation, signing, confirmation) — the call is safe to retry with the same
irn if it fails partway through; already-completed steps are skipped on retry.
| Field | Type | Required | Description |
|---|---|---|---|
| invoice_reference | string | Yes | Your invoice reference |
| business_id | string | Yes | FIRS business ID |
| irn | string | Yes | Invoice Reference Number |
| issue_date | date | Yes | |
| due_date | date | No | |
| issue_time | string | No | |
| invoice_type_code | string | Yes | FIRS invoice type code |
| payment_status | string | No | PENDING, PAID, or CANCELLED |
| tax_point_date | date | No | |
| document_currency_code | string | Yes | 3-letter currency code |
| tax_currency_code | string | No | |
| accounting_cost | number | No | |
| buyer_reference | string | No | |
| invoice_category | string | Yes | B2C, B2B, or B2G |
| accounting_supplier_party | object | Yes | party_name, tin, email required; telephone optional (regex: +[6-15 digits] if present); postal_address {street_name, city_name, postal_zone, country (2-letter)} — all required |
| accounting_customer_party | object | Conditional | Required unless invoice_category is B2C. Same shape as supplier party; telephone optional |
| legal_monetary_total | object | Yes | line_extension_amount, tax_exclusive_amount, tax_inclusive_amount, payable_amount |
| invoice_line | array | Yes | hsn_code, product_category, invoiced_quantity, line_extension_amount, item, price — min 1 |
note, invoice_delivery_period,
billing_reference, document references, tax_total, payment_means,
payment_terms_note, and allowance_charge — same shape as
Create Invoice (Full).
Each invoice_line also accepts discount_rate/discount_amount and
fee_rate/fee_amount, and item.description and
price.price_unit are required (price_unit max 3 chars, must exist in
Invoice Quantity Codes).
{
"invoice_reference": "ITW20853450",
"business_id": "72e9b677-d3d1-41dd-a526-9b5546b863b8",
"irn": "ITW20853450-6997D6BB-20240703",
"issue_date": "2024-05-14",
"invoice_type_code": "396",
"document_currency_code": "NGN",
"invoice_category": "B2B",
"accounting_supplier_party": {
"party_name": "Dangote Group",
"tin": "TIN-000001",
"email": "info@dangote.com",
"postal_address": { "street_name": "32, Owonikoko Street", "city_name": "Gwarikpa", "postal_zone": "023401", "country": "NG" }
},
"accounting_customer_party": {
"party_name": "Acme Corp Ltd",
"tin": "TIN-000002",
"email": "finance@acme.ng",
"postal_address": { "street_name": "5 Industrial Avenue", "city_name": "Abuja", "postal_zone": "900001", "country": "NG" }
},
"legal_monetary_total": {
"line_extension_amount": 340.50,
"tax_exclusive_amount": 340.50,
"tax_inclusive_amount": 366.04,
"payable_amount": 366.04
},
"invoice_line": [
{
"hsn_code": "CC-001",
"product_category": "Food and Beverages",
"invoiced_quantity": 15,
"line_extension_amount": 340.50,
"item": { "name": "item name", "description": "item description" },
"price": { "price_amount": 22.70, "base_quantity": 1, "price_unit": "C62" }
}
]
}
{
"success": true,
"message": { "...": "The submission result is currently returned in this field, not in \"data\" — see the note below" },
"data": "Invoice submitted successfully"
}
message and data swapped — the submission
result object is returned under message, and the literal string "Invoice submitted
successfully" is returned under data. Read the payload from message until
this is corrected.
Transmit Invoice
/v1/app/invoices/transmit
Transmits a previously submitted invoice to FIRS. Before transmitting, this runs a system health check and confirms both the supplier's and customer's access points are online — if either check fails, transmission is rejected without contacting FIRS's transmit endpoint.
| Field | Type | Required | Description |
|---|---|---|---|
| irn | string | Yes | The IRN to transmit |
{ "irn": "INV001-9ES2AR5C-20251007" }
Failures (health-check down, an access point offline, or FIRS rejecting the transmission) return
500 with a descriptive message.
Confirm Transmission Receipt
/v1/app/invoices/transmit/{irn}/confirm-receipt
Confirms that a transmitted invoice was received.
| Field | Type | Required | Description |
|---|---|---|---|
| irn | string (path) | Yes | Invoice IRN |
{
"success": true,
"message": "Invoice transmission receipt confirmed",
"data": { "...": "Confirmation result" }
}
A receipt that's already been confirmed once returns FIRS's own idempotency message (e.g. "this invoice transmission has been acknowledged by you before") rather than reprocessing it.
Report Invoice
/v1/app/invoices/report
Reports an invoice to FIRS for VAT fiscalization — a separate FIRS pipeline from validate/submit/transmit.
| Field | Type | Required | Description |
|---|---|---|---|
| agent_tin | string | Yes | TIN of the reporting agent (max 50 chars) |
| beneficiary_tin | string | Yes | TIN of the invoice beneficiary (max 50 chars) |
| irn | string | Yes | Invoice Reference Number (max 100 chars) |
| currency | string | Yes | 3-letter ISO currency code |
| item_description | string | Yes | Description of invoice items (max 255 chars) |
| transaction_date | date | Yes | |
| base_amount | number | Yes | Base amount before VAT |
| other_taxes | number | No | Any additional taxes |
| vat_rate | number | Yes | VAT rate percentage |
| vat_calculated | number | Yes | Calculated VAT amount |
| total_amount | number | Yes | Total invoice amount |
| integrator_service_id | string | Yes | Integrator or service provider ID (max 100 chars) |
| vat_status | string | Yes | STANDARD_VAT, EXEMPT, NON_VATABLE, or ZERO_RATED |
{
"agent_tin": "29039239-0001",
"beneficiary_tin": "29334238-0001",
"irn": "INV001-E9E0C0D3-20240619",
"currency": "NGN",
"item_description": "Items",
"transaction_date": "2024-11-18",
"base_amount": 100000,
"vat_rate": 7.5,
"vat_calculated": 7500,
"total_amount": 112500,
"integrator_service_id": "772392",
"vat_status": "STANDARD_VAT"
}
{
"success": true,
"message": "Invoice reported successfully",
"data": {
"invoice_number": "INV-2024-001",
"ok": true,
"irn": "ITW001-CD8BDCE9-20240703"
}
}
A failed report currently surfaces as 500 rather than the documented error envelope — check the
message field either way for the underlying FIRS error.
Update Payment Status
/v1/app/invoices/{irn}/update-payment-status
| Field | Type | Required | Description |
|---|---|---|---|
| irn | string (path) | Yes | Invoice IRN |
| payment_status | string | Yes | PAID, PENDING, or REJECTED |
| remark | string | No | Reason for the update (max 255 chars) |
payment_status values (PAID, PENDING,
REJECTED) are a different set from Submit Invoice's
payment_status field (PENDING, PAID, CANCELLED) — they're
not interchangeable.
{ "payment_status": "REJECTED", "remark": "Customer rejected the invoice" }
Confirm Invoice
/v1/app/invoices/{irn}/confirm
Confirms the current transmission/delivery status of an invoice.
{
"success": true,
"message": "Invoice confirmed successfully",
"data": {
"issue_date": "2024-05-14",
"due_date": "2024-05-14",
"sync_date": "2024-05-14",
"payment_status": "PENDING",
"transmitted": true,
"delivered": false
}
}
Download Invoice
/v1/app/invoices/{irn}/download
Download a previously submitted invoice — the full FIRS invoice document, decrypted server-side.
| Field | Type | Required | Description |
|---|---|---|---|
| irn | string (path) | Yes | Invoice IRN |
{
"success": true,
"message": "Success",
"data": {
"business_id": "24ea0738-dce9-4199-aff0-3fc0181787e2",
"irn": "GEMSP002-CD8BDCE9-20240703",
"payment_status": "PENDING",
"issue_date": "2024-05-14",
"issue_time": "17:59:04",
"due_date": "2024-06-14",
"invoice_type_code": "396",
"document_currency_code": "NGN",
"tax_currency_code": "NGN",
"accounting_supplier_party": { "...": "Party object" },
"accounting_customer_party": { "...": "Party object" },
"legal_monetary_total": { "line_extension_amount": 340.5, "tax_exclusive_amount": 400, "tax_inclusive_amount": 430, "payable_amount": 430 },
"invoice_line": [ { "...": "Line item, same shape as submitted" } ]
}
}
Returns 404 with {"success": false, "message": "Invoice File not found"} if the
invoice hasn't been signed yet.