# POST /api/v2/budgets

Create a budget

- MCP id: budgets.store
- Scope: api:write:budgets
- Permission: budgets::create
- Rate cost: 1 unit, write bucket. A write costs the same whatever `view` you ask for.
- Cache-Control: private, no-store

Creates a budget on the project named in the body and returns it with a `Location` header.

`project_id` is settable here and nowhere else: a budget cannot be moved to another project afterwards. budget and spendable are minutes, not money. The position is set by the server to the highest existing position in the organisation plus one. The write also bumps the owning project's `updated_at`, so the project's `ETag` changes and it reappears in an incremental `filter[updated_at][gte]` window, and it is recorded in the project's activity timeline rather than a timeline of its own.

## Parameters
- view (string, optional): Which field set to render.
- 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.
- Idempotency-Key (header, optional): A caller-chosen key that makes this POST safe to retry. Replaying it returns the original response with Idempotency-Replayed: true.

## Request body
- project_id (integer → Project.id, required): Owning project. Settable at create time only; a budget cannot be moved to another project.
- 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.
- budget (integer ≥ 0, required): Budgeted amount of work, in minutes.
- spendable (integer ≥ 0, optional): The portion of the budget that may be allocated to tasks, in minutes. A stored value, never decremented by logged time.
- status (string, optional): Lifecycle status of the budget.
- is_open (boolean, optional): 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, optional): Whether time logged on this budget is billable.
- start_date (date (YYYY-MM-DD) or null, optional): First day the budget covers, `null` when it has no start.
- end_date (date (YYYY-MM-DD) or null, optional): Last day the budget covers, `null` when it has no end. Must be on or after start_date.
- budget_group_id (integer or null → BudgetGroup.id, optional): Budget group this budget is filed under, `null` when ungrouped.

Unknown keys are refused with `422` validation-failed, naming the key.

## Response 201
- 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.
- 409 idempotency-key-in-flight | idempotency-key-reused: Conflict.
- 415 unsupported-media-type: Unsupported media type.
- 422 unprocessable-relationship | validation-failed: Unprocessable content.
- 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 -X POST \
  'https://<your-url>/api/v2/budgets' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <key>' \
  -d '{
    "project_id": 1,
    "description": "Front-end development",
    "budget": 4800,
    "spendable": 4200,
    "is_open": false,
    "is_billable": true
  }'
```
