NRS API Through Your Domain

Call well-known NRS endpoints through the approved Gateway host and understand which routes are protected, relayed, and billed.

Your approved domain is an authenticated NRS API entry point for the operations listed on this page. For a supported direct NRS operation, replace the NRS origin with your Gateway origin and keep its documented HTTP method, path, and NRS request body.

NRS path through your Gateway host
NRS path:     /api/v1/invoice/resources/tax-categories
Gateway URL:  https://acme.invoice.tryduplo.com/api/v1/invoice/resources/tax-categories

Send the API key generated in Duplo Dashboard as x-api-key on every request, as shown in the examples below.

Use only the Gateway headers documented for each operation. Gateway applies the approved business, state, and route rules before forwarding an eligible NRS request. A valid hostname and API key do not authorize an arbitrary URL path.

If you send x-request-id, Gateway treats it only as optional caller correlation metadata. It does not become the trusted Gateway or support identifier. Capture the Gateway-generated X-Request-ID on every response; for a Problem Details error, it equals body requestId.

X-Duplo-Gateway-Hop is a reserved loop-detection header. Do not send it or copy it from an internal request. A non-zero value on an inbound request is rejected with 508 GW_ROUTING_LOOP_DETECTED and requires an operator to correct routing.

The path is an allowlist, not a catch-all proxy

Gateway accepts only the exact method and canonical path shapes in the supported-endpoint table below, plus the Gateway-owned management endpoints in the API reference. An unlisted path returns GW_ROUTE_NOT_FOUND; a known path with the wrong method returns GW_METHOD_NOT_ALLOWED and an Allow header. Never construct a Gateway URL from untrusted input.

NRS publishing a new route, resource name, HTTP method, or query parameter does not make it available through the Gateway. Wait for a Duplo Gateway release and the corresponding update to this documentation before using it through your approved domain.

Billing rule

Ordinary NRS calls through the domain are unbilled. The only billable route is the exact POST /api/v1/invoice/sign operation. A sign attempt is debited before NRS is called; a definitive rejection is reversed, while an unconfirmed NRS outcome can retain the charge temporarily for reconciliation. Read Billing and safe retries before automating sign retries.

How routing works

  1. Authenticate

    The host and API key resolve one approved business and state in Duplo Dashboard.

  2. Authorize

    Gateway matches one documented method and canonical path, then applies tenant, mode, and represented-business rules.

  3. Relay

    For an eligible direct NRS route, Gateway forwards only the approved operation.

The supported surface has distinct handling rules:

  • POST /api/v1/invoice/sign is intercepted for mode handling, tenant checks, billing, and recovery.
  • POST /api/v1/invoice/validate, GET /api/v1/invoice/transmit/lookup/{irn}, and POST /api/v1/invoice/transmit/{irn} are represented-business-bound Pass-through routes.
  • The resource, recipient, health, entity, and NRS-login operations listed below are bounded direct NRS routes. Inbound download and acknowledgement are recognized but blocked as described below.
  • Every unlisted method/path combination, including another shape under /api/v1/invoice/transmit, is rejected instead of being forwarded.
  • /sub-businesses, /invoices, /health, and platform webhook/administration paths belong to Duplo, not NRS.

Gateway matches the same canonical path for authorization, billing classification, and forwarding. Query parameters are rejected on direct NRS relay routes. Gateway-owned collection reads accept only their explicitly documented query fields; for example, GET /sub-businesses accepts page and limit. Do not send encoded path separators, dot segments, backslashes, fragments, duplicate separators, malformed percent escapes, oversized paths, unknown or repeated query fields, or duplicate security-sensitive headers. Those values are rejected; Gateway does not normalize one target for authorization and forward a different target.

Requests with a JSON body must send Content-Type: application/json; unsupported or missing media types are rejected with GW_REQUEST_MEDIA_TYPE_UNSUPPORTED before a body is forwarded.

Header requirements by route

Operationx-api-keyx-sub-business-idIdempotency keyCharge
Listed resource, health, recipient, entity, or NRS-login callRequiredNot a Gateway requirementNot a Gateway requirementNo
List or read represented-business mappingsRequiredDo not sendNot requiredNo
Validate raw invoiceRequiredRequiredNot requiredNo
Sign invoiceRequiredRequiredStrongly recommended in Pass-through; required in StoredYes
Look up a signed Pass-through IRNRequiredRequiredNot requiredNo
Transmit a signed Pass-through IRNRequiredRequiredStandard Idempotency-Key or legacy x-idempotency-key is optional in the current version; preserve it when suppliedNo
Download or acknowledge an inbound IRNNot currently availableNot currently availableNot applicableNo
Stored invoice read/transmitRequiredSent where that operation documents itRequired for transmitNo additional sign charge

