Build Invoice Payloads
Prepare identity-bound Pass-through and Stored invoice documents with current NRS resource values.
The Gateway accepts two invoice document shapes. Your domain's approved mode, either PASSTHROUGH or STORED, determines which one POST /api/v1/invoice/sign expects. Duplo controls that mode, so you cannot choose a shape per request.
| Concern | Pass-through document | Stored document |
|---|---|---|
| Schema owner | NRS | Duplo's intake schema, which Duplo converts to the NRS document |
| Invoice identity field | irn | invoiceReferenceNumber |
| Supplier identity | business_id plus accounting_supplier_party.tin | supplierData.name plus supplierData.tin |
| Buyer | NRS party objects | A customerId that already exists under your top-level business |
| Items | NRS invoice_line | Duplo items |
| Totals | You calculate NRS monetary totals | Duplo calculates totals from items, taxes, allowances, charges, and discount |
| Immediate response | NRS validation/sign response | Duplo 202 Accepted response |
Shared identity rules
Whichever mode you use:
- Send the represented business's
gatewaySubBusinessIdin thex-sub-business-idheader. - Use that same business's NRS-verified TIN and name in the supplier fields.
- Build the IRN from that business's current NRS IRN template.
- Use an issue date of today or earlier, formatted
YYYY-MM-DD. - Fetch NRS resource codes at runtime. The codes in the examples below are illustrative, not permanent configuration.
- Use tax rates in percentage points, so
7.5means 7.5% and not0.075. In Pass-through, also make your own arithmetic agree: line amounts must add up to the tax subtotals and thelegal_monetary_totalyou send.
The Gateway checks issuer ownership
For Pass-through, business_id and supplier TIN must match the server-stored NRS identity. For Stored, supplier name and TIN must match the represented business. A mismatch returns 400 before billing or NRS signing.
IRN handling
An Invoice Reference Number is the durable identity used across validation, signing, lookup, transmission, billing, and support. Build it from the represented business's current NRS template, which joins three segments with hyphens:
INV000001-2A3A045D-20260818| Segment | In the example | Where it comes from |
|---|---|---|
| Invoice reference | INV000001 | Your own reference for this invoice |
| Service ID | 2A3A045D | The represented business's NRS record, shown as Service ID in the NRS taxpayer dashboard |
| Issue date | 20260818 | This invoice's issue date, as YYYYMMDD |
Do not invent the Service ID, reuse another represented business's template, or change an IRN after a sign attempt. Persist the IRN before the first request.
Pass-through NRS document
This is a compact B2B service-invoice example. Replace all identity and resource values with current values for your represented business and transaction.
{
"business_id": "5be71d46-045d-4098-a466-4708afe7edac",
"irn": "INV000001-2A3A045D-20260818",
"issue_date": "2026-08-18",
"issue_time": "10:30:00",
"invoice_type_code": "381",
"invoice_kind": "B2B",
"payment_status": "PENDING",
"document_currency_code": "NGN",
"tax_currency_code": "NGN",
"accounting_cost": "1000",
"accounting_supplier_party": {
"party_name": "Acme Retail Limited",
"tin": "12345678-0001",
"email": "invoice@acme.example",
"telephone": "+2348012345678",
"postal_address": {
"street_name": "1 Integration Road",
"city_name": "Lagos",
"state": "Lagos",
"country": "NG",
"postal_zone": "100001"
}
},
"accounting_customer_party": {
"party_name": "Example Buyer Limited",
"tin": "87654321-0001",
"email": "ap@buyer.example",
"telephone": "+2348098765432",
"postal_address": {
"street_name": "2 Buyer Avenue",
"city_name": "Abuja",
"state": "FCT",
"country": "NG",
"postal_zone": "900001"
}
},
"bill_party": {
"party_name": "Example Buyer Limited",
"tin": "87654321-0001",
"email": "ap@buyer.example",
"telephone": "+2348098765432",
"postal_address": {
"street_name": "2 Buyer Avenue",
"city_name": "Abuja",
"state": "FCT",
"country": "NG",
"postal_zone": "900001"
}
},
"ship_party": {
"party_name": "Example Buyer Limited",
"tin": "87654321-0001",
"email": "ap@buyer.example",
"telephone": "+2348098765432",
"postal_address": {
"street_name": "2 Buyer Avenue",
"city_name": "Abuja",
"state": "FCT",
"country": "NG",
"postal_zone": "900001"
}
},
"invoice_line": [
{
"isic_code": "6201",
"service_category": "Computer programming activities",
"invoiced_quantity": 1,
"line_extension_amount": 1000,
"item": {
"name": "Integration service",
"description": "Integration service"
},
"price": {
"price_amount": 1000,
"base_quantity": 1,
"price_unit": "HUR"
}
}
],
"tax_total": [
{
"tax_amount": 75,
"tax_subtotal": [
{
"taxable_amount": 1000,
"tax_amount": 75,
"tax_category": {
"id": "STANDARD_VAT",
"percent": 7.5
}
}
]
}
],
"legal_monetary_total": {
"line_extension_amount": 1000,
"tax_exclusive_amount": 1000,
"tax_inclusive_amount": 1075,
"payable_amount": 1075
}
}The NRS document can include additional UBL (Universal Business Language) fields such as due date, payment means, payee, tax representative, allowances, charges, and billing references. Consult the NRS versioned reference for the complete current contract.
NRS expresses reductions and additions differently at each level, and neither uses a type discriminator:
| Level | Fields | Shape |
|---|---|---|
| Document | allowance_charge | { "charge_indicator": true, "amount": 800.60 }, where true is a charge and false an allowance. Amounts only; there is no percentage form. |
| Line | discount_rate and discount_amount, fee_rate and fee_amount | Paired fields, so you send the rate or the amount rather than declaring which kind it is. |
Stored intake document
Stored mode accepts Duplo's own invoice input and derives the routing values itself. It requires an invoice customer that already exists under your top-level business in Duplo Dashboard, in the same test/live state as the request.
{
"invoiceNumber": "INV-0001",
"invoiceReferenceNumber": "INV000001-2A3A045D-20260818",
"invoiceType": "381",
"issueDate": "2026-08-18",
"documentCurrency": "NGN",
"taxCurrency": "NGN",
"paymentStatus": "pending",
"supplierData": {
"name": "Acme Retail Limited",
"email": "invoice@acme.example",
"phoneNumber": "+2348012345678",
"tin": "12345678-0001",
"country": "NG",
"state": "Lagos",
"city": "Lagos",
"address": "1 Integration Road",
"postcode": "100001"
},
"customerId": "55555555-5555-4555-8555-555555555555",
"items": [
{
"description": "Integration service",
"unit": "Hour",
"quantity": 1,
"unitPrice": 1000,
"taxes": [
{
"name": "STANDARD_VAT",
"rate": 7.5
}
],
"serviceCategory": "Computer programming activities",
"isicCode": "6201"
}
],
"notes": "Payment due in 30 days"
}Each line is either a product line or a service line. A product line carries productCategory and hsnCode; a service line carries serviceCategory and isicCode, as in the example above. Send one pair per line and never mix the two, so a line with productCategory and isicCode is not valid. HSN codes currently take the shape NNNN.NN, such as 8471.30.
A Stored invoice can also include:
- Per-line
allowancesandcharges, each expressed as either afixedamount or apercentage. - An invoice-level
discount, expressed as either afixedamount or apercentage. payeeData,taxRepresentativeData,paymentDetails,paymentTerms, andtaxPointDate.billingReferencesfor credit and debit notes.
Duplo derives the following fields from the approved host, the API key, and the x-sub-business-id header. Stored requests must not send them:
businessId
parentBusinessId
subBusinessId
gatewaySubBusinessId
domainConfigId
domain
businessState
modeVersionStored intake rejects a request that sends any of them rather than ignoring the extra fields, so strip them before you forward an object straight from your own database.
Invoice types and original-invoice references
Stored intake recognizes these NRS invoice type codes in invoiceType. Pass-through carries the same codes in invoice_type_code, where NRS owns the rules. NRS publishes more codes than Duplo's Stored intake accepts, so read the current list from GET /api/v1/invoice/resources/invoice-types before you rely on one that is not in this table:
| Code | Type | Billing reference |
|---|---|---|
380 | Credit note | Required |
381 | Commercial invoice | Not required |
384 | Debit note | Required |
385 | Self-billed invoice | Not enforced by Duplo; follow current NRS requirements |
388 | Factored invoice | Not enforced by Duplo; follow current NRS requirements |
389 | Statement of account | Not enforced by Duplo; follow current NRS requirements |
For credit and debit notes, include at least one original invoice reference:
{
"billingReferences": [
{
"irn": "ORIGINAL1-2A3A045D-20260801",
"issueDate": "2026-08-01"
}
]
}Fetch resources at runtime
Resource catalogs change independently of your application deployment, so read them at runtime rather than hard-coding values. Cache each response with an expiry rather than indefinitely, and refresh when a validation failure points at one of these codes. These reads are unbilled:
GET /api/v1/invoice/resources/tax-categories
GET /api/v1/invoice/resources/vat-exemptions
GET /api/v1/invoice/resources/hs-codes
GET /api/v1/invoice/resources/services-codes
GET /api/v1/invoice/resources/invoice-types
GET /api/v1/invoice/resources/invoice-quantity-codes
GET /api/v1/invoice/resources/payment_means
GET /api/v1/invoice/resources/currencies
GET /api/v1/invoice/resources/countries
GET /api/v1/invoice/resources/states
GET /api/v1/invoice/resources/lgaspayment_means is the one path that uses an underscore rather than a hyphen.
See NRS API context for request headers and response examples.
How is this guide?
Last updated on