Guides
Accounting Playbooks
Ordered, multi-step workflows for the operations partners run most: billing a customer, paying a vendor bill, migrating historical books, and closing a month. 9 playbooks, 54 steps, each naming the exact tool to call and the field that usually goes wrong.
The same playbooks your agent already has
These are ported from the workflows the COUNT MCP server serves to agents throughCOUNT_playbooks. An agent calling that tool and a developer reading this page get the same sequence, so you can hand either one the same plan. Each playbook below shows the COUNT_playbooks id it corresponds to.Steps name MCP tool names because that is the shortest way to be unambiguous. The equivalent REST route is derivable from the tool name — COUNT_list_customers is GET /partners/customers — and the MCP Server page documents the mapping in full. The rules behind these steps live in Ledger Semantics & Lifecycles.
Period close
Review a workspace before closing the books on a period.
Month-end workspace health review
Pull a workspace snapshot, clear open AP and AR, scan for transactions that have not reached the general ledger, then run a trial balance for the closing period.
- 1
Pull a CFO-style snapshot of cash, receivables, payables, and profitability.
COUNT_get_workspace_statsquery: { include: "cash,receivables,payables,profitability" }.
- 2
Review draft and unpaid vendor bills.
COUNT_list_billsquery: { approvalStatus: "draft" }, or filter by status and approvalStatus as needed.
- 3
Review overdue and open customer invoices.
COUNT_list_invoicesTake the pagination and status filters from describe_endpoint for list_invoices.
- 4
Scan for uncategorized and unreconciled bank transactions in two passes.
COUNT_list_transactionsFirst query: { uncategorized: true } for register rows that have not posted to the general ledger — add reviewed: true for the ones already signed off, since bank cash and GL cash stay apart until those are categorized. Then query: { reconciled: false } for rows never matched against a statement. Fix categories with change_transaction_category.
- 5
Run a trial balance for the closing period.
COUNT_generate_trial_balancequery: { startDate, endDate }. Use generate_profit_and_loss instead when you want income-statement review.
Accounts receivable
Billing a customer, correcting a mistake, and putting a schedule on autopilot.
Create, approve, send, and collect on an invoice
The full receivable: create the invoice as a draft, approve it so journals post, send it to the customer, then optionally apply a bank deposit as payment.
Where this usually goes wrong
Approving is the step that posts journals, and an empty-body approve call does not always persist. Re-read the invoice before telling anyone it is approved.- 1
Resolve the customer and product UUIDs.
COUNT_resolve_referencesPass customerName and/or productName. Prefer this over list_customers/list_products for a pure lookup — those render a user-facing card on every call in agent surfaces.
- 2
Create the invoice in draft state.
COUNT_create_invoicebody: { customerUuid, date, dueDate, products: [{ productUuid, quantity, unitPrice, description? }], tagUuids? }. dueDate is effectively required for invoiceType "invoice" and "estimate" — 400 without it, despite being schema-optional.
- 3
Approve the draft so journals post, then verify it took.
COUNT_approve_invoiceid: the invoice UUID from the create response. Follow with get_invoice and check approved/isDraft rather than trusting the 200 — an empty-body approve can be a no-op.
- 4
Send the invoice to the customer.
COUNT_send_invoiceid: the same invoice UUID. Optional body: to (defaults to the customer email), subject, message, sendCopy. cc, bcc, attachPdf, and recipients are not real fields on this route.
- 5
Apply a bank deposit as payment.Optional
COUNT_assign_transaction_to_bills_invoicesid: the Income transaction UUID. body: { matchingType: "invoice", records: [{ id: "<invoice-uuid>", paymentAmount }] }.
Apply a credit memo to an invoice, and fix an approved invoice
There is no revert-to-draft route, so correcting an approved invoice means applying a credit memo rather than editing the original.
Where this usually goes wrong
The memo must belong to the same customer as the invoice. A memo raised against the wrong customer can never be applied — check customerUuid at creation, not at application.- 1
Create the credit memo for the same customer as the invoice you need to correct.
COUNT_create_invoicebody: { invoiceType: "memo", customerUuid: "<same customer as the target invoice>", date, products: [...] }. Cross-customer application always fails later, so double-check customerUuid now.
- 2
Approve the memo — memos follow the same draft → approved lifecycle as invoices.
COUNT_approve_invoiceid: the memo UUID from step 1. Verify with get_invoice afterwards.
- 3
Apply the approved memo to the target invoice.
COUNT_apply_multiple_credits_to_single_invoiceid: the target invoice UUID. body: { creditMemos: [{ id: "<memo-uuid>", amount }] }. Both documents must be approved and share a customer, and the memo must not already be fully applied — 400 otherwise.
- 4
Confirm the invoice open balance dropped by the applied amount.
COUNT_get_invoiceThe same invoice UUID as step 3.
Create a recurring invoice template and actually make it active
New templates default to isDraft: true and stay invisible and inactive even after being resumed. This walks through the extra step that is easy to miss.
Where this usually goes wrong
resume_recurring_invoice_template does not clear isDraft. Only update_recurring_invoice_template with isDraft: false activates a template.- 1
Resolve the customer and product UUIDs.
COUNT_resolve_referencesPass customerName and/or productName rather than listing customers and products.
- 2
Create the template.
COUNT_create_recurring_invoice_templatebody: { customerUuid, date, dueDate, products: [...], recurrencePattern }. The response comes back with isDraft: true — the default — and the template generates nothing in that state.
- 3
Activate the template by clearing isDraft explicitly. Do not skip this step.
COUNT_update_recurring_invoice_templateid: the template UUID from step 2. body: { isDraft: false, nextInvoiceDate: "<first scheduled date>" }. Always include nextInvoiceDate on this and every future update — omitting it silently nulls the value out and pauses the schedule.
- 4
Confirm the template is now active and visible.
COUNT_list_recurring_invoice_templatesThis tool only ever shows isDraft: false templates. If the template you just activated is missing, re-check step 3 rather than assuming a listing bug — get_recurring_invoice_template works regardless of isDraft and is the better debugging call.
Accounts payable
Settling a vendor bill against a real bank transaction.
Pay a vendor bill with a bank transaction
Find an approved bill, locate or create the matching Expense transaction, and apply it as payment.
Where this usually goes wrong
Bills accept Expense transactions only. An Income transaction against a bill always 400s, and vendor credit memos go through apply_vendor_memos_to_bill instead.- 1
List approved bills for the vendor or period you want to pay.
COUNT_list_billsquery: { approvalStatus: "approved", vendorUuids: "<vendor-uuid>", page: 1, limit: 50 }. Use vendorUuids from list_vendors — the numeric vendors filter is rejected.
- 2
Load the bill detail and confirm amountDue, currency, and billType.
COUNT_get_billid: the bill UUID from the list_bills row id field.
- 3
Find an existing Expense transaction to apply, or create one if the payment is new.
COUNT_list_transactionsFilter for unreconciled Expense rows matching the amount and date. Otherwise use create_transaction with type Expense, accUuid, categoryAccountUuid, amount, and postedDate.
- 4
Apply the transaction to the bill.
COUNT_assign_transaction_to_bills_invoicesid: the transaction UUID. body: { matchingType: "bill", records: [{ id: "<bill-uuid>", paymentAmount }] }. The bill must be approved and the transaction must be Expense type.
- 5
Reload the bill and confirm paidAmount and amountDue updated.Optional
COUNT_get_billThe same bill UUID as step 2.
Onboarding and migration
Standing up a new workspace and moving historical books across.
Set up a chart of accounts and import historical transactions
Confirm scope, inventory existing accounts, create everything missing, resolve references, preflight each batch, import, then verify the totals with a report.
Where this usually goes wrong
Finish the whole chart of accounts before importing anything. Once an account has journal entries it can no longer be deleted, only deactivated.- 1
Confirm which workspace you are setting up and importing into.
COUNT_auth_statusWhen more than one workspace is authorized, also call list_workspaces and pass workspace_id on every subsequent call.
- 2
Inventory existing bank, cash, income, and expense accounts for import mapping.
COUNT_list_accountsquery: { search: "<name fragment>", type: "Expenses" } for categories; omit type for bank and cash accounts. Copy the id UUIDs for accUuid and categoryAccountUuid.
- 3
Resolve the sub-type for each missing account type before creating anything.
COUNT_list_account_sub_typesFilter query.type by Assets (bank), Liabilities (credit card), Income, or Expenses. Copy the matching row’s integer id — never guess subTypeId values.
- 4
Create every missing bank, credit card, and category account. Complete the full chart of accounts before importing bills or transactions.
COUNT_bulk_create_accountsbody: { accounts: [{ name, subTypeId }, ...] } — the same shape as create_account per row. Chunk at 100 rows per call, ~25 recommended. Retry only rows where success is false.
- 5
Resolve source-system vendor, customer, and account names to UUIDs.
COUNT_resolve_referencesPass vendorName, customerName, customerEmail, accountName, and accountType as needed.
- 6
Re-fetch account UUIDs for anything created in step 4.
COUNT_list_accountsquery: { search: "<account name>" }. Copy the id UUIDs into your import rows.
- 7
Preflight each batch payload before calling a bulk create tool.
COUNT_validate_payloadtoolName: COUNT_bulk_create_transactions or COUNT_bulk_create_journal_entries. Set verifyReferences: true to confirm the UUIDs exist.
- 8
Import in batches of ~25 rows, and retry only the failed row indices.
COUNT_bulk_create_transactionsBank register rows go through bulk_create_transactions; GL-only historical postings through bulk_create_journal_entries. Hard cap is 100 rows per call. Read errorCount first.
- 9
Spot-check a sample of imported rows for dates, amounts, and categories.
COUNT_list_transactionsFilter to the imported date range, and use get_transaction for individual row detail.
- 10
Sanity-check the totals with a profit and loss report for the imported period.
COUNT_generate_profit_and_lossquery: { startDate, endDate, basis: "accrual" } under the top-level query key.
Bulk import customers or bank transactions from another system
Confirm workspace scope, map accounts, resolve name references, preflight each batch, import in ~25-row chunks, then sanity-check with a P&L.
Where this usually goes wrong
Bulk calls return HTTP 201 when the envelope is accepted, even if every row inside it failed. Read errorCount before treating an import as done.- 1
Confirm which workspace you are importing into.
COUNT_auth_statusWhen more than one workspace is authorized, also call list_workspaces and pass workspace_id on every subsequent call.
- 2
Map bank accounts and income/expense category accounts in the chart of accounts.
COUNT_list_accountsquery: { search: "<name fragment>", type: "Expenses" } for categories; omit type for bank and cash accounts.
- 3
Resolve vendor, customer, and account names to UUIDs before building bulk rows.
COUNT_resolve_referencesPass vendorName, customerName, customerEmail, accountName, and accountType as needed.
- 4
Preflight each batch payload before calling a bulk create tool.
COUNT_validate_payloadtoolName: COUNT_bulk_create_transactions or COUNT_bulk_create_customers. body: { transactions: [...] }. Set verifyReferences: true to confirm the UUIDs exist.
- 5
Import in batches of ~25 rows, and retry only the failed row indices.
COUNT_bulk_create_transactionsbody: { transactions: [<same shape as create_transaction>] }. Hard cap 100 rows per call. Read errorCount first, then retry rows where success is false.
- 6
Sanity-check the totals with a profit and loss report for the imported period.
COUNT_generate_profit_and_lossquery: { startDate, endDate, basis: "accrual" } under the top-level query key.
Budgeting
Planning a budget and reviewing it against actuals.
Plan a budget and review actuals against it
Reuse or create a draft budget, load planned amounts, publish, then compare actual and budget columns in the grid.
Where this usually goes wrong
Create the budget with actualPeriods at least 1, otherwise the grid has no trailing actual columns to review variance against.- 1
Check existing budgets and the workspace Overall Budget before creating a new plan.
COUNT_list_budgetsOptional query.status: draft or published. get_overall_budget shows whether an Overall Budget is already configured.
- 2
Reuse a draft budget from step 1, or create one when none exists for the period.
COUNT_create_budgetSkip this when a usable draft already exists — carry that budget UUID and draft versionNumber forward. Otherwise body: { name, startPeriod, cadence, actualPeriods, budgetPeriods } with actualPeriods >= 1.
- 3
Export the budget grid structure for planning.
COUNT_get_budget_gridid: the budget UUID. query: { includeActuals: "false", versionNumber: <draft version> }.
- 4
Resolve account names to UUIDs when building rows from a spreadsheet.
COUNT_resolve_referencesPass accountName plus accountType (Income or Expenses).
- 5
Preflight each import batch before writing budget cells.
COUNT_validate_payloadtoolName: COUNT_bulk_update_budget_cells. body: { updates: [{ accountUuid, periodStart, amount }] }.
- 6
Load planned amounts in batches of ~25 rows, and retry only failed indices.
COUNT_bulk_update_budget_cellsid + versionNumber + body.updates[]. Use periodStart values from the get_budget_grid columns. Hard cap 100 rows.
- 7
Publish the budget once the planned amounts are final.
COUNT_publish_budgetid: the budget UUID. Optional body.versionNumber for the draft version to publish.
- 8
Review actuals against budget by account and period.
COUNT_get_budget_gridquery: { includeActuals: "true", versionNumber: <published version> }. Optional query.reportType: accrual or cash.
- 9
Deep-dive a specific period or category with a P&L report.Optional
COUNT_generate_profit_and_lossquery: { startDate, endDate, basis: "accrual" }.
Export, edit, and re-import a P&L budget
Export the budget grid as JSON, edit the amounts offline in a spreadsheet, then bulk-import the cell updates onto a draft version.
Where this usually goes wrong
Partner budget tools are JSON-only — there is no CSV upload. Keep accountUuid, periodStart, and amount columns aligned with the exported grid.- 1
List budgets, or create a new draft budget for the planning period.
COUNT_list_budgetsOptional query.status: draft. Otherwise create_budget with name, startPeriod, cadence, actualPeriods, and budgetPeriods.
- 2
Export the budget grid with account UUIDs and period columns.
COUNT_get_budget_gridid: the budget UUID. query: { includeActuals: "false", versionNumber: <draft version> } for a faster budget-only export.
- 3
Resolve account names to UUIDs when building import rows from the spreadsheet.
COUNT_resolve_referencesPass accountName plus accountType (Income or Expenses).
- 4
Preflight each import batch before writing cells.
COUNT_validate_payloadtoolName: COUNT_bulk_update_budget_cells. body: { updates: [{ accountUuid, periodStart, amount }] }.
- 5
Import the edited amounts in batches of ~25 rows, and retry only failed indices.
COUNT_bulk_update_budget_cellsid + versionNumber + body.updates[]. Use periodStart values from the get_budget_grid columns. Read errorCount first.
- 6
Publish the budget when the amounts are final.
COUNT_publish_budgetid: the budget UUID. Optional body.versionNumber for the draft version to publish.
