Skip to main content

What Is Accounts Payable? Definition, Examples & AP vs AR (2026)

Published Last updated 18 min readMike ThriftMike Thrift
What Is Accounts Payable? Definition, Examples & AP vs AR (2026)
On this page

Accounts payable (AP) is the money your business owes to its suppliers for goods or services you’ve already received but haven't paid for yet. In the world of accounting, AP is classified as a current liability on your balance sheet—an amount typically due within the next year, and often within 30 to 60 days.

This concept is central to accrual accounting, where you record the expense and the corresponding liability the moment a bill arrives, not when you actually send the cash. This guide will show you how to manage the entire AP workflow cleanly and efficiently using the plain-text accounting tool, Beancount.


Quick Summary

Before we dive into the details, let's cover the essentials:

  • Accounts Payable (AP) represents your short-term debts to vendors. You'll find it under the Liabilities section of your balance sheet.
  • Accrual vs. Cash: AP is a concept that exists only if you keep your books on an accrual basis. Beancount fully supports accrual workflows, and its web interface, Fava, will display your liabilities correctly.
  • AP vs. AR: It's simple: Payables are what you owe, while Receivables (AR) are what others owe you.

Where AP Lives in Beancount (and Fava)

To start tracking AP, you first need to declare an account for it in your ledger. A standard convention is:

Liabilities:AccountsPayable

You can optionally create subaccounts for major vendors (e.g., Liabilities:AccountsPayable:ForestPaintSupply).

In Fava, this account will appear on your Balance Sheet under Liabilities. You can click on it to drill down and see the postings that make up the balance. You can even see this in action in Fava's public example ledger, which includes a Liabilities:AccountsPayable account.

Sign convention: Beancount stores liability balances as credits (negative numbers). A balance of -500.00 USD on Liabilities:AccountsPayable means you owe $500. The open-item report below flips the sign with -SUM(number) so each unpaid invoice shows a positive outstanding amount. A fully settled invoice sums to zero and is excluded by HAVING.


Beancount Building Blocks You’ll Use

A robust AP workflow in Beancount relies on a few core features:

  1. Accounts: You'll primarily use your Liabilities:AccountsPayable account, a cash account like Assets:Bank:Checking, and your various expense accounts (e.g., Expenses:Supplies).
  2. Metadata: You can attach key-value data to any transaction. For AP, you'll use metadata like invoice:, due:, terms:, and document:. Set option "documents" to a folder so Beancount can discover files there; in Fava, dropping a file onto a journal row inserts a document: metadata entry, and enabling plugin "fava.plugins.link_documents" links those entries to discovered Document directives.
  3. Tags & Links: Use #tags (like #ap) for easy filtering and ^links (like ^INV-10455) to programmatically tie a bill and its subsequent payment together. This creates a clear, auditable trail in the journal — but links alone are not enough for the open-invoice query below.
  4. Queries (BQL): Beancount's SQL-like query language (BQL) allows you to run powerful reports, like listing all open payables sorted by due date, directly from the command line with bean-query or on Fava's "Query" page.

Invoice identity (required for open-invoice queries): Every bill and every settling payment — full, partial, or early-discount — must carry the same invoice: and due: transaction metadata, plus the same ^link. The query groups by payee, any_meta('invoice'), and any_meta('due'). If a payment omits those keys, its AP posting lands in a separate (payee, null, null) group and a settled bill still looks open. Keep (payee, invoice) unique across vendors. Optional bill-only fields such as terms: and document: need not repeat on payments.


Core AP Workflow in Beancount

Managing AP in your ledger involves two or three key steps: recording the bill, paying it, and sometimes handling partial payments or discounts.

1) Record the Vendor Bill (This Creates the Liability)

First, you book the expense and create the payable when the invoice arrives.

; @m49-fragment env-documents
; Optionally set your documents folder in your main Beancount file:
option "documents" "documents"
 
