Transactions and spend
Every credit that enters or leaves an organization is written to an append-only ledger. The Billing page renders it two ways: a chart for the shape of your spending, and a table for the exact entries behind it.
The transaction list
The table is ordered newest first and shows 50 entries at a time; Load more fetches the next page.
| Column | What it holds |
|---|---|
| Date | When the entry was written, to the second |
| Type | What kind of entry it is |
| Detail | For a charge, the instance it belongs to and how long it covers; for other entries, see below |
| Amount | The signed change to the balance, positive for credits added |
| Balance | The balance immediately after this entry |
Types you will see:
| Type | Console label | What produced it |
|---|---|---|
topup | Credits added | A confirmed Stripe payment |
charge_gpu | GPU usage | Running time, one row per instance per clock hour |
charge_storage | Storage | Stopped disk time, one row per instance per clock hour |
adjustment | Adjustment | Any signed move of the balance that is neither a top-up nor metered usage — mostly Stripe clawbacks, posted automatically. See below |
An adjustment is not only a manual correction. Stripe events post them on their own, and the row's meta.reason says which. The Type column then shows the reason instead of Adjustment:
meta.reason | Console label | Sign | What happened |
|---|---|---|---|
refund | Refund | debit | A charge was refunded. Only the amount this event moved is debited, so a second partial refund does not take the first one again |
dispute_opened | Dispute opened | debit | A chargeback was filed and the funds are gone |
dispute_funds_withdrawn | Dispute — funds withdrawn | debit | The funds were withdrawn on a dispute that arrived as an inquiry first, so nothing was debited when it opened |
dispute_won | Dispute won | credit | You won the dispute and its debit is reversed |
dispute_reinstated | Dispute — funds partly returned | credit | You lost the dispute, but the card network returned part of the money. What comes back is credited once, and only as far as it was debited: when an earlier refund had already kept part of the payment from being debited, that part is not credited a second time |
dispute_capped | Dispute — adjusted to the payment | credit | A refund on the same payment arrived after the dispute was debited, so part of that debit is given back: the payment is never taken back twice |
staff_adjustment | Support adjustment | either | Superheat support moved the balance by hand. The Detail cell shows the note they wrote for you |
A dispute never takes back more than the payment has left to give: the payment's amount (or the disputed amount, when the card issuer folds several payments into one dispute), less the refunds already recorded on it. So a $10 top-up that was refunded $4 and then disputed and lost costs $10 in total, whatever order the refund and the dispute arrive in — never $14. When a card issuer replaces a dispute with a second one for a corrected amount, each dispute is tracked on its own. If the card network returns part of a lost dispute in another currency, or without saying how much, nothing is credited automatically; Superheat staff are alerted to review it.
A formal dispute also freezes the organization while it is open: POST /v1/instances and starting a stopped instance both answer 402 PAYMENTS_FROZEN, so a chargeback cannot fund GPU time. Since a stopped instance cannot start meanwhile, its GPUs are released too, and a Stop pressed during the freeze releases them at once, in both cases where the machine supports it — see While a payment dispute is open. Closing the dispute lifts the freeze whichever way it went — a loss keeps its debit, and the balance is what gates you from there — but only once no other formal dispute is still open against the organization. Superheat staff can also lift a freeze after reviewing the dispute. An inquiry, where the issuer is asking a question and no money has moved, neither freezes the account nor posts a ledger row: debiting on a question that often goes nowhere would take credits from someone who has done nothing wrong. If the inquiry escalates into a formal dispute, the freeze and the debit follow then. Each dispute is debited at most once, and a won dispute credits back exactly what it debited.
For a charge, the Detail cell links to that instance's Billing tab and gives the time the row covers, such as 47m 0s. The row for the hour that is still running is marked in progress. Top-ups have no instance and name the payment method instead (via Stripe). A refund or dispute row says Refunded or Payment dispute, a support adjustment shows the note support wrote, and any other adjustment shows a dash.
One row per instance per hour
Charges are still settled about once a minute, exactly as How billing works describes, but they no longer land in a row each. Each instance has one open charge row for the current clock hour (UTC), and each minute's settlement is added to it: its amount and the time it covers grow, and the balance after it goes down, until the row closes. A row closes, and the next settlement opens a new one, when:
- the clock hour ends;
- the instance changes state — a stop, a start, or a destroy writes a closed row of its own for the time up to that moment;
- the rate changes, or the instance moves to another machine;
- a top-up or an adjustment is written after it. A payment's balance is never rewritten, so a row never grows past one.
So an instance that runs all day produces about 24 rows, not 1,440, and a stop in the middle of an hour ends that hour's row early. For a per-instance total, open the instance and read Settled spend, and for a day-by-day total use the chart. Sub-cent amounts are shown to four decimal places, so a small storage charge reads as something like $0.0007 rather than $0.00.
The spend chart
Spend — last 30 days plots one point per day, with the 30-day total beside the heading. Hovering a point gives that day's figure.
What the chart counts:
- GPU charges and storage charges only. Top-ups and adjustments are excluded — this is spending, not cash flow.
- Days are calendar days in your browser's time zone, so a late-evening run lands on the day you ran it. The console sends that zone to the API, which buckets the charges by it. A charge row counts on the day its hour started: in a time zone whose offset is not a whole hour, such as India's, up to an hour of usage around midnight can land on the neighbouring day.
- Days with no charges are plotted as zero rather than skipped, which is what gives the chart its flat stretches between bursts of work.
The endpoints
Both endpoints read the current organization's ledger. Send a bearer token — a signed-in session or an shk_ organization API key. With a session, X-Org-Id selects the organization, and without that header the personal organization is used; an API key is pinned to its own organization, and sending an X-Org-Id for any other one is rejected with ORG_MISMATCH. Neither endpoint requires admin, so a member or an API key can read them. See Authentication.
GET /v1/billing/transactions
curl "$SUPERHEAT_API/v1/billing/transactions?limit=100" \
-H "Authorization: Bearer $SUPERHEAT_TOKEN" \
-H "X-Org-Id: $ORG_ID"
| Parameter | Range | Default | Effect |
|---|---|---|---|
limit | 1 to 200 | 50 | Entries per page |
cursor | an entry id | — | Returns entries older than this id |
instance_id | an instance id | — | Restricts the page to one instance |
Entries come back newest first in items, alongside a next_cursor. Pass that value as cursor to get the next page; when it comes back null, you have reached the end of the ledger.
Each entry carries its id (which doubles as the pagination cursor), its type, the instance_id it belongs to or null for a top-up or an adjustment, a created_at timestamp, its signed amount, and the balance that resulted from it. An adjustment carries meta.reason from the table above, and reason repeats it at the top level of the entry (null on an entry with none).
A metered charge carries a meta object with period_start, period_end, seconds (the time the row bills), rate_micro_per_hour and settlements (how many minute settlements it adds up; rows written before hourly rows carry no count and are one settlement each). Its created_at is when its first settlement ran, and the row only takes settlements from that same clock hour. accruing is true on the row an instance is still adding to: until it closes, its amount, meta.seconds, meta.period_end and meta.settlements grow, and the balance after it, and after any charge rows that follow it, goes down by the same amount. Every other entry is final as written. If you copy the ledger elsewhere, re-read rows that were accruing until they come back false.
To pull the charges for one instance:
curl "$SUPERHEAT_API/v1/billing/transactions?instance_id=$INSTANCE_ID&limit=200" \
-H "Authorization: Bearer $SUPERHEAT_TOKEN" \
-H "X-Org-Id: $ORG_ID"
GET /v1/billing/spend-daily
curl "$SUPERHEAT_API/v1/billing/spend-daily?days=90" \
-H "Authorization: Bearer $SUPERHEAT_TOKEN" \
-H "X-Org-Id: $ORG_ID"
| Parameter | Range | Default | Effect |
|---|---|---|---|
days | 1 to 90 | 30 | How far back to aggregate |
tz | an IANA time zone name, such as Europe/Berlin | UTC | Whose calendar days the charges are grouped by. A name the API does not know returns 422 INVALID_TIME_ZONE |
This is the chart's data source: GPU and storage charges grouped by calendar day in tz, oldest first, each entry carrying a date and that day's total. Only days that had charges appear, so a quiet week produces no entries rather than a run of zeros — fill the gaps yourself if you are plotting a dense series.