API reference

Invoices

Raise invoices into the organization's approval workflow, read them, create links for your customers, and start online payments.

EndpointWhat it does
POST /api/v1/invoicesRaise an invoice
GET /api/v1/invoicesList invoices
GET /api/v1/invoices/{id}Get an invoice
POST /api/v1/invoices/{id}/linksCreate invoice links
GET /api/v1/invoices/{id}/paymentsList payment attempts
POST /api/v1/invoices/{id}/checkoutStart a checkout

The API raises and reads invoices. It can't update, approve, return or void them: people do that in the Flux app, and you hear about it by webhook. See Invoice lifecycle.

The invoice object#

An invoice raised into the organization's approval workflow.

FieldDescription
idintegerFlux's id for the invoice.
numberstringThe invoice number. Its format depends on the organization's settings: treat it as an opaque string.
externalRefstring · nullableYour reference, if you sent one.
customerIdintegerThe customer billed.
currencystringISO currency code. One of NGN, USD, CAD, EUR, GBP.
statusstringWhere the invoice is in its lifecycle. One of awaiting_approval, awaiting_final_approval, returned, approved, paid, voided.
overduebooleanDerived: approved, unpaid, not voided and past dueDate.
paymentStatusstring · nullableA note set by payment processing, such as successful or refunded: NGN 1,000.00. Display it; don't branch on it.
invoiceTypestring · nullableThe kind of invoice. One of setup, build, subscription, usage, performance, mixed, other.
periodStartdate · nullableStart of the billing period.
periodEnddate · nullableEnd of the billing period.
nextDueDatedate · nullableWhen the next cycle's invoice is due. Informational.
createdAttimestampWhen the invoice was raised. Server time, no offset.
approvedAttimestamp · nullableWhen it was fully approved.
dueDatedateWhen payment is due.
paidAttimestamp · nullableWhen it was paid.
subtotalnumber (decimal)Sum of the lines.
discountnumber (decimal)Discount on the subtotal.
taxableNetnumber (decimal)Amount VAT is charged on.
vatAmountnumber (decimal)VAT charged, from the organization's VAT setting.
vatLabelstring · nullableHow the VAT line is labelled, such as VAT (7.5%). Comes from the organization's VAT setting.
totalnumber (decimal)Amount the customer pays.
linesarray of Invoice lineThe invoice lines.
paymentsarray of Payment attempt · nullableOnline payment attempts. Set only by GET /invoices/{id} when the key has payments:read; null everywhere else.
Invoice
{
  "id": 5012,
  "number": "MAX-2026-0042",
  "externalRef": "ws_42-2026-11",
  "customerId": 812,
  "currency": "NGN",
  "status": "approved",
  "overdue": false,
  "paymentStatus": null,
  "invoiceType": "subscription",
  "periodStart": "2026-11-01T00:00:00",
  "periodEnd": "2026-11-30T00:00:00",
  "nextDueDate": "2026-12-01T00:00:00",
  "createdAt": "2026-11-01T09:00:00",
  "approvedAt": "2026-11-01T11:30:00",
  "dueDate": "2026-11-15T00:00:00",
  "paidAt": null,
  "subtotal": 800000.00,
  "discount": 0.00,
  "taxableNet": 800000.00,
  "vatAmount": 60000.00,
  "vatLabel": "VAT (7.5%)",
  "total": 860000.00,
  "lines": [
    {
      "serviceId": 7,
      "description": "Subscription — 3 entities (3 × 250,000.00)",
      "detail": "3 entities",
      "quantity": 3,
      "unitPrice": 250000.00,
      "amount": 750000.00
    },
    {
      "serviceId": 9,
      "description": "Onboarding",
      "detail": null,
      "quantity": 1,
      "unitPrice": 50000.00,
      "amount": 50000.00
    }
  ],
  "payments": [
    {
      "reference": "T12-MAX-2026-0042-K3P9QZ_1",
      "channel": "paystack",
      "succeeded": false,
      "attemptedAt": "2026-11-02T08:15:00"
    }
  ]
}

