API Documentation

https://api.invota.ng/api/v1

INVOTA Platform

API Documentation

Complete REST API reference for the INVOTA e-invoicing platform. All endpoints conform to the FIRS NRS specification for e-invoice generation, signing, and transmission in Nigeria.

Base URLhttps://api.invota.ng/api/v1
Version1.0
AuthBearer Token / API Key
FormatJSON
01

Overview

The INVOTA API gives you programmatic access to every part of the e-invoicing lifecycle, from creating a draft to confirming NRS acknowledgement. All requests go to the base URL above over HTTPS.

Authentication

The dashboard API uses bearer token authentication. After logging in, include the token in every subsequent request:

Authorization: Bearer {your-token}

The B2B enterprise API uses API key authentication. See Section 8 for details.

Rate Limits

API GroupLimitWindow
Dashboard API (all authenticated routes)60 requestsPer minute
B2B Enterprise API100 requestsPer minute

When a rate limit is exceeded the API returns HTTP 429 with a Retry-After header.

Response Format

All responses are JSON. Successful responses include a data key. Error responses include a message key and, where applicable, an errors key with field-level details.

// Success
{ "data": { ... } }

// Validation error
{
  "message": "Validation failed.",
  "errors": {
    "invoice_number": ["The invoice number field is required."]
  }
}

Multi-Tenancy

Every authenticated request is automatically scoped to the organisation of the authenticated user. It is not possible to access data belonging to another organisation through this API.

Invoice Status Flow

Invoices progress through the following statuses as they move through the NRS pipeline:

draft›validated›signed›transmitting›transmitted›acknowledged›reported

Any stage can transition to failed on NRS rejection. Failed invoices can be retried. They return to draft.

02

Authentication

All authentication endpoints are public. No token required.

POST/auth/register

Registers a new organisation and creates the first user account (the owner). The user receives the finance role with is_owner = true.

Request Body

FieldTypeRequiredDescription
org_namestringRequiredName of the business
tinstringRequiredFIRS Tax Identification Number
emailstringRequiredBusiness email address
passwordstringRequiredMinimum 8 characters
namestringRequiredFull name of the registering user
phonestringOptionalBusiness phone number
addressstringOptionalBusiness address

Response: 201 Created

{
  "data": {
    "token": "1|abcdefghijklmnopqrstuvwxyz",
    "user": {
      "id": "uuid", "name": "Adaeze Okonkwo",
      "email": "adaeze@acme.ng", "role": "finance"
    },
    "organization": { "id": "uuid", "name": "Acme Nigeria Ltd" }
  }
}
POST/auth/login

Authenticates a user and returns a bearer token.

Request Body

FieldTypeRequired
emailstringRequired
passwordstringRequired

Response: 200 OK

{
  "data": {
    "token": "2|abcdefghijklmnopqrstuvwxyz",
    "user": {
      "id": "uuid", "name": "Adaeze Okonkwo",
      "email": "adaeze@acme.ng",
      "role": "finance", "is_owner": true
    }
  }
}
POST/auth/logout

Revokes the current bearer token. The token becomes invalid immediately.

Response: 200 OK

{ "data": { "message": "Logged out successfully." } }
03

Invoice Management

All routes require authentication. Write operations require an active subscription. Supplier party details are automatically derived from the authenticated organisation's taxpayer profile.

GET/invoices

Returns a paginated list of invoices for the authenticated organisation.

Query Parameters

ParameterTypeDescription
statusstringFilter by: draft, validated, signed, transmitting, transmitted, acknowledged, reported, failed
searchstringSearch by invoice number or buyer name
pageintegerPage number (default: 1)

Response: 200 OK

{
  "data": [
    {
      "id": "uuid",
      "irn": "INV001-2741A890-20260701",
      "invoice_number": "INV001",
      "issue_date": "2026-07-01",
      "status": "signed",
      "currency_code": "NGN",
      "total_amount": 150000.00,
      "vat_amount": 19565.22
    }
  ],
  "meta": { "total": 42, "per_page": 15, "current_page": 1, "last_page": 3 }
}
POST/invoices

Creates a new invoice in draft status. Supplier party details are taken from the organisation's taxpayer profile. The IRN is built and validated server-side. Per-line amounts and totals are calculated server-side.

Request Body