Pass-through sign also accepts a positive x-attempt-number, which defaults to 1. Keep it stable with the idempotency key for one logical attempt; read Billing and safe retries before creating a later attempt.

The credential IP list covers this hostname

ipWhitelisted is enforced on authenticated customer-facing custom-domain Gateway requests as well as direct Spend API calls. Duplo's trusted edge overwrites the configured client-IP header, and Gateway carries that address over its authenticated internal authorization hop. Gateway never trusts a caller's X-Forwarded-For value. An empty list is unrestricted; when the list is configured, a missing, malformed, or non-matching trusted address returns 403. See Webhook and network settings.

NRS may impose additional fields or semantics on an ordinary proxied route. Follow the official NRS e-Invoicing documentation, version 1.1.6 for the regulator-owned contract.

NRS contract compatibility

Gateway uses an explicit NRS adapter selected independently for TEST and LIVE. The current Gateway adapter version is v1; it is a Duplo integration-contract version and is distinct from the version label in NRS's public documentation.

For an ordinary successful relay, Gateway preserves the NRS 2xx status and body bytes after filtering response headers. Unknown additive response fields therefore remain compatible. Gateway performs stricter runtime validation only when its own safety decision depends on the response, including onboarding authentication/entity selection, represented-business identity, sign success, and IRN ownership.

A provider change may require a Gateway release

A new NRS path, method, API version, credential header, redirect, renamed decision field, or changed field type is not adopted automatically. Gateway redirects are disabled. Duplo tests a new adapter with golden fixtures and the NRS sandbox in TEST, then uses a limited LIVE canary before changing the selected contract version for that state.

Gateway never treats an unfamiliar sign envelope as success merely because its HTTP status is 2xx. Signing requires HTTP 2xx plus body code: 201 and data.ok: true; malformed JSON, 202, empty 204, a renamed field, or another unknown result is GW_SIGN_OUTCOME_UNCONFIRMED after debit. Deployment-owned NRS credential rejections are mapped to an operator dependency failure rather than an invoice or customer-credential rejection.

Credential rotation may stage a current and next credential, but Gateway does not retry an uncertain mutation with another credential. Identity drift is also explicit: changed name, template, or active status can be reviewed, while business-ID or TIN drift requires an audited migration. Never silently replace the stored NRS identity or stop accepting a previously published identifier alias.

Supported NRS endpoints

This table is the complete NRS route allowlist exposed through the Gateway for the current release. It is still not a replacement for the NRS payload reference: response fields and regulator-owned resource values can change independently of Gateway. However, a new NRS route, resource endpoint, method, or query option remains unavailable until both the Gateway and this table are released with support for it.

Method and pathPurposeGateway handling
POST /api/v1/utilities/authenticateAuthenticate an NRS portal user and obtain an entity identifierDirect, unbilled diagnostic
GET /api/v1/entity/{entityId}Read the entity and its NRS businessesDirect and unbilled
GET /api/v1/invoice/transmit/lookup/tin/{tin}Check whether a recipient TIN can receiveDirect and unbilled
GET /api/v1/invoice/transmit/self-health-checkCheck the current sender/NRS connectionDirect and unbilled
GET /api/v1/invoice/resources/tax-categoriesFetch current tax categories and ratesDirect and unbilled
GET /api/v1/invoice/resources/vat-exemptionsFetch current VAT exemptionsDirect and unbilled
GET /api/v1/invoice/resources/hs-codesFetch product classification codesDirect and unbilled
GET /api/v1/invoice/resources/services-codesFetch service classification codesDirect and unbilled
GET /api/v1/invoice/resources/invoice-typesFetch the current invoice type codesDirect and unbilled
GET /api/v1/invoice/resources/invoice-quantity-codesFetch invoice unit/quantity codesDirect and unbilled
GET /api/v1/invoice/resources/payment_meansFetch payment means codes. Note the underscoreDirect and unbilled
GET /api/v1/invoice/resources/currenciesFetch currency codesDirect and unbilled
GET /api/v1/invoice/resources/countriesFetch country codesDirect and unbilled
GET /api/v1/invoice/resources/statesFetch Nigerian state codesDirect and unbilled
GET /api/v1/invoice/resources/lgasFetch Nigerian local government areasDirect and unbilled
POST /api/v1/invoice/validateValidate a raw NRS invoicePass-through-only, child-bound, unbilled
POST /api/v1/invoice/signSign an invoice or accept it for Stored processingGateway-controlled and billable at sign
GET /api/v1/invoice/transmit/lookup/{irn}Check parties for an already signed IRNPass-through-only, child/IRN-bound, unbilled
POST /api/v1/invoice/transmit/{irn}Transmit an already signed IRNPass-through-only, child/IRN-bound, unbilled
GET /api/v1/invoice/download/{irn}Download inbound encrypted invoice materialRecognized but blocked by Gateway with 503 GW_INVOICE_OPERATION_UNAVAILABLE; no NRS call is made
PATCH /api/v1/invoice/transmit/{irn}Acknowledge an inbound invoiceRecognized but blocked by Gateway with 503 GW_INVOICE_OPERATION_UNAVAILABLE; no NRS call is made

