Core concepts
Money and amounts
How to write an amount, how prices and fees are calculated, and why a balance is always calculated from the ledger and never stored.
Amounts are strings of micro-dollars
Every amount that crosses the API is a string of integer micro-dollars: millionths of a dollar, so "1000000" is $1.00. That is fine enough to price a call at a tenth of a cent and still take an exact fee from it. Every USD field says so in its name: priceMicros, amountMicros, balanceMicros.
| You mean | You send |
|---|---|
| $1.00 | "1000000" |
| $0.05 | "50000" |
| $0.001 | "1000" |
| $250.00 | "250000000" |
{ "priceMicros": "1000000", "amountMicros": "50000000", "dailyBudgetMicros": "500000000" }A number is an error, not a guess
"priceMicros": 1000000 (a JSON number) is rejected with 400 VALIDATION_FAILED. Floats can't represent every amount exactly, and a value that's silently rounded is how money goes missing. Keep amounts as strings in your code too. In TypeScript, use bigint when you need to do arithmetic.
Every response uses the same format: {"balanceMicros":"49000000","availableMicros":"49000000","currency":"USD"} is $49.00. The ledger's currency is USD.
The unit changed from cents
Before October 2026 the unit was the cent, and the fields were named …Minor (priceMinor: "100" was $1.00). The gateway now refuses an old field name with 400 MONEY_UNIT_CHANGED, naming the …Micros field to send instead, rather than reading it 10,000 times too small. Upgrade @zanora/sdk, @zanora/middleware-express, @zanora/middleware-mcp, @zanora/mcp and @zanora/mcp-proxy to their current versions.
A few fields keep Minor, because they are in a payment rail's own unit, not the ledger's: a rail transaction's railAmountMinor (cents on a card, 6-decimal units for USDC) and a treasury transfer's amounts. Each sits next to the asset it's counted in.
Prices in seller code
When you declare a price in @zanora/middleware-mcp, you can write it in either of two unambiguous forms:
| Field | Example | Meaning |
|---|---|---|
price | "$1.00", "$0.001" | dollars, with the $ sign, up to six decimal places. Parsed as a string, never as a float |
priceMicros | "1000000", "1000" | micro-dollars |
price: "100" is refused, because it could mean $100 or $0.0001.
A price is free or at least $0.0001 ("100"). Below that, the 5% platform fee would round to nothing.
Prices in policies
Spending policies are the one place that uses dollars: price > 5 means more than $5.00. Policies are written by people, and price > 500 to mean $5 would be a trap.
Fees
Sellers pay two fees. Buyers pay no platform fee.
- The platform fee, a percentage of each paid call: 5%. The seller gets the rest.
- The payout fee, a flat amount per payout: $0.25. It comes out of the amount paid out and covers the network fee for sending USDC.
GET /v1/fees (no API key) returns both fees in force (takeRateBps, payoutFeeMicros) and any announced change. At 5%:
| Buyer pays | Seller receives | Platform fee |
|---|---|---|
"1000000" ($1.00) | "950000" ($0.95) | "50000" ($0.05) |
"100000" ($0.10) | "95000" ($0.095) | "5000" ($0.005) |
"1000" ($0.001) | "950" ($0.00095) | "50" ($0.00005) |
The platform fee is rounded down to the micro-dollar, so any rounding goes to the seller. Fee plus net always equals the price exactly. The console's seller screens show the current fees.
Fee changes. An increase in either fee is announced at least 30 days before it takes effect, and every seller is emailed. A decrease can take effect straight away. A call pays the platform fee in force when it was paid for, even if it settles after a change. A payout pays the payout fee in force when it is requested.
The ledger
- Append-only. Entries are never edited or deleted. A refund is a new credit that offsets the original debit, so your history shows both.
- Double-entry. Every movement is a set of entries that sum to zero, so money is never created or lost between wallets.
- Balances are calculated.
GET /v1/wallets/:id/balanceadds up the ledger every time.availableMicrosis the balance minus anything on hold, such as a payment that is being authorized. - No personal data. Ledger entries carry opaque ids only. To find out what a charge bought, look at the receipt.
Rails: how money gets in and out
Two rails are supported: card for funding, and USDC on Base for funding and payouts.
| Rail | Direction | Status | Notes |
|---|---|---|---|
card | in | supported | a card top-up, in whole cents. It credits when the card network confirms |
usdc_base | in and out | supported | USDC on Base, to the last on-chain unit (USDC's six decimals are the ledger's micro-dollars). Deposits are credited 1:1 while USDC is within ±0.5% of $1; outside that band they are held, not credited at a guessed rate |
ach | in | preview | a bank debit. Not offered in the console |
bank_transfer | out | preview | seller payouts by ACH, wire or SEPA. Not offered in the console |
A card moves whole cents. When money leaves by card (a refund of a leftover balance) or by bank, it is rounded down to the cent and the part that doesn't fit stays in your wallet: it is never sent and never kept by the platform.
Preview rails exist in the API but aren't generally available. A gateway refuses them with NOT_SUPPORTED unless it has turned preview rails on, and leaves them out of GET /v1/rail/providers. Their behaviour can change, so don't build on them yet.
Which rails exist depends on the deployment. Ask rather than assume: GET /v1/rail/providers lists what the gateway can do. See Funding a wallet and Payouts.