# API reference: spend

Cloud work an AI agent's key asked for beyond the free allowance, held until a person confirms it. The agent's call answers `202` with a hold. A person approves or denies the hold from the Inbox. Approving creates the job under the agent's recorded request, attributed to its key.

## GET /v1/spend/holds

`listSpendHolds` · scope `key:read` · List held cloud spend

The holds the caller can see, newest first: those on their own account, on an organization they belong to, and those their keys filed. A key limited to repos sees only the holds it filed itself. `status` narrows the list. A pending hold past its `expires_at` reads as `expired`.

Parameters:

- `status` (query)
- `limit` (query): Page size.
- `cursor` (query): Opaque cursor from the previous page's `next_cursor`.

Responses:

- `200`: Page of holds.
- `401`: Missing or invalid credentials.
- `422`: Request failed validation.

## GET /v1/spend/holds/{hold_id}

`getSpendHold` · scope `key:read` · Get one held request

One hold, with what was asked, the estimate, what approving would reserve, the free allowance's share, and the account's spend cap as it stands now. An agent polls this after a `202`. A hold the caller may not see is `404`.

Parameters:

- `hold_id` (path, required): Held request id (`spend_...`).

Responses:

- `200`: The hold.
- `401`: Missing or invalid credentials.
- `404`: Resource not found (or hidden from the caller).

## POST /v1/spend/holds/{hold_id}/approve

`approveSpendHold` · scope `session` · Approve a held request and create its job (web session only)

A person of the account confirms the spend. **`session` scope**: no API key of any scope may approve, so an agent cannot confirm its own hold. The hub then creates the job from the recorded request, as the key that asked, with the agent limit lifted for this one job. The job is attributed to that key for usage. The reservation is taken now, and the account's spend cap is applied now, not when the hold was filed. A `429` leaves the hold pending. A hold that is no longer pending, or whose key no longer works, is `409`. Audited as `spend.approve`.

Parameters:

- `hold_id` (path, required): Held request id (`spend_...`).

Responses:

- `200`: Approved, with the job's id in `result_id`.
- `401`: Missing or invalid credentials.
- `403`: Authenticated but not allowed (visibility, membership or scope).
- `404`: Resource not found (or hidden from the caller).
- `409`: State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).
- `422`: Request failed validation.
- `429`: The account's spend cap is in `stop` mode, and this job would carry the month past it. The month counts recorded Lucen cloud spend, what queued and running cloud work could still cost, and this job's own most possible cost. `errors[].type` is `quota.spend_cap`. `Retry-After` counts the seconds until the month ends. Nothing was recorded, and work already running keeps running.
- `503`: A service this endpoint needs is not configured in this deployment. The API boots with zero secrets. An endpoint that needs one says so instead of returning a stack trace.

## POST /v1/spend/holds/{hold_id}/deny

`denySpendHold` · scope `session` · Deny a held request with a reason (web session only)

A person says no, and says why. The reason goes into the record the agent reads. **`session` scope**, like approving. `409` when the hold is no longer pending. Audited as `spend.deny`.

Parameters:

- `hold_id` (path, required): Held request id (`spend_...`).

Request body: `application/json`, required.

Responses:

- `200`: Denied.
- `401`: Missing or invalid credentials.
- `403`: Authenticated but not allowed (visibility, membership or scope).
- `404`: Resource not found (or hidden from the caller).
- `409`: State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).
- `422`: Request failed validation.

## POST /v1/spend/holds/{hold_id}/cancel

`cancelSpendHold` · scope `key:write` · Withdraw a held request

The key that filed the hold, or a person of the account, takes it back. Nothing was started, so nothing is undone. `409` when the hold is no longer pending. Audited as `spend.cancel`.

Parameters:

- `hold_id` (path, required): Held request id (`spend_...`).

Responses:

- `200`: Canceled.
- `401`: Missing or invalid credentials.
- `403`: Authenticated but not allowed (visibility, membership or scope).
- `404`: Resource not found (or hidden from the caller).
- `409`: State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).
