# Invoice list fields: what an agent gets per row

> Every row from the invoice list tool carries the business name an operator recognises, the registered legal entity, the live overdue position judged against the tenant's own calendar day, and a ready-made ageing phrase. Read `client_name` when telling a human who an invoice is for, `client_legal_name` when reconciling against books, `is_overdue` for lateness, and `due_phrase` instead of doing date arithmetic yourself.

URL: https://biloh.com.au/docs/reference/invoice-list-fields
Category: Reference | Audience: agent | Updated: 2026-08-27

The invoice list tool is the receivables surface an assistant works from. This page is the field reference: what arrives on each row, and which field answers which question.

The short version: **do not re-derive anything on this payload.** Identity, lateness and ageing are all resolved server-side, in the tenant's timezone, in one batched read.

## Identity fields

| Field | Meaning | Use it when |
|---|---|---|
| `client_name` | The name the operator knows the business by — the trading name by default | Telling a human who an invoice belongs to |
| `client_legal_name` | The registered legal entity | Reconciling against books, or quoting what the tax invoice says |
| `client_display_secondary` | The other name, or `null` when it would only repeat the first | Rendering a two-line row |
| `client_id` | Stable identifier | Any follow-up call |

`client_name` follows the tenant's `directory.entity_display_name_preference` setting, so on a tenant configured `legal_first` the two name fields swap which is which. Read the field, never the preference.

`client_display_secondary` is `null` in three distinct situations — no second name exists, the tenant turned the second line off, or both names normalise to the same business. Treat `null` as "there is nothing more to say about this name", not as missing data.

## Overdue and ageing fields

| Field | Meaning |
|---|---|
| `status` | The **stored** enum, exactly as persisted |
| `display_status` | The stored status, except a past-due sent invoice displays `overdue` |
| `is_overdue` | True whenever `display_status` is `overdue`, stored or derived |
| `days_overdue` | Whole days past due, or `null` |
| `days_until_due` | Whole days until due, or `null` |
| `draft_age_days` | Days since the draft was created, or `null` |
| `due_phrase` | The ready-made sentence: `14 days overdue`, `due in 3 days`, `due today`, `draft · 6 days old`, or empty |
| `as_of_date` | Response-level: today in the tenant's timezone, the date every derivation above was judged against |

The three counters are mutually exclusive by construction. An unpaid invoice past due has `days_overdue` and `null` for the other two; a draft has `draft_age_days` only; a paid or voided invoice has all three `null` and an empty `due_phrase`.

`due_phrase` is the same string the operator's screen renders. Using it means a human and an assistant describing the same invoice cannot word it differently — which is the point of pre-computing it rather than leaving each surface to phrase its own.

## Why is ageing pre-computed at all?

Because "today" is not a global fact.

An assistant computing days overdue from `due_date` uses its own clock, which is almost never the operator's calendar day. For an Australian tenant that shows yesterday as today for most of the working morning — on a counter the operator acts on. The server resolves the day in the tenant's timezone once per call, publishes it as `as_of_date`, and derives every counter from it. The related pattern is in [whose "today" is it](/docs/engineering-notes/whose-today-is-it).

## Filtering

`status: "overdue"` is **derived-inclusive**: it returns stored-overdue invoices *and* sent invoices already past their due date, so chasing late payers never misses an invoice the once-daily sweep has not reached. Every other status filter keeps exact stored-enum semantics.

`search` matches the invoice number by exact value or prefix, **or** the client's legal or trading name by case-insensitive substring. A term that matches nothing returns an empty list — never the newest invoices — so an empty result is a trustworthy "no such invoice" rather than a silent fallback an assistant would read as success.

Test rows are hidden by default; pass the include-test flag to surface them.

## What this replaced

Until v71.7.0 the tool returned `client_id` and no client name at all. An assistant asked *"which invoices are late and who are they for?"* could answer the first half and not the second without a second round of lookups — leaving it less informed than the operator looking at the same data on screen.

Surface parity is a platform rule rather than a nicety: if a human can read it, an agent reads it at the same depth. A capability that ships to one surface only is treated as incomplete.

## Related

- [Work your invoice list without opening a single invoice](/docs/how-to/work-your-invoice-list)
- [Trading name versus legal entity](/docs/explanation/trading-name-versus-legal-entity)
- [The attention digest: a dashboard your agent actually reads](/docs/reference/attention-digest)
- [Connecting Biloh over MCP](/docs/reference/mcp-overview)
- [Whose "today" is it? Timezones in a scheduling engine](/docs/engineering-notes/whose-today-is-it)
