# GET /api/v2/subscription-products

List subscription products

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

Returns the organisation's sellable subscription products, sorted by name A to Z.

Defaults to the summary view; `view=detail` carries the same fields on this resource (no field of it is detail-only) and costs three read units instead of one. Search with `q=` over the product name, and pass `trashed=with|only` to see deleted products.

## 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[is_hosting] (boolean, optional): Whether the product is sold against a concrete site or server.
- filter[price_interval] (string, optional): Billing interval the product is priced per. Operators: = ne in.
- 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.
- 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.
- name (string ≤ 255, required): Product name as shown in the UI and on the subscription line.
- description (string or null, required, multiline): Invoice-line template for subscription lines built from this product. Supports the placeholder tokens {{$domain}}, {{$site}}, {{$server}}, {{$period}}, {{$product}} and {{$customer}}, which are filled in when the line is invoiced; the template itself is kept verbatim.
- purchase_price (decimal (2 dp) or null, required): What the product costs us, as a decimal string. Null when unknown.
- sales_price (decimal (2 dp) or null, required): What the customer is billed per interval, as a decimal string. Null when the line carries its own price.
- price_interval (string, required): Interval the sales price is charged per.
- is_hosting (boolean, required): Whether this is a hosting product: one sold against a concrete site or server, which is what lets a subscription line name the infrastructure it covers.
- vat_rate (integer or null, required): VAT rate as a whole percentage: 21 means 21%, not 0.21. Null when the rate is decided at invoicing time.
- exact_item_id (string or null ≤ 255, required): Exact Online article this product maps to, by Exact's own external identifier (`exact_items.exact_id`), not by a local id.
- exact_vat_code (string or null ≤ 255, required): Exact Online VAT code, by code (`exact_vat_codes.code`), not by id.
- exact_cost_center (string or null ≤ 255, required): Exact Online cost centre, by code (`exact_cost_centers.code`), not by id.
- 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 (SubscriptionProductLinks, 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/subscription-products' \
  --data-urlencode 'filter[is_hosting]=true' \
  --data-urlencode 'sort=name' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json'
```
