COUNTCOUNT
Sign Up

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 through COUNT_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. 1

    Pull a CFO-style snapshot of cash, receivables, payables, and profitability.

    COUNT_get_workspace_stats

    query: { include: "cash,receivables,payables,profitability" }.

  2. 2

    Review draft and unpaid vendor bills.

    COUNT_list_bills

    query: { approvalStatus: "draft" }, or filter by status and approvalStatus as needed.

  3. 3

    Review overdue and open customer invoices.

    COUNT_list_invoices

    Take the pagination and status filters from describe_endpoint for list_invoices.

  4. 4

    Scan for uncategorized and unreconciled bank transactions in two passes.

    COUNT_list_transactions

    First 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. 5

    Run a trial balance for the closing period.

    COUNT_generate_trial_balance

    query: { 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. 1

    Resolve the customer and product UUIDs.

    COUNT_resolve_references

    Pass 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. 2

    Create the invoice in draft state.

    COUNT_create_invoice

    body: { 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. 3

    Approve the draft so journals post, then verify it took.

    COUNT_approve_invoice

    id: 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. 4

    Send the invoice to the customer.

    COUNT_send_invoice

    id: 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. 5

    Apply a bank deposit as payment.Optional

    COUNT_assign_transaction_to_bills_invoices

    id: 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. 1

    Create the credit memo for the same customer as the invoice you need to correct.

    COUNT_create_invoice

    body: { invoiceType: "memo", customerUuid: "<same customer as the target invoice>", date, products: [...] }. Cross-customer application always fails later, so double-check customerUuid now.

  2. 2

    Approve the memo — memos follow the same draft → approved lifecycle as invoices.

    COUNT_approve_invoice

    id: the memo UUID from step 1. Verify with get_invoice afterwards.

  3. 3

    Apply the approved memo to the target invoice.

    COUNT_apply_multiple_credits_to_single_invoice

    id: 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. 4

    Confirm the invoice open balance dropped by the applied amount.

    COUNT_get_invoice

    The 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. 1

    Resolve the customer and product UUIDs.

    COUNT_resolve_references

    Pass customerName and/or productName rather than listing customers and products.

  2. 2

    Create the template.

    COUNT_create_recurring_invoice_template

    body: { customerUuid, date, dueDate, products: [...], recurrencePattern }. The response comes back with isDraft: true — the default — and the template generates nothing in that state.

  3. 3

    Activate the template by clearing isDraft explicitly. Do not skip this step.

    COUNT_update_recurring_invoice_template

    id: 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. 4

    Confirm the template is now active and visible.

    COUNT_list_recurring_invoice_templates

    This 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. 1

    List approved bills for the vendor or period you want to pay.

    COUNT_list_bills

    query: { approvalStatus: "approved", vendorUuids: "<vendor-uuid>", page: 1, limit: 50 }. Use vendorUuids from list_vendors — the numeric vendors filter is rejected.

  2. 2

    Load the bill detail and confirm amountDue, currency, and billType.

    COUNT_get_bill

    id: the bill UUID from the list_bills row id field.

  3. 3

    Find an existing Expense transaction to apply, or create one if the payment is new.

    COUNT_list_transactions

    Filter for unreconciled Expense rows matching the amount and date. Otherwise use create_transaction with type Expense, accUuid, categoryAccountUuid, amount, and postedDate.

  4. 4

    Apply the transaction to the bill.

    COUNT_assign_transaction_to_bills_invoices

    id: the transaction UUID. body: { matchingType: "bill", records: [{ id: "<bill-uuid>", paymentAmount }] }. The bill must be approved and the transaction must be Expense type.

  5. 5

    Reload the bill and confirm paidAmount and amountDue updated.Optional

    COUNT_get_bill

    The 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. 1

    Confirm which workspace you are setting up and importing into.

    COUNT_auth_status

    When more than one workspace is authorized, also call list_workspaces and pass workspace_id on every subsequent call.

  2. 2

    Inventory existing bank, cash, income, and expense accounts for import mapping.

    COUNT_list_accounts

    query: { search: "<name fragment>", type: "Expenses" } for categories; omit type for bank and cash accounts. Copy the id UUIDs for accUuid and categoryAccountUuid.

  3. 3

    Resolve the sub-type for each missing account type before creating anything.

    COUNT_list_account_sub_types

    Filter query.type by Assets (bank), Liabilities (credit card), Income, or Expenses. Copy the matching row’s integer id — never guess subTypeId values.

  4. 4

    Create every missing bank, credit card, and category account. Complete the full chart of accounts before importing bills or transactions.

    COUNT_bulk_create_accounts

    body: { 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. 5

    Resolve source-system vendor, customer, and account names to UUIDs.

    COUNT_resolve_references

    Pass vendorName, customerName, customerEmail, accountName, and accountType as needed.

  6. 6

    Re-fetch account UUIDs for anything created in step 4.

    COUNT_list_accounts

    query: { search: "<account name>" }. Copy the id UUIDs into your import rows.

  7. 7

    Preflight each batch payload before calling a bulk create tool.

    COUNT_validate_payload

    toolName: COUNT_bulk_create_transactions or COUNT_bulk_create_journal_entries. Set verifyReferences: true to confirm the UUIDs exist.

  8. 8

    Import in batches of ~25 rows, and retry only the failed row indices.

    COUNT_bulk_create_transactions

    Bank 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. 9

    Spot-check a sample of imported rows for dates, amounts, and categories.

    COUNT_list_transactions

    Filter to the imported date range, and use get_transaction for individual row detail.

  10. 10

    Sanity-check the totals with a profit and loss report for the imported period.

    COUNT_generate_profit_and_loss

    query: { 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. 1

    Confirm which workspace you are importing into.

    COUNT_auth_status

    When more than one workspace is authorized, also call list_workspaces and pass workspace_id on every subsequent call.

  2. 2

    Map bank accounts and income/expense category accounts in the chart of accounts.

    COUNT_list_accounts

    query: { search: "<name fragment>", type: "Expenses" } for categories; omit type for bank and cash accounts.

  3. 3

    Resolve vendor, customer, and account names to UUIDs before building bulk rows.

    COUNT_resolve_references

    Pass vendorName, customerName, customerEmail, accountName, and accountType as needed.

  4. 4

    Preflight each batch payload before calling a bulk create tool.

    COUNT_validate_payload

    toolName: COUNT_bulk_create_transactions or COUNT_bulk_create_customers. body: { transactions: [...] }. Set verifyReferences: true to confirm the UUIDs exist.

  5. 5

    Import in batches of ~25 rows, and retry only the failed row indices.

    COUNT_bulk_create_transactions

    body: { transactions: [<same shape as create_transaction>] }. Hard cap 100 rows per call. Read errorCount first, then retry rows where success is false.

  6. 6

    Sanity-check the totals with a profit and loss report for the imported period.

    COUNT_generate_profit_and_loss

    query: { 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. 1

    Check existing budgets and the workspace Overall Budget before creating a new plan.

    COUNT_list_budgets

    Optional query.status: draft or published. get_overall_budget shows whether an Overall Budget is already configured.

  2. 2

    Reuse a draft budget from step 1, or create one when none exists for the period.

    COUNT_create_budget

    Skip 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. 3

    Export the budget grid structure for planning.

    COUNT_get_budget_grid

    id: the budget UUID. query: { includeActuals: "false", versionNumber: <draft version> }.

  4. 4

    Resolve account names to UUIDs when building rows from a spreadsheet.

    COUNT_resolve_references

    Pass accountName plus accountType (Income or Expenses).

  5. 5

    Preflight each import batch before writing budget cells.

    COUNT_validate_payload

    toolName: COUNT_bulk_update_budget_cells. body: { updates: [{ accountUuid, periodStart, amount }] }.

  6. 6

    Load planned amounts in batches of ~25 rows, and retry only failed indices.

    COUNT_bulk_update_budget_cells

    id + versionNumber + body.updates[]. Use periodStart values from the get_budget_grid columns. Hard cap 100 rows.

  7. 7

    Publish the budget once the planned amounts are final.

    COUNT_publish_budget

    id: the budget UUID. Optional body.versionNumber for the draft version to publish.

  8. 8

    Review actuals against budget by account and period.

    COUNT_get_budget_grid

    query: { includeActuals: "true", versionNumber: <published version> }. Optional query.reportType: accrual or cash.

  9. 9

    Deep-dive a specific period or category with a P&L report.Optional

    COUNT_generate_profit_and_loss

    query: { 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. 1

    List budgets, or create a new draft budget for the planning period.

    COUNT_list_budgets

    Optional query.status: draft. Otherwise create_budget with name, startPeriod, cadence, actualPeriods, and budgetPeriods.

  2. 2

    Export the budget grid with account UUIDs and period columns.

    COUNT_get_budget_grid

    id: the budget UUID. query: { includeActuals: "false", versionNumber: <draft version> } for a faster budget-only export.

  3. 3

    Resolve account names to UUIDs when building import rows from the spreadsheet.

    COUNT_resolve_references

    Pass accountName plus accountType (Income or Expenses).

  4. 4

    Preflight each import batch before writing cells.

    COUNT_validate_payload

    toolName: COUNT_bulk_update_budget_cells. body: { updates: [{ accountUuid, periodStart, amount }] }.

  5. 5

    Import the edited amounts in batches of ~25 rows, and retry only failed indices.

    COUNT_bulk_update_budget_cells

    id + versionNumber + body.updates[]. Use periodStart values from the get_budget_grid columns. Read errorCount first.

  6. 6

    Publish the budget when the amounts are final.

    COUNT_publish_budget

    id: the budget UUID. Optional body.versionNumber for the draft version to publish.