Onboard Represented Businesses

Verify each NRS-registered issuer, create its Gateway mapping, and optionally store the material used for invoice QR codes.

A represented business is the legal entity whose verified NRS identity appears on an invoice. It sits under the top-level business you manage in Duplo Dashboard: the top-level business owns the approved domain, API key, wallet, billing, and webhooks, while the represented business owns only its NRS identity, invoice routing, and optional QR material. It gets no Duplo Dashboard login, API key, or wallet of its own. Onboarding one is unbilled; only invoice signing carries a charge.

Prerequisites for each represented business

Confirm all of the following before you send the onboarding request:

  • The NRS entity, and at least one business under it, are active in the NRS environment that your API key resolves to. The key carries its own test or live state, and that state alone selects the environment; no header or body field overrides it.
  • The business has selected Duplo as its Access Point Provider (APP) in the NRS portal. If it has not, onboarding fails with 403.
  • You have that business's NRS portal email and password. The Gateway uses them for this one verification request.
  • The entity contains exactly one active business, or you know the exact TIN of the one to select.
  • That business has an ID, name, TIN, and a non-empty IRN template in NRS.
  • You have chosen an x-idempotency-key for the request and have durable storage for the identifiers it returns. See Design stable idempotency keys for naming patterns.

NRS QR material is optional

Omit nrsCryptoKeys when you do not need invoice QR generation yet. The business is onboarded with cryptoVersion: 0, and the absence of these values does not block validation, signing, or transmission. Duplo skips QR generation until both values have been stored. To send the material now, see where it comes from first.

Send the onboarding request

Send the request to your approved domain, replacing acme with your own domain label from Choose a domain label. The request is scoped to the top-level business that owns the API key, so it carries no x-sub-business-id header, and x-idempotency-key is required.

This minimal request is valid when the authenticated NRS entity has exactly one active business:

curl --request POST \
  --url https://acme.invoice.tryduplo.com/sub-businesses \
  --header 'content-type: application/json' \
  --header 'x-api-key: pk_test_replace_with_your_key' \
  --header 'x-idempotency-key: onboard-acme-retail-0001' \
  --data '{
    "nrsCredentials": {
      "email": "nrs-owner@acme.example",
      "password": "replace-at-runtime"
    }
  }'

One optional field, tin, selects which business to onboard. Omit it when the authenticated NRS entity has exactly one active business, and send it when the entity has more than one, so that it picks out a single issuer:

{
  "nrsCredentials": {
    "email": "nrs-owner@acme.example",
    "password": "replace-at-runtime"
  }
}

Request-field rules

Duplo verifies the business's identity with NRS and stores what NRS returns. Only nrsCredentials and tin affect which business that is. Every other field below is a local label of your own: it never selects the business and never overrides the NRS-verified values.

FieldRequirement and use
nrsCredentialsRequired while the onboarding intent is new or pending. Both email and password are required inside the object. A replay of a completed onboarding can omit the object.
tinOptional selector, 1 to 255 characters. Matched trimmed and case-insensitively, and it must select exactly one active NRS business. When omitted, the entity must contain exactly one active business.
name, legalNameOptional local profile values, each limited to 50 characters.
emailOptional local contact address, validated as an email and limited to 254 characters. This is not the NRS portal login.
phoneNumberOptional local contact number in E.164 format, such as +2348012345678, limited to 20 characters.
invoiceNumberPrefixOptional local value from 1 to 32 characters.
nrsCryptoKeysOptional QR material. When present, both certificate and publicKey must be non-blank strings; the UTF-8 limits are 32 KiB and 16 KiB respectively.

The Gateway rejects unknown fields with 400. Do not send existingBusinessId, businessId, parentBusinessId, entityId, entityReference, firsBusinessId, nrsBusinessId, businessName, or irnTemplate; those identity and ownership values are server-resolved.

What happens during onboarding

  1. Bind tenant

    Gateway resolves the approved host and re-checks on this request that the API key belongs to the business owning it, in the same test/live state.

  2. Create intent

    Spend normalizes and records the optional profile fields and the idempotency key before Gateway contacts NRS.

  3. Verify NRS

    Gateway authenticates, loads the active entity, and selects one issuer using the optional TIN, or the only active business.

  4. Create mapping

    Gateway sends Spend a short-lived signed identity proof plus optional QR material; Spend creates the represented-business record in one step.

The Gateway uses the NRS password only for that verification call. It is not logged, echoed back, stored, or forwarded to Spend. Spend receives only the signed NRS identity proof and, when supplied, the optional QR values.

Selection fails with:

  • 422 when no active business is available, the selected business is inactive, or its IRN template is missing.
  • 404 when a supplied TIN matches no NRS business.
  • 409 when the entity has several active businesses and no TIN narrows them to one.
  • 502 when NRS returns incomplete or malformed identity data.

Save the response