2025-08-05 * "Forest Paint Supply" "Paint order INV-10455" ^INV-10455 #ap
  invoice: "INV-10455"
  due: "2025-09-04"
  terms: "2/10, n/30"
  document: "invoices/2025-08-05-forest-paint-INV-10455.pdf"
  Expenses:Supplies:Paint         500.00 USD
  Liabilities:AccountsPayable    -500.00 USD

This single entry accomplishes two critical things:

  1. It immediately recognizes the $500 expense in the correct period (August) — once. Do not re-post the expense on the payment.
  2. It credits AP by $500 (-500.00 USD), recording that you owe Forest Paint Supply that amount.

The ^INV-10455 link plus the invoice: / due: metadata form the invoice's identity. Every later payment of this bill must reuse all three.

2) Pay the Bill (This Clears the Liability)

When you pay the invoice, you create a transaction that moves money from your bank account to clear the liability. Copy invoice: and due: from the bill onto the payment so both postings share one query group.

a) Standard Payment (No Discount):

2025-09-01 * "Forest Paint Supply" "Payment INV-10455" ^INV-10455
  invoice: "INV-10455"
  due: "2025-09-04"
  Liabilities:AccountsPayable     500.00 USD
  Assets:Bank:Checking           -500.00 USD

This entry reduces your AP balance by $500 and your checking account balance by the same amount. The liability for this invoice is now cleared (the group's AP sum is zero). The expense stays on the August bill — the payment does not touch Expenses:.

b) Early-Payment Discount (e.g., "2/10, n/30") — pick exactly one method:

If the terms are "2/10, n/30", you can take a 2% discount if you pay within 10 days. For our $500 invoice, that's a $10 discount. The fence below shows two mutually exclusive alternatives: Option 1 is live; Option 2 is fully commented out so a paste cannot book both. To use Option 2 instead, comment out Option 1 and uncomment Option 2. Never leave both active — that clears AP twice and invents a second $10 benefit. Which income vs. expense account you use is an accounting-policy choice for your books; Beancount does not require one over the other.

; Pick EXACTLY ONE of the two transactions below for a given invoice.
; Do not paste both into the same ledger — that clears AP twice.
 
; Option 1: Record the discount as other income
2025-08-12 * "Forest Paint Supply" "Early payment discount INV-10455" ^INV-10455
  invoice: "INV-10455"
  due: "2025-09-04"
  Liabilities:AccountsPayable     500.00 USD
  Assets:Bank:Checking           -490.00 USD
  Income:Discounts:Payables       -10.00 USD
 
; Option 2: Reduce the original expense directly
; 2025-08-12 * "Forest Paint Supply" "Early payment discount INV-10455" ^INV-10455
;   invoice: "INV-10455"
;   due: "2025-09-04"
;   Liabilities:AccountsPayable     500.00 USD
;   Assets:Bank:Checking           -490.00 USD
;   Expenses:Supplies:Paint         -10.00 USD

In either alternative alone, you clear the full $500 liability, reduce your bank balance by the $490 you actually paid, and account for the $10 benefit. Replace the standard full-payment transaction with the chosen alternative — do not stack a discount payment on top of the $500 cash payment, and do not combine this fence with section 2a for the same invoice.

3) Handling Partial Payments

Reuse the same invoice: / due: / ^link identity on every installment so each payment reduces the same open-invoice group.

; Invoice for $1,200
2025-08-10 * "Acme Parts" "INV-9001" ^INV-9001
  invoice: "INV-9001"
  due: "2025-09-09"
  Expenses:Parts                 1200.00 USD
  Liabilities:AccountsPayable   -1200.00 USD
 
; First payment of $400
2025-08-20 * "Acme Parts" "Payment INV-9001 (1/3)" ^INV-9001
  invoice: "INV-9001"
  due: "2025-09-09"
  Liabilities:AccountsPayable     400.00 USD
  Assets:Bank:Checking           -400.00 USD
 
; Final payment of $800
2025-09-05 * "Acme Parts" "Payment INV-9001 (final)" ^INV-9001
  invoice: "INV-9001"
  due: "2025-09-09"
  Liabilities:AccountsPayable     800.00 USD
  Assets:Bank:Checking           -800.00 USD

