# GET /api/v2/budgets

List budgets

- MCP id: budgets.index
- Scope: api:read:budgets
- Permission: budgets::read
- Rate cost: 1 unit, read bucket. `view=detail` makes it 3. Adds: `q` +2.
- Cache-Control: private, no-store

Returns the organisation's budgets, lowest position first, each with the number of kanban tasks the caller may see.

There is no `projects/{project}/budgets` path: pass `filter[project_id]` to read one project's budgets, or `filter[budget_group_id]` to read one group's. Positions are numbered across the whole organisation and renumbered per project in plnnrly, so a page spanning several projects interleaves and ties break by id. Pass `view=detail` for the full payload at three read units instead of one; the two views are identical on this resource.

## 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. 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_group_id] (integer, optional): Budget group the budget is filed under. 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[end_date] (date (YYYY-MM-DD), optional): Last day the budget covers. Operators: = gte lte null.
- filter[is_billable] (boolean, optional): Whether time logged on this budget is billable.
- filter[is_open] (boolean, optional): Whether the budget is open-ended, i.e. its spendable cap is ignored and the over-budget warning is skipped.
- filter[project_id] (integer, optional): Owning project. Operators: = in.
- filter[start_date] (date (YYYY-MM-DD), optional): First day the budget covers. Operators: = gte lte null.
- filter[status] (string, optional): Lifecycle status of the budget. 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 `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.
- 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): Owning project. Settable at create time only; a budget cannot be moved to another project.
- budget_group_id (integer or null, required): Budget group this budget is filed under, `null` when ungrouped.
- order_column (integer, required, server-assigned): Position within the project as arranged in plnnrly. Set on create to the highest existing position plus one, and renumbered 1..n per project whenever the project's budget page is saved in plnnrly. Not writable.
- description (string ≤ 255, required): Budget description as shown in the UI. This is the budget's label and the only field `?q=` searches; a budget has no title.
- start_date (date (YYYY-MM-DD) or null, required): First day the budget covers, `null` when it has no start.
- end_date (date (YYYY-MM-DD) or null, required): Last day the budget covers, `null` when it has no end.
- budget (integer, required): Budgeted amount of work, in minutes.
- spendable (integer, required): The portion of the budget that may be allocated to tasks, in minutes. A stored value, never decremented by logged time.
- status (string, required): Lifecycle status of the budget.
- is_open (boolean, required): Whether the budget is open-ended: when `true` the spendable cap is ignored, plnnrly shows "open" in place of a minute total and the over-budget warning is skipped.
- is_billable (boolean, required): Whether time logged on this budget is billable.
- 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.
- project (Ref or ProjectSummary, required): The project this budget belongs to.
- budget_group (Ref or BudgetGroupSummary or null, required): The budget group this budget is filed under.
- tasks_count (integer ≥ 0, required): Number of kanban tasks on this budget that the caller may see.
- links (BudgetLinks, required)
  - self (url, required): The canonical URI of this record.
  - tasks (url, required): Kanban tasks filed under this budget.

## 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/budgets' \
  --data-urlencode 'filter[project_id]=1' \
  --data-urlencode 'filter[is_open]=true' \
  --data-urlencode 'sort=order_column' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json'
```
