Month end and reconciliation

How to close out monthly client billing without missing unbilled hours or expenses

Work the month in one order: read list_unbilled to the end and resolve anything missing a rate or a client, draft one invoice per client and currency, review what is late with list_invoices, then read revenue_summary for the period. Draft and cancelled invoices are excluded from revenue totals, so the number you finish with is money actually invoiced.

Written and reviewed by the Billr team. Published , last reviewed .

The short version

Every client billed for the month, nothing billable left unread, and a per-currency revenue figure you can reconcile against the invoices themselves.

What you need first

  • Work logged through the month, either from agent sessions or from the app.
  • One clear decision about when the month ends for billing, so late entries land in the right period.
  • A key with the Read and draft profile for the reading and drafting steps.

Cautions

  • list_unbilled totals cover one page. If the response carries truncated: true, or next_offset is not null, you have not seen everything yet.
  • Totals are never combined across currencies. A business billing in USD and EUR has two sets of numbers, on purpose.
  • Draft invoices are excluded from revenue totals until they are sent. A draft is a plan, not a receivable.

Who this is for: Freelancers and small studios who bill several clients a month and want the close to be repeatable rather than a scramble.

list_unbilledupdate_time_entryupdate_expensecreate_invoicelist_invoicesrevenue_summary

Five questions, asked in this order

A month-end close is not one task, it is five questions asked in an order that makes each answer trustworthy. What is unbilled? Does any of it have nowhere to go? What should be drafted from it? What is late from earlier months? What does the ledger say the month came to?

The order matters because later questions depend on earlier answers. Revenue totals count invoices that have already been issued and exclude the drafts you are about to write, so reading them first answers a different question. Drafting before resolving unassigned work leaves billable time behind. Doing the late-invoice review last, rather than first, keeps reminders from going out on work you have not finished billing.

A month-end close that ends in a known number

Step 1

Read every unbilled page

Keep paging until next_offset is null, so no group is missed.

Step 2

Resolve unassigned work and missing rates

Assign a client, set the rate, or park the entry as non-billable.

Step 3

Draft one invoice per client and currency

Source work is claimed as the draft is written, once.

Step 4

Check what is late

Read the unpaid list and the payment history before any reminder.

Step 5

Read the month in the ledger

Invoiced, paid, outstanding and overdue, per currency.

Excluded from the final number: drafts, cancelled invoices, and anything nobody logged in the first place.

Each step answers one question. The last step is a check on the ledger, not a guess: totals come from invoices and recorded payments.

Read the unbilled list to the end

list_unbilled groups billable time and expenses that are not yet attached to an invoice, by client and currency, and totals each group. The response is paged at 500 rows of time and 500 rows of expenses, and the totals describe the page you asked for.

That last detail is what keeps a close honest. Keep passing next_offset back as offset until it returns null. If either collection filled its page, the response also sets truncated: true and the summary says so, which means the groups you can see are a partial picture.

One thing the tool does not do is filter by month. Every entry comes back with its own date, including work logged in earlier months that was never billed. Selecting the rows you want is a reading step: collect the entries whose date falls inside the month you are closing, note what is older than that, and decide whether this month pays for the carryover or whether it stays for a later conversation.

list_unbilled, paging through the month

Example
list_unbilled  { "business_id": "b1f2c3d4-5e67-4a89-9b01-2c3d4e5f6a7b" }
list_unbilled  { "business_id": "b1f2c3d4-5e67-4a89-9b01-2c3d4e5f6a7b", "offset": 500 }

Repeat the second call with each new next_offset until the field is null. Keep the filters unchanged between pages.

The three things that stop a clean close
SignalWhat it meansWhat to do
missing_rate: trueTime was logged before a rate existed, so nothing is priced for it yet.update_time_entry to set the agreed rate, or mark the entry non-billable if it should not be charged.
unassignedBillable work with no client. It cannot be grouped into a client invoice.Assign a client with update_time_entry or update_expense, or accept that it is not billable this month.
truncated: trueThe page hit its row cap, so the totals are partial.Keep paging with next_offset before you draft anything.

Draft one invoice per client and currency

Work through the client groups and draft an invoice for each. Passing time_entry_ids and expense_ids prices the source work and links it in one atomic step, so the entries cannot be billed again by a later invoice.

A client group is keyed on client and currency together, which is why a client billed in two currencies appears twice. Do not merge those groups onto one invoice: create_invoice refuses source work in a currency other than the invoice currency rather than inventing a conversion.

