What the account has used, and how much of it has settled for billing.
Base URL https://app.mandala.computer/api/v1. Every request carries an API key, and failures take the shape under Errors.
GET/usage
Read usageviewer+
What this account has used — running hours weighted by cores and memory, and the storage it holds — with the per-computer breakdown behind the totals. The same figures the dashboard shows, and the read to build a spend check around: a loop that launches computers is the caller that can run up a bill without noticing.
The default window is the period your plan bills on, which is what makes this comparable with an invoice — up to the 62 days this endpoint reads at once. A period longer than that (an annual plan, a long trial) is reported in period in full and MEASURED over the most recent 62 days of it; from and to in the response always say which window the figures cover, so compare those against period before reconciling. from and to override the default, and you will want them for a window that has closed — the billing period is the CURRENT one, and by the time an invoice arrives the period it covers is not.
READ degraded AND unmetered BEFORE USING THE NUMBERS. Every figure here is a sum across the hypervisors your computers are on, so a host that did not contribute does not leave a hole you could notice — it leaves a total that is quietly too small. Those two flags are how that says so, and it is why this endpoint answers 200 with a caveat rather than the 503 the listings use: one of the two shortfalls never clears by retrying.
The account, not the key: a workspace-scoped key reads the whole account’s totals, because usage is metered per account and billed per account. What it does not get is usage.computers, which would name machines outside its scope.
Only from and to are read. Any other query parameter is ignored rather than refused, and a bound sent twice is read from its first value.
Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.
curl
base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body "${base_url%/}"'/usage' \
-H "Authorization: Bearer $MANDALA_API_KEY"
Python — Mandala SDK 0.16.0
# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client
with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
result = client.usage.read()
print(result)
TypeScript — Mandala SDK 0.16.0
// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";
const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const result = await client.usage.read();
console.log(JSON.stringify(result, null, 2));
The start of the window, RFC 3339 with a time zone — 2026-08-01T00:00:00Z. A stamp without one is refused rather than guessed at, because the zone that would be assumed is ours and not yours. Defaults to the start of the billing period, so send it together with to when you are asking about a period that has closed — to on its own is measured from the current period and refuses. When you send NEITHER bound and the billing period is longer than 62 days, the default is the most recent 62 days of it rather than a refusal, because you did not choose that window; from and to in the response say so. Records go back about 399 days, to a UTC day boundary: the refusal names the exact instant, and asking from that instant works. An older from is refused rather than answered with the zeroes an expired ledger would otherwise produce. A from at or after the present instant is a 400 too: that period has not started yet.
to
string
The end of it, same format. Defaults to now — always now, so omitting it always measures up to the present instant and never to the end of a period that has closed. A to in the future is answered as now too — the response says which instant was used. The window itself may be at most 62 days: every hypervisor replays its ledger a day at a time to answer, so a longer one THAT YOU NAMED is refused rather than quietly shortened — you chose it and can narrow it. The default window is the one exception, because you did not choose it: a billing period longer than 62 days is measured over its most recent 62 and from/to report that. Two billing periods is the pair a reconciliation compares; an older period is still readable by naming both bounds.
Response
The totals for the window, and whether anything is missing from them.
The period this account is billed on. NOT necessarily the window that was measured — see from and to, which are.
fromalways
string
The start of the window these figures cover, RFC 3339.
toalways
string
The end of it, RFC 3339, and worth reading rather than assuming. A to in the future is answered as now, because the future holds no usage; everything else is the instant you asked for.
A hypervisor could not be reached, so every figure above may be too small. This is NOT a 503, and it is not a gap: each figure is a sum across the fleet, so a host that did not answer leaves a total that is quietly short rather than an obviously missing row. Do not reconcile against an invoice while this is true — retry, and it clears when the host comes back.
unmeteredalways
boolean
The same shortfall from the other cause, and it is separate because it does NOT clear on its own: a hypervisor is up and running a daemon older than the meter, so it has no hours to report. Waiting does not fix this one.
reported_throughalways
string or null
The last UTC day (YYYY-MM-DD) whose usage has settled for billing — a contiguous prefix, so a day still being held back stops the count where it is. Null when none of the window has settled yet. This is not a caveat on the totals: those are live from the ledger and true through to. It answers the other question, which is how much of the same window has reached the billing system, and it is the one to check before comparing these numbers with an invoice. NULL, not absent, until something has settled — which is every account today.
Failures
403
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.
GET/credits
Read AI creditsviewer+
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.
Set MANDALA_API_KEY to your bearer key. MANDALA_BASE_URL, when set, is the full API base including /api/v1. Replace the illustrative resource IDs with yours. These pinned 0.16.0 SDK and direct HTTP examples do not read saved device-login credentials.
curl
base_url="${MANDALA_BASE_URL:-}"
if [ -z "$base_url" ]; then base_url='https://app.mandala.computer/api/v1'; fi
curl --fail-with-body "${base_url%/}"'/credits' \
-H "Authorization: Bearer $MANDALA_API_KEY"
Python — Mandala SDK 0.16.0
# Python 3.10+: pip install mandala-computer==0.16.0
import os
from mandala_computer import Client
with Client(api_key=os.environ["MANDALA_API_KEY"], base_url=os.environ.get("MANDALA_BASE_URL") or "https://app.mandala.computer/api/v1") as client:
result = client.credits.read()
print(result)
TypeScript — Mandala SDK 0.16.0
// Node.js 22+: npm install mandala-computer@0.16.0
import { Client } from "mandala-computer";
const apiKey = process.env.MANDALA_API_KEY;
if (!apiKey) throw new Error("Set MANDALA_API_KEY");
const client = new Client({apiKey, baseUrl: process.env.MANDALA_BASE_URL || "https://app.mandala.computer/api/v1"});
const result = await client.credits.read();
console.log(JSON.stringify(result, null, 2));
Pass as before for older entries; null on the final page. Balances and lots are current on every page.
Failures
403
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.