Skip to content
Knowledge ERP Docs

API Reference ↗
Purchasing

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 plus charges; 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.

Currency #

A vendor invoice is in its vendor's currency, set when it is made, and a payment is in its bill's currency. A bill in another currency has an Exchange rate, filled in from the ECB's and editable on the form until the bill is approved; its approval limit and its cash match tolerance are weighed in US dollars, at the higher of its rate and the ECB's. In a currency the ECB doesn't publish, its approval limit is weighed at the rate typed, and it has no cash match tolerance, only the percentage one. A bill in another currency is not sent to QuickBooks, and its page says so: enter it there by hand. Over the API a vendor invoice also returns amount_due_home, its amount due in your home currency at its own rate. See Currency on the purchase order page for how the rate is set and checked.

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_paid equals amount_due; no balance remaining.
  • Disputed — flagged with a disagreement; immune to automatic status changes until resolved.
  • Overdue — past the due date (on your account's own calendar) 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 the bill's currency in the UI, stored and sent in its smallest unit, such 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.

Charges #

Freight, duty, and anything else the vendor charged beyond the lines go on the Charges tab. Each charge has a type (Freight, Duty, or Other), an optional description, and an amount, in dollars in the form and in cents over the API (5000 = $50.00). Charges are added to amount_due. They are not taxed, except Freight where the Location of the bill's purchase order has Tax freight and shipping charges on purchases turned on: then it is taxed at the bill's tax rate, together with the lines. Duty and Other are never taxed, and a bill with no purchase order has no Location, so its freight is not taxed.

A charge is for a purchase order. It defaults to the bill's own, and for that order you only need permission to change the bill. You can name a different one — a carrier's bill for freight on goods you bought from another vendor names that vendor's order — and then the order has to be in your account, at a Location where you work and can update purchase orders.

If a bill's exceptions have already been released, adding a charge, removing one, or changing the amount of one puts it back on hold until the new figure is released. Changing a charge's description does not. Nor does changing its type, except where the bill's Location taxes freight: there, changing a charge between Freight and another type changes the tax and so the amount due, and puts a released bill back on hold too. A bill with nothing else wrong is not held for having a charge.

A bill recorded from a purchase order takes the order's total as its amount, and that total includes the charges you expected on the order. If the bill does not carry them and the difference is beyond the match tolerance, it is held, and the reason says what the order expected and what the bill has. Add the vendor's charges to the bill if it billed them.

Moving a bill to a different order changes which order its charges are "for". A charge that named the bill's old order would then name a different one, so if you do not work where that order is, or cannot update purchase orders there, the change is refused and the message says why. A deleted charge counts too, whoever deleted it, because Undo brings it back. Choosing another order shows the reason under the order field; clearing the order shows it on a page that says you may not do that. Ask somebody who can update purchase orders there to make the change.

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 plus charges, 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. On the price check, a bill in a currency the ECB doesn't publish gets the percentage only, with no cash amount (see Currency); its quantity check is the same as any other bill's.

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, an approval limit that covers what the bill currently asks for (administrators whatever their limit), 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. Undoing the release puts the bill back on hold. Undo needs permission to edit the bill, plus the same permission and approval limit as releasing, checked against the bill as it stands now.

Who can change a bill #

A bill follows the Location of its purchase order. Somebody whose roles place them at particular Locations can change or delete a bill, match it, approve a variance on it, or record a payment against it over the API or a connected assistant, only when they have a role at that Location, even when they hold the bill permissions account-wide. Choosing the purchase order is choosing that Location, so a bill can be put on an order only at a Location where they have a role. A bill with no purchase order is not narrowed this way. See Users, Roles & Permissions.

Which bills you see #

The same rule decides which bills you see. Somebody whose roles place them at particular Locations sees a bill, in the Vendor Invoices list, in search, on its own page, in the API and in a connected assistant, only when they may view bills at the Location of one of its purchase orders. A bill counts as belonging to its own purchase order, to any order named on one of its charges, and to the order of any of its lines, so a carrier's bill covering several Locations shows to somebody at any one of them. A bill with no purchase order shows to anyone who may view bills.

The same bills are the ones counted in AP Aging, the Purchasing dashboard and the Vendor Invoices report. A payment appears in the payment lists only when its bill does. A bill's lines and charges are reached through the bill, so a hidden bill's lines and charges are hidden with it. A unit's cost still includes its share of the charges on an approved bill for its order, such as the billed freight, even when that bill is hidden from you, so its landed cost can show you something of those charges.

Work that runs with nobody signed in, such as scheduled jobs and the QuickBooks sync, is not narrowed.

Who may approve a bill #

Approving a bill is authorizing 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:

  1. 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.
  2. 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.
  3. 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 authorized 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 aging has to be honest about it. Money leaving the bank is the other side of that line, and that is where the authorization 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 the bill currency's minor unit: 5000 = $50.00 in USD)
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
  }'

# Add a charge (amount in the bill currency's minor unit: 5000 = $50.00 in USD). purchase_order_id is optional
# and defaults to the bill's own order.
curl -X POST "https://your-domain.com/api/v1/vendor-invoice-charges" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "vendor_invoice_id": "<invoice-id>",
    "type": "freight",
    "amount": 5000
  }'

# Record a payment (amount in the bill currency's minor unit: 50000 = $500.00 in USD)
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 payment amount — are integers in the bill's smallest unit over the API, such as cents (5000 = $50.00). The UI shows them in the bill's currency. Payments cannot exceed the invoice's remaining balance.