Money in Zaroz is two things: invoices (what you owe) and a wallet (credit you hold). How a purchase turns into an invoice is in Subscriptions and billing. This page is the money side, call by call. The samples use the helper from Using the Zaroz API. All amounts are integer cents in the invoice's currency.
Read the wallet
An organization owns its wallet, and every member of it reads the same balance. Your own wallet is the one of your personal organization.
curl
Code
curl -sS "$ZAROZ_API/me/wallet" -H "$AUTH"curl -sS "$ZAROZ_API/organizations/$ORG/wallet" -H "$AUTH"curl -sS "$ZAROZ_API/me/wallet/entries?page=1&page_size=25" -H "$AUTH" # the ledger, newest first
import { call } from "./zaroz";const wallet = await call("GET", "/me/wallet");for (const b of wallet.body.balances) { console.log(b.currency, "spendable", b.spendable_cents / 100, "promo", b.promo_cents / 100);}const ledger = await call("GET", "/me/wallet/entries?page_size=25");for (const e of ledger.body.data) console.log(e.created_at, e.reason, e.amount_cents);
Python
Code
from zaroz import call_, wallet = call("GET", "/me/wallet")for b in wallet["balances"]: print(b["currency"], "spendable", b["spendable_cents"] / 100, "promo", b["promo_cents"] / 100)_, ledger = call("GET", "/me/wallet/entries?page_size=25")for e in ledger["data"]: print(e["created_at"], e["reason"], e["amount_cents"])
Only non-zero balances are returned, so a wallet that was never funded answers an empty balances list. Treat a missing currency as zero.
What the figures mean
Field
Meaning
spendable_cents
What a wallet payment can use right now. Use this to decide whether an invoice can be paid, not amount_cents
real_cents
Cash-like money: top-ups, refunds settled to the wallet
promo_cents
Promotional credit. It is never cash and cannot be withdrawn. It is not drawn like cash by a payment: it is applied automatically as a discount line when an invoice is issued (and when an unpaid invoice is paid from the wallet)
legacy_cents
Older money not yet classified; not spendable by a wallet payment
amount_cents
All of the above added together. For reconciliation only
The ledger has no running balance column: each entry is a signed movement (positive credits, negative debits) with a reason such as top_up_pay_pal, redeemed_code, invoice_autopay, invoice_refund or promo_grant.
Add funds
First ask which providers can take a top-up right now, and show only those:
const topup = await call("POST", "/me/wallet/top-ups", { amount_cents: 2500, provider: "paypal" });console.log("Send the customer to", topup.body.checkout_url);// nothing is credited until the provider confirms; pollfor (;;) { const t = await call("GET", `/me/wallet/top-ups/${topup.body.id}`); if (["credited", "expired", "failed", "review_required", "reversed"].includes(t.body.status)) { console.log(t.body.status); break; } await new Promise((r) => setTimeout(r, 5000));}
Python
Code
import timefrom zaroz import call_, topup = call("POST", "/me/wallet/top-ups", {"amount_cents": 2500, "provider": "paypal"})print("Send the customer to", topup["checkout_url"])while True: # nothing is credited until the provider confirms _, t = call("GET", f"/me/wallet/top-ups/{topup['id']}") if t["status"] in ("credited", "expired", "failed", "review_required", "reversed"): print(t["status"]) break time.sleep(5)
provider is paypal or nowpayments (cryptocurrency).
amount_cents must be one of the fixed EUR presets: 2500, 5000, 10000, 25000, 100000. Anything else is refused with invalid-field-value.
The call credits nothing. The wallet is credited once, when the provider confirms the payment. Only credited means money arrived; expired, failed, review_required and reversed are terminal.
Retrying a top-up after a timeout starts a second, separate checkout. Neither moves money until it is paid.
A provider that is not set up or does not answer is a 503 and nothing was charged.
An organization token needs organizations.wallet.top_up for this.
Redeem a wallet code
A wallet code (a gift or giveaway code) credits one wallet, once. preview needs no account, so a claim link can show what the code is worth before anyone signs in.
Whatever the reason a code fails (it does not exist, was used, was revoked, expired, or a per-person limit was reached), the answer is the same invalid-field-value. That is deliberate: it stops anyone from probing which codes exist. Always send an Idempotency-Key, so a retry after a timeout returns the first credit instead of a refusal.
Invoices
curl
Code
curl -sS "$ZAROZ_API/billing/invoices?page=1&page_size=25" -H "$AUTH" # every organization you may seecurl -sS "$ZAROZ_API/organizations/$ORG/invoices?status=pending" -H "$AUTH"curl -sS "$ZAROZ_API/billing/invoices/$INVOICE" -H "$AUTH" # with its linescurl -sS "$ZAROZ_API/billing/invoices/open-summary" -H "$AUTH" # count, overdue, total owedcurl -sS "$ZAROZ_API/subscriptions/$SUBSCRIPTION/invoices" -H "$AUTH"
TypeScript
Code
const open = await call("GET", "/billing/invoices/open-summary");console.log(`${open.body.count} open, ${open.body.overdue_count} overdue, total ${open.body.total_cents}`);const invoice = await call("GET", `/billing/invoices/${INVOICE}`);for (const line of invoice.body.lines) console.log(line.description, line.amount_cents, line.project_name);
Python
Code
_, open_ = call("GET", "/billing/invoices/open-summary")print(open_["count"], "open,", open_["overdue_count"], "overdue, total", open_["total_cents"])_, invoice = call("GET", f"/billing/invoices/{INVOICE}")for line in invoice["lines"]: print(line["description"], line["amount_cents"], line["project_name"])
An invoice's status is pending, paid, failed, refunded or cancelled; its kind is initial, renewal, resize, admin_resize or trial_purchase. total_amount_cents includes tax (tax_amount_cents, tax_rate, tax_label). Every member of the organization can read its invoices; paying needs the Owner, Admin or Billing role.
Pay an invoice
Three ways to settle a pending invoice.
From the wallet. Instant, and it needs spendable_cents to cover the invoice:
status, paid = call("POST", f"/billing/invoices/{INVOICE}/pay-with-wallet", headers={"Idempotency-Key": str(uuid.uuid4())})if status == 422: print("not enough spendable balance, or already paid")
A wallet that cannot cover the invoice is a 422 that names the balance and the amount needed (nothing was attempted). An invoice that is already paid is also 422. A card or PayPal payment that is already in progress answers 409; nothing was charged. Paying is the one call where a blind retry could cost real money twice, which is why the Idempotency-Key matters. An organization token needs organizations.wallet.spend.
By card (Mollie) or PayPal. Both return a hosted page to send the customer to:
Code
curl -sS -X POST "$ZAROZ_API/billing/invoices/$INVOICE/checkout/mollie" -H "$AUTH"curl -sS -X POST "$ZAROZ_API/billing/invoices/$INVOICE/checkout/paypal" -H "$AUTH"# {"checkout_url": "https://..."}
Code
const { body } = await call("POST", `/billing/invoices/${INVOICE}/checkout/paypal`);console.log("Open", body.checkout_url);
Code
_, body = call("POST", f"/billing/invoices/{INVOICE}/checkout/paypal")print("Open", body["checkout_url"])
When the customer finishes, the provider sends them back to the dashboard; the invoice becomes paid when the provider confirms. Poll GET /billing/invoices/{id} rather than trusting the redirect. Check what the deployment offers (mollie, paypal) first: a provider that is not configured answers 501 capability-not-available.
Autopay
By default the wallet pays your renewal invoices automatically when it holds enough. Turn it off if you would rather approve each payment:
Switching autopay on authorizes the platform to spend the wallet on every renewal, so it needs the spending authority (Owner or Admin), not just the right to top up.
If you do not pay
An overdue invoice does not disappear. Your organization's standing reflects it and can restrict new purchases; GET /organizations/{id}/standing tells you whether you are restricted and, for owners, why.