Invoices

List Invoices

GET /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)
bash
curl https://invoice-direct.gemspaysolution.com/api/v1/tax-payers/invoices?per_page=10 \
  -H "x-api-key: tpk_your_api_key"
json
{
  "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)

POST /v1/tax-payers/invoices

Create a single invoice using the simplified standard format. The system automatically handles FIRS formatting.

Requires an active subscription and a complete business profile (address, business email, NRS Business ID and NRS Service ID) — see Overview.
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
json
{
  "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
    }
  ]
}
json
{
  "success": true,
  "message": "Invoice created successfully",
  "data": { "...": "Invoice object — same structure as List Invoices" }
}
If an invoice with the same 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)

POST /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.

Requires an active subscription and a complete business profile.
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
Cross-field checks: 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.
json
{
  "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
    }
  ]
}
json
{
  "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)

POST /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.

Requires an active subscription and a complete business profile.
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.
json
{
  "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"
        }
      ]
    }
  ]
}
json
{
  "success": true,
  "message": "Invoices created successfully",
  "data": {
    "batch_no": "GSPB-001",
    "created": 5
  }
}
Per-invoice failures inside a batch (e.g. one invoice referencing a missing original invoice) do not fail the whole request and are not itemized in the response — only the aggregate created count comes back. Use List Invoices with the returned batch_no to confirm which invoices actually landed.

Get Invoice by ID

GET /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
json
{
  "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

POST /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
json
{
  "unique_no": "INV-2025-001",
  "unique_no_type": "INTERNAL_INVOICE_NO"
}

Returns 404 if no matching invoice is found.

Update Invoice

PUT /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.

Once an invoice has been validated, submitted, transmitted, or reported to FIRS, it's locked — this endpoint returns 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
You address the customer by your own 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
json
{
  "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"
    }
  ]
}
json
{
  "success": true,
  "message": "Invoice updated successfully",
  "data": { "...": "Invoice object, with is_edited: true and edited_at set" }
}

Update Invoice Status

PATCH /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)
json
{
  "unique_no": "INV-2025-001",
  "unique_no_type": "INTERNAL_INVOICE_NO",
  "status": "approved",
  "remark": "Approved after review"
}

Update Payment Status

PATCH /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)
An invoice must already be 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.
json
{
  "unique_no": "INV-2025-001",
  "unique_no_type": "INTERNAL_INVOICE_NO",
  "payment_status": "partial",
  "amount": 25000,
  "reference": "TRX-88213",
  "remark": "First installment received"
}
json
{
  "success": true,
  "message": "Invoice payment status updated successfully",
  "data": { "...": "Invoice object, with payment_status: \"PARTIAL\"" }
}

Get Supplier Invoice

POST /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
json
{ "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

POST /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
json
{ "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.

Continue As

Choose your account type to proceed

Don't have an account? Sign up here