# GET /api/v2/sales-invoices/{sales_invoice}/rows

List an invoice's lines

- MCP id: sales-invoices.rows.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 one sales invoice's lines in their printed order, `sort_order` ascending.

There is no organisation-wide collection of invoice lines: a line only means anything under the invoice it belongs to, so this nested path is the only way to list them and `filter[sales_invoice_id]` does not exist. A deleted invoice answers `404` for the whole path unless `trashed=with` is passed, which also includes the invoice's deleted lines. Defaults to the summary view; pass `include=sales_invoice` to expand the invoice each line points at.

## Parameters
- sales_invoice (integer or string, path): The SalesInvoice's numeric id. Or its invoice_number, matching `[A-Z]+-[0-9]{2}-[0-9]{5}|L-[0-9]+`. Accepts a soft-deleted record.
- 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. This collection's default sort is a display position that changes whenever someone drags the list into a new order in plnnrly, so a page number can skip or repeat a row if the list is reordered between two page requests. These collections rarely exceed one page; for a stable walk, send sort=id and follow links.next without page.
- filter[budget_id] (integer, optional): Project budget this invoice line settles. Operators: = in null.
- 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[is_aggregated_price] (boolean, optional): Whether the line carries one aggregated price rather than hours times an hourly rate.
- filter[subscription_item_id] (integer, optional): Subscription line this invoice line was generated from. Operators: = in 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 `name`. 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.
- sales_invoice_id (integer, required, server-assigned): The invoice this line belongs to. Taken from the path on create and never writable afterwards: a line cannot be moved to another invoice.
- sales_invoice (Ref or SalesInvoiceSummary, required): The invoice this line belongs to.
- subscription_item_id (integer or null, required): Subscription line this invoice line was generated from, when the row came out of a subscription renewal rather than being typed by hand.
- budget_id (integer or null, required): Project budget this invoice line settles, when the row was generated from budget hours.
- exact_item_id (string or null ≤ 255, required): Exact Online article identifier this line bills against. Empty is accepted, but a row without it fails the whole invoice's Exact sync.
- exact_vat_code (string or null ≤ 255, required): Exact Online VAT code, by code rather than by id.
- exact_cost_center (string or null ≤ 255, required): Exact Online cost-centre code.
- exact_gl_account (string or null ≤ 255, required): Exact Online general-ledger revenue account for this line.
- name (string ≤ 255, required): The line label, as printed on the invoice. Exact Online prints this when description is empty.
- description (string or null, required, multiline): Longer line text. Exact Online prints this in preference to name.
- quantity (decimal (2 dp), required): Number of units billed. Fractional and signed: a budget row carries hours, and a credit line carries a negative quantity.
- unit_price (decimal (2 dp), required): Price per unit, excluding VAT. This is what actually bills.
- hourly_rate (decimal (2 dp) or null, required): Rate plnnrly recomputes `unit_price` from when the aggregated-price toggle is switched on a budget row. Stored exactly as sent and `null` when omitted; it feeds no server-side computation.
- billable_minutes (integer, required): Minutes of budget time this line settles. Zero on a hand-typed row.
- is_aggregated_price (boolean, required): True: one line of quantity 1 priced at hours times rate. False: quantity carries the hours and `unit_price` the hourly rate.
- vat_rate (integer, required): VAT percentage as a whole number: 21 means 21%.
- vat_amount (decimal (2 dp), required, server-assigned): VAT on this line: quantity times `unit_price` times `vat_rate`, rounded to two decimals. Recomputed by the server whenever one of those three changes.
- total_amount (decimal (2 dp), required, server-assigned): Line total excluding VAT (quantity times `unit_price`), which is the convention the invoice form writes. Recomputed by the server whenever quantity or `unit_price` changes.
- sort_order (integer, required, server-assigned): Position of the line within its invoice, and the default sort key. Assigned on create; there is no way to reorder lines through the API.
- 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.
- deleted_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required): Soft-delete timestamp, `null` while live.
- links (SalesInvoiceRowLinks, 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/sales-invoices/1/rows' \
  --data-urlencode 'trashed=with' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json'
```
