API Documentation

Flowpoint is a NRS-certified System Integrator for e-invoicing in Nigeria.

Overview

This reference covers the tenant integration API for ERP and server-to-server connections. Register an API key in the portal (Settings → API Access), then create and submit invoices, poll status, and receive webhook events on invoice lifecycle changes.

If you are having trouble with the API or need help getting started, contact support.

  1. Create an API key in Settings → API Access (portal admin required once).
  2. POST /api/v1/invoices/create — create and queue a single invoice for NRS submission (HTTP 202).
  3. Poll GET /api/v1/invoices/{irn}/status or listen for invoice.certified / invoice.failed webhook events.
  4. Use POST /api/v1/invoices/{id}/retry to recover from failed NRS submissions.

API version 1.0.0

Base URL

https://api.flowpoint.ng

Common headers

  • X-Flowpoint-Key: <api_key> — required on every authenticated request
  • Content-Type: application/json — required on POST/PATCH JSON bodies

Authentication

All documented tenant integration endpoints require an API key. Send your key in the X-Flowpoint-Key header on every request.

API keys

  • Create keys in the portal under Settings → API Access (portal administrator required).
  • Send the plaintext key in the X-Flowpoint-Key header.
  • Revoked or expired keys return 401. Keys cannot access admin-only settings routes.
  • API keys are not restricted by portal role permissions.

Admin-only (not API key)

Tenant profile, crypto keys, locations, integration connectors, and webhook subscription management require a portal admin session. API keys receive 403 on those routes.

Rate Limits

Limits use a 1-minute window and expose RateLimit-* draft-7 headers. Exceeding limits returns HTTP 429 — implement exponential backoff.

Global limits (per IP)

