E-Invoicing

Validate and digitally sign your invoices to stay compliant with the Nigeria Revenue Service (NRS)

Atlas e-invoicing lets businesses registered on the NRS Merchant Buyer Solution (MBS) create, validate, and sign tax-compliant invoices without building a direct integration with the Nigeria Revenue Service (NRS).

Instead of managing NRS credentials, handling submission protocols, and maintaining a separate integration, you interact with a single Atlas API. Atlas acts as your Access Point Provider (APP), handling communication with NRS on your behalf.

Enabling E-invoicing on your Atlas account

To enable E-invoicing on your Atlas account, you must have selected Atlas as your Access Point Provider (APP) on the NRS platform.

Tip

If you used a different APP during your enablement and you wish to switch to Atlas, follow the instructions on page 8 of the Taxpayer's Manual

With Atlas as your preferred APP on your NRS dashboard, make a request to the Onboard Taxpayer endpoint with your NRS taxpayer portal login credentials. Your credentials are encrypted at rest and used solely to sign and submit invoices to NRS on your behalf.

curl -X POST "https://atlas.tryduplo.com/api/v1/e-invoicing/onboard" \
  -H "Authorization: Bearer <YOUR_ATLAS_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "email": "taxpayer@example.com",
    "password": "your-nrs-password"
  }'

On a successful request, you should receive a response similar to the one below:

JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-04-09 12:00:00",
  "message": "Taxpayer onboarded successfully.",
  "statusCode": 201,
  "data": {
    "reference": "NRS-REF-001",
    "businessId": "019a1639-ce25-70b7-ab20-16fcb2bac023",
    "isActive": true,
    "tin": "12345678-0001",
    "name": "Acme Corp Ltd",
    "nrsBusinessId": "NRS-BIZ-001",
    "nrsEntityId": "NRS-ENTITY-001",
    "nrsServiceId": "ABCD1234",
    "nrsEmail": "taxpayer@example.com",
    "duploIsApp": true
  }
}

Important

Onboarding is a one-time action. Your NRS credentials are saved to your business record and returned on subsequent login and profile calls and you will not need to repeat this step.

E-invoicing flow

When creating an invoice, you have two options:

  • Save as draft: the invoice is saved in Atlas but not submitted to NRS. Use this when you need to review or update the invoice before it is validated. You can update a draft as many times as needed, then validate it separately when ready.
  • Save and validate: the invoice is saved and submitted to NRS for validation in a single request. Use this when the invoice details are final and you want to skip the draft step.

Regardless of which path you take, once an invoice is validated the remaining steps are the same:

  1. Sign the invoice: Signing submits a finalization request to NRS and locks the invoice, and no further edits are allowed after this point. NRS assigns a digital stamp to confirm the invoice is compliant.
  2. Update the payment status: Once the invoice is settled between you and the buyer, update the status to PAID or REJECTED to keep your records accurate and reflect the outcome on Atlas.
  3. Confirm the invoice: Call the confirm endpoint with the IRN to check the invoice's confirmation status on NRS.

Creating an invoice

To create an invoice, make a POST request to the Create E-Invoice endpoint with the following fields.

Invoice details

  • invoice_number - your unique identifier for this invoice.
  • source_reference - your internal system reference, used to track the invoice on your end.
  • invoice_type - the UBL code for the type of invoice you are issuing (e.g. 388 for a tax invoice, 381 for a credit note). Call the Get NRS Resources endpoint with type=invoice-types to see all valid values.
  • issue_date - the date the invoice was issued, in YYYY-MM-DD format.
  • document_currency - the ISO 4217 currency code for the invoice amounts (e.g. NGN).
  • tax_currency - the ISO 4217 currency code for tax amounts. This is usually the same as document_currency.
  • save_and_validate - set to true to save the invoice and submit it to NRS for validation in a single request. Omit or set to false to save as a draft.

For example:

Invoice details
{
  "invoice_number": "INV-2026-001",
  "source_reference": "SRC-2026-001",
  "invoice_type": "388",
  "issue_date": "2026-04-29",
  "document_currency": "NGN",
  "tax_currency": "NGN",
  "save_and_validate": false
}

Supplier data

supplier_data is required and contains details of the business issuing the invoice: name, email, phone_number, tin (Tax Identification Number), country (ISO 3166-1 alpha-2 code), city, address, and postal_zone. You can also include state and business_description if applicable.

