Vendor Invoices & Payments
Track bills received from suppliers — with line items, tax, PO matching, and payments — from draft through to fully paid.
A vendor invoice is the bill a supplier sends you after they ship goods or provide a service. Knowledge ERP lets you record that bill, link it to the purchase order that generated it, verify every line matches what was actually received, apply payments, and track what you still owe — all in one place.
AP in a nutshell: a vendor invoice starts as a Draft, gets approved once the lines are verified against the PO, accumulates payments, and closes as Paid when the balance reaches zero.
Creating a vendor invoice #
New invoices are created from Purchasing → Vendor Invoices → New Vendor Invoice. The header captures:
- Vendor — the supplier sending the bill. Required.
- Purchase Order — the PO this invoice is billing against. Optional but recommended — it unlocks PO matching for three-way verification.
- Invoice Number — the vendor's own reference number (e.g. INV-2024-0042). Optional but useful for finding the bill later.
- Status — defaults to Draft (see Statuses).
- Invoice Date and Due Date — the bill date and when payment is expected.
- Tax Rate — an active tax rate applied to the
subtotal. Changing the tax rate instantly recalculates
amount_due. - Notes — any internal notes about the invoice.
- Amount Due — computed from line items plus tax; shown as read-only on the form and kept in sync automatically.
- Custom fields — any extra fields defined for vendor invoices appear at the bottom of the form. See Custom Fields.
Invoice statuses #
Every vendor invoice carries one of seven statuses, shown as a color-coded badge:
- Draft — newly created, not yet submitted for approval.
- Pending Approval — sent to a reviewer; still editable.
- Approved — verified and ready to pay.
- Partially Paid — one or more payments recorded; balance still outstanding.
- Paid —
amount_paidequalsamount_due; no balance remaining. - Disputed — flagged with a disagreement; immune to automatic status changes until resolved.
- Overdue — past the due date and unpaid; can be set manually or by automation.
Status transitions to Partially Paid and Paid happen automatically when payments are recorded. A Disputed invoice is the only status that blocks automatic promotion — you must resolve the dispute first.
Which statuses a bill can move to #
A bill is paid by paying it, so it cannot jump the queue. The status dropdown greys out the moves that are not available and tells you which ones are; the same rule is enforced on the record itself, so the REST API refuses the same jumps with the same message.
| From | Can move to |
|---|---|
| Draft | Pending Approval, Approved, Disputed |
| Pending Approval | Draft, Approved, Disputed |
| Approved | Draft, Pending Approval, Partially Paid, Paid, Disputed, Overdue |
| Partially Paid | Paid, Disputed, Overdue |
| Overdue | Approved, Partially Paid, Paid, Disputed |
| Disputed | Draft, Pending Approval, Approved |
| Paid | nothing — Paid is final |
Three rules produce that table:
- A bill is paid by paying it. Only an approved (or since-overdue) bill can reach Partially Paid or Paid. A draft bill sent straight to Paid would be a payment nobody submitted, nobody approved and nothing was matched against.
- It can always be sent back while nothing has been paid. Reject returns a pending bill to Draft, and a bill approved by mistake can go back the same way. It simply has to come forward again to be paid.
- It can be disputed at any time before it is paid, and Paid is final. A disputed bill is resolved back into the flow rather than paid from where it stands.
Marking a bill Paid or Partially Paid is also refused while it is held out of payment — writing the status down is not a way past the three-way match.
Line items #
The Items tab holds the individual lines of the invoice. Each line records:
- Product — the Product being billed (optional; can leave blank and use a free-text description instead).
- Description — a free-text label for the line; defaults to the Product's name.
- Quantity — how many units are being invoiced (supports up to four decimal places for fractional amounts).
- Unit Cost — the per-unit price billed, in dollars in the UI (stored and sent as cents over the API).
- Notes — any per-line notes.
Adding or editing a line item automatically recalculates the invoice's subtotal,
applies the selected tax rate, and updates amount_due.
PO matching #
When a vendor invoice is linked to a purchase order, the Match page (opened via the Match to PO action button on the invoice view page) lets you verify that what was invoiced aligns with what you actually received on the PO.
Each invoice line gets a match status against its linked PO line:
- Matched — quantity and price both agree exactly with the received quantity and PO unit cost.
- Qty Discrepancy — the invoiced quantity differs from what was received.
- Price Discrepancy — the invoiced unit cost differs from the PO cost.
- Discrepancy — both quantity and price differ.
- Unlinked — the invoice line has no corresponding PO line yet.
From the Match page you can run Auto-Match to have the system link each invoice line to a PO line by Product automatically, or link and unlink lines manually. Once lines are reviewed you can Approve the invoice (moving it to Approved) or Mark Disputed if something looks wrong.
Bills held out of payment #
Recording a bill is never blocked — you owe the money whether or not the invoice is right, and the accrual belongs on the books. What is blocked is paying one that does not add up. A bill against a purchase order is held when:
- A line was billed for more than was received, or above the price on the order. Being billed for less is not an exception.
- The bill asks for more than its own lines come to. The total the vendor is actually paid is checked against the line items plus tax, not just the lines against the PO — a bill whose lines agree with the order to the penny can still ask for more in the header.
- The bill has no lines on it at all. Nothing has been checked against the order, so it is not a match.
Bills with no purchase order are payable by default — rent, utilities and professional fees are ordinary bills with nothing to match against. If you raise an order for everything you buy, turn on hold bills without a purchase order on the account to close that route.
Tolerances #
Small differences are absorbed rather than queued, because an exceptions list nobody reads is worse than none. Each check allows the more forgiving of a percentage and an absolute amount, set on the account and overridable per vendor — so a supplier on a fixed-price contract can be held to zero while everything else keeps a working allowance.
The account-wide tolerances live under Account Settings → Vendor Bill Matching, along with the hold bills without a purchase order switch, and only your administrators can change them. They are what a vendor's bill is checked against, so the people who process bills are not the people who set the allowance; anyone else who can open Account Settings sees them greyed, with a note saying who to ask.
Working the queue #
Purchasing → Bills on Hold lists every held bill with the reason on the row,
and carries a badge with the count. To release one, open it and use Approve
Variance — it requires the approve_bill_variance permission and a written
reason, and records who released it. The approval covers the difference as it
stood: if the bill is raised again afterwards, it goes back on hold.
Who may approve a bill #
Approving a bill is authorising money to leave your bank on the strength of a document somebody else wrote, so it is a separate authority from being able to edit one.
Three things have to be true before a bill can move to Approved:
- You hold the Approve Vendor Bills permission. It has its own checkbox under Purchasing in the role editor (Users & Access → Roles). The Purchasing Manager template starts with it; Bookkeeper deliberately does not, so the role that enters bills is not by default the role that clears them. Administrators always hold it.
- The amount is within your approval limit. The same Approval Limit on your user record that governs what you may sign off on a purchase order — a bill is that same money arriving as an invoice rather than as an order. Administrators approve any amount. If yours is blank, ask an administrator to set it.
- You did not raise the bill yourself.
Why you cannot approve your own bill #
One person entering a supplier bill and clearing it for payment is the oldest hole in accounts payable, and it is the first thing an auditor asks about. So whoever raised a bill cannot approve it.
That rule only applies when there is somebody else it can apply to. If nobody else in your account holds bill approval with a limit covering the amount — a one-person business, or a small one where only the owner signs things off — the person who raised the bill can approve it, and the bill records that they did both. A rule with no way out gets worked around by paying the supplier outside the system, which is worse than a self-approval with a name against it. The moment a second person is set up to approve, the separation starts applying on its own; there is no setting to switch on.
The bill's view page has a Who signed this off panel showing who raised it, who approved it and when, and — while it is still waiting — who can approve it. Where you cannot, the Approve button stays on screen, greyed, with the reason and who to ask.
Bills with no purchase order behind them are covered too. Those are not held for matching, because there is nothing to match them against (see Bills held out of payment) — which is exactly why who authorised one matters more rather than less.
Bills raised before this feature shipped carry no author, so anyone with the authority can approve them. They are not attributed to somebody who may never have made the decision.
Sending an approved bill back to Draft or Pending Approval clears the approval record, because that approval no longer stands. A bill moving forward to Partially Paid, Paid or Overdue keeps it.
An approval covers an amount, not a record #
An approval is a statement about a figure, so it does not carry over to a different one. Change the amount due on an approved bill and the approval is withdrawn — the bill returns to Pending Approval and has to be approved again, at the new figure, by somebody entitled to approve it. Correcting a bill you keyed wrong is ordinary work and is not blocked; it simply costs the approval, which is what an approval of the old figure was worth.
If the bill has already been partly paid when the amount changes, the status is left alone — rewriting the status of a bill money has left the bank against would be dishonest — and the approval stays on the record as history. Instead the bill goes on payment hold with the difference named on the row, so nothing further is paid until somebody with the variance permission says why it is acceptable.
Paying a bill needs an approval that exists #
A bill cannot be paid until it has been approved. Recording a bill and paying one are different acts: the bill goes on the books the moment it arrives, because you owe the money whether or not anyone has signed it off yet and the AP ageing has to be honest about it. Money leaving the bank is the other side of that line, and that is where the authorisation has to already exist.
So Record Payment — and POST /api/v1/vendor-payments — are refused on a
bill in Draft or Pending Approval, and on one that was disputed before
anybody approved it. The button stays on screen, greyed, saying why. Get the
bill approved and the payment goes through.
Three things follow, and they are deliberate:
- Paying does not ask a second time who you are. It asks only that an approval exists. Someone who was allowed to approve a bill is allowed to pay it, so a one-person business is never left with a bill nobody can settle.
- A bill you approved and then disputed can still be part paid — the approval was given and never withdrawn, and paying the undisputed portion is ordinary work. A bill disputed before approval cannot.
- Sending an approved bill back to Draft or Pending Approval stops it being payable, because that is what withdrawing an approval means. The same is true of an approved bill whose amount is then changed.
Bills already part paid when this shipped stay payable: money having left is the strongest evidence there is that an approval existed.
Creating a bill directly in Approved, Partially Paid, Paid or Overdue — over the API, or when importing history — is treated as approving it, and takes the same authority. Those states all claim somebody signed the bill off, so posting one in without the permission would be a way of claiming an approval that was never given.
Bills that were already approved when this shipped are covered as well. We cannot know who approved one, so no approver is shown — but we do know what figure it stands at, so that figure is what its approval is pinned to. Nothing changes on the day of the upgrade; change one of those amounts afterwards and the approval is withdrawn like any other.
Over the API, created_by_id, approved_by_id and approved_at are read-only
fields on a bill; created_by_id is stamped from the token's owner. A PATCH
setting status to approved that fails any of the three tests above is
refused with 403, and the message names who to ask.
Bills with nothing on them #
A bill with no lines and no amount due cannot be submitted for approval or approved — there is nothing on it to approve. Put the vendor's lines or the amount they are asking for on it first. A bill for $0 with lines on it is fine (a warranty replacement, a free sample), and so is an expense bill that is just a header and an amount.
Recording payments #
Record each payment made against the invoice from its view page with the Record Payment action (the Payment History tab is a read-only log of them). The action is available on any invoice that is not in Draft or Paid status. It captures:
- Amount — how much was paid, in dollars in the UI (cents over the API). The payment is capped at the remaining balance — you cannot overpay.
- Payment Date — defaults to today.
- Payment Method — Cash, Check, Credit Card, Bank Transfer, or Other.
- Reference Number — a check number, wire reference, or transaction ID.
- Notes — any notes about the payment.
Each payment reduces amount_paid and the remaining balance, and the invoice
status advances to Partially Paid or Paid automatically.
The vendor invoice list #
Purchasing → Vendor Invoices shows every invoice with its vendor, linked PO number, status badge, invoice date, due date, and amount due. Filter by status or use the Trashed toggle to include soft-deleted records. Search by invoice number or vendor name.
How vendor invoices connect to the rest of Purchasing #
- Purchase Orders — link an invoice to a PO to unlock three-way PO matching and automatically scope the auto-match candidates to that PO's lines. See Purchase Orders.
- Vendors — every invoice belongs to a vendor. Vendor contacts and payment terms live on the vendor record.
- Tax Rates — invoices can reference any active tax rate to add tax to the subtotal automatically.
- Attachments — attach the original PDF bill or supporting documents via the Attachments tab, which is powered by the shared media library.
- Change History — every edit is logged on the Change History tab so there is a full audit trail of who changed what and when. See Change History & Undo.
Doing it from the API #
# Create a vendor invoice
curl -X POST "https://your-domain.com/api/v1/vendor-invoices" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"vendor_id": "<vendor-id>",
"purchase_order_id": "<po-id>",
"invoice_number": "INV-2024-0042",
"invoice_date": "2024-06-01",
"due_date": "2024-07-01",
"status": "draft"
}'
# Add a line item (unit_cost in cents: 5000 = $50.00)
curl -X POST "https://your-domain.com/api/v1/vendor-invoice-items" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"vendor_invoice_id": "<invoice-id>",
"product_id": "<product-id>",
"quantity": 10,
"unit_cost": 5000
}'
# Record a payment (amount in cents: 50000 = $500.00)
curl -X POST "https://your-domain.com/api/v1/vendor-payments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"vendor_invoice_id": "<invoice-id>",
"amount": 50000,
"payment_method": "bank_transfer",
"reference_number": "WIRE-20240601"
}'
All money fields —
amount_due,amount_paid,tax_amount,unit_cost, and paymentamount— are integers in cents over the API (5000= $50.00). The UI displays dollars; the API always uses cents. Payments cannot exceed the invoice's remaining balance.