A first-time onboarding that sends no QR material returns 201:

Onboarding response
{
  "onboardingIntentId": "33333333-3333-4333-8333-333333333333",
  "gatewaySubBusinessId": "44444444-4444-4444-8444-444444444444",
  "status": "completed",
  "replayed": false,
  "cryptoVersion": 0
}

onboardingIntentId identifies this onboarding intent. No endpoint takes it as input, so you do not need it for later invoice calls. gatewaySubBusinessId is the Duplo-issued UUID for the represented business. status is always completed on a 2xx response, so a successful call never leaves an intent for you to poll. replayed is true when the idempotency key matched an earlier completed onboarding and these identifiers came from that first attempt; a replay returns 200 rather than 201.

Store at least this mapping in your integration database:

Value to storeWhere it comes fromWhy you need it
Your own customer or legal-entity IDYour systemResolve the represented business from your application
gatewaySubBusinessIdThis responseSent as x-sub-business-id on invoice operations, and used as the path segment when storing QR material
NRS business_idNRS portal or the NRS entity endpointRequired as business_id in a Pass-through NRS invoice
Verified TIN and NRS nameNRS, by way of this onboardingMust match the issuer identity in invoice payloads
IRN template, including its service segmentNRS portal or the NRS entity endpointRequired to build a valid IRN
cryptoVersionThis response0 means no QR material, 1 is the first stored pair, and later replacements increment it

Onboarding deliberately does not return the NRS business ID or the IRN template. Read both from the NRS portal, or from the unbilled NRS authentication and entity endpoints described in NRS API context. You can do that before or after onboarding, because these values come from NRS rather than from Duplo.

Keep gatewaySubBusinessId in durable storage. It is the Duplo-issued UUID for this represented business, not its NRS business_id. Send it as x-sub-business-id on represented-business-scoped invoice requests, and use it as the path identifier when adding or replacing that business's QR material. The Gateway has no GET /sub-businesses list and no recovery endpoint, so you cannot read it back if you lose it.

Safe retry

If the request times out while the intent is new or pending, resend the same optional profile fields with the same x-idempotency-key, and include nrsCredentials again. Once onboarding has completed, the same profile and key return the original identifiers with replayed: true, and that replay may omit both nrsCredentials and nrsCryptoKeys. Changing any profile value under the same key returns 409. Do not create a new idempotency key just because the first HTTP response was lost, because a new key starts a second onboarding attempt.

Add or replace optional NRS QR material

You can supply nrsCryptoKeys during onboarding or store it later against the represented-business ID. When the object is present, both values are required.

NRS issues this material, so you do not generate the key pair yourself. In the NRS taxpayer portal, open API Integration, then Manage Cryptographic keys, and choose Generate cryptographic key. Name the key so you can recognize it later, then download the keys file. Despite its .txt extension the file is JSON, and it holds a base64-encoded public_key and certificate. Send certificate exactly as the file gives it. For publicKey, Duplo accepts either that same base64 value or the PEM it decodes to, which NRS extracts with:

Decode the NRS public key to PEM
jq -r '.public_key' crypto_keys.txt | openssl base64 -d -out public-key.pem
curl --request POST \
  --url https://acme.invoice.tryduplo.com/sub-businesses/44444444-4444-4444-8444-444444444444/nrs-crypto-material \
  --header 'content-type: application/json' \
  --header 'x-api-key: pk_test_replace_with_your_key' \
  --header 'x-idempotency-key: qr-material-acme-retail-0001' \
  --data '{
    "nrsCryptoKeys": {
      "certificate": "nrs-issued-certificate-value",
      "publicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n"
    }
  }'

A successful call returns 200, whether this is the first pair stored or a replacement. rotatedAt carries that name in both cases:

First QR-material response
{
  "gatewaySubBusinessId": "44444444-4444-4444-8444-444444444444",
  "cryptoVersion": 1,
  "rotatedAt": "2026-08-31T15:20:36.000Z"
}

At onboarding and replacement time, the Gateway treats both strings as opaque and stores them exactly as supplied. It does not parse an X.509 certificate, check certificate dates, normalize the values, measure RSA key size, or verify a certificate/key match. The QR generator interprets them only when a signed or received invoice needs a QR code: it embeds certificate in the payload and uses publicKey as plain PEM or base64-encoded PEM for RSA encryption. Unusable material can make QR generation fail, but QR generation is best-effort, and it never turns this material into an onboarding, validation, signing, or transmission requirement.

The endpoint re-checks the top-level business, the approved domain's test/live state, and that the represented business belongs to that top-level business. It sends the opaque values to Spend for storage and makes no NRS call. A represented-business ID that belongs to another account and one that does not exist at all both return the same 404, so the response never reveals whether another account holds that ID. Each successful replacement increments cryptoVersion; an idempotent replay does not.

Continue with Build invoice payloads.

How is this guide?

Last updated on

On this page