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 an invoice. Defaults to a draft (no GL); pass status: unsent to post it to the ledger at creation. The API never sends email.

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.
statusstringnodraft (default), unsent, or sent. A draft does not post to the ledger. unsent/sent post the invoice immediately (DR Trade Debtors / CR Revenue + VAT). The API never sends email, so unsent and sent both mean "posted, not emailed".

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_..."
  }
}

Post an invoice

POST/v1/invoices/{id}/post

Transitions a draft invoice to 'unsent' and posts it to the ledger (DR Trade Debtors / CR Revenue + VAT). No email. Mirrors credit-notes/debit-notes/journal-entries {id}/post.

Use this when you created the invoice as a draft and want to post the receivable later (e.g. at order confirmation). Only draft invoices can be posted; the call is idempotent — posting an already-posted invoice returns its current state with alreadyPosted: true. Payment is recorded separately by allocating a receipt against the invoice.

Example

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

Response — 200

{
  "success": true,
  "data": {
    "id": "abc123",
    "status": "unsent",
    "isPostedToGL": true,
    "glEntryId": "je_..."
  }
}

Errors

CodeWhen
NOT_FOUNDInvoice doesn't exist or belongs to another business.
INVALID_STATEInvoice is not a draft (already posted/sent), or is voided/cancelled.

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