After the first payment, the invoice group's AP sum is -800.00 USD (you still owe $800). After the final payment it is zero. Filtering the journal by ^INV-9001 still shows the complete history.


Helpful Queries (BQL)

You can run these queries in Fava’s “Query” tab or from the command line with bean-query (BQL reference). Verified below with Beancount 3.2.3 and beanquery 0.2.0.

Tip: The any_meta() function looks up a metadata key on the posting first, then falls back to the transaction — so transaction-level invoice: / due: values appear in the result set.

Open AP by Vendor (Balance View):

This query sums the liability inventory per supplier. A credit (negative) total is what you still owe that vendor.

SELECT payee, COST(SUM(position)) AS amount
WHERE account ~ "^Liabilities:AccountsPayable"
GROUP BY payee
ORDER BY payee;

Open invoices as of a fixed date (open-item report):

An open-item report answers “what remains unpaid?” It must (1) cut off at an explicit as-of date so later payments cannot silently clear an earlier snapshot, (2) group by the shared invoice identity (payee + invoice: + due:), (3) exclude settled (zero) groups, and (4) show a positive outstanding amount — not a raw signed AP credit.

Reporting date used everywhere below: 2025-09-04.

Downloadable AP starter ledger

Download accounts-payable-starter.bean — one self-contained ledger generated from the complete fence below (account opens, fictional opening cash, settled / partial / unpaid invoices, as-of 2025-09-04). Every name and amount is fictional sample data. No invoice PDF, documents folder, or other file is required to validate or query it.

First-run from a clean temporary directory (pinned Beancount 3.2.3 / beanquery 0.2.0):

# Save the download as accounts-payable-starter.bean in this directory, then:
uvx --from beancount==3.2.3 bean-check accounts-payable-starter.bean
 
uvx --from beanquery==0.2.0 --with beancount==3.2.3 bean-query accounts-payable-starter.bean \
  "SELECT payee, any_meta('invoice') AS invoice, any_meta('due') AS due, -SUM(number) AS outstanding WHERE account ~ '^Liabilities:AccountsPayable' AND date <= 2025-09-04 GROUP BY payee, invoice, due HAVING SUM(number) < 0 ORDER BY due, payee"

bean-check exits 0 with no output. The query returns the five open rows in the expected table below (Forest absent; Acme outstanding 800.00; sum of positive outstanding = 1500.00). Regenerate the committed download with yarn generate:ap-starter after editing the fence; yarn generate:ap-starter --check fails if the artifact is stale.

Paste this complete fixture (opens + opening cash + the three invoice shapes, plus due-today / overdue / edge-case rows for aging). Early-discount alternatives for INV-10455 stay fully commented — activate exactly one only if you replace the standard Forest payment; never leave both discount options live alongside that payment.

; accounts-payable-starter — complete runnable AP workflow
option "operating_currency" "USD"
 
2025-01-01 open Assets:Bank:Checking USD
2025-01-01 open Equity:Opening-Balances USD
2025-01-01 open Liabilities:AccountsPayable USD
2025-01-01 open Expenses:Supplies:Paint USD
2025-01-01 open Expenses:Parts USD
2025-01-01 open Expenses:Office USD
2025-01-01 open Income:Discounts:Payables USD
 
; Fictional opening cash — enough to cover every payment in this ledger
2025-01-01 * "Opening balance"
  Assets:Bank:Checking           5000.00 USD
  Equity:Opening-Balances       -5000.00 USD
 
; Settled before as-of — must disappear from the open report
2025-08-05 * "Forest Paint Supply" "Paint order INV-10455" ^INV-10455 #ap
  invoice: "INV-10455"
  due: "2025-09-04"
  Expenses:Supplies:Paint         500.00 USD
  Liabilities:AccountsPayable    -500.00 USD
 
2025-09-01 * "Forest Paint Supply" "Payment INV-10455" ^INV-10455
  invoice: "INV-10455"
  due: "2025-09-04"
  Liabilities:AccountsPayable     500.00 USD
  Assets:Bank:Checking           -500.00 USD
 
