# GET /api/v2/invoices

List invoices

- MCP id: invoices.index
- Scope: api:read:invoices
- Permission: invoices::read
- Needs the billing feature on your organisation
- Rate cost: 1 unit, read bucket. `view=detail` makes it 3. Adds: each include +1, `q` +2.
- Cache-Control: private, no-store

Returns the legacy per-project invoices, newest invoice date first.

Every invoice in the organisation is visible to any caller holding `invoices::read`; there is no per-record rule. Scope to one project with `filter[project_id]`, to a period with `filter[date][gte]` and `filter[date][lte]`, and search with `q=` over the description. Defaults to the summary view; `view=detail` carries the same fields on this resource (none of them is detail-only) and costs three read units instead of one. Line items are not published, so no total appears here.

## Parameters
- view (string, optional): Which field set to render.
- per_page (integer 1 to 100, optional): Rows per page. At most 25 when `include` names a to-many relation; a larger value is 400 invalid-query-parameter.
- cursor (string, optional): Opaque keyset cursor from a previous page's links.next. Never construct one by hand.
- page (integer 1 to 1000, optional): Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request.
- filter[created_at] (timestamp (YYYY-MM-DDThh:mm:ssZ), optional): Creation timestamp. Pair with `sort=id` for a stable full export. Operators: = gte lt.
- filter[date] (date (YYYY-MM-DD), optional): Invoice date. Combine gte and lte to select a period, e.g. one quarter. Operators: = gte lte.
- filter[project_id] (integer, optional): Project the invoice was raised for. This is how you list one project's invoices: there is no nested path for them. Operators: = in.
- filter[updated_at] (timestamp (YYYY-MM-DDThh:mm:ssZ), optional): Last-write timestamp. Use for incremental sync. Operators: = gte lt.
- filter[user_id] (integer, optional): Employee the invoice is attributed to. `null` selects the invoices whose recorder has since been deleted. Operators: = in null.
- q (string, optional): Free-text search over `description`. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.
- sort (string, optional): Up to three keys, comma-separated, each optionally prefixed with `-` for descending.
- include (string, optional): Comma-separated names to inline, at most five.
- X-Request-Id (header, optional): A correlation id of your own. It is recorded in our logs next to the server's request id but never returned: the response header always carries the server's id. A value that does not match the pattern is discarded silently, never rejected.
- If-None-Match (header, optional): The ETag a read is conditional on. A match is answered 304 with no body.

## Response 200
- id (integer, required): Unique identifier.
- project_id (integer, required): Project this invoice was raised for.
- project (Ref or ProjectSummary, required): The project this invoice was raised for.
- user_id (integer or null, required, server-assigned): Employee the invoice is attributed to. Assigned from the authenticated user on create and never writable; `null` once that user is deleted.
- user (Ref or UserSummary or null, required): The employee this invoice is attributed to. Expanding it with `?include=user` additionally requires the `users::read` permission and a token scope covering the users prefix.
- description (string, required, multiline): What was invoiced, as free text. This is the field `?q=` searches.
- date (date (YYYY-MM-DD), required): Invoice date, a zone-free calendar date. Newest first is the default sort.
- hourly_rate (decimal (2 dp) ≥ 0, required): Hourly rate this invoice was billed at, as a decimal string with two places. Never rounded or rescaled.
- created_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Creation timestamp.
- updated_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a legacy invoice has no `version` field.
- links (InvoiceLinks, required)
  - self (url, required): The canonical URI of this record.

## Errors
- 403 forbidden-by-policy | insufficient-permission | insufficient-token-scope: Forbidden.
- 304 : Not modified.
- 400 idempotency-key-required | invalid-cursor | invalid-query-parameter | malformed-request | unsupported-parameter: Bad request.
- 401 token-expired | unauthenticated: Unauthenticated.
- 404 endpoint-not-found | resource-not-found: Not found.
- 406 not-acceptable: Not acceptable.
- 429 rate-limit-exceeded: Too many requests.
- 500 internal-error: Server error.
- 503 tenant-unavailable: Service unavailable.

## Example
```bash
curl -G \
  'https://<your-url>/api/v2/invoices' \
  --data-urlencode 'filter[project_id]=1' \
  --data-urlencode 'filter[date][gte]=2026-01-01' \
  --data-urlencode 'filter[date][lte]=2026-03-31' \
  --data-urlencode 'sort=-date' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json'
```