ScopeDefault limit
Authentication (/api/v1/auth/*)10 requests/minute
All other /api/v1/*120 requests/minute

Invoice write limits (per tenant)

Keyed on tenant ID (not IP). Protects against a single compromised key flooding writes.

RouteLimit
POST /api/v1/invoices/create60 requests/minute
POST /api/v1/invoices/upload/csv/submit5 requests/minute

List pagination

Collection GETs return { "items": [], "total": 142, "limit": 50, "offset": 0 }. Query params: limit (default 50), offset (default 0), plus resource filters documented on each list endpoint.

EndpointMax limit
GET /api/v1/invoices500
GET /api/v1/customers200
GET /api/v1/items200

GET /api/v1/invoices returns certified invoices only.

Create invoice fields

POST /api/v1/invoices/create accepts a Flowpoint invoice request shape — not raw NRS JSON. The customer object in the request is the buyer. Your supplier (seller) is always your organisation as the Flowpoint tenant — derived from your business profile and primary location, not from the JSON body. B2B (Business-to-Business) and B2G (Business-to-Government) share the same buyer requirements; B2C (Business-to-Consumer) is lighter.

Common fields (all invoice kinds)

FieldB2B / B2GB2CNotes
invoice_kindRequiredRequiredB2B, B2C, or B2G
invoice_typeRequiredRequiredinvoice, credit_note, or debit_note
document_numberRequiredRequiredHuman-readable invoice number (not a UUID). Used to generate the IRN.
issue_date, due_dateRequiredRequiredISO dates YYYY-MM-DD
currencyRequiredRequiredDefault NGN
customer.nameRequiredRequiredBuyer display name
lines[]Required (≥1)Required (≥1)description, quantity, unit_price, hsn_code, tax_scheme, tax_rate

B2B (Business-to-Business) / B2G (Business-to-Government) — extra customer fields

Enforced on POST /create when invoice_kind is B2B or B2G (or inferred as B2G from the buyer TIN). Missing fields return HTTP 400 with CUSTOMER_VALIDATION_FAILED.

FieldRequiredNotes
customer.tinYesStandard TIN or RC-XXXXXXX (≥5 characters)
customer.emailYesValid email on the buyer party
customer.addressYesStreet address
customer.cityYesCity
customer.postal_codeYesPostal / ZIP code
customer.phoneNoOptional — included on NRS buyer party when set
customer.countryNoDefault NG

Tenant-derived NRS fields (autofilled)

The supplier (seller) on every invoice is your organisation — the Flowpoint tenant behind the API key. You never send business_id or accounting_supplier_party in the create JSON; Flowpoint derives them from your tenant profile. If something is not yet on your profile, Flowpoint uses what is already on file from signup or onboarding (for example, the portal admin email when no business email is set). NRS submission still requires a complete Business ID, supplier TIN, email, street, city, and postal code before a request will succeed.

Validated on POST /create. Incomplete data returns HTTP 400 with SUPPLIER_VALIDATION_FAILED.

NRS fieldWhere to setNotes
business_idSettings → Business profile (NRS business ID from onboarding)-
accounting_supplier_party.party_nameSettings → Business profileSeller party name on NRS
accounting_supplier_party.tinSettings → Business profileChecked on POST /create and at submit time
accounting_supplier_party.emailBusiness profileIf not set, Flowpoint uses the portal admin account email
accounting_supplier_party.telephoneBusiness profileOptional on NRS — must start with + when set
accounting_supplier_party.postal_addressSettings → Locations (primary location)Street name, city name, postal zone, state, lga, country — required for /sign
accounting_supplier_party.postal_address.city_nameSettings → Locations (primary location)City text
accounting_supplier_party.postal_address.stateSettings → Locations (primary location)NRS state code (e.g. NG-LA)
accounting_supplier_party.postal_address.lgaSettings → Locations (primary location)NRS LGA code (e.g. NG-LA-IKO); must match state

business_id and accounting_supplier_party appear in examples only to show what Flowpoint sends to NRS. Do not include them in your POST body — they are autofilled from your Flowpoint account (Your business profile / primary location).

Full request — B2B (Business-to-Business)

Use on POST /api/v1/invoices/create. Supplier profile must be complete before the request succeeds.

{
  "business_id": "autofilled from your account",
  "accounting_supplier_party": {
    "party_name": "autofilled from your account",
    "tin": "autofilled from your account",
    "email": "autofilled from your account",
    "telephone": "autofilled from your account",
    "business_description": "autofilled from your account",
    "postal_address": {
      "street_name": "autofilled from your account",
      "city_name": "autofilled from your account",
      "postal_zone": "autofilled from your account",
      "lga": "autofilled from your account",
      "state": "autofilled from your account",
      "country": "autofilled from your account"
    }
  },
  "invoice_kind": "B2B",
  "invoice_type": "invoice",
  "document_number": "INV-2026-001",
  "issue_date": "2026-04-25",
  "due_date": "2026-05-25",
  "currency": "NGN",
  "customer": {
    "name": "Acme Ltd",
    "tin": "12345678-0001",
    "email": "[email protected]",
    "phone": "+2348012345678",
    "address": "42 Marina Road",
    "city": "Lagos",
    "country": "NG",
    "postal_code": "101001"
  },
  "lines": [
    {
      "description": "Consulting services",
      "quantity": 1,
      "unit_price": 50000,
      "hsn_code": "8517.11",
      "tax_scheme": "STANDARD_VAT",
      "tax_rate": 7.5,
      "unit_of_measure": "Each"
    }
  ],
  "note": "Thank you for your business",
  "po_reference": "PO-7781"
}

B2G (Business-to-Government): use the same buyer fields and autofilled business_id / accounting_supplier_party behaviour as B2B — set "invoice_kind": "B2G".

Full request — B2C (Business-to-Consumer)

Only customer.name is required. Buyer TIN, email, and address are not required for B2C retail invoices. Same body on POST /api/v1/invoices/create. B2C invoices typically reach reported when certified;

{
  "business_id": "autofilled from your account",
  "accounting_supplier_party": {
    "party_name": "autofilled from your account",
    "tin": "autofilled from your account",
    "email": "autofilled from your account",
    "telephone": "autofilled from your account",
    "business_description": "autofilled from your account",
    "postal_address": {
      "street_name": "autofilled from your account",
      "city_name": "autofilled from your account",
      "postal_zone": "autofilled from your account",
      "lga": "autofilled from your account",
      "state": "autofilled from your account",
      "country": "autofilled from your account"
    }
  },
  "invoice_kind": "B2C",
  "invoice_type": "invoice",
  "document_number": "INV-2026-042",
  "issue_date": "2026-04-25",
  "due_date": "2026-04-25",
  "currency": "NGN",
  "customer": {
    "name": "Walk-in Customer"
  },
  "lines": [
    {
      "description": "Consulting services",
      "quantity": 1,
      "unit_price": 50000,
      "hsn_code": "8517.11",
      "tax_scheme": "STANDARD_VAT",
      "tax_rate": 7.5,
      "unit_of_measure": "Each"
    }
  ]
}

Invoices

Create, submit, and track invoices for NRS certification. Use POST /create to submit a single invoice for NRS. Returns PENDING with invoiceId — poll GET /invoices/{id} or subscribe to webhook events. Certified invoices appear on GET /invoices.

Customers & Items

Master data for repeat customers and catalogue items. Optional — you can inline customer and line data on invoice create. Use GET /customers/search and GET /items/search for typeahead (q required, 2–100 characters). List endpoints support optional broader search filters.

Invoice Webhook Events

Flowpoint POSTs JSON to HTTPS URLs you register in Settings → Webhooks (portal admin). These are outbound events — not API endpoints you call. Register one callback URL per tenant; it can subscribe to multiple invoice lifecycle events. Use PATCH to update events on an existing URL.

  • Delivery method: HTTP POST with Content-Type application/json
  • Required shared secret at registration; every delivery includes an HMAC signature in the x-flowpoint-signature header
  • Root status is pending, certified, or failed — matching the event lifecycle
  • Signed body includes timestamp (Unix ms) and emittedAt (ISO 8601); reject deliveries older than 5 minutes for replay protection
  • The source field identifies which step in the invoice lifecycle emitted the event

Webhook delivery

Flowpoint POSTs each event to your callback URL. Your endpoint must accept the JSON body and return any HTTP 2xx status within 10 seconds. An empty 204 No Content response is fine. Flowpoint does not read or validate your response body on success.

Delivery is at-least-once — the same event may arrive more than once. Use the envelope id field to dedupe on your side.

AttemptWhenOn failure
1ImmediateRetry after ~5s
2After ~5s backoffRetry after ~10s
3After ~10s backoffRetry after ~20s
4After ~20s backoffRetry after ~40s
5After ~40s backoffEndpoint auto-paused

A failed attempt is any non-2xx HTTP status, a request timeout (>10s), or a network error. After 5 failed attempts for one delivery, Flowpoint sets the webhook endpoint to inactive and stops sending new events to that URL. Fix your endpoint, then re-enable with PATCH /api/v1/integrations/webhooks/:id and { "active": true } (portal Settings → Webhooks → Edit).

Signature verification

A signing secret is required when you register the webhook. Flowpoint sends x-flowpoint-signature — an HMAC-SHA256 hex digest of the raw request body. Verify the signature with a timing-safe equality check, then reject any delivery where Date.now() - timestamp > 300000 (5 minutes).

const crypto = require('crypto');

const MAX_AGE_MS = 300000; // 5 minutes

function verifyFlowpointWebhook(rawBody, signatureHeader, secret) {
  let event;
  try {
    event = JSON.parse(rawBody);
  } catch {
    return false;
  }

  if (typeof event.timestamp !== 'number') return false;
  if (Date.now() - event.timestamp > MAX_AGE_MS) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader, 'utf8'),
    Buffer.from(expected, 'utf8'),
  );
}

Errors and status meanings

Failed requests return JSON with at least an error string. Many invoice routes also include reason (machine code), detail, and fields. Master-data POST routes use code instead of reason.

If you are having trouble integrating or interpreting API responses, contact support.

{
  "error": "human-readable message",
  "reason": "MACHINE_CODE",
  "code": "machine_code",
  "detail": "optional extra context",
  "fields": { "customer.tin": "required" }
}

Not every field appears on every error. Master-data routes use code instead of reason. Search endpoints return plain error when q is missing or out of range (2–100 characters).

HTTP status codes

HTTPWhenMeaningNext step
401Missing or invalid X-Flowpoint-Key headerRequest is not authenticatedSend a valid API key in the X-Flowpoint-Key header
403Valid API key used on a route that does not accept API keys (e.g. tenant settings)Route not available to API keysUse a portal admin session for admin routes, or confirm the endpoint supports API key access
400Validation failed, submission blocked, or bad query (e.g. search q too short)Request cannot be processed — see error and reason/codeFix the body or query and retry
404Invoice, draft, batch, or IRN not found for this tenantResource does not exist or is not visibleCheck the UUID or IRN
409Conflict — duplicate document number, draft not editable, customer/item existsState conflict — see reason or codeUse a different number or resolve the conflict
429Global or per-tenant rate limit exceededToo many requests in the windowBackoff and retry; check RateLimit-* headers
503GET /health when the API is unavailableAPI health degraded — response body { "status": "degraded" }Retry GET /health; contact support if persistent
500Unexpected server errorInternal failureRetry; contact support if persistent
502NRS payment confirm/update failed (PATCH /invoices/{id}/payment)Upstream NRS call failed — paymentReported stays falseRead nrsError in the body; fix and retry

Invoice machine codes (reason)

reasonHTTPMeaningNext step
CUSTOMER_VALIDATION_FAILED400B2B/B2G buyer fields missing or invalid on create or PATCH draftSend full customer TIN, email, address, city, postal code
SUPPLIER_VALIDATION_FAILED400Tenant supplier profile incomplete on POST /create or submitComplete NRS supplier fields in the portal
DOCUMENT_NUMBER_CONFLICT_INVOICE409A certified invoice already uses this document numberPick a different invoice number
invalid_document_number400Document number failed format rules (e.g. UUID-shaped)Use a human-readable invoice number
unresolved_b2b_missing_tin400B2B invoice could not resolve buyer TIN when savingSend customer.tin on create
unresolved_b2b_customer_address400B2B buyer address fields could not be resolvedSend full customer address fields
unresolvable_hsn400Line HSN could not be resolved when saving the invoiceSend valid HSN code on each line
unresolvable_unit_price400Line unit price could not be resolvedSend positive Unit price on each line
incomplete_unpatchable400Invoice validation failed and required fixes could not be applied automaticallyFix the payload per error detail
ZERO_VALUE400Invoice total payable is zeroEnsure lines sum to a positive payable amount
NO_SERVICE_ID400Tenant NRS Service ID not configuredComplete NRS Setup in the portal
NO_BUSINESS_ID400Tenant NRS Business ID not configuredComplete NRS Setup in the portal
NO_KEYS400No active cryptographic keys on the tenantUpload keys in Settings → Crypto Keys
BLOCKED_PENDING_REVIEW400Invoice held for review before NRS submissionComplete review in the portal, then retry
BLOCKED_PENDING_APPROVAL400Invoice awaiting approval before NRS submissionComplete approval in the portal, then retry
BLOCKED_CHECKLIST_FAILED400Invoice did not pass validation before NRS submissionFix validation errors in the response detail
SUBMISSION_ERROR500/502NRS submission failed unexpectedlyPOST /invoices/{id}/retry
NOT_SIGNED400Reconcile called on an invoice not yet certifiedSubmit to NRS first
NOT_ELIGIBLE400Payment report not allowed for this invoiceInvoice must be certified B2C in reported status with an IRN

Master data codes (code)

Returned by POST /api/v1/customers and POST /api/v1/items.

codeHTTPMeaningNext step
missing_field400Required field empty on POST /customers or POST /itemsSend all required fields in the body
invalid_tin400Customer TIN too short or invalid formatSend a valid TIN (≥5 characters)
invalid_email400Email missing or invalid formatSend a valid email address
invalid_phone400Phone missing or does not start with + country codeSend phone as +2348012345678
invalid_state400state is not a known NRS codeOmit state or use GET /helpers/states
invalid_lga400lga invalid, or state missing when lga is sentOmit lga or pair with a valid state from GET /helpers/lgas
invalid_kind400customerKind is not B2B or B2GSet customerKind to B2B or B2G
tin_exists409Customer with this TIN already existsUse GET /customers/search or list
invalid_price400unitPriceKobo missing, zero, or negativeSend an integer kobo amount greater than zero
invalid_cost400costKobo negative when providedOmit cost or send a non-negative amount
invalid_tax_scheme400Unrecognised taxScheme valueUse STANDARD_VAT or another supported scheme
item_exists409Item with this name already existsUse GET /items/search or list
create_failed500Record insert succeeded but could not be read backRetry; contact support if persistent

Create response status

The status field on POST /invoices/create.

statusHTTPMeaningNext step
PENDING202Invoice accepted for NRS processingPoll GET /invoices/{invoiceId} until certified or failed
BLOCKED:…201Invoice saved but not submitted to NRS (review, approval, or validation)Resolve the block in the portal, then retry

Payment status fields

From PATCH /invoices/{id}/payment.

FieldValuesMeaning
paymentStatus (Flowpoint)PAID · PARTIAL · UNPAIDLocal payment state after successful NRS update
nrsPaymentStatus (NRS)PENDING · PARTIAL · PAID · REJECTEDStatus sent to NRS on PATCH /payment
nrsErrorstringPresent on HTTP 502 when NRS confirm or update fails

Public IRN verify

GET /api/v1/invoices/verify/{irn} — no auth.

OutcomeHTTPMeaningNext step
valid: true200IRN exists and is certified for a tenantUse returned invoice summary
valid: false, reason: malformed IRN400IRN string failed format checkCorrect the IRN
valid: false, reason: IRN not found404No matching certified invoiceVerify IRN with issuer

Helpers

Reference lookups for standard codes and labels used on invoices — invoice types, tax categories, payment means, quantity units, product and service codes, and Nigeria states, LGAs, and countries. Authenticate with your API key. Responses may be cached for a short period; stay within rate limits.

Public Endpoints

No authentication required. API health check and public IRN verification.

API health (GET /health)

GET /health is an unauthenticated uptime check. It is not rate-limited. Returns { "status": "ok" } (HTTP 200) when operational, or { "status": "degraded" } (HTTP 503) when unavailable. The status field here means API health — not invoice or batch status.

body.statusHTTPMeaningNext step
ok200API is operationalNone
degraded503API health check failedRetry GET /health; contact support if persistent

The portal app exposes a public status page at /status for end users. It is a frontend page (not GET /status on the API). It polls GET /health and shows plain uptime only — never dependency detail. Open status page.

If you are having trouble integrating or interpreting API responses, contact support.