Billing
The client.billing resource exposes the prepaid credits surface:
client.billing.balance()— current balance, in cents and dollars.client.billing.topup(amount_usd=...)— create a Stripe Checkout session URL for a one-time top-up.client.billing.transactions(limit=...)— recent ledger events (debits, credits, refunds), newest first.
All three also work on dream.AsyncClient.
balance()
Returns a dream.BillingBalance:
import dream client = dream.Client() # reads DREAM_API_KEYb = client.billing.balance()print(b.customer_id) # 'cus_xxx'print(b.balance_mils) # 49755 — integer mils, exactprint(b.balance_cents) # 498 — integer cents, half-up displayprint(b.balance_usd) # 4.9755 — float dollars (exact)The storage unit is mils (1 mil = $0.0001 = 1/100 of a cent). At
the v0.2 list price of $0.0005/frame, every frame is exactly 5 mils,
so a canonical 49-frame DreamDojo rollout debits exactly 245 mils
($0.0245) — no rounding loss. balance_cents is a half-up display
helper; balance_usd is also derived from balance_mils and carries
sub-cent precision.
topup(amount_usd=...)
Creates a Stripe Checkout session. Open the returned url in a
browser to complete payment; once Stripe confirms, the engine's
webhook credits your balance.
session = client.billing.topup(amount_usd=25.00)print("Open this in a browser to pay:", session.url)The returned dream.TopupSession carries:
session_id— Stripe Checkout session id (cs_...).url— hosted Checkout URL.amount_cents/amount_usd— what you asked for.customer_id— your Stripe customer id.
Bounds
The SDK validates amount_usd client-side before any HTTP call:
| Limit | USD |
|---|---|
| Minimum top-up | $5 |
| Maximum top-up | $10,000 |
ValueError is raised on out-of-range. The engine enforces the same
bounds server-side.
transactions(limit=...)
Returns recent ledger events as list[dream.BillingTransaction], newest first.
Each event carries a signed amount_cents (negative for debits, positive
for credits and refunds) and a running balance_after_cents snapshot —
no need to replay the sequence to know the running balance.
for txn in client.billing.transactions(limit=20): sign = "+" if txn.amount_cents > 0 else "" print(f"{txn.kind:>7} {sign}${txn.amount_usd:.4f} {txn.detail}")| Attribute | Type | Notes |
|---|---|---|
id | str | request_id for predicts (joins your own logs); Stripe event.id for top-ups |
ts | float | POSIX seconds |
kind | str | "debit", "credit", or "refund" |
amount_mils | int | Signed, exact (storage unit; 1 mil = $0.0001) |
amount_cents / amount_usd | int / float | Signed display values |
balance_after_mils | int | Running balance after this event, exact |
balance_after_cents / balance_after_usd | int / float | Display |
detail | str | Human-readable label |
For exact ledger math, use amount_mils and balance_after_mils.
The _cents and _usd derivations are for display ergonomics only —
half-up cents lose sub-cent precision (e.g. 245 mils → 2¢) but
amount_usd (mils-derived) shows the exact $0.0245.
Limits: 1 ≤ limit ≤ 200. Higher values silently clamp to 200.
Engine errors that triggered a refund show as a "refund" row alongside
the original "debit" row — the audit trail is self-balancing, so the
sum of amount_cents matches the current balance modulo any newer
events.
Insufficient credits
When your balance can't cover the predicted cost of a predict /
predict_batch, the engine returns 402 before the model fires. The
SDK raises dream.InsufficientCreditsError:
try: rollout = model.predict(start_frame=img, actions=acts)except dream.InsufficientCreditsError as e: print(f"balance: ${e.balance_usd:.4f}") print(f"needed: ${e.requested_usd:.4f}") session = client.billing.topup(amount_usd=25.00) # Customer pays via session.url, then retry the predict.InsufficientCreditsError is a subclass of dream.DreamError, so a
single except dream.DreamError: catches it alongside the rest of the
typed-error taxonomy.
Async parity
import dream async def topup_if_low() -> None: async with dream.AsyncClient() as client: b = await client.billing.balance() if b.balance_cents < 1000: session = await client.billing.topup(amount_usd=25.00) print("Top up here:", session.url)Server endpoints
For users wiring the wire protocol directly:
GET /v1/billing/balance— authenticated; returns{customer_id, balance_cents, balance_usd}.GET /v1/billing/transactions?limit=N— authenticated; returns{customer_id, transactions: [...]}(newest first, clamped to 200).POST /v1/billing/topup— authenticated; body{"amount_cents": int}; returns{session_id, url, amount_cents, customer_id}.POST /v1/billing/webhook— unauthenticated by design. Stripe signs the payload with the webhook secret; the engine verifies. Idempotent onevent.id. Configured by Stripe dashboard, not callable by SDK clients.
See Pricing & limits for the full billing model.