Skip to main content

Reference

Invoices

Create, list, fetch, and update sales invoices with automatic GL posting.

Invoices are the main retail-sales record in Finora Business. Creating one through the API posts a draft invoice, and when the invoice is sent, accepted, or marked paid, journal entries post automatically — you don't call a separate GL endpoint.

All monetary amounts are dual-written: amountKobo / totalKobo (integer, source of truth) and amount / total (naira, convenience). Always compute on kobo.

List invoices

GET/v1/invoices

Returns the most recent invoices for a business, newest first.

Query parameters

FieldTypeRequiredNotes
businessIdstringyesBusiness whose invoices you want.
limitintegernoDefault 50, max 100.
statusstringnoFilter by draft, unsent, sent, partially_paid, paid, overdue, or cancelled.

Example

curl "https://api.finorabusiness.com/v1/invoices?businessId=$BID&limit=10&status=sent" \
  -H "Authorization: Bearer $FINORA_API_KEY"

Response — 200

{
  "success": true,
  "data": {
    "invoices": [
      {
        "id": "abc123",
        "businessId": "biz_001",
        "invoiceNumber": "INV-2026-001",
        "customerId": "cust_xyz",
        "invoiceDate": "2026-04-10T00:00:00.000Z",
        "dueDate": "2026-04-24T00:00:00.000Z",
        "lineItems": [
          {
            "description": "Website development",
            "quantity": 1,
            "unitPrice": 500000,
            "amountKobo": 50000000,
            "amount": 500000
          }
        ],
        "subtotalKobo": 50000000,
        "vatRate": 7.5,
        "vatAmountKobo": 3750000,
        "totalKobo": 53750000,
        "total": 537500,
        "amountDueKobo": 53750000,
        "status": "sent",
        "createdAt": "2026-04-10T09:15:00.000Z"
      }
    ],
    "count": 1
  },
  "meta": {
    "requestId": "req_a1b2c3d4e5f6g7h8",
    "rateLimit": { "limit": 60, "remaining": 58 }
  }
}

Get a single invoice

GET/v1/invoices/{id}

Fetches one invoice by ID.

Example

curl "https://api.finorabusiness.com/v1/invoices/abc123?businessId=$BID" \
  -H "Authorization: Bearer $FINORA_API_KEY"

Response — 200

Same invoice shape as list items, wrapped in data.

Errors

CodeWhen
NOT_FOUNDInvoice doesn't exist or belongs to another business.
FORBIDDENThe key's user isn't the business owner.

Create an invoice

POST/v1/invoices

Creates a draft invoice. GL posting happens automatically when you mark it sent.

Query parameters

FieldTypeRequiredNotes
businessIdstringyesBusiness to create under. May also be passed in body.

Body

FieldTypeRequiredNotes
customerIdstringyesMust be an existing customer in this business.
lineItemsarrayyes1–100 items. Each has description, quantity, unitPrice.
lineItems[].descriptionstringyes
lineItems[].quantitynumberyes0 < quantity <= 1,000,000.
lineItems[].unitPricenumberyesIn naira. 0 <= unitPrice <= 100,000,000.
invoiceDatestring (ISO 8601)noDefaults to today.
dueDatestring (ISO 8601)noDefaults to today + 14 days.
vatRatenumbernoDefaults to 7.5. Nigerian standard VAT. Pass 0 for zero-rated items.
notesstringnoFree-form note shown on the invoice PDF.

Example

curl -X POST "https://api.finorabusiness.com/v1/invoices?businessId=$BID" \
  -H "Authorization: Bearer $FINORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "cust_xyz",
    "dueDate": "2026-05-01",
    "lineItems": [
      {
        "description": "Monthly retainer — April",
        "quantity": 1,
        "unitPrice": 500000
      }
    ]
  }'

Response — 201

{
  "success": true,
  "data": {
    "id": "abc123",
    "invoiceNumber": "INV-2026-001",
    "total": 537500,
    "amountDue": 537500,
    "status": "draft",
    "createdAt": "2026-04-17T09:15:00.000Z"
  }
}

Validation errors

Invalid bodies come back as 400 VALIDATION_ERROR with a field hint:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "unitPrice must be between 0 and 100,000,000",
    "field": "lineItems[0].unitPrice",
    "requestId": "req_..."
  }
}

Update an invoice

PUT/v1/invoices/{id}

Edits a draft invoice. Finalised invoices are immutable — void and recreate instead.

Only four fields can be updated: customerId, dueDate, notes, lineItems. Updating line items recalculates totals from scratch.

Example

curl -X PUT "https://api.finorabusiness.com/v1/invoices/abc123?businessId=$BID" \
  -H "Authorization: Bearer $FINORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dueDate": "2026-05-15",
    "notes": "Net 15 from approval date"
  }'

Response — 200

{
  "success": true,
  "data": {
    "id": "abc123",
    "dueDate": "2026-05-15T00:00:00.000Z",
    "notes": "Net 15 from approval date",
    "updatedAt": "2026-04-17T10:30:00.000Z"
  }
}

Life cycle

draft  ─► (sent via email)  ─►  unsent / sent

                                    ├─► partially_paid ─► paid
                                    ├─► overdue
                                    └─► cancelled   (via void)
  • Draft invoices have no GL impact and can be edited or deleted freely.
  • Sent invoices post a journal entry: DR Trade Debtors, CR Revenue, CR VAT Provision. They can be voided but not edited — voiding reverses the JE.
  • Paid invoices (fully or partially) trigger receipt creation; the VAT provision transfers to VAT payable at that point.

Common flows

Record a sale in Finora Business from a WooCommerce order

  1. POST /v1/customers/find-or-create — resolve the shopper to a Finora Business customer.
  2. POST /v1/invoices — create the invoice with line items matching the order.
  3. When the Paystack payment confirms, hit POST /v1/receipts to record the payment.

Three API calls per order. After your trial, that's ₦300 per order. See billing for credit management.

Read reports based on API data

Once invoices exist, reports are a read away:

  • GET /v1/reports/profit-loss?businessId=X&startDate=...&endDate=...
  • GET /v1/reports/ar-aging?businessId=X

See the reports module for all options.

Related endpoints

Reference index

Back to all modules