Each entry in lines:

FieldDescription
serviceIdintegerThe service in the organization's catalogue.
descriptionstringReady to display: the service name, then — detail, then (qty × rate).
detailstring · nullableYour detail text for the line.
quantitynumber (decimal)Quantity. 1 when the line was sent as an amount.
unitPricenumber (decimal)Rate per unit.
amountnumber (decimal)Line amount, net of VAT.

Each entry in payments:

FieldDescription
referencestringThe payment reference sent to Paystack.
channelstringThe payment channel, such as paystack.
succeededbooleanWhether Flux verified and accepted the payment.
attemptedAttimestampWhen the attempt started. Server time, no offset.

Raise an invoice#

POST/api/v1/invoicesinvoices:writeIdempotency-Key

Raises an invoice into the organization's approval workflow, as if a person had raised it in the app. It's recorded as raised by the admin who created the API key. The customer isn't emailed until the invoice is approved.

Request body

FieldDescription
externalRefstringoptional Your stable reference for this invoice, such as ws_42-2026-11. At most 100 characters, unique per organization. A repeat returns the existing invoice.
customerIdintegerrequired A customer in the organization.
currencystringrequired A supported ISO currency code. One of NGN, USD, CAD, EUR, GBP.
dueDatedaterequired When payment is due. Send YYYY-MM-DD.
discountnumber (decimal)optional 0 or more, and no more than the subtotal. Default 0.
periodStartdateoptional Billing period start. Send it together with periodEnd, or neither.
periodEnddateoptional Billing period end. On or after periodStart.
invoiceTypestringoptional The kind of invoice. One of setup, build, subscription, usage, performance, mixed, other.
nextDueDatedateoptional When the next cycle's invoice is due. Informational.
commentsstringoptional A note for the organization's reviewers.
linesarray of Invoice line (request)required At least one line. The subtotal is the sum of the lines; you can't send a total.
  • Lines. At least one. Each references a serviceId in the organization's catalogue, and lines may use different services. Each takes either amount or quantity and unitPrice. quantity must be greater than 0, unitPrice 0 or more, and every line's amount greater than 0.
  • Totals. The subtotal is the sum of the lines; you can't send one. VAT comes from the organization's VAT setting.
  • Billing period. Send both periodStart and periodEnd, or neither. The end can't be before the start.
  • Numbering. The invoice number's prefix comes from the first line's service category.
  • No double billing. externalRef is checked permanently: a second request with the same value returns the existing invoice with 200. See Idempotency.

Request

curl -X POST "$FLUX_BASE_URL/api/v1/invoices" \
  -H "Authorization: ApiKey $FLUX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: invoice-ws_42-2026-11" \
  -d '{
  "externalRef": "ws_42-2026-11",
  "customerId": 812,
  "currency": "NGN",
  "dueDate": "2026-11-15",
  "discount": 0,
  "periodStart": "2026-11-01",
  "periodEnd": "2026-11-30",
  "invoiceType": "subscription",
  "nextDueDate": "2026-12-01",
  "comments": "November subscription",
  "lines": [
    {
      "serviceId": 7,
      "quantity": 3,
      "unitPrice": 250000,
      "detail": "3 entities"
    },
    {
      "serviceId": 9,
      "amount": 50000
    }
  ]
}'

Responses