Inbound download and acknowledgement are not production-ready

Gateway cannot yet prove that a requested raw inbound IRN belongs to the represented business. Both operations are therefore disabled in application code in every environment and return 503 GW_INVOICE_OPERATION_UNAVAILABLE before tenant resolution or an NRS call. Do not retry them repeatedly. Use the parent-scoped Stored invoice reads for materialized inbound invoices, or contact Duplo Support with the returned requestId. Duplo will enable these raw routes only after a Spend-backed IRN ownership check and cross-tenant tests are deployed.

Retrieve the stored represented-business identity

Successful Gateway onboarding returns gatewaySubBusinessId, the backward-compatible firsBusinessId, and the complete durable nrsIdentity object. At onboarding, the immutable Gateway identity is allocated from the selected NRS business.id, so the values initially match:

gatewaySubBusinessId == firsBusinessId == nrsIdentity.nrsBusinessId

After an audited identity migration, gatewaySubBusinessId stays unchanged while firsBusinessId and nrsIdentity.nrsBusinessId advance together to the current NRS mapping. Always use gatewaySubBusinessId in x-sub-business-id and represented-business paths; use the current nrsIdentity.nrsBusinessId as business_id in Pass-through invoice bodies. The identity object also contains the NRS entity ID, nullable reference, registered business name, TIN, and IRN template. Treat every identifier as an opaque string, not a UUID.

After onboarding, list the current mappings with GET /sub-businesses?page=1&limit=50, or read one with GET /sub-businesses/{gatewaySubBusinessId}. These requests use only the approved host and parent x-api-key; they need no x-sub-business-id, idempotency key, body, or NRS credentials. Both unbilled Gateway-owned reads use stored data and do not call NRS. See List represented businesses and Read a represented business back for complete requests, response contracts, and recovery guidance.

NRS authentication itself contributes only data.entity_id. Gateway then fetches the entity and selects one active business to obtain the remaining stored identity. Provider transaction IDs, status text, messages, and receipt timestamps from the raw authentication envelope are not durable business identity and are not published by onboarding or the read endpoint.

Verify the current identity directly with NRS

The onboarding and represented-business information responses are persisted snapshots, not live NRS reads. When you need to check whether NRS has since changed the entity, registered name, TIN, active state, or IRN template, authenticate the represented business's NRS portal user through this unbilled diagnostic:

curl --request POST \
  --url https://acme.invoice.tryduplo.com/api/v1/utilities/authenticate \
  --header 'content-type: application/json' \
  --header 'x-api-key: pk_test_replace_with_your_key' \
  --data '{
    "email": "nrs-owner@acme.example",
    "password": "replace-with-a-transient-secret"
  }'

A common response is:

Authentication response
{
  "code": 200,
  "data": {
    "entity_id": "entity-demo"
  }
}

Then read the entity:

curl \
  --url https://acme.invoice.tryduplo.com/api/v1/entity/entity-demo \
  --header 'x-api-key: pk_test_replace_with_your_key'
