Billing records¶
Your organization's ledger by org API token: every charge down to the node and the price window, daily totals in your calendar, the balance now with the hourly burn rate and the balance over time, the balance card with the card on file and the auto reload setting, spend attributed per workload, and the unit prices in force. Five writes start the console's billing actions for a person: three hand back a hosted page URL, one tightens the spend limit, one configures auto reload.
Contract: GET /api/billing/openapi.json.
| Route | What it serves |
|---|---|
GET /api/billing/transactions |
the itemized ledger: every charge and deposit, newest first, one entry per resource per price window |
GET /api/billing/daily |
one row per calendar day: charges, deposits, the resources charged and their unit-hours |
GET /api/billing/summary |
balance, lifetime totals, this month's charges, spend limit, hold state, the card on file, the auto reload setting |
GET /api/billing/balance |
the balance now, the hourly burn rate the meter last assessed, and what it is made of by kind, cluster and unit price |
GET /api/billing/balance/history |
the balance as a series over a window: start, step_s, one value per bucket |
GET /api/billing/usage/workloads |
spend attributed per workload over a preset range |
GET /api/billing/rates |
the unit prices in force for your organization |
POST /api/billing/checkout |
{usd}: a hosted payment page URL for a person to complete; the deposit lands when they pay |
POST /api/billing/setup |
a hosted page URL that saves a card and charges nothing |
POST /api/billing/portal |
the payment portal URL: receipts and card changes |
PUT /api/billing/limit |
{usd}: set the monthly spend limit where none exists, or lower it |
PUT /api/billing/reload |
{enabled, threshold_usd, amount_usd}: the auto reload setting, a standing charge on the saved card whenever the balance runs low; a token may only disable it, lower the amount or raise the threshold |
Any of your organization's tokens reads these routes, cluster-bound or org-wide. The organization is the token's own and nothing identifies it on the wire. There is no cluster selector: the ledger is organization wide, and each charge entry names its cluster. No call on this wire moves money by itself: the three hosted page routes return a URL that a person opens in a browser, and no card detail ever rides the token. Auto reload is the one standing authorization. Once enabled it charges the saved card whenever the balance falls under the threshold, so a token may only tighten it: enabling it stays a console action.
curl -H "Authorization: Bearer $NC_TOKEN" \
"https://nationalcompute.com/api/billing/daily?limit=31"
Units¶
Amounts are integers in microcredits: fields ending in _ucredits,
where 1,000,000 microcredits = 1 credit = $1. Stamps are RFC 3339
UTC to the second (2026-09-10T14:00:00Z). Where a route aligns to
your calendar it takes tz_offset_min, the JavaScript
getTimezoneOffset() value (minutes UTC is ahead of local; 0 is UTC
days; a value outside ±900 is refused with 422 bad-request).
The ledger¶
GET /api/billing/transactions returns one row per ledger transaction,
newest first:
{
"transactions": [
{
"type": "charge",
"start": "2026-09-10T14:00:00Z",
"end": "2026-09-10T14:15:00Z",
"amount_ucredits": …,
"source": "metered",
"note": null,
"balance_after_ucredits": 3998500000,
"charges": [
{
"kind": "burst",
"cluster": "acme-train",
"resource_id": "…",
"resource_type": "GPU_…",
"units": 8,
"unit_hourly_price_ucredits": …,
"start": "2026-09-10T14:00:00Z",
"end": "2026-09-10T14:15:00Z",
"amount_ucredits": …
}
]
}
],
"next": "1757513700000.2"
}
| Field | Meaning |
|---|---|
type |
charge or deposit |
start, end |
the window the row accounts for — metering windows are about 15 minutes |
amount_ucredits |
always positive; type gives the sign |
source |
metered (the platform's compute meter), inference (Marshall model usage charged to credits), public_research (a meter row made only of Public Research pool lease entries), card (a card payment), airdrop (the credits a public research organization receives at sign-up), manual (platform-authored: a wire deposit, a grant, an adjustment) |
note |
a platform-authored note on a manual row, else null |
charges[] |
the per-resource entries a metered row is made of — empty on deposits and manual rows; they sum to amount_ucredits |
balance_after_ucredits |
the balance after this row in ledger order: the newest row's is the balance now, and each older row's is the next newer one plus a charge or minus a deposit. Rows a filter hides still move it |
next |
the page cursor; pass it back as before for the next page. A full page always carries one (keyset paging cannot know whether more exists); a short or empty page ends the walk, next then null |
Charge entries¶
Each entry is one resource over one price window, so a metered row lists every node you held in that window, once per rate change:
| Field | Meaning |
|---|---|
kind |
burst: market capacity at the clearing rate. base_load: a base load block at its fixed rate. fixed: an attached node at the price posted for your organization. inference: one member's Marshall model usage over one metering pass. public_research: one whole node held under Public Research, at the flat rate in force when the lease was granted |
cluster |
the cluster the resource served; null on an inference or public_research entry |
resource_id |
the node's identity, reservation:<id> for a base load block, public_research:<request id> for a Public Research lease (the node rides node), or the shared volume's id |
resource_type |
the priced type — a GPU type, a CPU type, or STORAGE_SHARED_GIB. GPU and CPU type names carry the hardware model; read them from the payload |
units |
GPUs for a GPU type, nodes for a CPU type, GiB of used bytes for storage |
unit_hourly_price_ucredits |
the rate per unit-hour assessed for this window |
start, end |
the entry's own window inside the row's |
principal, model, prompt_tokens, completion_tokens |
inference entries only: the member (username), the public model id the calls named, and the token counts in the pass. resource_type and resource_id are null there; units and unit_hourly_price_ucredits are 0 |
principal_kind |
inference entries only: member when a person's credential made the calls (principal is then the username). token when a credential other than a person's made them (principal is then that credential's id) |
node, site, gpus, rate_ucr_per_gpu_hour, start_ms, end_ms |
public_research entries only: the node the lease held, the site id it ran on, the node's GPUs (the billed units), the flat rate per GPU-hour fixed when the lease was granted in microcredits (the same figure as unit_hourly_price_ucredits), and the entry's window as epoch milliseconds beside start and end. resource_type is the node's GPU type |
amount_ucredits |
(end − start) / 1 h × units × unit_hourly_price_ucredits, rounded to the microcredit |
Query parameters¶
| Parameter | Meaning |
|---|---|
limit |
rows per page, 1–100; default 25 |
before |
the previous page's next (opaque) |
type |
charge or deposit |
source |
metered, inference, public_research, card, airdrop, or manual |
since, until |
a half-open window on the row's end: RFC 3339 (2026-09-01T00:00:00Z; a bare date is midnight UTC) or epoch milliseconds |
A bad value is 422 bad-request; detail names the parameter.
What the ledger read hides¶
- Metered rows totalling $0 never appear — a metering window that
cleared at $0 leaves an auditable zero row in the books, but the read
returns neither it nor a day made only of such rows. Manual $0 rows
stay (an adjustment can carry a
noteworth reading). - Who acted never leaves: a manual row names no platform operator.
sourceis the class, nothing finer.
Daily totals¶
GET /api/billing/daily folds the ledger into one row per calendar day
in your zone, newest first:
{
"days": [
{
"date": "2026-09-10",
"start": "2026-09-10T00:00:00Z",
"end": "2026-09-11T00:00:00Z",
"today": true,
"charges_ucredits": …,
"deposits_ucredits": 0,
"transactions": 96,
"resources": [
{"kind": "burst", "resource_type": "GPU_…",
"unit_hourly_price_ucredits": …,
"units": 16, "unit_hours": 192.0}
],
"clusters": ["acme-train"],
"balance_end_ucredits": 3904000000
}
],
"next": "20706"
}
| Field | Meaning |
|---|---|
date |
YYYY-MM-DD in your zone (tz_offset_min) |
start, end |
the day's bounds as UTC stamps |
today |
the current local day — its totals are still running |
charges_ucredits, deposits_ucredits |
the day's totals |
transactions |
ledger rows folded into the day |
resources[] |
the day's charges per (kind, resource type, unit price), most unit-hours first: kind is burst / base_load / fixed / public_research as on a charge entry (inference charges fold into no resource line), units the distinct units charged that day, unit_hours their metered time |
clusters |
cluster names touched that day |
balance_end_ucredits |
the balance at the day's end; for today, the balance now |
limit counts days (1–100, default 31); before takes the previous
page's next, with the same full-page rule as the ledger. For the rows
behind a day, read the
ledger with since and until set to its bounds.
Balance and burn rate¶
GET /api/billing/balance is the one call a budget script polls: the
balance now, the hourly rate the meter last assessed your organization,
and what that rate is made of.
{
"balance_ucredits": 4213500000,
"burn_ucredits_hr": …,
"as_of": "2026-09-11T17:05:00Z",
"billing_hold": null,
"resources": [
{"kind": "burst", "cluster": "acme-train", "resource_type": "GPU_…",
"units": 16, "unit_hourly_price_ucredits": …,
"rate_ucredits_hr": …},
{"kind": "fixed", "cluster": "acme-train",
"resource_type": "STORAGE_SHARED_GIB", "units": 800,
"unit_hourly_price_ucredits": …, "rate_ucredits_hr": …}
],
"clusters": [{"cluster": "acme-train", "rate_ucredits_hr": …}]
}
| Field | Meaning |
|---|---|
balance_ucredits |
the balance now; negative when charges outran deposits |
burn_ucredits_hr |
microcredits per hour: the sum of units × unit price over every resource still held when the newest metering window closed. A node released or a rate replaced mid-window is not part of it; the entry that replaced it is |
as_of |
the close of the metering window the rate was read from; null before the first charge |
billing_hold |
the hold state the summary and the capacity reads carry, or null |
resources[] |
the rate by (kind, cluster, resource_type, unit_hourly_price_ucredits), largest first, with the units held at that price; kind is burst / base_load / fixed / public_research (a live Public Research lease, cluster null). Jobs holding nodes at different clearing rates are separate lines, so the fold says what each limit price is costing |
clusters[] |
the rate per cluster, largest first |
The rate is read from the meter, not the market, so it reconciles with
the ledger to the microcredit and covers every kind the
meter bills: burst, base load, attached nodes and storage, on VM and
Kubernetes clusters alike. The meter closes one window every 5 minutes,
about 5 minutes after the fact, so the figure is 5 to 10 minutes behind
real time and changes at most every 5 minutes; polling faster buys
nothing. The meter writes no window in which nothing was billed, so a
newest window older than 20 minutes reads as a rate of 0, with as_of
still naming it. For one Kubernetes cluster's live market rate, ahead
of the meter, read
GET /api/k8s/market/spend.
Balance over time¶
GET /api/billing/balance/history returns the balance as a series over
the last hours (1 to 8760, default 24; a value out of range is
422 bad-request). The path is the operation; hours rides the query
string.
{"start": "2026-09-10T17:10:00Z", "step_s": 300,
"balance_ucredits": [4300000000, 4300000000, 4289333333]}
step_s is the bucket width, max(300, span / 400) seconds, so a
window is at most 400 points; bucket i covers
(start + i × step_s, start + (i + 1) × step_s] and the last bucket
closes now. balance_ucredits[i] is the balance at the close of bucket
i: a balance is a step function, so buckets with no ledger event carry
the last value forward, seeded from the last event before the window,
and 0 before the first ever event. For the exact stamp of a deposit or
charge read the ledger, whose rows carry
balance_after_ucredits; for a day's closing balance read the
daily totals.
Balance and totals¶
GET /api/billing/summary:
| Field | Meaning |
|---|---|
balance_ucredits |
the current balance; negative when charges outran deposits |
deposited_ucredits, charged_ucredits |
lifetime totals |
month |
start, end, and charged_ucredits for the current calendar month in your zone |
spend_limit_ucredits |
the monthly spend limit, or null; set on the console, or tightened by token |
spend_limit_used_pct |
month.charged_ucredits as a percentage of the limit, one decimal; null without a limit. The console's limit bar warns at 75 and turns critical at 99. The value can exceed 100 |
billing_hold |
the hold state, or null — the same object the capacity read carries |
spend_limit_enforced |
true when reaching the spend limit holds your organization's capacity. false when the platform records the limit and does not act on it for your organization's clusters; the cap is then the instruction a person gave the agent and nothing else stops it |
billing_hold_enforced |
true when a balance at or below zero holds your organization's capacity. false when the platform records the balance and does not hold on it for your organization's clusters. spend_limit_enforced requires it |
base_load |
true when your organization is on a base load arrangement |
purchases |
true when self serve credit purchases are open to your organization on the console's Billing page |
payment_method |
the card on file as {brand, last4}, or null when none is saved |
card_max_usd, card_window_days |
the card cap: your organization's card payments may total card_max_usd USD inside any rolling card_window_days days (currently $50,000 per 7 days). Every card payment counts, checkout and auto reload alike |
card_window_remaining_usd |
whole dollars a card payment can still carry right now: the cap minus the card payments inside the window, never negative. It rises as payments age out of the window |
card_window_blocked |
true while card_window_remaining_usd is below the smallest purchase: no card payment goes through right now |
min_usd, max_usd |
the smallest and the largest single checkout, whole dollars (platform constants; the card cap still applies) |
auto_reload |
the auto reload setting: enabled, threshold_ucredits, amount_ucredits, last (status, reason, at) for the newest attempt whether or not auto reload is enabled now (null before the first), and cooldown_until: the stamp until which a failed attempt pauses new ones, or null |
wire |
the wire transfer rail, or null while purchases is false |
recent_deposits[] |
the last 20 deposits: at, amount_ucredits, source (card, airdrop or manual) |
inference |
Marshall model usage charged to credits, dollars with cents: billed (true when model usage is charged to the organization's credits), free_tier_usd (the lifetime free allowance per member; 0 when model usage is billed from the first call), free_tier_remaining_usd (what the caller has left of it; null for an org token, which has no person behind it, or while the allowance is 0), charged_month_to_date_usd and charged_lifetime_usd (the organization's charged model usage) |
public_research |
Public Research leases, as {gpu_hours_month_to_date, charged_month_to_date_usd}: GPU-hours this calendar month from the lease records (an open lease counts up to now; a free rate still counts) and the charged amount from the ledger's public_research entries, dollars with cents. Both null while the ledger cannot be read. The MCP billing_read summary view carries the same object |
These read what the console's balance card shows. The writes start three of the console's actions for a person (buying credits, saving a card, opening the payment portal), tighten the spend limit and configure auto reload.
Hold state¶
billing_hold is one object on the summary, the balance read
and the capacity reads, or null when nothing holds or counts down.
| Field | Meaning |
|---|---|
state |
hold: an active hold. grace: a countdown to a hold |
reason |
balance (the balance at or below zero), spend_limit (the month's charges at spend_limit_ucredits), or balance+spend_limit (both at once) |
stage |
hold only. notice: growth is blocked and nothing is reclaimed yet. enforce: capacity is being reclaimed |
enforce_after_ms |
hold only: epoch milliseconds when stage turns enforce |
remaining_min |
grace only: minutes until the hold opens. The grace is 0 by default, so this state is rare |
base_load_min |
present only while your organization holds a live base load block during a balance condition: minutes left before the block is cancelled |
A deposit cures a balance condition. Only a raised or cleared limit,
or the month rolling over, cures a spend_limit one. billing_hold_enforced
and spend_limit_enforced say whether either can open for your
organization at all.
Wire transfer¶
wire carries what the console's Billing page shows under Wire
transfer, for amounts the card cap refuses. It is null while
purchases is false: an organization on a reserved arrangement pays
by invoice.
| Field | Meaning |
|---|---|
bank_name, aba_routing, bank_address |
the receiving bank; aba_routing serves domestic wires and ACH |
aba_routing_alt |
the routing number to use when the sending bank does not recognize aba_routing |
swift_bic, intermediary_swift_bic |
international wires: the receiving bank's SWIFT code and the intermediary bank the wire must name |
beneficiary_name, account_number, account_kind, beneficiary_address |
the beneficiary |
reference |
your organization's memo for the current UTC day. It rolls at midnight UTC: read it on the day of the wire. It must ride the wire's reference or memo field, or the deposit is not matched to your organization promptly |
notify_email |
the address to email when the wire is sent |
The bank and beneficiary values are platform constants and appear on this wire and the console only.
Spend per workload¶
GET /api/billing/usage/workloads?range=7d re attributes charge
entries to the jobs and pods that occupied the charged nodes, from the
platform's occupancy samples. The figures are approximate by design:
idle capacity and spend that cannot be attributed ride as their own
labeled series, never smeared across jobs.
| Parameter | Meaning |
|---|---|
range |
one of 1d, 7d, 30d, 90d, mtd, ytd, lastmonth (default 7d); anything else is 422 bad-request |
tz_offset_min |
the calendar the buckets use, as on the other reads |
The response carries starts[] (one stamp per bucket) and end,
labels[] (HH:00 for 1d, MM-DD otherwise), today_index (the
bucket holding today, null for lastmonth), and groups with two
groupings, workload and user. Each grouping has order[], series
(per name: spend[] in microcredits and unit_ms[], milliseconds of
unit time, per bucket) and meta (per name: label, who, cluster,
unit, handover_ucredits; the user grouping adds workloads, how
many workloads the user ran). who names the person who started the
workload: the Slurm username, or the creator of the Kubernetes object
as the cluster's audit log recorded it (the console's "Started by").
A workload with no recorded creator carries its Helm release or app
group instead. A workload with neither carries an empty who, and the
user grouping lists it under "No user recorded". Series names are
workload ids plus three sentinels: IDLE (held capacity no job used),
UNATTRIBUTED (spend no sample explains) and OTHER (the fold of the
smaller workloads, the way the console's chart folds them). The payload
is served through a cache: cache_age is its age in seconds, and
stale: true means a refresh is in flight, so read again in a moment
for fresh figures.
A range nobody has read yet is computed on first request. When the
computation takes more than a few seconds, the call answers
202 Accepted with {"pending": true, "retry_after_s": 3} and a
Retry-After header instead of holding the connection. Poll the same
URL after that many seconds. The 200 lands once the figures are
ready, and later reads of the same range come from the cache.
Prices in force¶
GET /api/billing/rates lists every resource type priced specifically
for your organization right now:
{"rates": [
{"resource_type": "GPU_…", "label": "…", "unit": "GPU",
"unit_hourly_price_ucredits": …, "since": "2026-09-01T00:00:00Z"}
]}
unit is GPU, CPU, or node. Burst capacity does not appear here —
it bills at the clearing rate,
which the price feed publishes.
A public research organization also sees its
Public Research rate as one line per
site that offers the program: resource_type the node's GPU type,
label Public Research · <GPU model> GPU, unit GPU,
unit_hourly_price_ucredits the flat rate a lease granted now is
fixed at, and since the stamp the rate was last set. A free rate is
listed at 0; a standard organization never sees the line. The MCP
billing_read rates view carries the same rows.
Writes¶
The five writes are the console's billing actions an agent starts for the person it works for. Bodies are JSON; every amount is a whole number of dollars.
curl -X POST -H "Authorization: Bearer $NC_TOKEN" \
-H "Content-Type: application/json" -d '{"usd": 250}' \
https://nationalcompute.com/api/billing/checkout
{"url": "https://checkout.stripe.com/c/pay/…"}
| Route | Body | What comes back |
|---|---|---|
POST /api/billing/checkout |
{"usd": 250} |
a hosted payment page URL ($1 = 1 credit). Hand it to a person; the page never offers the organization's saved card, the person enters a card, so the URL alone cannot pay. The deposit lands on the ledger when they pay, so read the summary again afterwards. Card payments are capped at card_max_usd total per rolling card_window_days days per organization. card_window_remaining_usd on the summary is what a card can still carry right now. Larger amounts go by wire transfer |
POST /api/billing/setup |
none | a hosted page URL that saves a card and charges nothing. The on ramp for auto reload |
POST /api/billing/portal |
none | the payment portal URL: receipts and card changes. Before the first purchase there is no customer record to manage |
PUT /api/billing/limit |
{"usd": 500} |
{"ok": true, "spend_limit_ucredits": 500000000}. Sets the monthly spend limit where none exists, or lowers the one in force |
PUT /api/billing/reload |
{"enabled": true, "threshold_usd": 50, "amount_usd": 150} |
{"ok": true, "enabled": true, "threshold_ucredits": 50000000, "amount_ucredits": 150000000}: the setting as stored. Tightens the auto reload setting: lowers amount_usd, raises threshold_usd, or disarms it with {"enabled": false} (nothing else needed; the stored amounts stay for the next enable). A write that changes nothing is a no op |
The spend limit is the cap a person sets on the month's charges. Read
spend_limit_enforced on the summary before relying on it. When it is
true, reaching the limit holds capacity until the limit is raised or
the month rolls over. When it is false, the platform records the
limit and does not act on it for your organization's clusters. The
flag differs between organizations: read it, never assume it. A token
may only tighten the limit:
raising the limit, or clearing it with null, is refused
403 session-required and stays a console action. The hosted page URLs
are short lived; request a fresh one if it expired before the person
opened it.
Auto reload is a standing spending authorization: every charge it fires
runs off session with no further approval and counts against the card
cap. A token may only tighten it, the spend limit's posture: disable it,
lower amount_usd, or raise threshold_usd. Enabling it, raising the
amount, or lowering the threshold is refused 403 session-required and
stays a console action for a person. An update sends enabled: true
with both amounts (threshold_usd 1 to 1000000, amount_usd within
min_usd and max_usd; the bounds apply to every write, a disable
included). A failed charge pauses attempts for 24 hours, or until a
change is saved on the console, whichever comes first; the summary's
auto_reload.cooldown_until names the stamp, and a no op by token never
moves it. On the MCP server the
same write runs in both directions, because a person approves the
preview there: billing_write carries human_confirmation on it.
Errors¶
| Status | Code | When |
|---|---|---|
401 |
unauthorized |
missing, malformed, expired, or revoked token |
403 |
reserved-arrangement |
credits are managed by the platform team for your organization; purchases are not self serve |
403 |
session-required |
raising or clearing the spend limit by token, or enabling auto reload, raising its amount or lowering its threshold by token: console actions |
409 |
card-window |
the card cap for the rolling window is spent (card_window_remaining_usd is 0); wait for payments to age out of the window, or pay by wire transfer |
409 |
no-purchases |
the payment portal before the first purchase: nothing to manage yet |
409 |
no-card |
enabling auto reload with no card on file; POST /api/billing/setup hands back the page that saves one |
422 |
bad-request |
a query value or body field out of range or malformed; detail names it |
422 |
card-cap |
usd above what a card can still carry this window (card_window_remaining_usd on the summary); lower the amount, or pay larger amounts by wire transfer |
429 |
rate-limited |
past the token's request budget for the minute (300 by default; the vm contract states the figure); wait retry_after_s |
503 |
purchases-unavailable |
self serve purchases are off |
503 |
station-unavailable |
the ledger or the payment provider is unreachable; retry |
Every error is the plain {"error": code} body of the
API conventions.