---
title: "Read AI credits"
method: GET
path: "/credits"
tags: ["Usage"]
---

# Read AI credits

`GET /credits`

## Request

- Base URL: `https://app.mandala.computer/api/v1`
- URL: `https://app.mandala.computer/api/v1/credits`
- Auth: HTTP bearer

The authenticated account’s AI credit balance, live lots, current plan grant and paginated journal. Readable by every account role, including accounts with no plan or suspended accounts. Workspace-scoped keys are refused with 403 because the ledger belongs to the whole account. Amounts are integer USD micros (1,000,000 = $1), already including the funded markup. Operator notes, payment references, raw provider usage and internal request keys are never returned. Entries are newest first; follow next_before until null. Inserts made after the first page do not shift older pages. Summary values are current on each request, not pinned to the first page. This read never issues or expires credits. Unknown, duplicate, non-integer or out-of-range query parameters are refused with 400; the error names an unknown parameter, and says a repeated one is sent at most once.

Requires the `viewer` role or higher.

## Query parameters

- `before` integer
- `limit` integer

## Response `200`

Account credit summary and one journal page.

- Credits
  - `balance_micros` integer, required: Sum of unexpired, unspent lot remainders. One USD is 1,000,000 micros.
  - `available_micros` integer, required: Balance minus debt and held credits, saturated at the negative safe-integer bound. May be negative; zero while frozen. This is not rollout admission.
  - `held_micros` integer, required: Credits reserved for active model calls.
  - `debt_micros` integer, required: Outstanding debt, settled by future credit issuance.
  - `frozen` boolean, required: No recognized plan or account suspended. Credits are retained, but expiry continues.
  - `grant` CreditGrant, required
    - `period_start` string, required: Confirmed plan period start, RFC 3339 UTC; calendar month for operator plans.
    - `period_end` string, required: Exclusive end of the current grant period, RFC 3339 UTC.
    - `granted_micros` integer, required: Actually issued this period, including upgrade differences. Downgrading does not lower this high-water mark.
    - `used_micros` integer, required: Issued minus remaining. Includes spending, debt settlement, and removed credits; not solely model usage.
  - `lots` CreditLot[], required: Only unexpired lots with a positive remainder, earliest expiry first. Exhausted and expired lots are represented in journal history.
    - `id` string, required: Immutable lot identifier.
    - `kind` 'plan_grant' | 'admin_grant' | 'topup' | 'adjust', required
    - `amount_micros` integer, required: Original credit amount; one USD is 1,000,000 micros.
    - `remaining_micros` integer, required: Unspent amount, before account debt and active reservations.
    - `expires_at` string, required: Exclusive expiry instant, RFC 3339 UTC.
    - `created_at` string, required: Issue time, RFC 3339 UTC.
  - `entries` CreditEntry[], required
    - `id` integer, required: Journal sequence number. Entries are returned newest first.
    - `lot_id` string, nullable, required: The lot a credit, expire or debt_settle entry applies to. Always null on debit and clawback entries.
    - `kind` 'credit' | 'debit' | 'expire' | 'clawback' | 'debt_settle', required
    - `amount_micros` integer, required: Nonnegative amount. credit adds a lot; debit and clawback consume credits and may create debt; expire removes a remainder; debt_settle applies a new credit to existing debt. Do not sum kinds without their direction.
    - `debt_micros` integer, required: New debt created by this entry, not the running account debt balance.
    - `model` string, nullable, required: Billed model for a debit, otherwise null.
    - `run_id` string, nullable, required: Funded run identifier for a debit, otherwise null.
    - `step` integer, nullable, required: Model-call sequence within the run, otherwise null.
    - `created_at` string, required: Entry time, RFC 3339 UTC.
  - `next_before` integer, nullable, required: Pass as before for older entries; null on the final page. Balances and lots are current on every page.

## Other responses