FieldTypeRequiredDescription
invoice_numberstringRequiredAlphanumeric only. No special characters or spaces. Combined with the organisation's NRS Service ID and issue date to build the IRN.
invoice_type_codestringOptionalNRS invoice type code. Defaults to 381 (Commercial Invoice) when left out. Other examples: 380 = Credit Note, 384 = Debit Note, 385 = Self-Billed Invoice (see Reference Data below for the full list; these are NRS's own codes, not generic UBL/PEPPOL codes).
invoice_kindstringRequiredB2B, B2C, or B2G. Decides which buyer details are needed (see the buyer fields below) and sets our own 24-hour reporting deadline for B2C. Also sent to NRS as invoice_kind.
issue_datedateRequiredYYYY-MM-DD. Used to build the IRN.
due_datedateOptionalYYYY-MM-DD. NRS will not sign an invoice that has no due date, so if you leave this out we send NRS the issue_date as the due date. The invoice you saved is not changed. Set it yourself whenever the real due date is different.
issue_timestringOptionalHH:MM:SS, 24-hour time
tax_point_datedateOptionalYYYY-MM-DD. The date VAT becomes due, if different from issue_date.
actual_delivery_datedateOptionalYYYY-MM-DD. A single delivery date.
delivery_period_startdateOptionalYYYY-MM-DD. For a service covering a date range (e.g. a subscription period) rather than a single delivery date. Paired with delivery_period_end.
delivery_period_enddateOptionalYYYY-MM-DD
document_currency_codestringOptionalISO 4217 currency code. Defaults to NGN.
tax_currency_codestringOptionalISO 4217 currency code. Defaults to NGN.
payment_means_codestringOptionalNRS payment means code (see Reference Data above)
notestringOptionalFree-text note for the whole invoice. Sent to NRS as note. Stored encrypted on our side.
payment_terms_notestringOptionalFor example Net 30 or Due on receipt. Sent to NRS as payment_terms_note. Stored encrypted on our side.
terms_conditionsstringOptionalPrinted legal or boilerplate text for the invoice. Stored encrypted. This is not part of NRS's own schema, so it is never sent to NRS; it only appears on the PDF.
buyer_referencestringOptionalThe buyer's own purchase order or reference number. Sent to NRS as buyer_reference.
order_referencestringOptionalSent to NRS as order_reference. Many sellers send the same value as buyer_reference.
accounting_coststringOptionalAn internal cost-centre or accounting code. Sent to NRS as accounting_cost.
billing_reference_irnstringOptionalThe IRN of a related earlier invoice, for example the original invoice a credit note refers to. Send it together with billing_reference_date. Sent to NRS as billing_reference (an array of one entry). Format: capital letters, numbers and dashes only, no spaces (see Rules NRS enforces below).
billing_reference_datedateOptionalYYYY-MM-DD. The issue date of the referenced document.
dispatch_document_reference_irnstringOptionalReference of the dispatch document (for example a delivery note). Send it together with dispatch_document_reference_date. Sent to NRS as dispatch_document_reference. Same format rule as above.
dispatch_document_reference_datedateOptionalYYYY-MM-DD
receipt_document_reference_irnstringOptionalReference of the receipt document. Send it together with receipt_document_reference_date. Sent to NRS as receipt_document_reference. Same format rule as above.
receipt_document_reference_datedateOptionalYYYY-MM-DD
originator_document_reference_irnstringOptionalReference of the document this invoice originates from. Send it together with originator_document_reference_date. Sent to NRS as originator_document_reference. Same format rule as above.
originator_document_reference_datedateOptionalYYYY-MM-DD
contract_document_reference_irnstringOptionalReference of the contract the invoice relates to. Send it together with contract_document_reference_date. Sent to NRS as contract_document_reference. Same format rule as above.
contract_document_reference_datedateOptionalYYYY-MM-DD
additional_document_reference_irnstringOptionalA general-purpose document reference, for anything not covered by the specific reference types above. Send it together with additional_document_reference_date. Sent to NRS as additional_document_reference (an array of one entry). Same format rule as above.
additional_document_reference_datedateOptionalYYYY-MM-DD
invoice_adjustment_amountdecimalOptionalOne adjustment applied to the whole invoice, on top of the line totals, for example an early-payment discount or a flat processing charge. Sent to NRS as allowance_charge. Use together with invoice_adjustment_is_charge.
invoice_adjustment_is_chargebooleanRequired with invoice_adjustment_amounttrue if the amount is a charge that adds to the total, false if it is an allowance (a discount) that takes away from it
buyer.party_namestringRequired if buyer is presentBuyer company or individual name
buyer.tinstringRequired for B2B/B2GThe buyer's TIN. For a B2B or B2G buyer you can send incorporation_number instead. A B2C buyer does not need one.
buyer.incorporation_numberstringOptionalThe buyer's company registration number. Accepted in place of buyer.tin for B2B/B2G. When it is used instead of a TIN, NRS receives it as RN-{number}.
buyer.emailstringRequired once a buyer is on the invoiceNot required at all for a B2C sale with no formal buyer record
buyer.telephonestringOptional
buyer.street_namestringRequired once a buyer is on the invoice
buyer.city_namestringRequired once a buyer is on the invoice
buyer.state_codestringOptionalNRS state code (see Reference Data below). Sent to NRS as postal_address.state. Send it together with lga_code.
buyer.lga_codestringOptionalNRS LGA code (see Reference Data below). Sent to NRS as postal_address.lga.
buyer.country_codestringOptional3-letter ISO code (e.g. NGA), not the 2-letter form. Defaults to NGA.
buyer.postal_zonestringRequired once a buyer is on the invoice
buyer.business_descriptionstringOptionalA short description of what the buyer's business does
payee.party_namestringRequired if payee is presentOnly set this if payment for the invoice actually goes to a different entity than the supplier. Most invoices never need this. Sent to NRS as payee_party. If you send a payee, NRS needs its TIN and a full postal address, so the fields marked required below must all be present.
payee.tinstringRequired if payee is presentNRS rejects a payee with no TIN
payee.emailstringOptional
payee.telephonestringOptionalSent to NRS in international format, for example +2348012345678. A Nigerian number starting 0 is converted for you.
payee.street_namestringRequired if payee is present
payee.city_namestringRequired if payee is presentNRS rejects a party address with no city
payee.state_codestringRequired if payee is presentNRS state code (see Reference Data below). Sent to NRS as postal_address.state.
payee.lga_codestringRequired if payee is presentNRS LGA code (see Reference Data below). Sent to NRS as postal_address.lga.
payee.country_codestringOptional3-letter ISO code (e.g. NGA), not the 2-letter form. Defaults to NGA.
payee.postal_zonestringOptional
bill.party_namestringRequired if bill is presentOnly set this if the bill/invoice copy should go to a different contact than the buyer (for example, a separate accounts payable office). Sent to NRS as bill_party. NRS needs its TIN and a full postal address, so the fields marked required below must all be present.
bill.tinstringRequired if bill is presentNRS rejects a billing contact with no TIN
bill.emailstringOptional
bill.telephonestringOptionalSent to NRS in international format, for example +2348012345678. A Nigerian number starting 0 is converted for you.
bill.street_namestringRequired if bill is present
bill.city_namestringRequired if bill is presentNRS rejects a party address with no city
bill.state_codestringRequired if bill is presentNRS state code (see Reference Data below). Sent to NRS as postal_address.state.
bill.lga_codestringRequired if bill is presentNRS LGA code (see Reference Data below). Sent to NRS as postal_address.lga.
bill.country_codestringOptional3-letter ISO code (e.g. NGA), not the 2-letter form. Defaults to NGA.
bill.postal_zonestringOptional
ship.party_namestringRequired if ship is presentOnly set this if goods or services are actually delivered somewhere other than the buyer's own address (for example, a separate warehouse or site). Sent to NRS as ship_party. NRS needs its TIN and a full postal address, so the fields marked required below must all be present.
ship.tinstringRequired if ship is presentNRS rejects a delivery party with no TIN
ship.emailstringOptional
ship.telephonestringOptionalSent to NRS in international format, for example +2348012345678. A Nigerian number starting 0 is converted for you.
ship.street_namestringRequired if ship is present
ship.city_namestringRequired if ship is presentNRS rejects a party address with no city
ship.state_codestringRequired if ship is presentNRS state code (see Reference Data below). Sent to NRS as postal_address.state.
ship.lga_codestringRequired if ship is presentNRS LGA code (see Reference Data below). Sent to NRS as postal_address.lga.
ship.country_codestringOptional3-letter ISO code (e.g. NGA), not the 2-letter form. Defaults to NGA.
ship.postal_zonestringOptional
linesarrayRequiredAt least one line item
lines[].item_typestringRequiredgoods or services
lines[].item_namestringRequiredSent to NRS as item.name
lines[].item_descriptionstringOptionalA longer description of the item. Sent to NRS as item.description. If left out, the item name is used.
lines[].sellers_item_identificationstringOptionalThe seller's own SKU or item code
lines[].product_categorystringRequiredFree-text category, required on every line regardless of item_type
lines[].hsn_codestringRequired when item_type is goodsFormat: 0000.00
lines[].isic_codestringRequired when item_type is servicesExactly 4 digits
lines[].service_categorystringRequired whenever isic_code is present
lines[].invoiced_quantitydecimalRequiredHow many units are being invoiced
lines[].price_unitstringRequiredNRS unit-of-measure code (e.g. EA, DAY; see Reference Data below)
lines[].price_amountdecimalRequiredPrice of one unit, before tax. Line amount = invoiced_quantity x price_amount.
lines[].tax_category_idstringRequiredNRS tax category code, e.g. STANDARD_VAT, ZERO_VAT, Exempted (see Reference Data below)
lines[].tax_rate_percentdecimalOptionalIf left out, the rate is looked up from tax_category_id (7.5 for STANDARD_VAT). If you send it, it must be exactly 7.5 when tax_category_id is STANDARD_VAT.
lines[].base_quantitydecimalOptionalThe quantity that price_amount applies to. Defaults to 1.
lines[].discount_ratedecimalOptionalPercent, 0-100. The discount is taken off the line before VAT. Used to work out the discount when discount_amount is not sent.
lines[].discount_amountdecimalOptionalA fixed discount for the line, taken off before VAT. If you send both this and discount_rate, this amount is used. Sent to NRS together with discount_rate.
lines[].charge_ratedecimalOptionalPercent, 0-100. An extra charge on this line, separate from its own price (for example a rush or handling fee). Used to work out the charge when charge_amount is not sent. Sent to NRS as fee_rate.
lines[].charge_amountdecimalOptionalA fixed extra charge for the line. If you send both this and charge_rate, this amount is used. Added to the line before VAT. Sent to NRS as fee_amount.

Supplier details (name, TIN, email, address) are never sent in this request; they are always taken from the organisation's own taxpayer profile, set up once during onboarding. Every field in the table above that has an NRS name is sent to NRS in the signing request, so nothing you send here is dropped on the way.

Rules NRS enforces

NRS checks these when the invoice is validated and signed. The API rejects what it can straight away with a 422 and a message naming the field. Everything else comes back from NRS as the invoice's failure_reason.

RuleWhat to do
Due dateNRS will not sign an invoice with no due date. If you do not send due_date, we send NRS the issue_date as the due date. Send your real due date whenever it differs.
Payee, billing contact, delivery addressEach one is optional as a whole. If you send one, it needs a TIN and a full postal address: street_name, city_name, state_code and lga_code. NRS rejects it otherwise.
Document reference formatEvery *_document_reference_irn and billing_reference_irn must be capital letters, numbers and dashes only, with no spaces. REF001-2741A890-20260601 is accepted. IRN REF001-2741A890-20260601 (with the word IRN and a space) and ref001 (lower case) are rejected.
Buyer on B2B and B2G invoicesNeeds a name, a TIN (or an incorporation_number), and street_name, city_name and postal_zone. A B2C invoice may leave the buyer out.
Goods and services linesEvery line needs product_category. A goods line needs an hsn_code in the form 0000.00. A services line needs a 4-digit isic_code and a service_category.
Tax rateLeave tax_rate_percent out and it is looked up from the tax category. If you send it for STANDARD_VAT it must be exactly 7.5.
Phone numbersNRS wants international format (+234...). A number such as 08012345678 is converted for you.

Example Request

{
  "invoice_number": "INV001",
  "invoice_type_code": "381",
  "invoice_kind": "B2B",
  "issue_date": "2026-07-01",
  "issue_time": "14:30:00",
  "due_date": "2026-07-31",
  "tax_point_date": "2026-07-01",
  "document_currency_code": "NGN",
  "tax_currency_code": "NGN",
  "payment_means_code": "30",
  "payment_terms_note": "Net 30",
  "buyer_reference": "PO-2026-014",
  "order_reference": "PO-2026-014",
  "accounting_cost": "500",
  "billing_reference_irn": "REF001-2741A890-20260601",
  "billing_reference_date": "2026-06-01",
  "buyer": {
    "party_name": "Sample Buyer Enterprises Ltd",
    "tin": "98765432-0001",
    "email": "accounts@samplebuyer.example",
    "telephone": "08012345678",
    "street_name": "10 Admiralty Way",
    "city_name": "Lekki",
    "state_code": "NG-LA",
    "lga_code": "NG-LA-EOS",
    "country_code": "NGA",
    "postal_zone": "106104"
  },
  "bill": {
    "party_name": "Sample Buyer Finance Department",
    "tin": "98765432-0001",
    "email": "finance@samplebuyer.example",
    "street_name": "3 Bourdillon Road",
    "city_name": "Ikoyi",
    "state_code": "NG-LA",
    "lga_code": "NG-LA-EOS",
    "postal_zone": "101233"
  },
  "lines": [
    {
      "item_type": "services",
      "item_name": "Invota e-invoicing platform - annual subscription",
      "product_category": "Software Services",
      "isic_code": "6202",
      "service_category": "Computer consultancy and computer facilities management activities",
      "invoiced_quantity": 1,
      "price_unit": "EA",
      "price_amount": 250000,
      "tax_category_id": "STANDARD_VAT"
    }
  ]
}

Response: 201 Created

{
  "id": "uuid",
  "irn": "INV001-2741A890-20260701",
  "invoice_number": "INV001",
  "status": "draft",
  "created_at": "2026-07-01T09:00:00+00:00"
}
GET/invoices/{id}

Returns the full details of a single invoice including all line items and party details.

PATCH/invoices/{id}

Updates a draft invoice. Only allowed when status is draft. Only the account owner (is_owner = true) may edit an invoice. If the invoice number or issue date changes, the IRN is rebuilt and re-validated server-side.

Request Body

Send only the fields you want to change, using the same field names and rules as POST /invoices. To remove the payee, billing contact or delivery address, send null for it (for example "bill": null). A failed invoice that was rejected before NRS accepted it (it has no validated date) can also be edited and submitted again. Any other invoice that has left draft cannot be changed.

DELETE/invoices/{id}

Soft-deletes a draft invoice. Only allowed when status is draft. Only the account owner may delete. Deleted invoices are not permanently removed from the database.

POST/invoices/bulk

Creates up to 100 invoices in a single request, for quick imports. This dashboard bulk route accepts a shorter set of fields than a single invoice does: invoice_number, issue_date, due_date, document_currency_code, payment_means_code, note, buyer (party_name, email, telephone), and for each line item_name, item_type, invoiced_quantity, price_amount, price_unit, tax_category_id, tax_rate_percent, hsn_code, isic_code and discount_rate. For the full set of NRS fields (document references, payee, billing contact, delivery address and so on), create invoices one at a time with POST /invoices, or use the enterprise bulk route POST /b2b/invoices/bulk, which accepts the full body for every invoice.

Request Body

{ "invoices": [ { ... }, { ... } ] }

Response: 207 Multi-Status

Returns a result for each submitted invoice: success or the specific validation error for that item.

04

Invoice Lifecycle

These endpoints move an invoice through the NRS submission pipeline. All background communication with NRS runs as queued jobs. The API returns 202 Accepted immediately and processing continues in the background.

POST/invoices/{id}/submit

Submits a draft invoice to NRS for validation and signing. Dispatches a background job that first validates the invoice against NRS, then signs it and generates the QR code. The invoice status moves from draft through validated to signed if both steps succeed.

If NRS rejects the invoice, its status becomes failed and the reason NRS gave is saved in failure_reason, which you can read with GET /invoices/{id}. A rejection that retrying cannot fix (for example a missing TIN or a badly formed reference) fails straight away. A temporary problem on the NRS side, such as a timeout or a "try again later" reply, is retried automatically up to three times before the invoice is marked failed. Fix the cause, then submit the invoice again.

A B2C invoice is complete once it is signed. It is registered with NRS at that point and is not transmitted to a buyer system.

Response: 202 Accepted

{ "data": { "message": "Invoice submitted. Processing in background." } }
GET/invoice/confirm/{irn}

Polls NRS to confirm the current status of an invoice by its IRN. Use this to check whether a submitted invoice has been signed or to surface any NRS-side issue.

Response: 200 OK

{ "data": { "irn": "...", "status": "signed", "nrs_message": "..." } }
POST/invoices/{id}/transmit

Transmits a signed invoice to the NRS exchange network so the buyer's system can receive it. Only available once the invoice status is signed. Runs as a background job.

Response: 202 Accepted

{ "data": { "message": "Invoice queued for transmission." } }
PATCH/invoices/{id}/payment

Updates the payment status of an invoice. When status is set to PAID or PARTIAL, the system automatically queues a VAT post-payment report to NRS and sends a receipt email to the buyer.

Request Body

FieldTypeRequiredDescription
payment_statusstringRequiredPENDING, PARTIAL, PAID, or REJECTED
partial_amount_paiddecimalRequired when payment_status is PARTIALThe amount received so far. Cleared automatically when the status moves to anything else.
payment_referencestringOptionalYour own reference for the payment, for example a bank transfer reference. Up to 255 characters.

The report to NRS only goes out once the invoice has been signed. If you mark an invoice paid before it is signed, the report is sent automatically as soon as signing completes.

GET/invoices/{id}/pdf

Streams the invoice as a downloadable PDF. Includes the invoice header, supplier and buyer details, line items, tax totals, the IRN, and the QR code image.

Response

Content-Type: application/pdf. File is streamed as a download.

GET/invoices/{id}/qr

Returns a time-limited pre-signed URL to the QR code image in file storage. Only available once the invoice is signed.

Response: 200 OK

{ "data": { "url": "https://storage.invota.ng/qr-codes/org-id/irn.png?token=..." } }
POST/invoices/{id}/email

Queues an email to the buyer containing invoice details and a PDF attachment.

Response: 202 Accepted

{ "data": { "message": "Email queued for delivery." } }
05

Customers

Customers are buyer contacts saved to the organisation's address book. They can be selected when creating an invoice to pre-fill buyer details.

GET/customers

Returns a paginated list of customers for the authenticated organisation.

Query Parameters

ParameterTypeDescription
searchstringFilter by name or email
pageintegerPage number (default: 1)
POST/customers

Creates a new customer record.

Request Body

FieldTypeRequired
namestringRequired
emailstringRequired
tinstringOptional
phonestringOptional
addressstringOptional
GET/customers/{id}

Returns the full details of a single customer.

PATCH/customers/{id}

Updates a customer record. All fields are optional.

DELETE/customers/{id}

Deletes a customer record.

06

Items

Items are saved products or services that can be selected when adding lines to an invoice. Each item stores NRS classification codes so they do not need to be entered each time.

GET/items

Returns a paginated list of items for the authenticated organisation.

Query Parameters

ParameterTypeDescription
searchstringFilter by name or description
typestringFilter by goods or services
pageintegerPage number (default: 1)
POST/items

Creates a new item.

Request Body

FieldTypeRequiredDescription
namestringRequired
descriptionstringOptional
unit_pricedecimalRequired
product_categorystringRequiredgoods or services
hsn_codestringRequired for goodsFormat: 0000.00
isic_codestringRequired for services4-digit ISIC code
tax_category_codestringRequiredNRS tax category
GET/items/{id}

Returns the full details of a single item.

PATCH/items/{id}

Updates an item record.

DELETE/items/{id}

Deletes an item record.

07

Reports

Reporting endpoints provide summary and export data for the authenticated organisation. Requires the auditor or finance role.

GET/reports/summary

Returns aggregate invoice statistics over a given date range.

Query Parameters

ParameterTypeDescription
fromdate (YYYY-MM-DD)Start of date range
todate (YYYY-MM-DD)End of date range

Response: 200 OK

{
  "data": {
    "total_invoices": 127,
    "total_value": 4500000.00,
    "total_vat": 586956.52,
    "by_status": {
      "draft": 5, "signed": 12,
      "acknowledged": 98, "reported": 12, "failed": 0
    }
  }
}
GET/reports/vat

Returns VAT post-payment report records for the authenticated organisation.

Query Parameters

ParameterTypeDescription
fromdateStart of period
todateEnd of period
statusstringpending, submitted, failed
GET/reports/export

Exports a list of invoices as a CSV file for the given date range and optional status filter.

Query Parameters

ParameterTypeDescription
fromdateStart of period
todateEnd of period
statusstringOptional filter by invoice status
formatstringcsv (default)

Response

Content-Type: text/csv. File is streamed as a download.

08

B2B Enterprise API

The B2B API is for enterprise clients that connect their ERP or POS systems directly to INVOTA. It uses API key authentication instead of bearer tokens. Clients access this API only. They do not use the web dashboard.

Authentication

Include both headers on every B2B API request:

X-Api-Key: {your-api-key}
X-Api-Secret: {your-api-secret}

API credentials are provisioned by a Best Teck Nigeria Limited admin. Clients can rotate their own credentials at any time using the credentials endpoint below.

POST/b2b/invoices

Submits a single invoice through the B2B API. It takes exactly the same request body as POST /invoices (Section 3), with the same fields and the same rules, so every NRS field is available to you: document references, payee, billing contact, delivery address, discounts and charges, and the rest. The invoice is saved, then a background job validates and signs it with NRS. The response comes back straight away with the IRN, which you use to poll for progress.

Two differences from the dashboard route: the supplier is always your own business profile, and a B2B or B2G invoice must have a buyer with a TIN (or incorporation_number) and a full postal address. A B2C invoice may leave the buyer out. Invoices created with a sandbox key are marked sandbox and are never sent to NRS; a production key sends them for real.

Response: 202 Accepted

{
  "id": "uuid",
  "irn": "INV001-2741A890-20260701",
  "invoice_number": "INV001",
  "status": "draft",
  "payable_amount": "268750.00",
  "qr_url": null,
  "pdf_url": null
}

qr_url and pdf_url are empty at this point. The QR code is filled in once the invoice is signed; poll GET /b2b/invoices/{irn} to get it.

Errors

StatuscodeMeaning
401MISSING_API_CREDENTIALS / INVALID_API_CREDENTIALSThe X-Api-Key or X-Api-Secret header is missing or wrong
402SUBSCRIPTION_NOT_FOUNDNo billing subscription exists for the organisation
402NO_CREDITS_REMAINING / CREDIT_LIMIT_EXCEEDEDA prepaid account is out of credits, or a pay-as-you-go balance has reached its limit
403KYC_REQUIREDThe business has not finished identity verification
422SUPPLIER_PROFILE_INCOMPLETEYour business profile is missing fields NRS needs. The response lists them in missing_fields.
422TAXPAYER_PROFILE_MISSINGThe organisation has no taxpayer profile yet
422DUPLICATE_IRNAn invoice with the same number and issue date already exists
422(validation)A field is missing or wrong. The body is { message, errors } with one entry per field, for example errors.payee.tin.
429(none)More than 100 requests in a minute
GET/b2b/invoices/{irn}

Returns the current status of an invoice by its IRN. Poll this after submitting to track progress.

Response: 200 OK

{
  "id": "uuid",
  "irn": "INV001-2741A890-20260701",
  "invoice_number": "INV001",
  "invoice_kind": "B2B",
  "status": "signed",
  "payment_status": "PENDING",
  "payable_amount": "268750.00",
  "tax_amount": "18750.00",
  "issue_date": "2026-07-01",
  "due_date": "2026-07-31",
  "validated_at": "2026-07-01T09:00:03+00:00",
  "signed_at": "2026-07-01T09:00:06+00:00",
  "transmitted_at": null,
  "acknowledged_at": null,
  "failure_reason": null,
  "qr_url": "https://storage.invota.ng/qr-codes/...",
  "pdf_url": null
}

status moves through draft, validated, signed, transmitting, transmitted, acknowledged and reported, or ends at failed. When it is failed, failure_reason holds the reason NRS gave, in plain text. Fix the cause and submit a new invoice. qr_url is a temporary link that lasts 30 minutes, so request it again when you need it.

PATCH/b2b/invoices/{irn}/payment

Tells us a payment status from your own system, for example when your ERP or till marks an invoice paid. Once the invoice has been signed, a PAID or PARTIAL update sends the VAT report to NRS and emails the buyer a receipt, the same as marking it paid in the dashboard.

Request Body

FieldTypeRequiredDescription
payment_statusstringRequiredPENDING, PARTIAL, PAID, or REJECTED
partial_amount_paiddecimalRequired when payment_status is PARTIALThe amount received so far
payment_referencestringOptionalYour own payment reference. Up to 255 characters.

Response: 200 OK

{
  "id": "uuid",
  "irn": "INV001-2741A890-20260701",
  "payment_status": "PAID",
  "partial_amount_paid": null,
  "payment_confirmed_at": "2026-07-05T10:12:00+00:00",
  "nrs_vat_report_queued": true
}
POST/b2b/invoices/bulk

Submits up to 50 invoices in a single request. Every invoice takes the full request body, the same as a single POST /b2b/invoices. Each invoice is checked on its own: one with a mistake is reported against its position in your list and the others are still created, and the response is 207 Multi-Status. The account-level errors listed under POST /b2b/invoices (credentials, KYC, billing, supplier profile) reject the whole request, and so does a missing or empty list or more than 50 invoices.

Request Body

{ "invoices": [ { ... }, { ... } ] }

Response: 207 Multi-Status

{
  "created": [
    { "index": 0, "id": "uuid", "irn": "INV001-2741A890-20260701", "status": "draft" }
  ],
  "errors": [
    {
      "index": 1,
      "invoice_number": "INV002",
      "error": "Validation failed.",
      "errors": { "payee.tin": ["The payee.tin field is required when payee is present."] }
    },
    { "index": 2, "irn": "INV003-2741A890-20260701", "error": "Duplicate IRN" }
  ],
  "summary": { "total": 3, "created": 1, "failed": 2 }
}
GET/b2b/credentials

Returns the current active credential for the authenticated B2B client (key ID and creation date). The actual key and secret values are not returned after initial provisioning.

POST/b2b/credentials/rotate

Rotates the B2B API credentials. The old key is deactivated immediately. The new key and secret are returned once only.

Response: 200 OK

{
  "data": {
    "api_key": "new-key-shown-once",
    "api_secret": "new-secret-shown-once"
  }
}
Important: The api_secret is only returned in this response. It is stored encrypted and cannot be retrieved again. Rotate only when you are ready to update your system immediately.

Enterprise Dashboard (Account Management)

Everything above is called by your ERP/POS system using theX-Api-Key / X-Api-Secret pair. The endpoints below are different: they use ordinary Bearer token auth (the same login as the regular dashboard) and power the /dashboard/api developer portal a human enterprise user actually logs into to manage their account: viewing usage, rotating keys, switching billing mode, buying credits, and setting a webhook URL. Your integration code should not call these; they are for the person administering the integration, not the integration itself.

GET/enterprise/summary

Overview stats for the enterprise dashboard home page: submission counts by outcome, the 10 most recent invoices, and the organisation's current billing state.

Response: 200 OK

{
  "stats": {
    "total_submitted": 812, "validated_count": 790,
    "failed_count": 6, "pending_count": 16
  },
  "billing": {
    "mode": "prepaid",
    "outstanding_balance": 0,
    "credit_limit_ngn": 500000,
    "credits_balance": 340,
    "plan_slug": "enterprise",
    "per_invoice_rate_ngn": 50.00,
    "currency": "NGN",
    "available_packs": [
      { "id": "100", "credits": 100, "price_ngn": 5000 },
      { "id": "500", "credits": 500, "price_ngn": 22500 }
    ]
  },
  "recent_invoices": [ { "id": "uuid", "irn": "...", "status": "signed", "..." : "..." } ],
  "webhook_callback_url": "https://erp.example.com/hooks/invota"
}
GET/enterprise/credentials

Returns metadata for the active API credential (environment, created/rotated/revoked timestamps). The key and secret values are never returned here, only at the moment they are created or rotated. Credentials are issued automatically once KYC is approved; if none exist yet, credential is null.

POST/enterprise/credentials/rotate

Rotates the active credential within its current environment. The old key stops working immediately. Same one-time-reveal rule as /b2b/credentials/rotate above.

PATCH/enterprise/credentials/environment

Switches between sandbox and production. The environment is encoded in the key prefix itself, so switching issues a brand new key pair rather than relabeling the old one: every credential in the other environment is revoked at the same time, so nothing from the previous environment keeps working afterwards.

Request Body

FieldTypeRequired
environmentstringRequired
PATCH/enterprise/billing/mode

Switches how invoice submissions are billed: payg (Pay As You Go, a running balance invoiced per submission) or prepaid (a credits balance that decreases per submission, topped up manually). Switching to Prepaid is blocked while an outstanding PAYG balance is unpaid.

Request Body

FieldTypeRequired
modestringRequired
POST/enterprise/billing/pay/initialize

Starts a Paystack payment, either clearing an outstanding PAYG balance (purpose: "balance") or buying a prepaid credit pack (purpose: "credits", with a pack_id from the packs listed in GET /enterprise/summary). Returns a Paystack checkout URL to redirect the user to.

Request Body

FieldTypeRequiredDescription
purposestringRequiredbalance or credits
pack_idstringRequired if purpose is creditsMust match an available pack

Response: 200 OK

{ "authorization_url": "https://checkout.paystack.com/...", "reference": "ent_...", "amount_ngn": 5000 }
POST/enterprise/billing/pay/verify

Verifies a Paystack transaction reference after the user returns from checkout and applies it, clearing the balance or crediting the purchased pack onto credits_balance. Safe to call more than once for the same reference; an already-applied payment is reported back without being applied twice.

Request Body

FieldTypeRequired
referencestringRequired
PATCH/enterprise/webhook-callback-url

Sets the URL that receives NRS transmission events for this organisation's invoices (see Section 9). Must be https:// in production.

POST/enterprise/webhook/verify

Sends a single synthetic test event to the saved webhook URL right away, so the user can confirm it's reachable before relying on it. This is a one-off reachability check, not a real event; it is not retried and has no connection to the fire-and-forget delivery used for actual invoice events.

Response: 200 OK / 422

{ "verified": true, "reason": "ok", "status_code": 200 }
09

Webhooks

INVOTA receives event notifications from NRS and forwards them to your registered webhook callback URL. Set the callback URL on your organisation record.

NRS Event Receiver

POST/webhooks/nrs

This endpoint receives event notifications from NRS. It is registered with NRS as the callback URL for all transmission events. Always returns HTTP 200 to acknowledge receipt. Duplicate events are detected using SHA-256 hash checks and ignored automatically.

Events Forwarded to Your Callback URL

EventDescription
TRANSMITTINGNRS has received the invoice on the exchange network
TRANSMITTEDNRS confirms the invoice reached the buyer's access point
ACKNOWLEDGEDThe buyer's system has acknowledged the invoice
FAILEDNRS has reported a failure for this invoice

Webhook Payload

Each event is sent to your callback URL as a POST with a JSON body:

{
  "event": "TRANSMITTED",
  "irn": "INV001-2741A890-20260701",
  "status": "transmitted",
  "invoice_id": "uuid",
  "timestamp": "2026-07-01T14:30:00+00:00",
  "payload": { "irn": "INV001-2741A890-20260701", "message": "TRANSMITTED" }
}
FieldDescription
eventTRANSMITTING, TRANSMITTED, ACKNOWLEDGED or FAILED
irnThe invoice's IRN
statusThe invoice's status on our side after the event was applied
invoice_idThe invoice's id on our side
timestampWhen we forwarded the event (ISO 8601)
payloadThe original message NRS sent us, passed through unchanged
Delivery: Webhook delivery to your callback URL is best-effort. Each event is tried up to twice, three seconds apart, and each try waits at most 10 seconds for your reply. If both tries fail, the event is not sent again. Your endpoint should answer with any 2xx status. Use GET /b2b/invoices/{irn} as a fallback if your endpoint is temporarily unavailable.
10

Reference Data

The platform caches reference data from NRS and refreshes it daily at 02:00 WAT. These lists power the dropdown menus in the invoice form and validate classification codes at submission time.

GET/reference/{type}

Returns the cached list for the given type.

Available Types

TypeDescription
invoice_typesNRS invoice type codes (e.g. 381 for commercial invoice)
payment_meansPayment method codes
tax_categoriesVAT and tax category codes (S = Standard, Z = Zero, E = Exempt)
currenciesISO 4217 currency codes
quantity_codesUnit of measure codes for invoice lines
hs_codesHarmonised System codes for goods classification
services_codesISIC codes for services classification
countriesCountry codes and names
statesNigerian state codes
lgasNigerian Local Government Areas

Response: 200 OK

{
  "data": [
    { "code": "380", "name": "Credit Note",         "resource_type": "invoice_types" },
    { "code": "381", "name": "Commercial Invoice",  "resource_type": "invoice_types" }
  ]
}
11

IRN Format

The Invoice Reference Number (IRN) is constructed by INVOTA as the System Integrator. NRS does not assign it. The platform builds and validates the IRN before saving each invoice.

Structure

{InvoiceNumber}-{ServiceID}-{YYYYMMDD}
PartRulesExample
InvoiceNumberAlphanumeric characters only. No spaces, dashes, or special characters.INV001
ServiceID8-character alphanumeric NRS-assigned code for the taxpayer. Stored on the organisation record.2741A890
YYYYMMDDInvoice issue date. Must be a valid calendar date.20260701

Full Example

INV001-2741A890-20260701

IRN with Timestamp (used for NRS signing payload only)

The Unix timestamp is appended only in the cryptographic signing payload sent to NRS. It does not form part of the IRN printed on the invoice or stored in the database.

INV001-2741A890-20260701.1751328000

Uniqueness

No two invoices within the same organisation may share the same IRN. The platform checks for duplicates before saving. Once an IRN has been submitted to NRS and signed it cannot be reused or re-signed under a different invoice.

12

Error Codes

All errors follow a consistent structure. The HTTP status code indicates the general error type. The message field gives a human-readable explanation.

HTTP StatusMeaningCommon Cause
401UnauthorizedToken expired or revoked, missing Authorization header, or (B2B) a missing or wrong X-Api-Key / X-Api-Secret
402Payment RequiredEnterprise billing: no subscription, no prepaid credits left, or a pay-as-you-go balance at its limit. The code field says which.
403ForbiddenWrong role, editing an invoice that has left draft, a non-owner trying to edit or delete, or KYC not yet approved (code KYC_REQUIRED)
404Not FoundInvalid invoice ID or IRN, cross-tenant access attempt
422Unprocessable EntityA field is missing or wrong (the errors object names each one), an invalid or duplicate IRN (code DUPLICATE_IRN), or an incomplete business profile (code SUPPLIER_PROFILE_INCOMPLETE)
429Too Many RequestsMore than 100 requests in a minute, on both the dashboard API and the B2B API
500Internal Server ErrorAn unexpected error on our side. Problems reported by NRS do not appear here: they are saved on the invoice as failure_reason.
503Service UnavailableThe platform is in maintenance mode

Validation Error Response

{
  "message": "The given data was invalid.",
  "errors": {
    "invoice_number": ["The invoice number may only contain letters and numbers."],
    "lines.0.hsn_code": ["The HSN code must be in the format 0000.00."]
  }
}

When NRS Rejects an Invoice

NRS checks an invoice in the background, after the API has already answered 202. So a rejection does not come back as an HTTP error. The invoice's status becomes failed and the reason NRS gave is saved as failure_reason. Read it from GET /invoices/{id} or GET /b2b/invoices/{irn}. The text is NRS's own message, passed through as it was sent.

{
  "irn": "INV001-2741A890-20260701",
  "status": "failed",
  "failure_reason": "invoicerequest.invoice.billparty.tin is required"
}
13

KYC & Verification

Business identity verification. NRS invoice submission can be blocked for an organisation until KYC is approved, depending on the platform's current enforcement settings.

GET/kyc

Returns the authenticated organisation's overall KYC status plus the state of each individual required document.

Response: 200 OK

{
  "kyc_status": "pending",
  "documents": [
    {
      "type": "cac_certificate",
      "label": "CAC Certificate of Incorporation",
      "required": true,
      "status": "approved",
      "rejection_reason": null,
      "submitted_at": "2026-07-01T09:00:00Z",
      "reviewed_at": "2026-07-02T11:00:00Z"
    },
    {
      "type": "directors_id",
      "label": "Director's Government ID",
      "required": true,
      "status": "pending",
      "rejection_reason": null,
      "submitted_at": "2026-07-01T09:05:00Z",
      "reviewed_at": null
    }
  ]
}

Overall Status Values

StatusMeaning
not_submittedNo required document has been uploaded yet
pendingAt least one required document is awaiting review
approvedEvery required document has been approved
rejectedAt least one required document was rejected, re-upload it

Document Types

TypeLabelRequired
cac_certificateCAC Certificate of IncorporationRequired
directors_idDirector's Government IDRequired
proof_of_addressProof of Business AddressRequired
tin_certificateFIRS TIN CertificateOptional
POST/kyc/submit

Uploads one document for one type. Re-submitting a type that is still pending or was rejected replaces it; an already approved document cannot be overwritten by re-uploading. Send as multipart/form-data, not JSON.

Request Body (multipart/form-data)

FieldTypeRequiredDescription
document_typestringRequiredOne of the document types above
documentfileRequiredPDF, JPG or PNG, max 10 MB

Response: 201 Created

{
  "message": "Document submitted for review.",
  "document_type": "cac_certificate",
  "status": "pending",
  "submitted_at": "2026-07-01T09:00:00Z"
}
14

Billing & Subscriptions

Subscription plan billing for ordinary business and practice accounts via Paystack. Not to be confused with Section 8's Enterprise Dashboard billing, which uses a separate Pay As You Go / Prepaid Credits model for API integrators.

GET/billing

Returns the authenticated organisation's current subscription and payment method state.

Response: 200 OK

{
  "subscription": {
    "plan_slug": "growth",
    "plan_name": "Growth",
    "status": "active",
    "billing_type": "monthly",
    "monthly_fee_ngn": 15000,
    "invoice_limit": 200,
    "invoices_used": 47,
    "cycle_start_at": "2026-07-01T00:00:00Z",
    "cycle_end_at": "2026-08-01T00:00:00Z",
    "next_renewal_at": "2026-08-01T00:00:00Z",
    "outstanding_balance": 0,
    "gateway": "paystack",
    "has_payment_method": true,
    "card_last4": "4081",
    "card_type": "visa",
    "card_expiry": "09/28",
    "features": ["Up to 200 invoices/month", "Email support"],
    "trial_days": 0,
    "is_popular": true
  }
}
GET/billing/history

Returns a paginated list of payment events (charges, renewals, failures) for the authenticated organisation, newest first.

Query Parameters

ParameterTypeDescription
per_pageinteger5–50, default 10
GET/billing/history/export

Downloads the full payment history as a CSV file.

Response

Content-Type: text/csv. File is streamed as a download.

POST/billing/initialize

Starts a Paystack payment to activate or renew a subscription plan. Returns a checkout URL to redirect the user to. Consent note: activating a plan this way saves the card used and authorises automatic renewal charges each billing cycle until the plan is changed or cancelled; this is disclosed to the user in the activation dialog before they are redirected.

Request Body

FieldTypeRequiredDescription
plan_slugstringRequired
billing_typestringRequiredmonthly or yearly

Response: 200 OK

{
  "authorization_url": "https://checkout.paystack.com/...",
  "access_code": "...",
  "reference": "inv_...",
  "amount_ngn": 15000,
  "plan_name": "Growth",
  "billing_type": "monthly"
}
POST/billing/verify

Verifies a Paystack reference after the user returns from checkout and activates the subscription. Safe to call more than once for the same reference.

Request Body

FieldTypeRequired
referencestringRequired
15

Compliance & Audit Trail

Aggregate compliance reporting and the organisation's own activity trail. Distinct from Section 4's per-invoice VAT post-payment reporting: this is summary and historical view, not a write operation.

GET/compliance/summary

Returns a compliance score plus NRS submission and VAT reporting statistics for the authenticated organisation. The score is a weighted average: NRS acceptance rate × 0.6 + VAT submission rate × 0.4.

Response: 200 OK

{
  "compliance_score": 87,
  "nrs_acceptance_rate": 92.5,
  "vat_submission_rate": 75.0,
  "invoice_stats": { "total": 120, "transmitted": 111, "failed": 2, "pending": 7 },
  "vat_stats": { "total": 8, "submitted": 6, "failed": 0, "pending": 2 },
  "vat_trend": [
    { "month": "Apr", "submitted": 1, "pending": 0 },
    { "month": "May", "submitted": 2, "pending": 1 }
  ],
  "nrs_submissions": [
    { "irn": "INV045-2741A890-20260701", "status": "acknowledged", "date": "2026-07-01", "amount": 85000.00 }
  ],
  "breakdown": [
    { "label": "Invoices transmitted", "value": 111, "total": 120 },
    { "label": "VAT reports submitted", "value": 6, "total": 8 }
  ]
}
GET/compliance/export

Downloads the full NRS submission history (IRN, status, taxable/VAT/total amounts) as a CSV file.

GET/audit-trail

Returns the authenticated organisation's own activity trail: every tracked create/update/delete action taken within it, sourced from the platform's immutable audit log. Scoped strictly to the caller's own organisation; there is no way to see another organisation's activity through this endpoint.

Query Parameters

ParameterTypeDescription
per_pageinteger5–50, default 10
searchstringFilter by actor name, action, or IP address
sortstringrecency (default), last_6_months, or alphabetical

Response: 200 OK

{
  "entries": [
    {
      "id": "225",
      "timestamp": "2026-09-09T13:09:31Z",
      "actor_name": "Adaeze Okonkwo",
      "action": "invoice.updated",
      "action_label": "Invoice Updated",
      "action_tone": "neutral",
      "target": "Invoice",
      "ip_address": "102.89.23.4",
      "status": "successful"
    }
  ],
  "meta": { "current_page": 1, "last_page": 6, "per_page": 10, "total": 59 }
}
16

Service Level Agreement

This Service Level Agreement (SLA) outlines the terms of service between Best Teck Nigeria Limited (the Provider), operating the INVOTA e-invoicing platform, and registered taxpayers using the platform (the Client).

1. Service Description

The Provider delivers electronic invoice creation, digital signing, QR code generation, and transmission services through the NRS exchange network, accessible via the INVOTA web dashboard or the INVOTA B2B API for enterprise integrations.

2. Availability

  • Target platform uptime: 99.5% per calendar month, excluding scheduled maintenance windows and NRS-side outages outside the Provider's control.
  • Scheduled maintenance is communicated to affected clients in advance where practical.

3. Support and Response Times

SeverityAcknowledged WithinTarget Resolution
Critical (platform unavailable, transmission fully blocked)4 business hours1 business day
High (a specific feature broken, no safe workaround)1 business day3 business days
General enquiries and minor issues2 business days-

Support contact: hello@invota.ng

4. Data Security Commitments

  • Sensitive data (taxpayer TIN, email, API credentials, NRS signing keys) is encrypted at rest using AES-256-CBC with a random initialisation vector per write.
  • All traffic to and from the platform is encrypted in transit using TLS.
  • Every create, update, and delete action is recorded in an immutable, database-enforced audit log.
  • Access to client data is restricted by role-based permissions and scoped strictly to each organisation's own records.

5. Issue Resolution and Escalation

Reported issues are triaged by severity as defined in Section 3. Clients are kept informed of progress on any issue rated High priority or above until resolved.

6. Term and Review

This SLA applies for the duration of the Client's active subscription or API integration with the Provider and is reviewed periodically to reflect the platform's current capabilities.

INVOTA API Documentation, Version 1.0, Updated September 2026Best Teck Nigeria Limited, Confidential