Entity response
{
  "code": 200,
  "data": {
    "id": "entity-demo",
    "reference": "entity-reference-demo",
    "businesses": [
      {
        "id": "nrs-business-demo",
        "name": "Acme Retail Limited",
        "tin": "12345678-0001",
        "irn_template": "{{invoice_id}}-2A3A045D-{{YYYYMMDD}}",
        "is_active": true
      }
    ]
  }
}

Select only the active business with an exact normalized TIN match. Its id must match the current firsBusinessId and nrsIdentity.nrsBusinessId in the Gateway snapshot; its name, TIN, and IRN template should also match. gatewaySubBusinessId can intentionally remain the original stable routing identity after an audited migration. Remove the NRS password from memory and logs as soon as the diagnostic finishes. Do not call this authentication endpoint for every invoice.

The entity or business reference can be absent and is not an onboarding or invoice requirement. Gateway stores the bounded entity reference first, or the selected-business reference as a fallback, and returns entityReference: null when neither exists. If current NRS identity differs from the stored mapping, stop signing and use the reviewed identity-reverification workflow. Never silently overwrite your mapping or onboard a duplicate.

Recipient lookup and health

Check a receiver before building a transmission workflow:

curl \
  --url https://acme.invoice.tryduplo.com/api/v1/invoice/transmit/lookup/tin/11111111-0001 \
  --header 'x-api-key: pk_test_replace_with_your_key'
Recipient lookup response
{
  "code": 200,
  "data": {
    "up": true,
    "has_webhook_setup": true
  }
}

Check the NRS connection selected for the current domain and state:

curl \
  --url https://acme.invoice.tryduplo.com/api/v1/invoice/transmit/self-health-check \
  --header 'x-api-key: pk_test_replace_with_your_key'
Self-health response
{
  "code": 200,
  "data": {
    "ok": true
  }
}

These calls say nothing about your wallet balance in Duplo Dashboard or domain approval workflow. A Gateway host health check is separately available at GET /health and returns { "status": "ok" }.

Fetch NRS resources instead of hard-coding them

curl \
  --url https://acme.invoice.tryduplo.com/api/v1/invoice/resources/tax-categories \
  --header 'x-api-key: pk_test_replace_with_your_key'

A resource response can resemble:

Tax category response
{
  "code": 200,
  "data": [
    {
      "code": "STANDARD_VAT",
      "value": "Standard VAT",
      "percent": "7.5"
    }
  ]
}

Treat STANDARD_VAT, 7.5, service code 6201, and quantity code HUR in these guides as snapshots used to explain shape. Fetch the current NRS catalogs, cache them for a bounded period, and refresh them when NRS validation reports an unknown or changed value.

Invoice endpoint responses

Common NRS success bodies seen by the Gateway integration are:

Validate
{ "code": 200, "data": { "ok": true } }
Sign
{ "code": 201, "data": { "ok": true, "irn": "INV000001-2A3A045D-20260818" } }
Transmit
{ "code": 200, "data": { "ok": true } }

For signed-IRN lookup, current integration evidence shows party objects such as accounting_supplier_party, accounting_customer_party, bill_party, and ship_party, each with an up flag. The exact lookup schema is not sufficiently stable to use as a generated type; preserve unknown fields and check the current NRS documentation.

Understand response ownership

Your client can encounter three response families:

FamilyExamplesParsing rule
NRS success relayA successful resource, entity, recipient lookup, or other eligible direct NRS operationPreserve the sanitized NRS 2xx status, content type, and body; tolerate additive fields
Gateway applicationRoute/authentication failures, protected invoice rules, represented-business onboarding, Stored invoice operations, and every mapped NRS non-2xx responseFor a failure with application/problem+json, branch on code and retry.strategy; retain requestId; do not expect raw NRS error text
Duplo Dashboard APISignup, key settings, domain configurationRead the Duplo Dashboard API response envelope; it can also include timestamp and path

Use the response Content-Type before selecting a parser. A successful NRS body can use a different schema from a Gateway-owned success. A DNS, TLS, CDN, or load-balancer failure can occur before Gateway and will not have Gateway Problem Details.

Do not assume every successful response uses the same HTTP status. Log the HTTP status, sanitized response body, and Gateway-generated X-Request-ID without logging credentials, query secrets, or full invoice payloads. See the Gateway error code reference for the complete GW_* catalogue and Handle Gateway error responses for safe retry rules.

Continue with the Pass-through guide, Stored guide, or the complete official NRS reference.

How is this guide?

On this page