Invoice reference
The invoice object, the amounts it computes, and the eight calls that manage it.
An invoice is a document you issue, hand to a customer, and wait to be paid. Wajub stores the lines, computes the totals, hosts the page the customer pays on, and records what has been settled. It does not chase anybody and it does not debit anything: the whole product is a document with a public URL and a running balance. Billing & subscriptions walks through that cycle. This page is the reference for the object itself.
Invoicing is a live-mode product gated on your plan. A sandbox key gets 403 This feature is only available in live mode., a plan without the feature gets 403 Invoicing is not available on your plan., and a plan with a lifetime cap gets 403 You have reached the maximum number of invoices for your plan. once you hit it. Only documents of type invoice count toward that cap.
The eight calls
Everything the API does with invoices is here. /send is the call that e-mails an invoice, its PDF
is a public link (below), and no endpoint charges a customer.
| Call | What it does |
|---|---|
GET /invoices | List, filter and sort your invoices |
POST /invoices | Create one, always as draft |
GET /invoices/{id} | Retrieve one |
PUT /invoices/{id} | Replace one, only while it is a draft |
DELETE /invoices/{id} | Delete one, only while it is a draft |
POST /invoices/{id}/send | Number it, e-mail it with its PDF, and open its public page |
POST /invoices/{id}/mark-paid | Record a payment you collected elsewhere |
POST /invoices/{id}/cancel | Close it out |
{id} is the id the API returns, the inv_ string.
The invoice object
This is what every call returns. items is present whenever the invoice was loaded with its lines,
which is all eight of them.
invoice_number is null on a draft and assigned the first time the invoice is sent. It comes from
your account's numbering, the same sequence the Dashboard draws from, whether you send the invoice
here or there: the prefix (INV- unless you changed it), the year, then a four-digit counter, as set
in the Dashboard's invoice settings. The counter restarts at 0001 each year unless you turned that
off, and a number is unique within your account. status is one of draft, sent, viewed, partial, paid, overdue,
cancelled or refunded; the transitions between them and the webhooks they emit are on
Billing & subscriptions.
customer is the customer the invoice is addressed to, by the cus_ id the
Customers API returns, and null when you addressed it to a name and an
e-mail only. The response's customer_id refers to the same customer under another identifier:
ignore it and match on customer.
language is the language of the invoice's e-mail and PDF, fr or en, recorded when the invoice
is created, here or in the Dashboard. accept is the
restriction on the methods the payer is offered on the invoice's page, {} when there is none.
Two fields are computed rather than stored. amount_due is always total minus amount_paid,
floored at zero, so it never goes negative on an overpayment. amount_paid only moves through
mark-paid, through a payment made on the hosted page, or through a credit note issued from the
Dashboard, which lowers it by its amount. A payment on the page counts all three: the invoice's
online payments, plus what mark-paid recorded, less its credit notes. An invoice with a 1 000
credit note turns paid only once 1 000 more than its total has come in.
Creating one
customer_name, items, invoice_date and currency are the four required fields. Everything else
is optional, and the invoice is always created as draft whatever you send.
https://api.wajub.com/invoicescustomer_namestringrequiredcustomer_id.customer_idstringoptionalcus_ id. Its name, email, company, phone and address are copied onto the invoice and override the customer_* fields you sent. A customer that is not yours answers 422.customer_emailstringoptional/send e-mails the invoice and its PDF, and where the Dashboard sends its reminders.customer_company_namestringoptionalcustomer_phonestringoptionalcustomer_addressstringoptionalitemsarrayrequiredinvoice_datedaterequireddue_datedateoptionalinvoice_date.payment_terms_daysintegeroptionaldue_date to that many days from today, and only when due_date is absent. Counted from today, not from invoice_date.currencystringrequiredXAF, XOF, NGN and the rest of the [supported list](/resources/coverage). Any other code answers 422.languageenumoptionalfr or en: the language of the e-mail /send sends and of the PDF. Defaults to the preferred language of the customer customer_id names, else your account's language (**Settings → Regional → Language**), else en. Any other value answers 422.acceptobjectoptionalPOST /payments](/payments/payment-methods#choosing-what-a-payment-accepts).is_recurringbooleanoptionaldefault : falserecurring_intervalenumoptionaldaily, weekly, monthly, quarterly or yearly.payment_typeenumoptionaldefault : fullfull, split or milestone. Instalments are covered below.payment_schedulesarrayoptionalpayment_type is not full.notesstringoptionaltermsstringoptionalfooterstringoptionaltemplate_idstring (uuid)optionaldocument_typeenumoptionaldefault : invoiceinvoice, quote or estimate. Read the warning below before you touch it.Each entry in items is its own object.
namestringrequireddescriptionstringoptionalquantitynumberrequiredunit_priceintegerrequiredunitstringoptionalhour or day. Cosmetic.tax_ratenumberoptionaldiscount_typeenumoptionalpercentage or fixed.discount_valuenumberoptionalfixed.curl https://api.wajub.com/invoices \
-H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
-H "Content-Type: application/json" \
-d '{
"customer_name": "Traoré & Fils",
"customer_email": "[email protected]",
"invoice_date": "2026-09-12",
"payment_terms_days": 30,
"currency": "XAF",
"items": [
{ "name": "Consulting service", "quantity": 2, "unit_price": 150000, "tax_rate": 19.25 },
{ "name": "Onboarding", "quantity": 1, "unit_price": 100000, "discount_type": "percentage", "discount_value": 10 }
]
}'Creation is the only invoice call that honours Idempotency-Key. Replaying a create with the same
key returns the first invoice instead of issuing a second one; replaying a send or a mark-paid
runs again.
Amounts are in the major unit
unit_price, subtotal, discount_amount, tax_amount, total and amount_paid are all in the
major unit of the currency: 150000 means one hundred and fifty thousand francs, not fifteen hundred.
Decimals are accepted. "unit_price": 1500.50 is stored as sent, and a rate that does not divide
the line keeps its decimals: 19.25% of 1000 is 192.5. Wajub keeps two decimals on every amount.
How a line is computed
The line does its own arithmetic on every save, in this order. You send the first four values; the other four are computed and returned.
subtotal = quantity × unit_price
discount_amount = discount_type = percentage → subtotal × discount_value / 100
discount_type = fixed → discount_value
otherwise → 0
taxable = subtotal − discount_amount
tax_amount = taxable × tax_rate / 100
total = taxable + tax_amountTax is exclusive: it is added on top of the line, never carved out of it. And it is computed after the discount, so a 10% discount on a taxed line lowers the tax with it.
The invoice then sums the lines.
subtotal = Σ (quantity × unit_price)
tax_amount = Σ line tax_amount
total = subtotal + tax_amount
amount_due = max(0, total − amount_paid)A line discount lowers the tax but not the invoice total
Look at the two sums above. The invoice subtotal re-multiplies quantity by price and never
subtracts discount_amount, while the tax it adds is the one each line computed after its
discount. On the example higher up, the second line reports total: 90000, and the invoice still
counts 100000 for it. Keep discounts out of the API and price the line at what you actually want
to charge.
There is no invoice-wide discount
Discounts are per line. The create call also accepts has_global_discount, global_discount_type
and global_discount_value, and those three are ignored: they are validated and have no effect.
They return no error either, so an invoice sent with
a 15% global discount is stored at full price and looks like it worked.
Quotes and estimates
document_type accepts quote and estimate, and the document is created.
A quote created through the API cannot be read back
A quote or an estimate is not readable through the API: it is absent from GET /invoices, and
GET, PUT, DELETE, /send, /mark-paid and /cancel on its id all answer 404 Invoice not found. It exists, it is billable against nothing, and only the Dashboard can see it. Leave
document_type alone and raise quotes in the Dashboard.
Paying in instalments
Set payment_type to split or milestone and pass the schedule. The hosted page then charges one
entry at a time instead of the full balance.
amountnumberrequiredlabelstringoptionalInstallment 1 or Milestone 1.due_datedateoptionalThe customer opens the invoice and sees the first entry that is not yet paid, charged at its
amount capped by the remaining amount_due. When it settles, Wajub marks that entry paid, adds
the amount to amount_paid, and moves the invoice to partial. The next visit offers the next
entry. The invoice turns paid when amount_paid reaches the total, and once every entry is
settled with a balance still outstanding the page falls back to charging the remainder in one go.
You can set a schedule but you cannot read it back
The invoice object returns payment_type and never payment_schedules. Which instalments exist,
which are paid and which transaction settled each one are only visible in the Dashboard. Keep your
own copy of the schedule you sent if you need to follow it from your side.
Listing
GET /invoices returns your live invoices, newest first, twenty-five at a time.
https://api.wajub.com/invoicessearchstringoptionalstatusenumoptionaldraft, sent, viewed, partial, paid, overdue, cancelled or refunded.customer_idstringoptionalcus_ id.date_fromdateoptionalinvoice_date.date_todateoptionalinvoice_date, on or after date_from.due_date_fromdateoptionaldue_date.due_date_todateoptionaldue_date, on or after due_date_from.amount_minnumberoptionaltotal, in the major unit, as total reads: 10000 is ten thousand francs. Each invoice is compared in its own currency.amount_maxnumberoptionaltotal, in the major unit.payment_statusenumoptionalunpaid, partially_paid or fully_paid, read from amount_paid against total.sort_byenumoptionaldefault : created_atinvoice_date, due_date, total, amount_paid, status or created_at. The list follows it first, then the newest first among invoices that tie.sort_direnumoptionaldefault : descasc or desc.per_pageintegeroptionaldefault : 25A status, payment_status, sort_by or sort_dir outside these values is a 422 on that
field, as a per_page out of range is. So is a date bound that is not a date, or an upper bound
before its lower one.
curl -G https://api.wajub.com/invoices \
-H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
-d status=overdue \
-d customer_id=cus_7Kq2mX9vL4tRb3nP8sZcWd1eThe list is cursor-paginated on the default sort. Ask for a custom sort_by or sort_dir and it
falls back to page numbers.
Correcting an invoice
PUT /invoices/{id} replaces the document rather than patching it. customer_name, items,
invoice_date and currency are required on every call, so send the whole invoice back with your
change applied, not the change alone. The lines you send replace the previous ones outright: each
update replaces the lines and their ids, so every items[].id changes.
Only a draft can be edited
As soon as the invoice has left draft, and on a draft that has money recorded against it, PUT
answers 400 This invoice cannot be edited (paid, cancelled, or has payments). and DELETE
answers 400 This invoice cannot be deleted. A sent invoice is already frozen. Cancel it and
issue a replacement.
recurring_interval can be changed on update; the recurring_frequency alias accepted at creation
is not, so use recurring_interval everywhere. language is left as it is unless you send it,
even when customer_id changes. A PUT sends one invoice.updated, with the lines and totals it
saved, and none when it changes nothing. A deleted invoice is never returned to you again,
and its invoice number stays taken.
Recording a payment you collected elsewhere
mark-paid is for money that arrived outside Wajub, in cash or by bank transfer. It moves the
balance; it does not create a transaction and it does not settle anything.
https://api.wajub.com/invoices/{id}/mark-paidamountnumberoptionaltotal is recorded.payment_datedateoptionalpaid_at when this payment settles the invoice, a single payment of the whole amount included. Today when omitted.transaction_idstringoptionalpayment_methodstringoptionalnotesstringoptionalAn amount below the total adds to amount_paid and sets the status to partial. Call it again for
the next instalment: the amounts accumulate, and the invoice flips to paid with a paid_at the
moment they reach the total.
curl https://api.wajub.com/invoices/inv_LRQqYvlhrgUOE225KMKU/mark-paid \
-H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
-H "Content-Type: application/json" \
-d '{ "amount": 178875, "payment_date": "2026-09-20" }'The invoice has to be open: sent, viewed, overdue or partly paid. A draft answers 400, since it was
never sent nor numbered: send it first. An invoice already paid answers 400 too, and keeps its
paid_at; so do a cancelled and a refunded one, with 400 This invoice cannot be marked as paid.
An amount of zero or less answers 422.
The PDF
Once sent, an invoice has a PDF anyone holding its id can download, without a key: the copy your
customer files. A draft answers 404, as its page does.
https://api.wajub.com/i/inv_LRQqYvlhrgUOE225KMKU/pdfIt is written in the invoice's language. Add ?locale=fr or ?locale=en to have it in the other
one. This is the link the invoice page offers, so share it the way you share the page.
Cancelling
POST /invoices/{id}/cancel sets the status to cancelled, stamps cancelled_at, and closes the
public page. It accepts a reason and does not store it. Anything already paid, cancelled or
refunded answers 400 This invoice cannot be cancelled.
Editing and deleting are both closed on an invoice that has been partially paid. Cancelling closes it
without refunding what was collected. Refunding in full what was paid on it online, with
refunds on the underlying payments, turns it refunded instead, unless part of
it was recorded with mark-paid.