Billing API
Orbit is pay-as-you-go. There are no plan tiers or recurring subscriptions — every organization has a single prepaid USD wallet (organizations.credits,
stored in minor units / cents) that funds messaging, voice, and AI spend.
Top up the wallet through a one-time Stripe Checkout session; per-send charges
are deducted in real time and recorded in an append-only ledger
(credit_transactions).
Base path: /api/v1/billing
Status
Get Billing Status
GET /api/v1/billing/status
Returns whether Stripe is configured for this organization and whether a
customer record exists yet. The dashboard uses this to gate the “Open Stripe
Portal” CTA — top-up itself does not require a pre-existing customer (Checkout
auto-provisions one on first payment).
Wallet balance
Get Balance
GET /api/v1/billing/balance (alias: GET /api/v1/billing/credits)
Retrieve the current wallet balance in USD cents plus the outbound-pause
flag (set when the balance hits zero or a payment-method issue parks the
account).
balance_cents and credits are the same minor-unit (cents) value —
credits is preserved for backwards compatibility. balance_usd is the
same number divided by 100. When outbound_paused: true is set, all
outbound sends (SMS, voice, AI compose) return BALANCE_TOO_LOW until
a successful top-up clears the flag.Top up the wallet
Create Top-Up Session
POST /api/v1/billing/balance/top-up (alias: POST /api/v1/billing/credits/purchase)
Creates a Stripe Checkout session (mode: payment — one-time charge, NOT a
subscription) for adding USD credits to the wallet. Stripe webhook
checkout.session.completed credits the wallet asynchronously after payment
clears.
The Idempotency-Key header is required. Re-submitting the same key
within 60 seconds (Redis hot-path) or within the durable replay window
(Postgres topup_idempotency_records) returns the original Checkout URL
instead of creating a duplicate session.
Stable client-generated value, 8–255 characters. Re-using the key for a
different
(amount, currency) returns 409 IDEMPOTENCY_KEY_REUSED.Amount in minor units (cents). Min
1, max 1000000 ($10,000).usd or eur. The wallet stores values in the org’s default currency;
Stripe webhook handlers apply FX conversion when the charge currency
differs.Redirect URL on successful payment. Must be on a trusted origin
(
orbit.devotel.io).Redirect URL if the user cancels. Must be on a trusted origin.
Configure Auto-Top-Up
PUT /api/v1/billing/auto-topup
Set up automatic top-ups when the wallet balance falls below a threshold.
Companion routes: GET /api/v1/billing/auto-topup reads the current
config, DELETE /api/v1/billing/auto-topup disables and clears it. The
scheduled charge fires from the webhook-worker on a 15-minute tick once
the threshold is crossed and uses the org’s default off-session payment
method.
Enable or disable auto-top-up.
Balance threshold (in minor units, e.g. cents) that triggers a top-up.
Amount (in minor units, e.g. cents) to charge when triggered. Stripe rejects
amounts below the per-currency minimum charge.
Open Stripe Customer Portal
POST /api/v1/billing/portal
Mints a short-lived Stripe Customer Portal URL where the end user can manage
saved payment methods, download tax receipts, and view past charges. This
is NOT a subscription portal — Orbit has no recurring subscription state to
manage.
URL to redirect to after the portal session ends. Must be on a trusted
origin (
orbit.devotel.io).Transactions ledger
List Transactions
GET /api/v1/billing/transactions?limit=30&type=spend&cursor_ts=...&cursor_id=...
Cursor-paginated read of public.credit_transactions. Negative
amount_minor values are spends (sends, voice minutes, AI tokens, etc.);
positive values are top-ups and refunds. Cursor is composite
(created_at, id) so ties on the same nanosecond don’t skip or
duplicate.
Page size, max 100.
Optional filter:
topup, spend, refund, bonus.ISO-8601 timestamp from the previous page’s
next_cursor.cursor_ts.Row id from the previous page’s
next_cursor.cursor_id.Usage analytics
Get Usage By Channel
GET /api/v1/billing/usage-by-channel?days=30&group_by=channel
Per-channel spend + volume breakdown sourced from credit_transactions.
Powers the dashboard usage card. Supports group_by axes
channel | country | campaign | sender for slicing the same spend
window.
Lookback window, 1–90.
channel, country, campaign, or sender.Optional filter narrowing to a single channel (e.g.
sms, voice).Per-channel billing rules
- SMS / MMS — charged on
submitted(carrier accepted handoff). DLRdeliveredupdates the row’sstatusbut doesn’t double-bill. - Voice — billed in 60s increments after
answered;busy/no-answeris free. - WhatsApp BYO — Orbit does not charge messaging fees; the tenant’s own WABA cost is settled directly with Meta. Orbit only charges AI compose tokens when an agent generates the message body.
- RCS / Email / Push — charged on
submitted(carrier or Resend accepted). - Viber Tier 1 (SMPP) — charged on DLR
delivered(Viber state=5); submit-time and rejected messages are free.
Get Spend Series
GET /api/v1/billing/spend-series?days=30
Per-day spend (positive minor units) over the lookback window plus a
7-day moving-average forward projection. Anomaly days (spend > mean + 2σ)
are flagged for callout in the dashboard chart.
Lookback window in days, 1–90.
Get Burn Rate
GET /api/v1/billing/burn-rate?days=30
Returns the trailing average daily spend and the projected number of
days remaining before the wallet hits zero (balance_cents / avg_daily_spend).
Powers the dashboard’s BalanceWidget “X days remaining” inline copy.
Invoices
List Invoices
GET /api/v1/billing/invoices
Retrieve past Stripe invoices and one-time payment receipts for the org.
Invoice amounts are in the smallest currency unit (cents for USD). Divide by
100 for display.
Refunds (owner only)
POST /api/v1/billing/refunds issues a Stripe refund against a specific
top-up credit_transaction lot (30-day window). GET /api/v1/billing/refunds
lists the org’s refund history. Both endpoints are restricted to the org
owner role — unexpected refunds against a prepaid wallet are high-impact
and hard to reverse.
Examples
Node.js
A first-party Python SDK is on the roadmap but not yet shipped. Call the REST
endpoints above with
requests / httpx / any HTTP client.