; Early-payment discount alternatives for INV-10455 — mutually exclusive with
; the standard payment above AND with each other. To take 2/10 instead: delete
; (or comment out) the 2025-09-01 payment, then uncomment EXACTLY ONE option.
; Never leave both options active — that clears AP twice.
;
; Option 1: Record the discount as other income
; 2025-08-12 * "Forest Paint Supply" "Early payment discount INV-10455" ^INV-10455
;   invoice: "INV-10455"
;   due: "2025-09-04"
;   Liabilities:AccountsPayable     500.00 USD
;   Assets:Bank:Checking           -490.00 USD
;   Income:Discounts:Payables       -10.00 USD
;
; Option 2: Reduce the original expense directly
; 2025-08-12 * "Forest Paint Supply" "Early payment discount INV-10455" ^INV-10455
;   invoice: "INV-10455"
;   due: "2025-09-04"
;   Liabilities:AccountsPayable     500.00 USD
;   Assets:Bank:Checking           -490.00 USD
;   Expenses:Supplies:Paint         -10.00 USD
 
; Partial before as-of; final $800 payment is AFTER 2025-09-04
2025-08-10 * "Acme Parts" "INV-9001" ^INV-9001
  invoice: "INV-9001"
  due: "2025-09-09"
  Expenses:Parts                 1200.00 USD
  Liabilities:AccountsPayable   -1200.00 USD
 
2025-08-20 * "Acme Parts" "Payment INV-9001 (1/3)" ^INV-9001
  invoice: "INV-9001"
  due: "2025-09-09"
  Liabilities:AccountsPayable     400.00 USD
  Assets:Bank:Checking           -400.00 USD
 
2025-09-05 * "Acme Parts" "Payment INV-9001 (final)" ^INV-9001
  invoice: "INV-9001"
  due: "2025-09-09"
  Liabilities:AccountsPayable     800.00 USD
  Assets:Bank:Checking           -800.00 USD
 
; Unpaid — not yet due as of 2025-09-04
2025-08-15 * "Bright Office Co" "INV-220" ^INV-220
  invoice: "INV-220"
  due: "2025-09-14"
  Expenses:Office                 300.00 USD
  Liabilities:AccountsPayable    -300.00 USD
 
; Unpaid — due today (as-of == due)
2025-08-20 * "Helix Industries" "INV-440" ^INV-440
  invoice: "INV-440"
  due: "2025-09-04"
  Expenses:Parts                  200.00 USD
  Liabilities:AccountsPayable    -200.00 USD
 
; Unpaid — 30 days overdue as of 2025-09-04
2025-07-06 * "Metro Hardware" "INV-77" ^INV-77
  invoice: "INV-77"
  due: "2025-08-05"
  Expenses:Parts                  150.00 USD
  Liabilities:AccountsPayable    -150.00 USD
 
; Overpayment — credit balance (not an amount due; see edge cases)
2025-08-01 * "Credit Vendor" "INV-C1" ^INV-C1
  invoice: "INV-C1"
  due: "2025-08-20"
  Expenses:Parts                  100.00 USD
  Liabilities:AccountsPayable    -100.00 USD
 
2025-08-18 * "Credit Vendor" "Overpay INV-C1" ^INV-C1
  invoice: "INV-C1"
  due: "2025-08-20"
  Liabilities:AccountsPayable     150.00 USD
  Assets:Bank:Checking           -150.00 USD
 
; Missing due metadata — still open; age into a separate bucket, do not drop
2025-08-12 * "No Due Co" "INV-ND" ^INV-ND
  invoice: "INV-ND"
  Expenses:Office                  50.00 USD
  Liabilities:AccountsPayable     -50.00 USD

Open-item query (beanquery 0.2.0 supports HAVING SUM(number) < 0; it does not support comparing an inventory to 0 with COST(SUM(position)) != 0):

SELECT payee,
       any_meta('invoice') AS invoice,
       any_meta('due')     AS due,
       -SUM(number)        AS outstanding
WHERE account ~ "^Liabilities:AccountsPayable"
  AND date <= 2025-09-04