201 Created: a new invoice
201 Created: a new invoice
{
  "id": 5012,
  "number": "MAX-2026-0042",
  "externalRef": "ws_42-2026-11",
  "customerId": 812,
  "currency": "NGN",
  "status": "awaiting_approval",
  "overdue": false,
  "paymentStatus": null,
  "invoiceType": "subscription",
  "periodStart": "2026-11-01T00:00:00",
  "periodEnd": "2026-11-30T00:00:00",
  "nextDueDate": "2026-12-01T00:00:00",
  "createdAt": "2026-11-01T09:00:00",
  "approvedAt": null,
  "dueDate": "2026-11-15T00:00:00",
  "paidAt": null,
  "subtotal": 800000.00,
  "discount": 0.00,
  "taxableNet": 800000.00,
  "vatAmount": 60000.00,
  "vatLabel": "VAT (7.5%)",
  "total": 860000.00,
  "lines": [
    {
      "serviceId": 7,
      "description": "Subscription — 3 entities (3 × 250,000.00)",
      "detail": "3 entities",
      "quantity": 3,
      "unitPrice": 250000.00,
      "amount": 750000.00
    },
    {
      "serviceId": 9,
      "description": "Onboarding",
      "detail": null,
      "quantity": 1,
      "unitPrice": 50000.00,
      "amount": 50000.00
    }
  ],
  "payments": null
}
200 OK: an invoice with this externalRef already exists

Flux returns the existing invoice, in its current state, and raises nothing.

200 OK: an invoice with this externalRef already exists
{
  "id": 5012,
  "number": "MAX-2026-0042",
  "externalRef": "ws_42-2026-11",
  "customerId": 812,
  "currency": "NGN",
  "status": "approved",
  "overdue": false,
  "paymentStatus": null,
  "invoiceType": "subscription",
  "periodStart": "2026-11-01T00:00:00",
  "periodEnd": "2026-11-30T00:00:00",
  "nextDueDate": "2026-12-01T00:00:00",
  "createdAt": "2026-11-01T09:00:00",
  "approvedAt": "2026-11-01T11:30:00",
  "dueDate": "2026-11-15T00:00:00",
  "paidAt": null,
  "subtotal": 800000.00,
  "discount": 0.00,
  "taxableNet": 800000.00,
  "vatAmount": 60000.00,
  "vatLabel": "VAT (7.5%)",
  "total": 860000.00,
  "lines": [
    {
      "serviceId": 7,
      "description": "Subscription — 3 entities (3 × 250,000.00)",
      "detail": "3 entities",
      "quantity": 3,
      "unitPrice": 250000.00,
      "amount": 750000.00
    },
    {
      "serviceId": 9,
      "description": "Onboarding",
      "detail": null,
      "quantity": 1,
      "unitPrice": 50000.00,
      "amount": 50000.00
    }
  ],
  "payments": null
}
7 possible errors
StatusCodeWhen
400invalid_requestValidation failed. The message says what, such as Add at least one invoice item.
400invalid_idempotency_keyIdempotency-Key is longer than 255 characters.
409idempotency_in_progressA request with the same Idempotency-Key is still running.
422idempotency_key_reusedThe same Idempotency-Key was sent with a different request.
401unauthorizedNo API key, an unknown key or a revoked key.
403insufficient_scopeThe key lacks the scope. details names it: { "required": "invoices:write" }.
429rate_limitedToo many requests for this key. Wait for the Retry-After seconds.

List invoices#

GET/api/v1/invoicesinvoices:read

Lists invoices, newest first. Filters combine.

Query parameters

FieldDescription
customerIdintegerOnly this customer's invoices.
statusstringOnly invoices with this status. An unknown value gets 400 invalid_request. One of awaiting_approval, awaiting_final_approval, returned, approved, paid, voided.
overduebooleantrue for only overdue invoices.
externalRefstringOnly the invoice with this reference.
limitintegerItems per page, 1 to 100. Default 25.
cursorstringThe nextCursor from the previous page. Leave it out for the first page.

Request

curl "$FLUX_BASE_URL/api/v1/invoices?customerId=812&status=approved" \
  -H "Authorization: ApiKey $FLUX_API_KEY"

Responses

200 OK

List results always have payments: null.