For example:

Supplier data
{
  "supplier_data": {
    "name": "Acme Corp Ltd",
    "email": "billing@acme.com",
    "phone_number": "+2348012345678",
    "tin": "12345678-0001",
    "country": "NG",
    "state": "Lagos",
    "city": "Lagos",
    "address": "123 Business Ave",
    "postal_zone": "100001",
    "business_description": "Software services provider"
  }
}

Payee data

payee_data is optional, but if included, all its fields are required. It follows the same structure as supplier_data. Include it when you want to specify the buyer receiving the invoice.

Tip

If you invoice the same buyer regularly and want to avoid filling out their details each time, save them as an invoicing customer first. You can then reuse their saved details whenever you create an invoice for them.

For example:

Payee data
{
  "payee_data": {
    "name": "Customer Inc",
    "email": "accounts@customer.com",
    "phone_number": "+2348098765432",
    "tin": "87654321-0001",
    "country": "NG",
    "city": "Abuja",
    "address": "456 Client Street",
    "postal_zone": "900001"
  }
}

Payment details

payment_details is optional and contains your settlement bank account: bank_name, account_number, and account_name. This is stored by Atlas only and is not forwarded to NRS.

For example:

Payment details
{
  "payment_details": {
    "bank_name": "First Bank",
    "account_number": "1234567890",
    "account_name": "Acme Corp Ltd"
  }
}

Items

items is a required array of line items on the invoice. Each item requires a description, quantity, and unit_price. You can also include:

  • taxes - an array of taxes applied to the item. Each tax needs a name and a rate expressed as a decimal fraction (e.g. 0.075 for 7.5% VAT).
  • unit - the unit of measure (e.g. EA for each).
  • hsn_code - the Harmonized System Nomenclature code for the item.
  • product_category - the product or service category.

For example:

Items
{
  "items": [
    {
      "description": "Software License",
      "quantity": 2,
      "unit_price": 50000,
      "unit": "EA",
      "hsn_code": "998314",
      "product_category": "Software",
      "taxes": [
        {
          "name": "VAT",
          "rate": 0.075
        }
      ]
    }
  ]
}

Discount

discount is optional. Set type to percentage or fixed, and value to the discount amount. Discounts are calculated by Atlas and not forwarded to NRS.

For example:

Discount
{
  "discount": {
    "type": "percentage",
    "value": 5
  }
}

The examples below show a complete request combining all of the fields above.

curl -X POST "https://atlas.tryduplo.com/api/v1/e-invoicing/invoices" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "save_and_validate": false,
    "invoice_number": "INV-2026-001",
    "source_reference": "SRC-2026-001",
    "invoice_type": "388",
    "issue_date": "2026-04-29",
    "document_currency": "NGN",
    "tax_currency": "NGN",
    "supplier_data": {
      "name": "Acme Corp Ltd",
      "email": "billing@acme.com",
      "phone_number": "+2348012345678",
      "tin": "12345678-0001",
      "country": "NG",
      "state": "Lagos",
      "city": "Lagos",
      "address": "123 Business Ave",
      "postal_zone": "100001"
    },
    "payee_data": {
      "name": "Customer Inc",
      "email": "accounts@customer.com",
      "phone_number": "+2348098765432",
      "tin": "87654321-0001",
      "country": "NG",
      "city": "Abuja",
      "address": "456 Client Street",
      "postal_zone": "900001"
    },
    "payment_details": {
      "bank_name": "First Bank",
      "account_number": "1234567890",
      "account_name": "Acme Corp Ltd"
    },
    "items": [
      {
        "description": "Software License",
        "quantity": 2,
        "unit_price": 50000,
        "unit": "EA",
        "taxes": [
          {
            "name": "VAT",
            "rate": 0.075
          }
        ]
      }
    ],
    "discount": {
      "type": "percentage",
      "value": 5
    },
    "notes": "Payment due within 30 days",
    "payment_terms": "Net 30"
  }'

On a successful request, you should receive a response similar to the one below:

JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-04-29 12:00:00",
  "message": "Invoice created successfully.",
  "statusCode": 201,
  "data": {
    "id": "019a1639-ce25-70b7-ab20-16fcb2bac023",
    "invoiceReferenceNumber": "REF-2026-001",
    "sourceReference": "SRC-2026-001",
    "invoiceNumber": "INV-2026-001",
    "status": "DRAFT",
    "invoiceType": "388",
    "issueDate": "2026-04-29",
    "documentCurrency": "NGN",
    "taxCurrency": "NGN",
    "paymentStatus": "PENDING",
    "subtotalAmount": 100000,
    "taxTotal": 7500,
    "totalAmount": 107500,
    "amountDue": 107500
  }
}

Updating a draft

If you saved an invoice as a draft and need to make changes before validating, use the Update E-Invoice endpoint. Pass the invoiceReferenceNumber returned when the invoice was created as the reference path parameter.

All fields are optional; only the fields you include will be updated. You can update supplier_data, payee_data, tax_representative_data, payment_details, items, discount, notes, and payment_terms. Only invoices in DRAFT status can be updated; once an invoice is validated, it can no longer be modified.

curl -X PATCH "https://atlas.tryduplo.com/api/v1/e-invoicing/invoices/REF-2026-001" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "notes": "Please process before end of month.",
    "payment_terms": "Net 15",
    "items": [
      {
        "description": "Software License",
        "quantity": 3,
        "unit_price": 50000,
        "unit": "EA",
        "taxes": [
          {
            "name": "VAT",
            "rate": 0.075
          }
        ]
      }
    ]
  }'

Validating a draft

Once you are satisfied with your draft, submit it to NRS for validation using the Validate E-Invoice endpoint. The invoice must be in DRAFT status. On success, the status updates to VALIDATED.

Pass the invoiceReferenceNumber from the create invoice response as invoice_reference_number in the request body.

curl -X POST "https://atlas.tryduplo.com/api/v1/e-invoicing/invoices/validate-invoice" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "invoice_reference_number": "REF-2026-001"
  }'

On a successful request, you should receive a response similar to the one below:

JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-04-29 12:00:00",
  "message": "Invoice validated successfully.",
  "statusCode": 200,
  "data": {
    "id": "019a1639-ce25-70b7-ab20-16fcb2bac023",
    "invoiceReferenceNumber": "REF-2026-001",
    "invoiceNumber": "INV-2026-001",
    "status": "VALIDATED",
    "issueDate": "2026-04-29",
    "documentCurrency": "NGN",
    "taxCurrency": "NGN",
    "paymentStatus": "PENDING",
    "subtotalAmount": 100000,
    "taxTotal": 7500,
    "totalAmount": 107500,
    "amountDue": 107500
  }
}

Important

Once an invoice is validated, it can no longer be updated. Only invoices in DRAFT status can be modified.

Creating an invoicing customer

You can save customers you invoice regularly to your Atlas account, allowing you to skip the step of filling out their details each time you send them an invoice.

To create an invoice customer, make a POST request to the Create New E-invoicing Customer endpoint with the following details:

  • first_name - the customer's first name.
  • last_name - the customer's last name.
  • email - the customer's email address.
  • phone - the customer's phone number.
  • tin - the customer's Tax Identification Number.
  • country - the country code representing the customer or customer's business's registered country in ISO 3166-1 Alpha-2 format, e.g. NG.
  • state - the customer or customer's business's state code in ISO 3166-2 format, e.g. NG-AB.
  • lga - the customer or customer's business's local government area code in ISO 3166-2 format, e.g. NG-AB-ANO.
  • city - the city of the customer or customer’s business location.
  • address - the street address of the customer or customer’s business location.
  • postal_code - the postal code of the customer or customer’s business location.
  • description (optional) - a short description of the customer.
curl -X POST "https://atlas.tryduplo.com/api/v1/e-invoicing/customers" \
  -H "Authorization: Bearer <YOUR_ATLAS_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "first_name": "John",
    "last_name": "Doe",
    "email": "john.doe@example.com",
    "phone": "+2348098765432",
    "tin": "98765432-0001",
    "country": "NG",
    "state": "NG-AB",
    "lga": "NG-AB-ANO",
    "city": "Yaba",
    "address": "456 Market Street, Yaba",
    "postal_code": "100001",
    "description": "A leading provider of tech solutions in Nigeria"
  }'

On a successful request, you will receive a response containing the new customer's reference. Save this value — you will use it to populate the payee_data when creating invoices for this customer, and to retrieve or update the customer record later.

JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-05-14 14:02:37",
  "message": "Customer created successfully.",
  "statusCode": 201,
  "data": {
    "id": "019e26cb-e650-726d-bad6-95564a020b4a",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@example.com",
    "phoneNumber": "+2348098765432",
    "reference": "dp_cust_hrxp0rpkhlyc6r8",
    "status": "ACTIVE",
    "hasNrsCustomer": true,
    "source": "API",
    "createdAt": "2026-05-14 14:02:37"
  }
}

Listing invoicing customers

You can retrieve all e-invoicing customers saved under your business. This is useful for building customer pickers in your application, or checking whether a customer already exists before creating a new one.

To retrieve your customers, make a GET request to the List E-Invoicing Customers endpoint. The following optional query parameters are supported:

  • search - filter customers by name or email.
  • reference - fetch a specific customer by their reference.
  • sort - sort the results.
  • page - the page number to retrieve.
  • limit - the number of results per page.
curl -X GET "https://atlas.tryduplo.com/api/v1/e-invoicing/customers?page=1&limit=10" \
  -H "Authorization: Bearer <YOUR_ATLAS_API_KEY>" \
  -H "Accept: application/json"

The response includes a paginated list of customers along with meta and links objects to help you navigate through pages.

JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-05-14 14:02:37",
  "message": "Customers retrieved successfully.",
  "statusCode": 200,
  "data": [
    {
      "id": "019e26cb-e650-726d-bad6-95564a020b4a",
      "firstName": "John",
      "lastName": "Doe",
      "email": "john.doe@example.com",
      "phoneNumber": "+2348098765432",
      "reference": "dp_cust_hrxp0rpkhlyc6r8",
      "status": "ACTIVE",
      "hasNrsCustomer": true,
      "source": "API",
      "createdAt": "2026-05-14 14:02:37"
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 10,
    "total": 1,
    "last_page": 1
  },
  "links": {
    "first": "https://atlas.tryduplo.com/api/v1/e-invoicing/customers?page=1",
    "last": "https://atlas.tryduplo.com/api/v1/e-invoicing/customers?page=1",
    "prev": null,
    "next": null
  }
}

Getting a single invoicing customer

If you already have a customer's reference, you can fetch their full record directly without going through the list. This is the recommended approach when you know exactly which customer you need, for example when your application stores the reference internally.

To retrieve a customer, make a GET request to the Get E-Invoicing Customer endpoint, passing the customer's reference as a path parameter.

curl -X GET "https://atlas.tryduplo.com/api/v1/e-invoicing/customers/dp_cust_hrxp0rpkhlyc6r8" \
  -H "Authorization: Bearer <YOUR_ATLAS_API_KEY>" \
  -H "Accept: application/json"
JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-05-14 14:02:37",
  "message": "Customer retrieved successfully.",
  "statusCode": 200,
  "data": {
    "id": "019e26cb-e650-726d-bad6-95564a020b4a",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@example.com",
    "phoneNumber": "+2348098765432",
    "reference": "dp_cust_hrxp0rpkhlyc6r8",
    "status": "ACTIVE",
    "hasNrsCustomer": true,
    "source": "API",
    "createdAt": "2026-05-14 14:02:37"
  }
}

Updating an invoicing customer

When a customer's contact details or address changes, you can update their record without creating a new customer. Only the fields you include in the request are updated — omitted fields keep their existing values.

To update a customer, make a POST request to the Update E-Invoicing Customer endpoint, passing the customer's reference as a path parameter and the fields you want to change in the request body.

curl -X POST "https://atlas.tryduplo.com/api/v1/e-invoicing/customers/dp_cust_hrxp0rpkhlyc6r8/update" \
  -H "Authorization: Bearer <YOUR_ATLAS_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "email": "john.new@example.com",
    "address": "12 New Street, Victoria Island",
    "city": "Lagos"
  }'

The response returns the updated customer record.

JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-05-14 14:02:37",
  "message": "Customer updated successfully.",
  "statusCode": 200,
  "data": {
    "id": "019e26cb-e650-726d-bad6-95564a020b4a",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.new@example.com",
    "phoneNumber": "+2348098765432",
    "reference": "dp_cust_hrxp0rpkhlyc6r8",
    "status": "ACTIVE",
    "hasNrsCustomer": true,
    "source": "API",
    "createdAt": "2026-05-14 14:02:37"
  }
}

Customizing an invoice

You can apply your business branding to generated invoices by setting a logo and brand colors. Once configured, these settings apply to all invoices issued by your business.

