> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Credits

> Real-USD balance, metered ledger, and top-ups.

Billing is **real US dollars** held as a credit balance on the [organization](/docs/api/organizations). Every metered action debits the balance; top-ups credit it. All money fields are integer **micro-USD** (`*_micro_usd`, where 1 USD = 1,000,000 micro-USD).

## Balance

`GET /v1/credits/balance` returns the current balance.

<ResponseField name="object" type="string">Always `credit_balance`.</ResponseField>
<ResponseField name="balance_micro_usd" type="integer">Available balance, in micro-USD.</ResponseField>
<ResponseField name="mode" type="string">`live` or `test` — the tier this balance is held in.</ResponseField>

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/credits/balance \
  -H "authorization: Bearer sk_live_..."
```

<ResponseExample>
  ```json Response theme={"system"}
  { "object": "credit_balance", "balance_micro_usd": 48250000, "mode": "live" }
  ```
</ResponseExample>

## Ledger

`GET /v1/credits/ledger` lists metered entries, newest first. Cursor-paginated — see [Pagination](/docs/api/pagination). Each inference debit carries a **5-tier token line-item** breakdown of what was metered.

<ResponseField name="data" type="object[]">
  Ledger entries.

  <Expandable title="entry">
    <ResponseField name="id" type="string">Entry id (e.g. `led_01H...`).</ResponseField>
    <ResponseField name="type" type="string">`debit` (usage) or `credit` (top-up/adjustment).</ResponseField>
    <ResponseField name="amount_micro_usd" type="integer">Signed amount, in micro-USD.</ResponseField>
    <ResponseField name="balance_after_micro_usd" type="integer">Running balance after this entry.</ResponseField>

    <ResponseField name="actor" type="object">
      The principal responsible for the entry — the same shape as the [audit log](/docs/api/audit-logs) actor.

      <Expandable title="actor">
        <ResponseField name="type" type="string">`user`, `key`, or `system`.</ResponseField>
        <ResponseField name="id" type="string">Id of the member (`usr_…`) or API key (`key_…`). A settled top-up is credited by `{ "type": "system", "id": "payment_webhook" }`.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="session_id" type="string | null">The session that incurred the charge, if any.</ResponseField>
    <ResponseField name="agent_id" type="string | null">The agent that ran the session, if any.</ResponseField>
    <ResponseField name="deployment_id" type="string | null">The deployment whose run incurred the charge, if any.</ResponseField>

    <ResponseField name="line_items" type="object[]">
      The 5-tier token breakdown of a metered inference debit. Each inference debit records all five classes.

      <Expandable title="line_item">
        <ResponseField name="tier" type="string">One of `input`, `cache_write`, `cache_read`, `output`, `reasoning`.</ResponseField>
        <ResponseField name="amount_micro_usd" type="integer">Amount metered for this token class.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="created_at" type="string">Entry timestamp.</ResponseField>
  </Expandable>
</ResponseField>

Filter the ledger with any combination of `?type=`, `?session_id=`, `?agent_id=`, `?deployment_id=`, `?from=`, and `?to=` (the `from`/`to` bounds are RFC 3339 timestamps).

```bash theme={"system"}
curl -fsSL "https://api.vetta.sh/v1/credits/ledger?limit=20&type=debit&agent_id=agt_01H..." \
  -H "authorization: Bearer sk_live_..."
```

```json Response theme={"system"}
{
  "data": [
    {
      "id": "led_01H9KK...",
      "type": "debit",
      "amount_micro_usd": -412300,
      "balance_after_micro_usd": 48250000,
      "actor": { "type": "key", "id": "key_01H8XK..." },
      "session_id": "ses_01H9AB...",
      "agent_id": "agt_01H8XK...",
      "deployment_id": null,
      "line_items": [
        { "tier": "input",       "amount_micro_usd": 148000 },
        { "tier": "cache_write", "amount_micro_usd": 32100 },
        { "tier": "cache_read",  "amount_micro_usd": 11400 },
        { "tier": "output",      "amount_micro_usd": 196500 },
        { "tier": "reasoning",   "amount_micro_usd": 24300 }
      ],
      "created_at": "2026-08-20T17:05:04Z"
    }
  ],
  "has_more": true,
  "next_cursor": "led_01H9KK..."
}
```

## Top up

`POST /v1/credits/topups` opens a hosted charge and returns a **topup object** describing it. It does **not** move the balance: credit is applied when the payment provider's signed callback arrives and is verified, so poll the [balance](#balance) rather than reading the create response as settlement. Requires the `billing:write` scope.

<ParamField body="amount_micro_usd" type="integer" required>Amount to add, in micro-USD.</ParamField>

### The topup object

<ResponseField name="id" type="string">Top-up id (e.g. `top_01H...`).</ResponseField>
<ResponseField name="amount_micro_usd" type="integer">Amount requested, in micro-USD.</ResponseField>

<ResponseField name="status" type="string">
  Lifecycle state. One of:

  * `pending` — the charge is being processed.
  * `requires_action` — the payment needs additional authentication (e.g. 3-D Secure). Complete it via `action_url`, then poll.
  * `succeeded` — the charge cleared and the balance was credited.
  * `failed` — the charge was declined; the balance is unchanged.
</ResponseField>

<ResponseField name="action_url" type="string | null">Present only when `status` is `requires_action`: the URL to complete authentication.</ResponseField>
<ResponseField name="balance_after_micro_usd" type="integer | null">Always `null` on this response — the balance moves only when the provider callback settles the charge. Read the [balance](#balance) instead.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/credits/topups \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -H "idempotency-key: $(uuidgen)" \
  -d '{ "amount_micro_usd": 50000000 }'
```