Read every page before this step. Once an entry is attached to a draft it leaves the unbilled list, so a filter or a page you have not read yet is the easiest way to bill an incomplete month. If you do find something after drafting, bill a month of logged work covers the follow-up: correct the draft while it is still a draft.

create_invoice, once per group

Example
create_invoice  {
  "request_id": "d5a1c8f3-6b29-4e47-9c15-8f3a2d6b1e90",
  "client_name": "Harbor & Lane",
  "time_entry_ids": ["3f6c1a44-5b2d-4e71-9c08-7a1d3f5b9e22"],
  "expense_ids": ["5e2b8d31-7a4c-4f09-8d6b-1a3e5c7d9f02"]
}
create_invoice  {
  "request_id": "9b4e7c25-1d38-4a60-8f92-5c7e3b1a6d48",
  "client_name": "Northwind Type Co.",
  "currency": "EUR",
  "time_entry_ids": ["8a1f4c72-6d35-4b90-9e28-1c7a5f3d6b04"]
}

The second client is billed in EUR, so its own invoice carries that currency. Every invoice is written as a draft.

Drafts are the plan for the month

Nothing is sent from this step. Review each draft, correct what needs correcting, and send deliberately. If a draft turns out to be wrong, delete it rather than starting a second one; deleting releases the source work so it can be billed again.

Check what is late, before you write a reminder

Now look backwards. list_invoices accepts the synthetic status unpaid, which matches invoices that are sent, viewed, partial or overdue, and every row comes back with is_overdue already evaluated from its status and due date.

Reading this before reminders is what stops the two most common mistakes: chasing a draft that was never sent, and chasing an invoice that was paid this morning through a channel you have not recorded yet. Older months show up here too, which is why the unpaid list, not the revenue report, is where follow-up work lives. The review overdue invoices guide covers what to do with each row.

Invoices with their real state

Billr invoices screen listing fictional invoices with client, status, due date and balance, including invoices past their due date.
Status and balance are what you read before chasing anything. A draft is not a receivable, and a paid invoice needs no reminder.

Billr demo data, fictional throughout. View full size

list_invoices, unpaid only

Example
list_invoices  { "status": "unpaid", "limit": 50 }

Add client_id to narrow the list to one client, or page with offset to read further back.

Finish on a 2026-09 number that comes from the ledger

The last step is a check, not a guess. revenue_summary reports invoiced, paid, outstanding and overdue totals for this month, last month, this year or all time, with status counts and a top-five client breakdown.

Two details decide whether the number means what you think it means. Draft and cancelled invoices are excluded, so a draft you just wrote is not in the totals yet. And the period filters on the invoice issue date, not the payment date, so this is money invoiced in the month rather than cash that arrived during it. If you want cash, record the payments and read the payment history instead.

When a business bills in more than one currency, the response switches to a per-currency breakdown instead of a single combined number, because adding EUR to USD would be meaningless.

Fictional account, field names as returned by the server

You say

What did we invoice in 2026-09, and how much of that is still owed?

Your agent calls

  • revenue_summary
    {
      "period": "this_month"
    }

Arithmetic you can check

  • INV-1141 issued 2026-09-01 for $1,450.00$1,450.00 invoiced in USD this month
  • INV-1141 with nothing recorded against it$1,450.00 outstanding in USD
  • INV-1146 issued 2026-09-14 for €550.00€550.00 invoiced in EUR, reported separately

What came back

Abridged response from revenue_summary with period this_month, for a business billing in USD and EUR. The draft invoice of $300.00 is excluded from every total, and the two currencies are never added together.

summary
this month (mixed currencies): $1,450.00 invoiced; €550.00 invoiced.
period
this_month
currency
none
invoiced_total
none
paid_total
none
outstanding_total
none
overdue_total
none
by_currency
2 entries
by_status
1 field
top_clients
0 entries
Show the raw response
{
  "summary": "this month (mixed currencies): $1,450.00 invoiced; €550.00 invoiced.",
  "period": "this_month",
  "currency": null,
  "invoiced_total": null,
  "paid_total": null,
  "outstanding_total": null,
  "overdue_total": null,
  "by_currency": [
    {
      "currency": "USD",
      "invoiced_total": 1450,
      "paid_total": 0,
      "outstanding_total": 1450,
      "overdue_total": 1450,
      "top_clients": [
        {
          "name": "Harbor & Lane",
          "invoiced": 1450
        }
      ]
    },
    {
      "currency": "EUR",
      "invoiced_total": 550,
      "paid_total": 0,
      "outstanding_total": 550,
      "overdue_total": 0,
      "top_clients": [
        {
          "name": "Northwind Type Co.",
          "invoiced": 550
        }
      ]
    }
  ],
  "by_status": {
    "sent": 1
  },
  "top_clients": []
}