To set your branding, make a POST request to the Customize Invoice endpoint with the following fields:

  • logo_url - a publicly accessible URL pointing to your business logo.
  • primary_color - your primary brand color as a hex code (e.g. #1A73E8).
  • secondary_color - an optional secondary brand color as a hex code.
curl -X POST "https://atlas.tryduplo.com/api/v1/e-invoicing/customize-invoice" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "logo_url": "https://example.com/logo.png",
    "primary_color": "#1A73E8",
    "secondary_color": "#FF5722"
  }'

On a successful request, you should receive a response similar to the one below:

JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-04-09 12:00:00",
  "message": "Business brand updated successfully.",
  "statusCode": 200,
  "data": {
    "logo_url": "https://example.com/logo.png",
    "primary_color": "#1A73E8",
    "secondary_color": "#FF5722"
  }
}

Signing an invoice

Once an invoice is validated, you need to sign it before it is considered final. Signing submits a finalization request to NRS, which assigns a digital stamp to the invoice to confirm it is compliant. After signing, the invoice is locked and no further edits are allowed.

The invoice must be in VALIDATED status before you can sign it. Pass the invoiceReferenceNumber as the reference path parameter to the Sign E-Invoice endpoint.

curl -X POST "https://atlas.tryduplo.com/api/v1/e-invoicing/invoices/REF-2026-001/sign" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Accept: application/json"

On a successful request, you should receive a response similar to the one below:

JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-04-29 12:00:00",
  "message": "Invoice signed successfully.",
  "statusCode": 200,
  "data": {
    "id": "019a1639-ce25-70b7-ab20-16fcb2bac023",
    "invoiceReferenceNumber": "REF-2026-001",
    "invoiceNumber": "INV-2026-001",
    "status": "SIGNED",
    "issueDate": "2026-04-29",
    "documentCurrency": "NGN",
    "taxCurrency": "NGN",
    "paymentStatus": "PENDING",
    "subtotalAmount": 100000,
    "taxTotal": 7500,
    "totalAmount": 107500,
    "amountDue": 107500
  }
}

Updating the payment status

After an invoice is settled between you and the buyer, use the Update Invoice Payment Status endpoint to record the outcome. Set payment_status to PAID when payment has been received, or REJECTED if the payment was declined or the invoice was disputed.

You can also include an optional reference field to attach your internal payment reference to the record.

Pass the invoiceReferenceNumber as the reference path parameter.

curl -X PATCH "https://atlas.tryduplo.com/api/v1/e-invoicing/invoices/REF-2026-001/payment-status" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "payment_status": "PAID",
    "reference": "PAY-REF-001"
  }'

On a successful request, you should receive a response similar to the one below:

JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-04-29 12:00:00",
  "message": "Invoice payment status updated successfully.",
  "statusCode": 200,
  "data": {
    "id": "019a1639-ce25-70b7-ab20-16fcb2bac023",
    "invoiceReferenceNumber": "REF-2026-001",
    "invoiceNumber": "INV-2026-001",
    "status": "SIGNED",
    "issueDate": "2026-04-29",
    "documentCurrency": "NGN",
    "taxCurrency": "NGN",
    "paymentStatus": "PAID",
    "subtotalAmount": 100000,
    "taxTotal": 7500,
    "totalAmount": 107500,
    "amountDue": 107500
  }
}

Confirming an invoice

After signing, use the Confirm E-Invoice endpoint to confirm the invoice's status with NRS. Pass the IRN to check whether the invoice has been accepted and registered on the NRS system.

Pass the invoiceReferenceNumber as the reference path parameter.

curl -X GET "https://atlas.tryduplo.com/api/v1/e-invoicing/invoices/REF-2026-001/confirm" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Accept: application/json"

On a successful request, you should receive a response similar to the one below:

JSON
{
  "requestId": "3a6538fd-03f0-4b05-ba41-5d44769dd014",
  "requestTimestamp": "2026-04-29 12:00:00",
  "message": "Invoice confirmed successfully.",
  "statusCode": 200,
  "data": {
    "irn": "INV-2026-001|ABCD1234|20260429",
    "status": "CONFIRMED",
    "approvalDate": "2026-04-29 14:30:00",
    "digitalStamp": "abc123def456..."
  }
}

How is this guide?

Last updated on

On this page