# GET /api/v2/sales-invoices

List sales invoices

- MCP id: sales-invoices.index
- Scope: api:read:sales_invoices
- Permission: sales_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 organisation's sales invoices, newest invoice date first.

Defaults to the summary view; `view=detail` adds `sync_error` and `internal_notes` and costs three read units instead of one. Search with `q=` over the invoice number, and pass `trashed=with|only` to see deleted invoices. Combine `filter[invoice_date][gte]` and `[lte]` for a period, `filter[status]=draft` or `pending_approval` for the concept and approval queues in plnnrly, and `filter[sync_status]=failed` for invoices the accounting sync rejected. Sorting by total or invoice number is deliberately not offered, so a money-ordered page has to be sorted client-side.

## 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[customer_id] (integer, optional): Customer the invoice is billed to. Operators: = in.
- filter[customer_subscription_id] (integer, optional): Customer subscription the invoice was generated from. Pass `[null]` for invoices that were not generated from a renewal. Operators: = in null.
- filter[due_date] (date (YYYY-MM-DD), optional): Date payment is due. Pass `[null]` for invoices with no term applied. Operators: = gte lte null.
- filter[invoice_date] (date (YYYY-MM-DD), optional): Invoice date, a zone-free calendar date. Combine `[gte]` and `[lte]` for a period. Operators: = gte lte.
- filter[is_legacy] (boolean, optional): Whether the invoice was converted from a legacy invoice, and is therefore read-only.
- filter[legacy_invoice_id] (integer, optional): The id of the legacy invoice this invoice was converted from, so it can be matched with `GET /api/v2/invoices`. Operators: = in null.
- filter[paid_at] (timestamp (YYYY-MM-DDThh:mm:ssZ), optional): When the invoice was settled. Pass `[null]` for unsettled invoices. Operators: = gte lte null.
- filter[project_id] (integer, optional): Project the invoice was raised for. Pass `[null]` for invoices raised against no project. Operators: = in null.
- filter[should_email] (boolean, optional): Whether the invoice is emailed to the customer when it syncs.
- filter[status] (string, optional): Lifecycle status of the invoice. Operators: = ne in.
- filter[sync_attempted_at] (timestamp (YYYY-MM-DDThh:mm:ssZ), optional): When the invoice was last handed to the sync, successfully or not. Operators: = gte lte null.
- filter[sync_status] (string, optional): How far the invoice has got through the accounting sync. `filter[sync_status]=failed` reproduces the sync error page in plnnrly. Operators: = ne in.
- filter[synced_at] (timestamp (YYYY-MM-DDThh:mm:ssZ), optional): When the invoice last synced successfully. Pass `[null]` for invoices that have never synced. Operators: = gte lte null.
- filter[updated_at] (timestamp (YYYY-MM-DDThh:mm:ssZ), optional): Last-write timestamp. Use for incremental sync. Operators: = gte lt.
- q (string, optional): Free-text search over `invoice_number`. 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.
- trashed (string, optional): Whether soft-deleted rows are included.
- 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.
- customer_id (integer, required): Customer this invoice is billed to.
- customer (Ref or CustomerSummary, required): The customer this invoice is billed to. Expanding it with `?include=customer` additionally requires the `customers::read` permission and a token scope covering the customers prefix.
- project_id (integer or null, required): Project this invoice was raised for, when it was raised from one.
- customer_subscription_id (integer or null, required): Customer subscription this invoice was generated from, when it was generated from a renewal.
- invoice_number (string ≤ 255, required, server-assigned): Invoice number, allocated by the server from the organisation's yearly sequence: B-26-00001 for an invoice raised in this flow, L-1234 for one converted from a legacy invoice. It also works in the path: `GET sales-invoices/B-26-00001` works. This is the field `?q=` searches.
- reference (string or null ≤ 255, required): The customer's own reference, typically a purchase-order number, reproduced on the invoice.
- description (string or null ≤ 255, required): One-line description of what was invoiced.
- invoice_date (date (YYYY-MM-DD), required): Invoice date, a zone-free calendar date. Newest first is the default sort.
- due_date (date (YYYY-MM-DD) or null, required): Date payment is due, a zone-free calendar date. Null when no term was applied.
- status (string, required, server-assigned): Lifecycle status. Resolved by the server on create and never writable: a caller without `sales_invoices::finalize` creates a `pending_approval` invoice, and any update by such a caller returns the invoice to `pending_approval`.
- should_email (boolean, required): Whether the invoice is emailed to the customer when it syncs. The email is sent through the accounting integration, so it is forced to `false` in an organisation with no integration configured. Writing it requires `sales_invoices::finalize`.
- subtotal (decimal (2 dp), required, server-assigned): Sum of the row line totals excluding VAT, as a decimal string. Recomputed from the rows; never written directly.
- vat_total (decimal (2 dp), required, server-assigned): Sum of the row VAT amounts, as a decimal string. Recomputed from the rows; never written directly.
- total (decimal (2 dp), required, server-assigned): Subtotal plus VAT, as a decimal string. Recomputed from the rows; never written directly.
- exact_invoice_id (string or null ≤ 255, required, server-assigned): The accounting system's own identifier for this invoice, written by the sync. Not a local id.
- exact_journal (string or null ≤ 255, required, server-assigned): Sales journal the invoice is booked in, resolved from the organisation's accounting settings on create. Null in an organisation with no integration configured.
- sync_status (string, required, server-assigned): How far the invoice has got through the accounting sync. Set on create: local in an organisation with no integration configured, pending otherwise.
- sync_error (string or null, required, detail only, server-assigned, multiline): Why the last sync attempt failed, verbatim from the accounting system. Null while nothing has failed.
- synced_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required, server-assigned): When the invoice last synced successfully.
- sync_attempted_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required, server-assigned): When the invoice was last handed to the sync, successfully or not.
- paid_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required, server-assigned): When the invoice was settled. Set on create for a local invoice that is immediately definitive; otherwise written when payment is registered.
- is_legacy (boolean, required, server-assigned): Whether this invoice was converted from a legacy invoice. Such an invoice is read-only: PATCH and DELETE answer `409` `state-conflict` with reason `locked_by_legacy`.
- legacy_invoice_id (integer or null, required, server-assigned): The id of the legacy invoice this invoice was converted from, so it can be matched with `GET /api/v2/invoices`. The legacy invoice may since have been removed.
- internal_notes (string or null, required, detail only, multiline): Notes for colleagues. Never rendered on the invoice the customer receives.
- rows_count (integer ≥ 0, required): Number of line rows on this invoice.
- 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 sales invoice has no `version` field.
- deleted_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required): Soft-delete timestamp, `null` while live.
- links (SalesInvoiceLinks, required)
  - self (url, required): The canonical URI of this record.
  - rows (url, required): Line rows on this invoice.

## 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/sales-invoices' \
  --data-urlencode 'filter[customer_id]=1' \
  --data-urlencode 'filter[invoice_date][gte]=2026-01-01' \
  --data-urlencode 'filter[sync_status]=pending' \
  --data-urlencode 'sort=-invoice_date' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json'
```
