Finance
Finance — 8 operations on https://api.hanzo.ai.
The REST reference for Finance — 8 operations, generated from the OpenAPI document.
| Base URL | https://api.hanzo.ai |
| Operations | 8 |
| Auth | Authorization: Bearer $HANZO_API_KEY |
finance
GET /v1/finance/accounts
Returns the ledger accounts the caller may see, with their balances. It is tenant-isolated SERVER-SIDE: an ordinary caller sees ONLY accounts under its own "org:<tenant>:" prefix, never house accounts and never another tenant's. A SuperAdmin may widen with ?scope=house (the reserve, revenue and payout house accounts) or ?org=<tenant> — the only way to cross the tenant boundary, and only for platform sudo. The answer is honestly empty until a tenant has ledger postings.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
scope | query | string | — | Scope is "house" to read the reserve/revenue/payout house accounts. |
org | query | string | — | Org names another tenant to read. |
GET /v1/finance/balance
Answers the org's spendable prepaid balance typed for the
finance surfaces: availableCents, pendingCents, dueCents and the asOf
instant it was read.
It is the SAME wallet read /v1/billing/balance answers — one function, called by both, so the two surfaces cannot drift into disagreeing about a customer's money. Reshaped, never re-metered. Co-resident the number comes straight out of the org's own double-entry ledger file.
dueCents is a structural 0: this is a PREPAID wallet with no open-invoice
debt, so nothing is ever owed and a non-zero value here would be an invention.
pendingCents is 0 on the co-resident ledger, where authorization holds are
never posted; only a split-deploy upstream reports holds, and there spendable
is the balance NET of them, floored at 0 — a fully-held wallet reports 0
rather than money the gate would refuse.
Cents are ROUNDED from the ledger's exact 18-decimal USD. Scoped to the caller's own org from the validated IAM owner claim; 401 without a validated principal, and a balance that cannot be read is 502 — never 0, because unknown is not broke.
GET /v1/finance/credits
Answers the money PUT IN to the org's wallet — each staff grant, promo and settled top-up as a positive row with its id, label, cents and grant time.
Spend is not a credit. A posting counts here only when it moved money IN; debits belong to /v1/finance/usage (aggregated) and /v1/finance/ledger (signed). All three project ONE read of the same ledger through ONE vocabulary for what a posting means, so they cannot disagree about a row — nor silently drop one, which is what an empty credits page against a funded wallet was.
label falls back through the posting's notes, then its tags, then a bare
Credit — it is a description, never an identifier. remainingCents is
OMITTED: the wallet is one running balance, not per-grant buckets, so no grant
has a remainder to report and spend cannot be attributed to the credit that
funded it.
Cents are ROUNDED from the ledger's exact 18-decimal USD. Scoped to the caller's own org; 401 without a validated principal. An org with no grants gets an empty array — honest, never a fabricated figure.
GET /v1/finance/invoices
Answers an empty typed array, always. The fleet bills a PREPAID wallet — money in, metered debits out — and issues no customer invoices, so there is no invoice ledger to project. Nothing here is a fabricated figure and nothing is hidden behind a filter.
The shape is fixed, so the finance UI renders this lane today and the day an invoice ledger exists it fills with ZERO client change. Spend that actually happened is /v1/finance/usage; money in and out is /v1/finance/ledger; what is left to spend is /v1/finance/balance.
The gate is real even though the body is empty: 401 without a validated principal. It is the only finance read that touches no store, so it is also the only one that cannot 502.
GET /v1/finance/ledger
Answers the org's own postings inside range=, each as a signed
entry: a DEPOSIT CREDITS the wallet (positive, account credits:<org>) and
every other posting DEBITS it (negative, account usage:<org>), described by
its notes or its tags. The sign is the posting's own meaning, read through ONE
vocabulary shared with the ledger that wrote it — a reader with its own
spelling for deposit rendered a customer's grant as a charge.
This is the closest projection of the truth. The org's double-entry postings are the source of record — balanced, only ever appended, one file per org — and this lane is that list, widest of the three: /v1/finance/credits is its deposit half and /v1/finance/usage is its withdrawal half rolled up. All three come from ONE read, which is why they cannot contradict each other, and all three answer 501 where no commerce link is configured rather than reporting an empty wallet.
A row whose timestamp will not parse is KEPT rather than dropped — a malformed
date must show up in a money list, not vanish from it. balanceCents is
omitted: these are MOVEMENTS, and the standing balance is /v1/finance/balance.
Cents are ROUNDED from the ledger's exact 18-decimal USD. Scoped to the caller's own org, where the org's ledger file is the tenant boundary; 401 without a validated principal.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
range | query | string | — | Range is the window: 24h, 7d, 30d or 90d. Anything else — including absent — is 30d, so a typo silently widens the… |
GET /v1/finance/payment-methods
Answers the masked card descriptors for the caller's resolved WALLET — id, brand, last four, expiry, default flag — reshaped into the finance contract.
It re-masks defensively: whatever the upstream sends, at most the trailing
four DIGITS survive into last4. No card number, no security code and no
processor token exists in this shape at all, so an over-returning upstream
still cannot leak one through this lane.
Read the sibling difference before trusting a mismatch. This keys the store on the resolved wallet; /v1/billing/methods keys it on the org SLUG, which is also the key a card is SAVED under — identical for an org paying from its shared pool, different wherever the payer is a person. When the two lists disagree, the billing one is what was saved.
401 without a validated principal. An upstream that answers non-2xx or cannot be reached is 502 — never an empty list, because no cards and could not ask must not look alike.
GET /v1/finance/treasury
Returns the reserve fund's health and the current revenue-share policy for any validated caller. It is a TRANSPARENCY view — a partner or author can see that the pool backing their payouts is solvent — and NOT per-org money, which is the customer's own commerce balance at /v1/billing/balance. The policy is read-only here; only a SuperAdmin sets it.
GET /v1/finance/usage
Answers metered spend inside range=: the window total, a time
series to plot, and one line per usage TAG. Aggregated from the same charged
ledger the balance comes off — projected, never re-metered.
Only DEBIT postings count; deposits are credits and are excluded. Buckets are hourly at 24h and daily otherwise, in UTC; a posting whose timestamp will not parse is dropped rather than mis-bucketed.
Lines group by the posting's tag (Usage where it carries none) and units
counts POSTINGS, not tokens. The dimensions here are time and tag. For
per-request rows and a per-PRODUCT breakdown, read /v1/billing/usage instead —
the same money, cut a different way.
Cents are ROUNDED from the ledger's exact 18-decimal USD, so a window made of sub-cent token calls totals LOW here. Scoped to the caller's own org; 401 without a validated principal.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
range | query | string | — | Range is the window: 24h, 7d, 30d or 90d. Anything else — including absent — is 30d, so a typo silently widens the… |
How is this guide?
Files
Package exec is the code interpreter: run a snippet in a sandbox and move files in and out.
Finetune
Package ai is Hanzo AI — the model API on /v1 (/v1/chat/completions, /v1/messages, /v1/models and the rest of hanzoai/ai's surface) — mounted into a cloud binary with the money, ingest and telemetry…