```json Response theme={"system"}
{
  "id": "top_01H9LL...",
  "object": "topup",
  "amount_micro_usd": 50000000,
  "status": "requires_action",
  "action_url": "https://checkout.example/pay/cs_01H9LL...",
  "balance_after_micro_usd": null,
  "created_at": "2026-08-20T17:35:00Z"
}
```

### Confirming a charge that is not yet terminal

A charge can land in `pending` or `requires_action`, so the `status` on the create response is not always the final word.

<Warning>
  **There is no `GET /v1/credits/topups/{id}`.** A top-up is not individually addressable — that route is not served and answers `404 not_found`. Confirm a charge by re-reading the balance or the ledger, not by polling a top-up id.
</Warning>

Two ways to reach the terminal state:

* **Poll the balance.** `GET /v1/credits/balance` reflects the credit as soon as the charge clears. This is the simplest check when you know the amount you expect.
* **Read the ledger.** `GET /v1/credits/ledger` lists entries newest-first; the settled top-up appears as a `credit` entry. Match it by `created_at` and `amount_micro_usd`.

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/credits/ledger?limit=1 \
  -H "authorization: Bearer sk_live_..."
```

When `status` comes back `requires_action`, send the payer to `action_url` first; the balance does not move until they complete authentication.

<Note>
  When the balance is too low to cover a call, the API returns `insufficient_credits` (402); when a call would breach a configured per-agent or per-session cap it returns `budget_exceeded` (402). Top up (or raise the cap), then retry — see [Errors](/docs/api/errors) and [Agents](/docs/api/agents). Successful top-ups are recorded in the [audit log](/docs/api/audit-logs) as `credits.topped_up`.
</Note>

## The plan

Credits pay for what an agent *does*. The **plan** is separate: it is the right to use the organization at all. Every organization needs an active plan — **\$20 per month** — before any route outside billing will answer. Without one the API returns `subscription_required` (402) with the message telling you exactly what to do.

Creating an organization does not start a plan. Buy one, then the organization works.

### Reading the plan

`GET /v1/credits/subscription` returns the current plan. Requires the `billing:read` scope, and is answerable whether or not a plan is active.

<ResponseField name="status" type="string">
  One of:

  * `none` — no plan has ever started. Every gated route is refused.
  * `active` — the organization can be used.
  * `past_due` — the last payment did not go through. The organization keeps working until `grace_until`, then is refused.
  * `canceled` — the plan ended. Every gated route is refused.
</ResponseField>

<ResponseField name="current_period_end" type="string | null">When the paid period ends, or `null` if no plan has ever started.</ResponseField>
<ResponseField name="grace_until" type="string | null">While `past_due`, the moment the organization stops working. `null` means no grace remains.</ResponseField>
<ResponseField name="cancel_at_period_end" type="boolean">`true` once a cancellation has been asked for. The organization stays usable until `current_period_end` and is refused after it.</ResponseField>
<ResponseField name="action_url" type="string | null">Always `null` on a read; see below.</ResponseField>

<Note>
  **An `active` plan whose `current_period_end` is more than three days in the past stops being honoured.** Renewals reach us as signed callbacks, and a callback that never arrives must not become permanent free access. Any later callback moves the deadline forward again, so a plan that is genuinely paid heals itself the moment the next event lands.
</Note>

```bash theme={"system"}
curl -fsSL https://api.vetta.sh/v1/credits/subscription \
  -H "authorization: Bearer sk_live_..."
