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, charged in 30-second increments rounded up — a 0–30s call bills 30 seconds, 31–60s bills one minute, and so on. 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 Free and Enterprise; roles are runtime pricing choices. There are four roles: operator, specialist, executive, and concierge.
Response 200
{
"roles": {
"operator": {
"rate_per_minute": 0.05,
"rate_per_second": 0.00083333,
"display_name": "Operator"
},
"specialist": { "rate_per_minute": 0.10, "rate_per_second": 0.00166667, "display_name": "Specialist" },
"executive": { "rate_per_minute": 0.25, "rate_per_second": 0.00416667, "display_name": "Executive" },
"concierge": { "rate_per_minute": 0.40, "rate_per_second": 0.00666667, "display_name": "Concierge" }
}
}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,
"topup_minutes_remaining": 60,
"roles": { "operator": 0.05, "specialist": 0.10, "executive": 0.25, "concierge": 0.40 },
"is_active": true,
"tone": "ok",
"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 in the active credit bucket at the operator rate. The active bucket is the included quota while any remains, then top-up credits |
balance_usd | The active bucket in USD |
estimated_minutes_remaining | The active bucket in whole minutes |
topup_minutes_remaining | Top-up bucket only, in minutes — purchased credit that survives the included quota reset |
is_active | Whether the tenant can dispatch live calls (false when total balance ≤ 0) |
tone | UI hint — ok, low (under 30 min), or critical (under 5 min or inactive). Reflects total balance (included quota + top-up), so a draining included bucket doesn't false-alarm when top-up credit remains |
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_...",
"call_id": null,
"amount_seconds": 36000,
"amount_usd": 30.0,
"transaction_type": "stripe_topup",
"description": "Top-up via Stripe",
"metadata": {},
"created_at": "2026-04-01T10:00:00Z"
}
]
}Per-call deductions carry the call_id they were billed against; top-ups and grants have call_id: null.
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 |
GET Response 200
{ "thresholds_usd": [50, 100], "low_balance_pct": 20, "email_enabled": true }Tenants that have never saved preferences get the defaults shown above.
PUT Response 200
{ "ok": 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": []
}Acknowledging returns { "id": "...", "acknowledged_at": "..." }, or 404 if the alert doesn't exist, was already acknowledged, or belongs to another tenant.
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 (default operator) |
duration_seconds | number | Optional; when provided, est_for_duration_usd is included in the response |
Response 200
{
"tier": "specialist",
"low_usd_per_min": 0.10,
"high_usd_per_min": 0.10,
"est_for_duration_usd": 0.2417
}Usage Summary
GET /v1/billing/usage-summaryLane-aware usage summary across the three metered surfaces: voice runtime (in minutes — the customer-facing unit), Studio AI units, and post-call intelligence units. This is what the dashboard's usage cards render, and what the SDKs' billing.usage_summary() returns.
Response 200
{
"voice_runtime": { "remaining_minutes": 300, "status": "ok" },
"studio_ai": {
"used_units": 12,
"included_units": 100,
"quota_percent": 12,
"status": "ok",
"overage_enabled": false
},
"post_call_intelligence": {
"status": "quota",
"used_units": 4,
"included_units": 50,
"quota_percent": 8
}
}Plan & Billing State
GET /v1/billing/plan
POST /v1/billing/checkout/startThe dashboard uses these to read the current plan state and start prepaid top-up checkout.
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,
"extra_usage_enabled": true,
"plan_active_since": "2026-04-01T00:00:00Z",
"preferred_gateway": "stripe",
"preferred_currency": "USD",
"usd_to_inr_rate": 84.0,
"billing_mode": "prepaid_only"
}billing_mode is currently prepaid_only in the MVP: included plan quota runs first, then prepaid wallet balance covers extra usage when enabled.
POST /billing/checkout/start Body
| Field | Type | Required | Description |
|---|---|---|---|
pack_usd | number | yes | Dollar value to add to the prepaid wallet |
billing_country | string | no | ISO country code used for gateway selection |
tax_id | string | no | Tax identifier used for gateway selection |
save_card | boolean | no | Register the card for auto-recharge when supported |
Returns a Razorpay order payload for India-region checkout or a Stripe Checkout URL when Stripe checkout is enabled for the tenant region.
Errors
| Status | Meaning |
|---|---|
503 | Gateway not configured (missing Stripe / Razorpay env) |

