Before you start
Every recipe assumes:
export BEESWAX_TOKEN=YOUR_API_TOKEN
export BEESWAX=https://app.beeswaxapp.com/new_api/v1
and a token with the scopes named at the top of each recipe. Writes carry an Idempotency-Key; see Sync & safe retries for why. Money is sent as a string with two decimals and comes back as a decimal string.
Most writes need reference ids first: a client, a project, an account for each line, a tax code. The first recipe shows how to find them; the rest assume you have.
1. Look up the reference data
Scopes: companies:read, projects:read, transaction_accounts:read, transaction_templates:read
Clients and suppliers
curl "$BEESWAX/active_account/companies?role=client&name=acme" -H "Authorization: Bearer $BEESWAX_TOKEN"
Projects
curl "$BEESWAX/active_account/projects" -H "Authorization: Bearer $BEESWAX_TOKEN"
Chart of accounts. Each row has type (income, expense, asset, bank and so on), active, system, default and bank_account. Bank accounts are the rows with "bank_account": true. The account's default income and expense accounts are at /active_account/transaction_accounts/default.
curl "$BEESWAX/active_account/transaction_accounts" -H "Authorization: Bearer $BEESWAX_TOKEN"
curl "$BEESWAX/active_account/transaction_accounts/default" -H "Authorization: Bearer $BEESWAX_TOKEN"
Tax codes
curl "$BEESWAX/active_account/taxes" -H "Authorization: Bearer $BEESWAX_TOKEN"
Products & services (to price lines from your catalogue). side=sell returns what you can put on an invoice or quote; side=buy what you can put on a bill.
curl "$BEESWAX/transaction_templates?side=sell&active=true&query=design" -H "Authorization: Bearer $BEESWAX_TOKEN"
Cache these. They change rarely, and updated_since works on all of them.
2. Create and finalise an invoice
Scopes: invoices:write, plus the read scopes above
An invoice is created whole: header, sections (groups) and each section's lines in one request. It lands as a draft with its number already issued and posted: false.
curl -X POST "$BEESWAX/invoices" \
-H "Authorization: Bearer $BEESWAX_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice:order:5541" \
-d '{
"invoice": {
"company_id": 231,
"project_id": 88,
"title": "Brand refresh, phase 2",
"sent_on": "2026-09-07",
"tax_inclusive": false,
"groups": [
{
"name": "Design",
"lines": [
{ "description": "Art direction", "quantity": "12", "unit_amount": "180.00",
"transaction_account_id": 5012, "tax_id": 3 },
{ "description": "Logo suite", "transaction_template_id": 77, "quantity": "1" }
]
},
{
"name": "Expenses",
"lines": [
{ "description": "Stock imagery", "amount": "240.00", "transaction_account_id": 5012, "tax_id": 3 }
]
}
]
}
}'
Line rules:
- Give either
quantity+unit_amount, or a flatamount. transaction_template_idfills description, account, tax and price from your catalogue; any field you also pass overrides it.- Every
transaction_account_id,tax_id,company_idandproject_idmust belong to the token's account, or the whole request is refused with a 422 naming the offender. Nothing is half-written. company_idmay be omitted; it defaults from the project's client, then the account's default contact, as in the web app.
The response is the full invoice, including id, number, state: "draft", posted: false, computed total, and groups with line ids.
Finalise it (posts to the ledger; still emails nobody):
curl -X POST "$BEESWAX/invoices/16102/finalise" \
-H "Authorization: Bearer $BEESWAX_TOKEN" \
-H "Idempotency-Key: invoice:order:5541:finalise"
Or do both in one step by adding "status": "finalised" to the create body. The same guards apply either way: a negative total or a locked project is refused with a 422.
Edit a draft or an unpaid finalised invoice. Header fields go through PATCH /invoices/:id. Structure goes through the sub-resources, so a header edit can never silently drop a hundred lines:
# add a section
curl -X POST "$BEESWAX/invoices/16102/transaction_groups" -H "Authorization: Bearer $BEESWAX_TOKEN" \
-H "Content-Type: application/json" -d '{ "transaction_group": { "name": "Print" } }'
# add a line to section 9021
curl -X POST "$BEESWAX/invoices/16102/transaction_groups/9021/transactions" -H "Authorization: Bearer $BEESWAX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "transaction": { "description": "Business cards", "quantity": "500", "unit_amount": "0.42", "transaction_account_id": 5012, "tax_id": 3 } }'
# change, reorder, or remove
curl -X PATCH "$BEESWAX/invoices/16102/transaction_groups/9021/transactions/44012" ... -d '{ "transaction": { "quantity": "1000" } }'
curl -X POST "$BEESWAX/invoices/16102/transaction_groups/reorder" ... -d '{ "group_ids": [9022, 9021] }'
curl -X DELETE "$BEESWAX/invoices/16102/transaction_groups/9021/transactions/44012" ...
editable: false on the invoice means a payment has been applied or it has been voided; writes will be refused.
Undo. There is no unfinalise. DELETE /invoices/:id works while it is a draft. Once finalised, POST /invoices/:id/void retires it: the number, lines and history are kept, and its postings leave the ledger and receivables.
Expenses (bills) follow the same shape at /expenses, with an extra external_number for the supplier's own invoice number. Quotes follow it at /quotes, never post, and are deleted rather than voided.
3. Record a payment
Scopes: payments:write, invoices:read (or expenses:read), transaction_accounts:read
A payment moves money from a bank account onto one or more finalised documents, in one all-or-nothing request. Applying it creates one payment record per allocated document, exactly as the web payment form does.
curl -X POST "$BEESWAX/payments" \
-H "Authorization: Bearer $BEESWAX_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: payment:bank-line:88213" \
-d '{
"payment": {
"bank_account_id": 412,
"paid_on": "2026-09-08",
"allocations": [
{ "journal_entry_id": 16102, "amount": "4950.00" },
{ "journal_entry_id": 16087, "amount": "300.00" }
]
}
}'
Rules, each of which is a 422 with a message naming the line:
bank_account_idmust be one of this account's bank accounts.- Every
journal_entry_idmust be a finalised, unpaid or part-paid invoice (or expense; do not mix the two in one payment). - Each
amountis positive and no more than that document'spayable. paid_onisYYYY-MM-DD.
The response lists the payments created and the documents they settled. Afterwards each invoice shows the new paid, payable, fully_paid and state (partial_paid or paid).
Wrong payment? There is no edit. DELETE /payments/:id removes it and reopens the document; then apply it again correctly.
Reading payments. GET /payments lists them; GET /payments/:id/transactions shows the bank and receivable postings; applied_to on each payment names the documents it settled.
4. Post a manual journal
Scopes: journal_entries:write, transaction_accounts:read
The most common write for accountants: a balanced general-journal entry. It posts immediately unless you stage it as a draft.
curl -X POST "$BEESWAX/raw_journal_entries" \
-H "Authorization: Bearer $BEESWAX_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: journal:vehicle-writeoff:2026-04" \
-d '{
"manual_journal": {
"date": "2026-04-01",
"narration": "Vehicle write-off",
"lines": [
{ "account_id": 5123, "debit": "41854.00", "description": "Loss on write-off" },
{ "account_id": 5456, "credit": "41854.00", "description": "Carrying value" }
]
}
}'
- At least two lines; each line has exactly one of
debitorcredit, positive. - Debits must equal credits, or the request is refused:
Unbalanced: debits 41854.00 do not equal credits 41845.00. narrationis at most 255 characters. Put detail on the lines.- Every
account_idmust belong to the account.
Add "status": "draft" to stage it for review. A draft can carry a number and still be posted: false. Post it later with POST /raw_journal_entries/:id/finalise, or remove it with DELETE while it is a draft. Posted journals are immutable through the API.
5. Add a client or supplier
Scope: companies:write
A company must be a client, a supplier, or both. People are optional and can be added later with a PATCH.
curl -X POST "$BEESWAX/active_account/companies" \
-H "Authorization: Bearer $BEESWAX_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: company:crm:00417" \
-d '{
"company": {
"name": "Acme Pty Ltd",
"client": true,
"supplier": false,
"address": "12 Example St", "city": "Sydney", "state": "NSW", "postcode": "2000", "country": "Australia",
"web_address": "https://acme.example",
"default_tax_id": 3,
"people": [
{ "first_name": "Dana", "last_name": "Lee", "email": "dana@acme.example", "mobile": "+61 400 000 000" }
]
}
}'
PATCH /active_account/companies/:id updates the header and adds people. The account's own company and system companies are read-only through the API. Match on your side by storing the returned id; there is no external-reference field on companies today, so keep the mapping in your system.
6. Attach a file to a document
Scope: the document's :write scope
Invoices, expenses and quotes take attachments at /…/:id/files. Send multipart with the field named asset, or, from a client that cannot do multipart, JSON with base64 content.
# multipart
curl -X POST "$BEESWAX/expenses/9917/files" -H "Authorization: Bearer $BEESWAX_TOKEN" \
-F "asset=@receipt.pdf"
# JSON
curl -X POST "$BEESWAX/expenses/9917/files" -H "Authorization: Bearer $BEESWAX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "file": { "name": "receipt.pdf", "content_type": "application/pdf", "base64": "JVBERi0xLjQK..." } }'
GET …/files lists attachments, DELETE …/files/:file_id removes one, and GET …/files/:file_id/versions shows a file's version history. Uploads count against the account's storage; a full quota is a 422.
To get a supplier bill processed by Beeswax AI rather than attached to an existing expense, POST /invoice_uploads instead and read the results from /expenses/uncategorized.
7. Reconcile an account against the ledger
Scopes: transaction_accounts:read **and* transactions:read*
The per-account ledger is the reconciliation query: posted postings only, oldest first, with opening balance, running balance, period totals and closing balance.
curl "$BEESWAX/active_account/transaction_accounts/412/transactions?from=2026-07-01&to=2026-07-31&per_page=100" \
-H "Authorization: Bearer $BEESWAX_TOKEN"
{
"transaction_account": { "id": 412, "name": "Business Cheque", "type": "AssetAccount", "bank_account": true, ... },
"period": { "from": "2026-07-01", "to": "2026-07-31", "basis": "accrual" },
"opening_balance": 18250.4,
"debit_total": 22100.0,
"credit_total": 19875.15,
"closing_balance": 20475.25,
"posted_only": true,
"transactions": [
{ "id": 771201, "date": "2026-07-01", "description": "Acme Pty Ltd — INV-1041", "debit": 4950.0, "credit": 0.0,
"running_balance": 23200.4, "system": false, "tax_id": null,
"journal_entry": { "id": 16087, "type": "IncomePayment", "number": "PAY-0912", "title": "Payment", "state": "finalised", "posted": true, "company_name": "Acme Pty Ltd" } }
],
"meta": { "current_page": 1, "total_pages": 2, "total_count": 143, "per_page": 100 }
}
To reconcile: compare closing_balance to the bank statement's closing balance; walk transactions against statement lines; anything on the statement with no ledger line is unrecorded (record it with recipe 3 or 4), and any ledger line with no statement match is a query for the bookkeeper. Note this endpoint returns numbers rather than decimal strings; convert on arrival.
8. Work a bank statement check
Scopes: reconciliations:read, reconciliations:write
The reconciliation page as an API. Read the statement check, decide which rows appear on the statement, tick them, and ask what is doubled up. Anyone with the page open sees each tick land as you make it.
curl "$BEESWAX/reconciliations/1906" -H "Authorization: Bearer $BEESWAX_TOKEN"
{
"reconciliation": { "id": 1906, "bank_account_id": 246, "start_on": "2026-04-01", "finish_on": "2026-04-30",
"opening_balance": 18250.4, "closing_balance": 20475.25, "total_received": 22100.0, "total_spent": 19875.15,
"difference": 0.0, "state": "balanced", "statement_processing": { "log_id": 512, "status": "completed", "total_lines": 41, "matched": 39, "unmatched": 2 } },
"rows": [
{ "key": "remittance:812", "remittance_id": 812, "line_item_id": null, "date": "2026-04-03", "description": "April rent",
"company_name": "Bond-Eye Australia", "received": 0.0, "spent": 250.0, "ticked": false, "ticked_on_another_statement": false,
"document": { "id": 16087, "type": "ExpensePayment", "number": "PAY-0912", "state": "finalised" },
"paying_for": { "id": 16001, "type": "Expense", "number": "BILL-0410" } }
],
"row_counts": { "total": 41, "ticked": 39, "unticked": 2, "ticked_on_another_statement": 0 },
"statement_lines": [
{ "id": 9001, "line_number": 7, "date": "2026-04-03", "description": "RENT APRIL", "amount": -250.0, "status": "unmatched", "remittance_id": null }
],
"web_url": "/bank-reconciliations/246/reconciliation/1906"
}
Tick the rows that are on the statement, and record why a statement line is a given row:
curl -X POST "$BEESWAX/reconciliations/1906/confirm" \
-H "Authorization: Bearer $BEESWAX_TOKEN" -H "Content-Type: application/json" \
-d '{ "remittance_ids": [812], "line_item_ids": [] }'
curl -X POST "$BEESWAX/reconciliations/1906/statement_lines/9001/match" \
-H "Authorization: Bearer $BEESWAX_TOKEN" -H "Content-Type: application/json" \
-d '{ "remittance_id": 812, "reasoning": "Same amount and date; narrative says RENT" }'
Then ask what might be doubled up before signing off:
curl "$BEESWAX/reconciliations/possible_duplicates?reconciliation_id=1906" -H "Authorization: Bearer $BEESWAX_TOKEN"
A statement line with status: unmatched and no row for it is a transaction missing from Beeswax: record it with recipe 3 or 4 and it appears as a new row to tick. A row that stays unticked after the whole statement is walked is either dated wrong or never cleared — a query for the bookkeeper, not something to force.
9. Version history and restore
Scopes: the document's :read to list, :write to restore
Invoices and expenses keep an append-only version history. GET /invoices/:id/versions lists versions, GET …/versions/:n returns one in full, and POST …/versions/:n/restore writes a new version with that content rather than rewinding, so nothing is ever lost. Project documents work the same way at /project_documents/:id/versions.
Where next
- Every endpoint and parameter: Endpoint reference.
- Doing this from Zapier, n8n, Make or an AI assistant: Automation tools & assistants.
- Running it in production: Best practices for apps.