GROUP BY payee, invoice, due
HAVING SUM(number) < 0
ORDER BY due, payee;

Expected rows as of 2025-09-04 (derived independently from the transactions: bill − payments dated ≤ as-of; Forest nets to 0 and is absent):

payeeinvoicedueoutstanding
No Due CoINV-ND(empty)50.00
Metro HardwareINV-772025-08-05150.00
Helix IndustriesINV-4402025-09-04200.00
Acme PartsINV-90012025-09-09800.00
Bright Office CoINV-2202025-09-14300.00

Sum of positive outstanding = 1,500.00. Forest’s $500 bill is gone. Acme still shows $800 ($1,200 − $400); the $800 payment on 2025-09-05 is after the as-of date, so it cannot close this snapshot.

Reconcile to the AP liability at the same cut-off:

SELECT COST(SUM(position)) AS ap_balance
WHERE account ~ "^Liabilities:AccountsPayable"
  AND date <= 2025-09-04;

Expected: ap_balance = (-1450.00 USD). Independently: open outstanding $1,500 minus the $50 vendor credit (below) = $1,450 owed → liability inventory -1450.00 USD.

Move the as-of one day later (date <= 2025-09-05) and re-run the open-item query: Acme’s row disappears (final payment included), and ap_balance becomes (-650.00 USD). A payment after the reporting date never rewrites the earlier report.

Vendor credits / overpayments: HAVING SUM(number) < 0 keeps only amounts you still owe. An overpayment flips the group’s AP sum positive. List those separately — do not treat a positive AP balance as “amount due”:

SELECT payee,
       any_meta('invoice') AS invoice,
       any_meta('due')     AS due,
       SUM(number)         AS credit
WHERE account ~ "^Liabilities:AccountsPayable"
  AND date <= 2025-09-04
GROUP BY payee, invoice, due
HAVING SUM(number) > 0
ORDER BY payee;

Expected: one row — Credit Vendor / INV-C1 / 2025-08-20 / credit = 50.00 (you overpaid by $50).

Missing due:: INV-ND still appears in the open report with an empty due. Age it into a dedicated missing-due bucket (below); never silently omit it.

List Bills with Attached PDFs:

SELECT date, payee, any_meta('invoice') AS invoice, any_meta('document') AS file
WHERE account ~ "^Liabilities:AccountsPayable"
ORDER BY date DESC;

Where to See AP in Fava

  • Balance Sheet: Navigate to Balance SheetLiabilitiesAccountsPayable to see the total balance and drill down into the transaction details.
  • Journal: Filter the journal by account:Liabilities:AccountsPayable or a specific link like ^INV-xxxx to see a bill's complete lifecycle.
  • Documents: With option "documents" set, Beancount discovers files under that tree. Fava's Documents views and the link_documents plugin (see above) connect those files to transactions that carry document: metadata.

AP Aging, Turnover, and Cash-Flow Awareness

Aging schedule (reproducible export + buckets): beanquery 0.2.0 can emit the open-item rows above, but it does not provide a built-in aging-bucket expression over due metadata. Export the open report, then bucket deterministically against the same as-of date.

bean-query -f csv -o open-ap.csv accounts-payable-starter.bean \
  "SELECT payee, any_meta('invoice') AS invoice, any_meta('due') AS due, -SUM(number) AS outstanding WHERE account ~ '^Liabilities:AccountsPayable' AND date <= 2025-09-04 GROUP BY payee, invoice, due HAVING SUM(number) < 0 ORDER BY due, payee"

CSV columns: payee, invoice, due, outstanding.

Bucket rule for as-of date D = 2025-09-04 and each row’s due (ISO YYYY-MM-DD):

ConditionBucket
due emptymissing-due
due > Dnot-yet-due
due == Ddue-today
1 ≤ (D − due) ≤ 301-30-overdue
31 ≤ (D − due) ≤ 6031-60-overdue
(D − due) ≥ 6161+-overdue

Compact Python (stdlib only) that implements that table:

from datetime import date
 