- `400`: The request was malformed — a bad resolution string, a missing `command`, a `{pid}` or `{window}` that is not one. It is also what a well-formed request gets when the thing it names cannot be acted on and never will be — a restore onto a computer that is gone, for instance. Either way it is final: the message says what is wrong, and sending it again unchanged answers the same. That is the whole difference from a `503`, which means ask again shortly.
- `401`: Platform authentication required or refused: reason is missing, invalid, or revoked, with WWW-Authenticate: Bearer (invalid_token for invalid/revoked).
- `403`: The credential is recognized but its holder lacks the required role or membership, or the holder/account is suspended, or the holder has not verified their email address. An ordinary denial for too low a role may carry no `reason`. The email one carries none either, and its message begins `Verify your email before`: it answers a create, a clone of a computer or a snapshot, a new API key and a new SSH key, and is cleared by verifying the address in the dashboard. `reason: "revoked"` marks authority that is gone: a suspended account or a lost membership (refused as the request arrives, or if it happens while the request is in flight), or a role that was lowered while the request was in flight. The message identifies the refusal; replacing a valid key alone does not restore authority, and sending the same call again does not help. A key confined to a workspace cannot read this account-wide credit ledger. Use an account-wide key. A suspended account may still make this read: a `403` here is about the credential or the person holding it, never the account’s suspension.
- `405`: This public path exists but does not accept that method. Allow lists only its public methods, with HEAD for GET and metadata OPTIONS.
- `429`: You are asking faster than your plan allows. `Retry-After` says how many seconds to wait, and every metered response — anything past authentication and the re-check that your credential’s authority still holds — carries `RateLimit-Remaining`, so you do not have to be refused to find out. A `401`, a `403` from that re-check, an `OPTIONS` answer and an unexpected `500` carry none. The budget is spent in proportion to what a call costs rather than per request. What a call costs, in budget units, by kind (each operation’s exact figure is its `x-rate-cost` in the OpenAPI document): 1 to read one thing; 5 for a listing that spans the fleet, and 5 for a call into a running guest such as a screenshot or an exec; 10 for a power change and 10 to move file bytes; 25 for `GET /usage`, `GET /credits`, creating a public URL, and creating, rotating or testing a webhook; 50 to capture a snapshot; 100 to create, clone or restore a computer, and 100 to start an agent run, whose actions are then charged as they happen. The account-wide budget, as what an idle caller may spend at once and what refills each minute: No plan 1200 at once and 1200 a minute; Solo 4800 at once and 4800 a minute; Studio 12000 at once and 12000 a minute; Swarm 21600 at once and 21600 a minute. A single API key gets half of either. A `401` for a missing or unusable credential costs nothing: it is refused before anything is metered. A few refusals at the door cost one unit: a `403` because the role is too low or the route refuses an API key confined to a workspace, a `404` for a path that matches nothing, a `405`, and an `Idempotency-Key` answered from its stored result or still in progress. Everything else is charged in full when it is admitted, before it is carried out, so a refusal after that has spent the whole cost: a `400`, `402` or `409` from the route, a `403` its handler sends (on most routes, a suspended account included), and a model provider’s `401` on an agent run. The budget belongs to the account and is shared across everything on it, and any single API key may spend at most half of it. `RateLimit-Limit` is whichever of those two is binding on the request that carried it — usually the per-key half, and the account-wide figure when other keys have already spent more of it than this one has. All three `RateLimit-*` headers describe the same one, so `limit` minus `remaining` is what has been spent against it. A few `429`s are not about this budget, say so in their message, and can carry no `Retry-After`: a model provider’s rate limit relayed on an agent run. The cap on forks of one snapshot waiting for their secrets sends its own bounded expiry-based `Retry-After` with `reason: "contention"` (see `POST /snapshots/{id}/clone`).
- `500`: The application could not finish this: an unexpected exception, a build that broke, or a storage fault. Application refusals carry the request correlation ID; unexpected internal details are not returned.

---

[API](https://skmtc.dev/mandala/apis/mandala-computer-api.md) · [All operations](https://skmtc.dev/mandala/apis/mandala-computer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mandala/apis/mandala-computer-api/revisions/2afbb87fa703?raw)