200 OK
{
  "data": [
    {
      "id": 5012,
      "number": "MAX-2026-0042",
      "externalRef": "ws_42-2026-11",
      "customerId": 812,
      "currency": "NGN",
      "status": "approved",
      "overdue": false,
      "paymentStatus": null,
      "invoiceType": "subscription",
      "periodStart": "2026-11-01T00:00:00",
      "periodEnd": "2026-11-30T00:00:00",
      "nextDueDate": "2026-12-01T00:00:00",
      "createdAt": "2026-11-01T09:00:00",
      "approvedAt": "2026-11-01T11:30:00",
      "dueDate": "2026-11-15T00:00:00",
      "paidAt": null,
      "subtotal": 800000.00,
      "discount": 0.00,
      "taxableNet": 800000.00,
      "vatAmount": 60000.00,
      "vatLabel": "VAT (7.5%)",
      "total": 860000.00,
      "lines": [
        {
          "serviceId": 7,
          "description": "Subscription — 3 entities (3 × 250,000.00)",
          "detail": "3 entities",
          "quantity": 3,
          "unitPrice": 250000.00,
          "amount": 750000.00
        },
        {
          "serviceId": 9,
          "description": "Onboarding",
          "detail": null,
          "quantity": 1,
          "unitPrice": 50000.00,
          "amount": 50000.00
        }
      ],
      "payments": null
    }
  ],
  "nextCursor": null,
  "hasMore": false
}
4 possible errors
StatusCodeWhen
400invalid_requestValidation failed. The message says what, such as Add at least one invoice item.
401unauthorizedNo API key, an unknown key or a revoked key.
403insufficient_scopeThe key lacks the scope. details names it: { "required": "invoices:write" }.
429rate_limitedToo many requests for this key. Wait for the Retry-After seconds.

Get an invoice#

GET/api/v1/invoices/{id}invoices:read

Returns the invoice with its lines, period, type and status. With payments:read, it also includes the payment attempts.

Path parameters

FieldDescription
idintegerrequired The invoice's id.

Request

curl "$FLUX_BASE_URL/api/v1/invoices/5012" \
  -H "Authorization: ApiKey $FLUX_API_KEY"

Responses

200 OK

This example is for a key with payments:read. Without it, payments is null.

200 OK
{
  "id": 5012,
  "number": "MAX-2026-0042",
  "externalRef": "ws_42-2026-11",
  "customerId": 812,
  "currency": "NGN",
  "status": "approved",
  "overdue": false,
  "paymentStatus": null,
  "invoiceType": "subscription",
  "periodStart": "2026-11-01T00:00:00",
  "periodEnd": "2026-11-30T00:00:00",
  "nextDueDate": "2026-12-01T00:00:00",
  "createdAt": "2026-11-01T09:00:00",
  "approvedAt": "2026-11-01T11:30:00",
  "dueDate": "2026-11-15T00:00:00",
  "paidAt": null,
  "subtotal": 800000.00,
  "discount": 0.00,
  "taxableNet": 800000.00,
  "vatAmount": 60000.00,
  "vatLabel": "VAT (7.5%)",
  "total": 860000.00,
  "lines": [
    {
      "serviceId": 7,
      "description": "Subscription — 3 entities (3 × 250,000.00)",
      "detail": "3 entities",
      "quantity": 3,
      "unitPrice": 250000.00,
      "amount": 750000.00
    },
    {
      "serviceId": 9,
      "description": "Onboarding",
      "detail": null,
      "quantity": 1,
      "unitPrice": 50000.00,
      "amount": 50000.00
    }
  ],
  "payments": [
    {
      "reference": "T12-MAX-2026-0042-K3P9QZ_1",
      "channel": "paystack",
      "succeeded": false,
      "attemptedAt": "2026-11-02T08:15:00"
    }
  ]
}
4 possible errors
StatusCodeWhen
404not_foundUnknown id, or an id that belongs to another organization.
401unauthorizedNo API key, an unknown key or a revoked key.
403insufficient_scopeThe key lacks the scope. details names it: { "required": "invoices:write" }.
429rate_limitedToo many requests for this key. Wait for the Retry-After seconds.
POST/api/v1/invoices/{id}/linksinvoices:read