AS_OF = date(2025, 9, 4)
# Apply bucket() to each CSV row's due column (ISO YYYY-MM-DD or empty).
 
def bucket(due: str | None) -> str:
    if not due:
        return "missing-due"
    days = (AS_OF - date.fromisoformat(due)).days
    if days < 0:
        return "not-yet-due"
    if days == 0:
        return "due-today"
    if days <= 30:
        return "1-30-overdue"
    if days <= 60:
        return "31-60-overdue"
    return "61+-overdue"

Expected aging for the open rows as of 2025-09-04:

invoiceduedays past duebucketoutstanding
INV-ND(empty)missing-due50.00
INV-772025-08-05301-30-overdue150.00
INV-4402025-09-040due-today200.00
INV-90012025-09-09−5not-yet-due800.00
INV-2202025-09-14−10not-yet-due300.00

Due-today is not folded into “current”: due == as-of is its own bucket. Not-yet-due requires due > as-of.

  • AP Turnover Ratio: Total Supplier Purchases ÷ Average AP. A related metric, Days Payable Outstanding (DPO), is roughly 365 ÷ Turnover Ratio.
  • If You Can’t Pay on Time: AP is meant for short-term debt. If a vendor agrees to formal, longer-term repayment, reclassify out of AP into a note payable — keep the same invoice identity on the conversion so the AP group clears.
2025-10-01 * "Helix Industries" "Convert overdue AP to 12-month note" ^INV-1110
  invoice: "INV-1110"
  due: "2025-09-15"
  Liabilities:AccountsPayable    2000.00 USD
  Liabilities:NotesPayable      -2000.00 USD

Best Practices for AP in a Plain-Text Ledger

  • Go Paperless: Store invoice PDFs under your documents tree and attach them with the document: metadata key (and Fava upload / link_documents when you use Fava).
  • Reuse Invoice Identity on Payments: Put the same invoice:, due:, and ^link on the bill and on every payment that settles it.
  • Report Against an As-Of Date: Cut open-item and aging queries with date <= YYYY-MM-DD so a later payment cannot rewrite an earlier snapshot; age with the same D.
  • Keep Metadata Tidy: Consistently using invoice:, due:, and terms: improves search, queries, and financial reviews.
  • Accrual All the Way: If you want useful AP reporting, commit to keeping your books on an accrual basis. Beancount and Fava handle accrual balances on the Balance Sheet without special plugins.

Copy-Paste Starter: Vendor Bill + Payment

For the full open-item workflow (settled + partial + unpaid, as-of date, aging), prefer the downloadable AP starter above. The minimal bill+payment pair below still shows identity on both legs — open the accounts first (or include these opens in your main ledger). Optional document: metadata is omitted here so nothing outside the ledger is required.

option "operating_currency" "USD"
 
2025-01-01 open Assets:Bank:Checking USD
2025-01-01 open Liabilities:AccountsPayable USD
2025-01-01 open Expenses:Supplies:Paint USD
 
; ---- Bill ----
2025-08-05 * "Forest Paint Supply" "Paint order INV-10455" ^INV-10455 #ap
  invoice: "INV-10455"
  due: "2025-09-04"
  Expenses:Supplies:Paint         500.00 USD
  Liabilities:AccountsPayable    -500.00 USD
 
; ---- Payment (no discount) ----
2025-09-01 * "Forest Paint Supply" "Payment INV-10455" ^INV-10455
  invoice: "INV-10455"
  due: "2025-09-04"
  Liabilities:AccountsPayable     500.00 USD
  Assets:Bank:Checking           -500.00 USD

With Beancount 3.2.3 and beanquery 0.2.0, loading this starter produces no errors, the AP account nets to zero, and the invoice+due query returns a single group (Forest Paint Supply, INV-10455, 2025-09-04) whose amount is empty (settled) — not a billed row plus an orphan (…, null, null) payment row.


This guide is for educational purposes and does not constitute tax, legal, or financial advice.

References & Further Reading:

Share this article

Source: https://beancount.io/blog/2025/08/20/what-is-accounts-payable

Published: August 20, 2025

Last updated: September 15, 2026