The month view leaves out anything issued earlier that is still unpaid: INV-1136 and INV-1129 were issued in earlier months, so they only appear when you read the ledger across all time or when you read the unpaid list. Run the same call with period: "all" when you want that reconciliation.

Fictional account, field names as returned by the server

You say

Now show me the same figures across every invoice.

Your agent calls

  • revenue_summary
    {
      "period": "all"
    }

Arithmetic you can check

  • $1,450.00 + $890.00 + $500.00 + $774.00 USD invoiced$3,614.00 invoiced
  • $180.00 + $774.00 USD paid$954.00 paid
  • $1,450.00 + $890.00 + $320.00 USD still owed$2,660.00 outstanding, all of it overdue

What came back

The all-time view of the same ledger. Older invoices are still open, which is exactly the difference between a month close and a reconciliation.

summary
all time (mixed currencies): $3,614.00 invoiced; €550.00 invoiced.
period
all
currency
none
invoiced_total
none
paid_total
none
outstanding_total
none
overdue_total
none
by_currency
2 entries
by_status
4 fields
top_clients
0 entries
Show the raw response
{
  "summary": "all time (mixed currencies): $3,614.00 invoiced; €550.00 invoiced.",
  "period": "all",
  "currency": null,
  "invoiced_total": null,
  "paid_total": null,
  "outstanding_total": null,
  "overdue_total": null,
  "by_currency": [
    {
      "currency": "USD",
      "invoiced_total": 3614,
      "paid_total": 954,
      "outstanding_total": 2660,
      "overdue_total": 2660,
      "top_clients": [
        {
          "name": "Harbor & Lane",
          "invoiced": 2724
        },
        {
          "name": "Larkspur Audio",
          "invoiced": 890
        }
      ]
    },
    {
      "currency": "EUR",
      "invoiced_total": 550,
      "paid_total": 0,
      "outstanding_total": 550,
      "overdue_total": 0,
      "top_clients": [
        {
          "name": "Northwind Type Co.",
          "invoiced": 550
        }
      ]
    }
  ],
  "by_status": {
    "sent": 1,
    "viewed": 1,
    "partial": 1,
    "paid": 1
  },
  "top_clients": []
}

Limits of this routine

  • Revenue totals come from invoices and recorded payments, and the period filters on the invoice issue date. A payment that arrived but was never recorded is still outstanding in the ledger, so record it if you want the figure to be true.
  • list_unbilled has no date filter, so the month boundary is a selection you make from each entry's date. Older unbilled work is visible on every run until somebody bills it or marks it non-billable.
  • Currency conversion is not implemented. Per-currency reporting is the honest way to read a mixed ledger.
  • A client group with no client attached is surfaced as unassigned work rather than silently billed to anyone.
  • The month is an accounting shape you choose. Billr reports on issue dates and due dates; it does not decide when your month closes.

Two drafts is a duplicate waiting to be sent

If you drafted an invoice twice, delete the one you do not want from the app. Deleting an unsent draft releases the source work, and a released entry can be billed again, so nothing is stranded. Agent keys cannot delete invoices, so this is a human step.

Frequently asked questions

How do I know the unbilled list was complete?
Keep calling with the returned next_offset until it is null, and treat truncated: true as an instruction to keep reading. Page totals describe only the page you asked for.
Why are my draft invoices missing from the revenue totals?
Draft and cancelled invoices are excluded from invoiced, paid, outstanding and overdue totals. A draft becomes part of the numbers once it is sent.
What happens to work with no client attached?
It is excluded from client groups and reported in the unassigned section of the unbilled response, counted in the summary. Assign a client if it should be billed.
Can I get one combined total for a month billed in two currencies?
No, and that is deliberate. revenue_summary returns a per-currency breakdown when a business bills in more than one currency rather than summing amounts that are not comparable.
What is the fastest way to see what is late?
list_invoices with status: "unpaid" returns sent, viewed, partial and overdue invoices, each with an is_overdue flag computed from its status and due date.

Guides and docs for the individual steps

Each article covers a whole job. These are the narrower references behind it.

Sources

Reviewed 2026-09-20 against Billr's own tool implementations and the pages linked here.

The month-end guide in the docs walks the same routine with the individual tool calls, if you want to follow it step by step.

Read the month-end guide

Every example on this page uses a fictional account. Field names come from Billr's own tool implementations, and the arithmetic is recomputed by the same helpers the server uses. All articles.