Returns signed links to the invoice PDF and its payment page, safe to show to your customer. payUrl is null when the invoice is paid or voided. Ask for fresh links each time you show them. See Signed links.

Path parameters

FieldDescription
idintegerrequired The invoice's id.

Request

curl -X POST "$FLUX_BASE_URL/api/v1/invoices/5012/links" \
  -H "Authorization: ApiKey $FLUX_API_KEY"

Responses

200 OK
200 OK
{
  "pdfUrl": "https://api.flux.example/public/invoices/NDIuNTAxMi5pcC4xNzk2MTE2NDAw.mJb2Kc0r1oB7yWq4sZ0eD9xVh3tLnA6uPfRkGiE8ScQ/pdf",
  "payUrl": "https://api.flux.example/public/invoices/NDIuNTAxMi5wYXkuMTc5NjExNjQwMA.Q2pXv8nTz1sLcR0aB4yHk7uWm3eJd6gFoNiV5qYtKxE/pay",
  "expiresAt": "2026-12-01T10:00:00.5236411Z"
}
5 possible errors
StatusCodeWhen
404not_foundUnknown id, or an id that belongs to another organization.
503links_not_configuredThe server has no link-signing key configured.
401unauthorizedNo API key, an unknown key or a revoked key.
403insufficient_scopeThe key lacks the scope. details names it: { "required": "invoices:write" }.
429rate_limitedToo many requests for this key. Wait for the Retry-After seconds.

List payment attempts#

GET/api/v1/invoices/{id}/paymentspayments:read

The invoice's online payment attempts, as an array. An attempt that didn't succeed may still be in progress: wait for the webhook.

Path parameters

FieldDescription
idintegerrequired The invoice's id.

Request

curl "$FLUX_BASE_URL/api/v1/invoices/5012/payments" \
  -H "Authorization: ApiKey $FLUX_API_KEY"

Responses

200 OK
200 OK
[
  {
    "reference": "T12-MAX-2026-0042-K3P9QZ_1",
    "channel": "paystack",
    "succeeded": false,
    "attemptedAt": "2026-11-02T08:15:00"
  }
]
4 possible errors
StatusCodeWhen
404not_foundUnknown id, or an id that belongs to another organization.
401unauthorizedNo API key, an unknown key or a revoked key.
403insufficient_scopeThe key lacks the scope. details names it: { "required": "invoices:write" }.
429rate_limitedToo many requests for this key. Wait for the Retry-After seconds.

Start a checkout#

POST/api/v1/invoices/{id}/checkoutpayments:write

Starts an online payment with Paystack and returns the checkout URL. Redirect the payer there. The redirect back from Paystack isn't proof of payment: wait for invoice.paid. See Payments.

Path parameters

FieldDescription
idintegerrequired The invoice's id.

Request

curl -X POST "$FLUX_BASE_URL/api/v1/invoices/5012/checkout" \
  -H "Authorization: ApiKey $FLUX_API_KEY"

Responses

200 OK
200 OK
{
  "checkoutUrl": "https://checkout.paystack.com/7xk2m9q4fz0b1ac"
}
9 possible errors
StatusCodeWhen
404not_foundUnknown id, or an id that belongs to another organization.
409invoice_voidedCheckout: the invoice is voided.
409already_paidCheckout: the invoice is already paid.
409not_onlineCheckout: the invoice wasn't approved for online payment.
409payment_not_availableCheckout: online payment can't start, for example because the organization's payment settings aren't complete. The message explains.
502gateway_errorPaystack didn't start the payment. details holds the gateway's response.
401unauthorizedNo API key, an unknown key or a revoked key.
403insufficient_scopeThe key lacks the scope. details names it: { "required": "invoices:write" }.
429rate_limitedToo many requests for this key. Wait for the Retry-After seconds.