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 minor units
Every amount that crosses the API is a string of integer minor units, meaning cents for USD:
| You mean | You send |
|---|---|
| $1.00 | "100" |
| $0.05 | "5" |
| $250.00 | "25000" |
{ "priceMinor": "100", "amountMinor": "5000", "dailyBudgetMinor": "50000" }A number is an error, not a guess
"priceMinor": 100 (a JSON number) is rejected with 400 VALIDATION_FAILED. Floats can't represent every cent 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: {"balanceMinor":"4900","availableMinor":"4900","currency":"USD"}. The ledger's currency is USD.
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" | dollars, with the $ sign. Parsed as a string, never as a float |
priceMinor | "100" | cents |
price: "100" is refused, because it could mean $100 or $1.00.
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
Zanora takes a platform fee on each paid call, 3% by default. The seller gets the rest:
| Buyer pays | Seller receives | Platform fee |
|---|---|---|
"100" ($1.00) | "97" | "3" |
"50" ($0.50) | "49" | "1" |
"10" ($0.10) | "10" | "0" |
The fee is rounded down to the cent, so any rounding goes to the seller. Fee plus net always equals the price exactly. The console's seller screens show the current fee.
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.availableMinoris 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
| Rail | Direction | Notes |
|---|---|---|
card | in | a card top-up. It credits when the card network confirms |
ach | in | a bank debit |
usdc_base | in and out | USDC on Base. 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 |
bank_transfer | out | seller payouts by ACH, wire or SEPA |
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.