```

```json Response theme={"system"}
{
  "object": "subscription",
  "status": "active",
  "current_period_end": "2026-09-22T17:30:00Z",
  "grace_until": null,
  "cancel_at_period_end": false,
  "action_url": null
}
```

### Starting or renewing the plan

`POST /v1/credits/subscription` opens a hosted checkout page and returns its URL as `action_url`. It takes no body. Requires the `billing:write` scope.

Like a top-up, it changes **nothing** on its own: the organization becomes usable once the payment is confirmed, which is normally a second or two after the customer completes the page. Re-read `GET /v1/credits/subscription` until `status` is `active`.

```bash theme={"system"}
curl -fsSL -X POST https://api.vetta.sh/v1/credits/subscription \
  -H "authorization: Bearer sk_live_..."
```

```json Response theme={"system"}
{
  "object": "subscription",
  "status": "none",
  "current_period_end": null,
  "grace_until": null,
  "cancel_at_period_end": false,
  "action_url": "https://checkout.example/c/pay/..."
}
```

### Cancelling the plan

`DELETE /v1/credits/subscription` stops the plan **at the end of the period already paid for** — never immediately, and nothing is refunded or charged again. Requires the `billing:write` scope, and like the two routes above it answers whether or not the organization is currently usable: a recurring charge you can only stop by paying first is not one a customer can stop.

The organization keeps working until `current_period_end`, and is refused after it. Re-read `GET /v1/credits/subscription` to see `cancel_at_period_end` become `true` — the state follows the payment provider's confirmation, so the read is the truth, not this response.

```bash theme={"system"}
curl -fsSL -X DELETE https://api.vetta.sh/v1/credits/subscription \
  -H "authorization: Bearer sk_live_..."
```

```json Response theme={"system"}
{
  "object": "subscription",
  "status": "active",
  "current_period_end": "2026-09-22T17:30:00Z",
  "grace_until": null,
  "cancel_at_period_end": true,
  "action_url": null
}
```

To restart after cancelling, `POST /v1/credits/subscription` again.

### Managing the payment method, invoices and cancellation

`POST /v1/credits/billing_portal` returns a link to the payment provider's own hosted account page, where a customer can change the card on file, download past invoices, and cancel. It takes no body and requires the `billing:write` scope. The link is single-use and expires — follow it, do not store it.

Like every other route on this page it changes **nothing** by itself. Whatever the customer does on that page arrives back as the same confirmed events that already move the plan, so re-read `GET /v1/credits/subscription` afterwards rather than assuming an outcome.

```bash theme={"system"}
curl -fsSL -X POST https://api.vetta.sh/v1/credits/billing_portal \
  -H "authorization: Bearer sk_live_..."
```

```json Response theme={"system"}
{
  "object": "billing_portal_session",
  "action_url": "https://billing.example/session/..."
}
```

An organization that has never held a plan has no account to manage, and this answers `404 not_found` rather than an empty page:

```json 404 theme={"system"}
{
  "error": {
    "type": "not_found",
    "code": "not_found",
    "message": "this organization has no billing account to manage; start the plan with `POST /v1/credits/subscription` first",
    "request_id": "req_..."
  }
}
```

### What stays open without a plan

An organization with no plan can still identify itself, read its own billing state, and pay. Everything else is refused with `subscription_required` (402).

| Open without a plan                                                                    | Refused without a plan                                   |
| -------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `POST /v1/organizations`, `POST /v1/auth/session`, `POST /v1/auth/users`               | `/v1/agents/*`, `/v1/sessions/*`, `/v1/computers/*`      |
| `GET /v1/me`, `GET /v1/organizations`, `GET /v1/organizations/{id}`                    | `/v1/deployments/*`, `/v1/files/*`, `/v1/skills/*`       |
| `GET`, `POST` and `DELETE /v1/credits/subscription`, `POST /v1/credits/billing_portal` | `/v1/api_keys`, `/v1/webhooks`, `/v1/vaults/*`           |
| `GET /v1/credits/balance`, `GET /v1/credits/ledger`, `POST /v1/credits/topups`         | `/v1/identities/*`, `/v1/domains/*`, `/v1/connections/*` |

<Note>
  **A session already running is never cut off mid-turn.** The plan is checked when work is *admitted* — starting a session, sending it a message, or a schedule firing — not on every turn of a session already under way. A lapse stops new work; it does not kill a task in flight. Plan changes are recorded in the [audit log](/docs/api/audit-logs) as `subscription.updated`, and a customer's own cancellation as `subscription.canceled`.
</Note>
