Billing
Programmatic access to your tenant's credit balance, transactions, invoices, per-call cost breakdowns, and spend alerts. The dashboard's Billing & Usage screen is built entirely on these endpoints.
Rymi bills per minute of connected call audio against a prepaid credit balance. Failed calls (busy, no-answer) don't bill — only calls that connect and exchange audio.
Get Pricing Rates
GET /v1/billing/agent-rolesReturns per-role pricing rates. Same rates apply across all plans; Free / Hobby / Pro determine which roles are unlocked, not the rate.
Response 200
{
"roles": {
"operator": {
"rate_per_minute": 0.05,
"rate_per_second": 0.00083333,
"display_name": "Operator"
},
"specialist": { "rate_per_minute": 0.12, "rate_per_second": 0.002, "display_name": "Specialist" }
}
}Get Balance
GET /v1/billing/balanceReturns the tenant's current credit balance plus composition signals the dashboard uses to render the right headline format (single-role tenant, runway estimate, role range, etc.).
Response 200
{
"tenant_id": "550e8400-...",
"balance_seconds": 18000,
"balance_usd": 15.0,
"estimated_minutes_remaining": 300,
"roles": { "operator": 0.05, "specialist": 0.12 },
"is_active": true,
"tone": "healthy",
"tenant_state": "has_calls",
"primary_role": "specialist",
"agent_roles_in_use": [],
"recent_daily_spend_usd": 1.20,
"runway_days_estimate": 12
}| Field | Description |
|---|---|
balance_seconds | Remaining seconds of call time at the cheapest rate |
balance_usd | Same balance in USD |
is_active | Whether the tenant can dispatch live calls (false when balance ≤ 0) |
tone | UI hint — healthy, low, critical, or depleted |
tenant_state | no_agents, agents_no_calls, or has_calls |
primary_role | Most-used role across the last 30 days, or null if no role accounts for >50% of recent calls |
recent_daily_spend_usd | 7-day rolling daily-spend average (only set after ≥7 days of activity) |
runway_days_estimate | balance_usd / recent_daily_spend_usd, only set when both are populated |
List Transactions
GET /v1/billing/transactionsPer-tenant credit ledger — top-ups, monthly grants, refunds, and per-call deductions.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Max records (cap 200) |
from | ISO date | — | Inclusive lower bound on created_at |
to | ISO date | — | Inclusive upper bound on created_at (extended to end of day) |
Response 200
{
"transactions": [
{
"id": "tx_...",
"amount_seconds": 36000,
"amount_usd": 30.0,
"transaction_type": "stripe_topup",
"description": "Top-up via Stripe",
"created_at": "2026-04-01T10:00:00Z"
}
]
}List Invoices
GET /v1/billing/invoicesCursor-paginated list of invoices for the tenant.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 25 | Max records (cap 100) |
cursor | string | — | Resume token returned in next_cursor |
Response 200
{
"invoices": [
{ "id": "inv_...", "total_usd": 30.0, "status": "paid", "created_at": "..." }
],
"next_cursor": null
}Get Invoice
GET /v1/billing/invoices/:idReturns the invoice plus its line items.
Response 200
{
"invoice": { "id": "inv_...", "total_usd": 30.0, "status": "paid" },
"items": [ { "description": "Top-up", "amount_usd": 30.0 } ]
}| Status | Meaning |
|---|---|
404 | Invoice does not exist for this tenant |
Per-Call Cost Breakdown
GET /v1/billing/callsLists recent calls with cost / margin metadata. Useful for reconciliation against your own records.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Max records (cap 200) |
from | ISO date | — | Inclusive lower bound on started_at |
to | ISO date | — | Inclusive upper bound on started_at |
agent_id | string | — | Filter to a single agent |
Response 200
{
"calls": [
{
"id": "call_...",
"started_at": "2026-04-01T10:00:00Z",
"agent_id": "agent_...",
"agent_name": "Support Bot",
"agent_role": "specialist",
"duration_seconds": 145,
"customer_revenue": 0.29,
"provider_cost": 0.07,
"gross_margin": 0.22
}
]
}Export CSV
GET /v1/billing/export.csvDownloads up to 5,000 calls as CSV. Filterable by date range, agent, and call status.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
from | ISO date | Inclusive lower bound on started_at |
to | ISO date | Inclusive upper bound on started_at |
agent_id | string | Filter to a single agent |
status | string | Filter by call status (completed, failed, …) |
Returns text/csv with header row:
call_id,started_at,agent_id,agent_role,duration_seconds,customer_revenue_usd,provider_cost_usd,gross_margin_usdSpend Alerts
GET /v1/billing/alerts
PUT /v1/billing/alertsConfigure threshold-based spend alerts.
PUT Request Body
| Field | Type | Description |
|---|---|---|
thresholds_usd | number[] | Spend thresholds in USD (max 10) |
low_balance_pct | integer (0–100) | Alert when remaining balance ≤ this percent of last top-up |
email_enabled | boolean | Whether to email the alert |
Response 200
{ "thresholds_usd": [50, 100], "low_balance_pct": 20, "email_enabled": true }Spend-Velocity Alerts
GET /v1/billing/spend-alerts
POST /v1/billing/spend-alerts/:id/acknowledgeReturns un-acknowledged spend-velocity alerts (Phase 2B safety net) and the most recent ten acknowledged ones, plus an endpoint to dismiss a banner.
GET Response 200
{
"unacknowledged": [
{ "id": "alert_...", "alert_type": "spend_velocity", "severity": "critical", "amount_usd": 12.5, "threshold_usd": 10.0 }
],
"recent": []
}Estimate
POST /v1/billing/estimateEstimates cost for a given role / duration. Used by the studio's call-planner.
Request Body
| Field | Type | Description |
|---|---|---|
tier | string | operator, specialist, executive, or concierge |
duration_seconds | number | Optional duration; defaults to a typical call |
Response 200
{ "rate_per_minute": 0.12, "estimated_usd": 0.29, "duration_seconds": 145 }Plan & Subscription
GET /v1/billing/plan
GET /v1/billing/subscription
POST /v1/billing/plan/upgrade
POST /v1/billing/plan/checkout/start
POST /v1/billing/plan/cancelThe dashboard's plan-selector modal uses these to read the current plan, kick off a checkout, and cancel.
GET /billing/plan Response 200
{
"plan_id": "free",
"plan": { "id": "free", "display_name": "Free", "monthly_credit_cents": 100, "byok_allowed": false },
"monthly_credit_remaining_cents": 80,
"monthly_credit_resets_at": "2026-05-01T00:00:00Z",
"topup_balance_cents": 1500,
"preferred_gateway": "stripe",
"preferred_currency": "USD"
}POST /billing/plan/upgrade Body
| Field | Type | Required | Description |
|---|---|---|---|
target_plan | enum | yes | free, hobby, pro, or enterprise |
billing_cycle | enum | no | monthly or annual (default monthly) |
free downgrades immediately. enterprise returns 501 sales_led with a contact email. hobby / pro route through /billing/plan/checkout/start, which returns a Razorpay short URL (IN-resident tenants) or a Stripe Checkout URL (everyone else).
POST /billing/plan/cancel
Schedules cancellation at the end of the current billing period when an active subscription exists; immediately reverts to free otherwise.
Errors
| Status | Meaning |
|---|---|
409 | A non-terminal subscription already exists — cancel before starting a new checkout |
501 | Subscription billing not yet enabled in this environment, or sales-led plan |
503 | Gateway not configured (missing Stripe / Razorpay env) |

