Invoices
List Invoices
/v1/tax-payers/invoices
Retrieve a paginated list of your invoices.
| Field | Type | Required | Description |
|---|---|---|---|
| batch_no | string | No | Filter invoices by batch number (returned from Create Invoices (Bulk)) |
| page | integer | No | Page number (default: 1) |
| per_page | integer | No | Items per page (default: 10) |
curl https://invoice-direct.gemspaysolution.com/api/v1/tax-payers/invoices?per_page=10 \
-H "x-api-key: tpk_your_api_key"
{
"success": true,
"message": "Invoice retrieved successfully",
"data": {
"current_page": 1,
"total": 50,
"per_page": 10,
"last_page": 5,
"data": [
{
"id": 1,
"irn": "ITW20853450-6997D6BB-20240703",
"invoice_no": "INV-2025-001",
"external_invoice_no": "EXT-INV-001",
"invoice_category": "B2B",
"invoice_type": "Standard",
"invoice_date": "2025-10-24",
"due_date": "2025-11-07",
"currency": "NGN",
"created_at": "2025-10-24 10:30:00",
"updated_at": "2025-10-24 10:30:00",
"is_edited": false,
"edited_at": null,
"payment_status": "PENDING",
"firs_data": { "qr_code": "base64_encoded_qr_code", "status": "SUBMITTED" },
"customer": {
"external_customer_no": "CUST-001",
"name": "John Doe Enterprises",
"tin": "1234567890",
"email": "customer@example.com"
},
"invoice_lines": [
{
"item_name": "Consulting Services",
"item_no": "ITEM-001",
"item_type": "service",
"unit_price": 50000.00,
"quantity": 2,
"tax_category": "STANDARD_VAT",
"product_category": null,
"service_category": "0111"
}
]
}
]
}
}
payment_status is always returned upper-case (PENDING, PAID,
REJECTED, PARTIAL), even though it's sent lower-case on write. The
invoice_lines response shape only includes the fields shown above — discount_rate,
hsn_code/isic_code, and other write-only fields aren't echoed back.
Create Invoice (Standard)
/v1/tax-payers/invoices
Create a single invoice using the simplified standard format. The system automatically handles FIRS formatting.
| Field | Type | Required | Description |
|---|---|---|---|
| invoice_no | string | Yes | Your unique invoice number |
| invoice_category | string | Yes | B2B, B2C or B2G |
| invoice_type | string | Conditional | Free-text invoice type label. Required if invoice_type_code not provided |
| invoice_type_code | string | Conditional | FIRS invoice type code — must exist in Invoice Types. Required if invoice_type not provided |
| original_invoice_reference | string | Conditional | Identifier of the original invoice — required when invoice_type_code is 380 (Credit Note) or invoice_type is CREDIT_NOTE |
| original_invoice_reference_type | string | Conditional | IRN, EXTERNAL_INVOICE_NO, INTERNAL_INVOICE_NO, or PORTAL_ID — required alongside original_invoice_reference |
| invoice_date | date | Yes | Invoice issue date (YYYY-MM-DD) |
| due_date | date | Yes | Invoice due date (YYYY-MM-DD) |
| currency | string | Yes | 3-letter currency code — must exist in Currencies |
| customer | object | Yes | Buyer details — fields below |
| customer.no | string | Yes | Your own customer number |
| customer.name | string | Yes | |
| customer.email | string | Yes | Valid email address |
| customer.tin | string | B2B | Tax Identification Number — also required for B2G, optional for B2C |
| customer.business_description | string | B2B | Min 5 characters — also required for B2G, optional for B2C |
| customer.address_street_name | string | Yes | |
| customer.address_city | string | Yes | |
| customer.address_state | string | Yes | |
| customer.address_postal_code | string | Yes | |
| customer.address_country | string | Yes | |
| invoice_lines | array | Yes | Line items — minimum 1. Each: item_name, item_no, item_type (PRODUCT|SERVICE), unit_price, quantity, price_unit, tax_category (all required). price_unit is max 3 characters and must exist in Invoice Quantity Codes, e.g. C62; product_category (PRODUCT, format 9999.99) or service_category (SERVICE, format 9999); discount_rate (0-100), discount_amount — optional |
{
"invoice_no": "INV-2025-001",
"invoice_category": "B2B",
"invoice_type": "Standard",
"invoice_date": "2025-10-24",
"due_date": "2025-11-07",
"currency": "NGN",
"customer": {
"no": "CUST-001",
"name": "John Doe Enterprises",
"tin": "1234567890",
"email": "customer@example.com",
"business_description": "IT consulting services",
"address_street_name": "12 Market Street",
"address_city": "Lagos",
"address_state": "Lagos State",
"address_postal_code": "100001",
"address_country": "Nigeria"
},
"invoice_lines": [
{
"item_name": "Consulting Services",
"item_no": "ITEM-001",
"item_type": "SERVICE",
"unit_price": 50000,
"quantity": 2,
"price_unit": "C62",
"tax_category": "STANDARD_VAT",
"service_category": "0111",
"discount_rate": 0
}
]
}
{
"success": true,
"message": "Invoice created successfully",
"data": { "...": "Invoice object — same structure as List Invoices" }
}
invoice_no already exists for your account, this returns
200 with message: "Invoice already exist" and the existing invoice's data — it is
not treated as an error.
Create Invoice (Full)
/v1/tax-payers/invoices/full
Create an invoice with full control over the FIRS payload structure — pre-computed totals, detailed tax breakdowns, and other FIRS-specific fields.
| Field | Type | Required | Description |
|---|---|---|---|
| invoice_no | string | Yes | Your unique invoice number |
| invoice_category | string | Yes | B2B, B2C or B2G |
| irn | string | Yes | Format: {invoice_no}-{service_id}-{YYYYMMDD}. The service-id segment must match your account's firs_service_id, or the request is rejected |
| issue_date | date | Yes | Invoice issue date — cannot be in the future |
| due_date | date | No | Must be on or after issue_date |
| issue_time | string | No | HH:mm:ss |
| invoice_type_code | string | Yes | FIRS invoice type code — must exist in Invoice Types |
| payment_status | string | No | PENDING, PAID, or CANCELLED — defaults to PENDING |
| note | string | No | Max 1000 characters |
| tax_point_date | date | No | Defaults to issue_date if omitted |
| document_currency_code | string | Yes | 3-letter currency code — must exist in Currencies |
| tax_currency_code | string | No | 3-letter currency code — defaults to document_currency_code if omitted |
| accounting_cost | number | No | Defaults to 0 |
| buyer_reference | string | No | Max 255 characters |
| accounting_customer_party | object | Yes | Buyer details — fields below |
| accounting_customer_party.customer_no | string | Yes | Your own customer number. Max 255 characters |
| accounting_customer_party.party_name | string | Yes | Max 255 characters |
| accounting_customer_party.email | string | Yes | Valid email address |
| accounting_customer_party.tin | string | B2B | Max 50 characters — also required for B2G, optional for B2C |
| accounting_customer_party.business_description | string | B2B | 5-500 characters — also required for B2G, optional for B2C |
| accounting_customer_party.telephone | string | No | Digits with optional leading +, spaces, dashes or brackets — 6-20 characters |
| accounting_customer_party.postal_address | object | Yes | |
| accounting_customer_party.postal_address.street_name | string | Yes | Max 255 characters |
| accounting_customer_party.postal_address.city_name | string | Yes | Max 100 characters |
| accounting_customer_party.postal_address.postal_zone | string | Yes | Max 50 characters |
| accounting_customer_party.postal_address.country | string | Yes | 2-letter ISO country code, e.g. NG |
| legal_monetary_total | object | Yes | line_extension_amount, tax_exclusive_amount, tax_inclusive_amount, payable_amount — all required, min 0 |
| invoice_line | array | Yes | Minimum 1 — see field table below |
| invoice_delivery_period | object | No | start_date, end_date |
| billing_reference | array | No | Each: irn, issue_date |
| dispatch_document_reference | object | No | irn, issue_date |
| receipt_document_reference | object | No | irn, issue_date |
| originator_document_reference | object | No | irn, issue_date |
| contract_document_reference | object | No | irn, issue_date |
| additional_document_reference | array | No | Each: irn, issue_date |
| actual_delivery_date | date | No | |
| payment_means | array | No | Each: payment_means_code (must exist in Payment Means), payment_due_date |
| payment_terms_note | string | No | Max 1000 characters |
| allowance_charge | array | No | Each: charge_indicator (boolean), amount |
| tax_total | array | No | Each: tax_amount, tax_subtotal[] {taxable_amount, tax_amount, tax_category {id, percent}} |
Invoice line fields (invoice_line[])
| Field | Type | Required | Description |
|---|---|---|---|
| item | object | Yes | name, description (required); sellers_item_identification (optional); type (PRODUCT or SERVICE) — drives which category field below is required |
| price | object | Yes | price_amount, base_quantity (required, min 0.01); price_unit (required, max 3 chars, must exist in Invoice Quantity Codes — e.g. C62) |
| invoiced_quantity | number | Yes | Min 0.01 |
| line_extension_amount | number | Yes | Min 0.01. The sum across all lines must equal legal_monetary_total.line_extension_amount |
| product_category | string | Conditional | Required when item.type is PRODUCT — format 9999.99, must exist in Product Codes |
| service_category | string | Conditional | Required when item.type is SERVICE — format 9999, must exist in Service Codes |
| hsn_code | string | No | PRODUCT lines only — format 9999.99 |
| isic_code | string | No | SERVICE lines only — format 9999 |
| tax_category | string | Yes | Must exist in Tax Categories |
| total_tax | number | Yes | Min 0 |
| discount_rate / discount_amount | number | No | discount_rate is a 0-100 percentage; discount_amount is a flat amount |
| fee_rate / fee_amount | number | No | fee_rate is a 0-100 percentage; fee_amount is a flat amount |
tax_exclusive_amount must equal line_extension_amount (within
0.01), tax_inclusive_amount must be ≥ tax_exclusive_amount, and the sum of every
line's line_extension_amount must equal the top-level
legal_monetary_total.line_extension_amount (within 0.01). A PRODUCT line cannot also carry
service_category, and vice versa.
{
"invoice_no": "INV-2025-002",
"invoice_category": "B2B",
"irn": "INV-2025-002-6997D6BB-20251024",
"issue_date": "2025-10-24",
"due_date": "2025-11-07",
"invoice_type_code": "380",
"document_currency_code": "NGN",
"accounting_customer_party": {
"customer_no": "CUST-002",
"party_name": "Acme Corp Ltd",
"tin": "9876543210",
"email": "finance@acmecorp.ng",
"business_description": "Retail and distribution",
"postal_address": {
"street_name": "5 Industrial Avenue",
"city_name": "Abuja",
"postal_zone": "900001",
"country": "NG"
}
},
"legal_monetary_total": {
"line_extension_amount": 100000.00,
"tax_exclusive_amount": 100000.00,
"tax_inclusive_amount": 107500.00,
"payable_amount": 107500.00
},
"invoice_line": [
{
"product_category": null,
"service_category": "0111",
"invoiced_quantity": 2,
"line_extension_amount": 100000.00,
"item": { "name": "Software License", "description": "Annual enterprise software license", "type": "SERVICE" },
"price": { "price_amount": 50000.00, "base_quantity": 1, "price_unit": "C62" },
"tax_category": "STANDARD_VAT",
"total_tax": 7500.00
}
]
}
{
"success": true,
"message": "Invoice created successfully",
"data": { "...": "Invoice object" }
}
Returns 201 on creation. If an invoice with the same irn or invoice_no
already exists, returns 200 with message: "Invoice with this IRN already exists"
and the existing invoice's data.
Create Invoices (Bulk)
/v1/tax-payers/invoices/bulk
Submit multiple invoices in one request. Each item uses the same fields as Create Invoice (Standard). A batch number is returned for tracking.
customer.tin is optional on this endpoint, even for B2B (the single-invoice endpoints require it
for B2B/B2G). customer.business_description is still required for B2B and B2G.
invoice_lines.*.quantity must be at least 1.
{
"invoices": [
{
"invoice_no": "INV-2025-010",
"invoice_category": "B2C",
"invoice_type": "Standard",
"invoice_date": "2025-10-24",
"due_date": "2025-11-07",
"currency": "NGN",
"customer": {
"no": "CUST-100",
"name": "Jane Smith",
"email": "jane@example.com",
"address_street_name": "1 Broad Street",
"address_city": "Lagos",
"address_state": "Lagos",
"address_postal_code": "100001",
"address_country": "Nigeria"
},
"invoice_lines": [
{
"item_name": "Web Design",
"item_no": "SVC-001",
"item_type": "SERVICE",
"unit_price": 200000,
"quantity": 1,
"price_unit": "C62",
"tax_category": "STANDARD_VAT",
"service_category": "0112"
}
]
}
]
}
{
"success": true,
"message": "Invoices created successfully",
"data": {
"batch_no": "GSPB-001",
"created": 5
}
}
created count comes
back. Use List Invoices with the returned batch_no to confirm which
invoices actually landed.
Get Invoice by ID
/v1/tax-payers/invoices/{id}
Retrieve a single invoice using the portal's internal ID.
| Field | Type | Required | Description |
|---|---|---|---|
| id | integer (path) | Yes | The invoice's portal ID |
{
"success": true,
"message": "Invoice retrieved successfully",
"data": { "...": "Invoice object" }
}
Returns 404 with {"success": false, "message": "Invoice not found", "data": null} if
the invoice doesn't exist.
Get Invoice by Unique Number
/v1/tax-payers/invoices/get-by-unique-no
Retrieve an invoice using different identifier types.
| Field | Type | Required | Description |
|---|---|---|---|
| unique_no | string | Yes | The identifier value to search by |
| unique_no_type | string | Yes | PORTAL_ID, IRN, EXTERNAL_INVOICE_NO, or INTERNAL_INVOICE_NO |
{
"unique_no": "INV-2025-001",
"unique_no_type": "INTERNAL_INVOICE_NO"
}
Returns 404 if no matching invoice is found.
Update Invoice
/v1/tax-payers/invoices/update
Replace a draft invoice's customer, line items, and other editable details in place. The
invoice is located by unique_no/unique_no_type rather than a path parameter.
409 with "This invoice has already been processed by FIRS and can no longer be
edited."
| Field | Type | Required | Description |
|---|---|---|---|
| unique_no | string | Yes | The identifier value |
| unique_no_type | string | Yes | See Get Invoice by Unique Number |
| customer | object | Yes | Same shape as Create Invoice. Matched by no, then tin — created from these details if neither matches |
| customer.no | string | Yes | Your own customer number |
| customer.name | string | Yes | |
| customer.email | string | Yes | Valid email address |
| customer.tin | string | B2B | Also required for B2G, optional for B2C |
| customer.business_description | string | B2B | Min 5 characters — also required for B2G, optional for B2C |
| customer.address_street_name / address_city / address_state / address_postal_code / address_country | string | Yes | |
| invoice_category | string | Yes | B2B, B2C or B2G |
| invoice_type | string | Yes | FIRS invoice type code — must exist in Invoice Types |
| original_invoice_reference | string | Conditional | Required when invoice_type is 380 (Credit Note) — must resolve to another of your invoices by IRN |
| due_date | date | Yes | |
| note | string | No | |
| lines | array | Yes | Minimum 1 — each line is either catalog-linked (item_id) or freeform (item_name + item_type + tax_category + category), see below |
customer.no — the same value returned as
customer.external_customer_no when you read an invoice back, so a fetched invoice round-trips
without translation. If no or tin matches a customer you already hold, that customer
is used as-is and the rest of the block is ignored: an edit to one invoice must not silently rewrite the
customer record shared by all your other invoices. The one exception is tin, which is filled in if
we hold the customer without one. Sending an invoice_category that contradicts the customer type
already on file returns 422.
Line fields (lines[])
| Field | Type | Required | Description |
|---|---|---|---|
| item_id | integer | No | One of your own items. If provided, item_name/item_type/tax_category are not required — the item must already have a FIRS product/service category set on its catalog record |
| item_name | string | Conditional | Required if item_id is not provided |
| item_no | string | No | |
| item_type | string | Conditional | PRODUCT or SERVICE — required if item_id is not provided |
| tax_category | string | Conditional | Must exist in Tax Categories — required if item_id is not provided |
| product_category / service_category | string | Conditional | Required for freeform PRODUCT/SERVICE lines respectively |
| quantity | number | Yes | Min 0.01 |
| unit_price | number | Yes | Min 0 |
| price_unit | string | Yes | Max 3 characters — must exist in Invoice Quantity Codes, e.g. C62 |
| description | string | No | |
| discount_rate / discount_amount | number | No |
{
"unique_no": "INV-2025-001",
"unique_no_type": "INTERNAL_INVOICE_NO",
"customer": {
"no": "CUST-001",
"name": "John Doe Enterprises",
"tin": "1234567890",
"email": "customer@example.com",
"business_description": "IT consulting services",
"address_street_name": "12 Market Street",
"address_city": "Lagos",
"address_state": "Lagos State",
"address_postal_code": "100001",
"address_country": "Nigeria"
},
"invoice_category": "B2B",
"invoice_type": "396",
"due_date": "2025-11-07",
"note": "Corrected quantity on line 1",
"lines": [
{
"item_id": 17,
"quantity": 3,
"unit_price": 50000,
"price_unit": "C62"
},
{
"item_name": "Ad-hoc Consulting",
"item_type": "SERVICE",
"service_category": "0111",
"tax_category": "STANDARD_VAT",
"quantity": 1,
"unit_price": 25000,
"price_unit": "C62"
}
]
}
{
"success": true,
"message": "Invoice updated successfully",
"data": { "...": "Invoice object, with is_edited: true and edited_at set" }
}
Update Invoice Status
/v1/tax-payers/invoices/update-status
Update the workflow status of an invoice — see Invoice Lifecycle. This is separate from payment status.
| Field | Type | Required | Description |
|---|---|---|---|
| unique_no | string | Yes | The identifier value |
| unique_no_type | string | Yes | See Get Invoice by Unique Number |
| status | string | Yes | draft, approved, or cancelled |
| remark | string | Yes | Reason for the change (min 3 characters) |
{
"unique_no": "INV-2025-001",
"unique_no_type": "INTERNAL_INVOICE_NO",
"status": "approved",
"remark": "Approved after review"
}
Update Payment Status
/v1/tax-payers/invoices/update-payment-status
Update the payment status of an invoice, including partial payments.
| Field | Type | Required | Description |
|---|---|---|---|
| unique_no | string | Yes | The identifier value |
| unique_no_type | string | Yes | See Get Invoice by Unique Number |
| status | string | Conditional | pending, paid, rejected, or partial. status and payment_status are aliases of each other — send either (payment_status wins if both are sent) |
| payment_status | string | Conditional | Same values as status |
| amount | number | Conditional | Required when status is partial — must be greater than 0. The running amount_paid cannot exceed the invoice's payable amount |
| reference | string | No | Your payment reference (e.g. bank transaction ID). Max 255 characters |
| remark | string | Yes | Reason for the update (min 3 characters) |
approved before it can be marked paid. Marking an
invoice paid also advances its workflow status to paid — it isn't only a payment
field. A partial update accumulates against amount on each call (e.g. two
partial updates of 5,000 each result in 10,000 total paid), and cannot push the total above the
invoice's payable amount.
{
"unique_no": "INV-2025-001",
"unique_no_type": "INTERNAL_INVOICE_NO",
"payment_status": "partial",
"amount": 25000,
"reference": "TRX-88213",
"remark": "First installment received"
}
{
"success": true,
"message": "Invoice payment status updated successfully",
"data": { "...": "Invoice object, with payment_status: \"PARTIAL\"" }
}
Get Supplier Invoice
/v1/tax-payers/invoices/get-supplier-invoice
Retrieve an incoming invoice sent to you by a supplier, from FIRS, using the supplier's IRN.
| Field | Type | Required | Description |
|---|---|---|---|
| supplier_irn | string | Yes | The IRN of the supplier's invoice — must be one you've been notified about |
{ "supplier_irn": "ITW20853450-6997D6BB-20241015" }
Returns 403 if the referenced IRN wasn't addressed to you. On success, data is the
full decrypted FIRS invoice document (same shape as Download Invoice).
Acknowledge Supplier Invoice
/v1/tax-payers/invoices/acknowledge-supplier-invoice
Acknowledge receipt of a supplier invoice, confirming to FIRS that you have received it.
| Field | Type | Required | Description |
|---|---|---|---|
| supplier_irn | string | Yes | The IRN of the supplier's invoice to acknowledge |
{ "supplier_irn": "ITW20853450-6997D6BB-20241015" }
Returns 403 if the referenced IRN wasn't addressed to you, or an error if it's already been
acknowledged.