Guides

Billing

How the wallet, price book, ledger, plans, invoices and top-ups work, and what happens at zero balance.

Calls are paid from a prepaid wallet. Every wallet movement is a ledger entry, and prices come from a price book the operator configures.

In the dashboard: Settings → Billing. Reading the balance, plans and prices needs any role. Reading the ledger, invoices and payments needs admin. Changing anything (plan, top-up, tax details, alerts) needs owner. An API key acts as admin, so it can read billing records but cannot top up or change the plan.

Note: The current list prices are on the Pricing page. The rates that apply to your workspace are the ones in its price book: read them in Settings → Billing or with GET /billing/price-book. Amounts in the examples below are examples.

Units

  • The wallet holds major units of the billing currency (rupees, dollars).
  • Rates and call costs (total_cost, cost_breakdown) are in minor units: 100 minor units are one major unit.
  • The billing endpoints on this page return money in major units.

Balance

bash
curl https://api.sevennadi.si/billing/balance \
  -H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"
json
{
  "balance": 42.5,
  "currency": "INR",
  "paid_by_parent": false,
  "low_balance_threshold": 1.0,
  "low": false,
  "plan": {"id": "payg", "name": "Starter", "kind": "payg", "status": "active", "period": "2026-10",
           "monthly_fee": 0.0, "concurrency_limit": 10, "included_minutes": 0.0,
           "included_minutes_left": 0.0, "included_minutes_used": 0.0},
  "payments": {"provider": "mock", "available": true, "min": 1.0, "max": 500000.0},
  "tax": {"rate_percent": 0.0, "mode": "exclusive"}
}

The values here are examples. paid_by_parent is true for a sub-account, which spends its parent's wallet.

How a call is priced

text
cost = minutes x ( platform fee
                 + telephony rate for the provider and direction
                 + transcriber rate + LLM rate + synthesizer rate )
  • Every rate is per minute. Billing is by the second.
  • A component is charged only if the engine reported using it.
  • The platform's own models (provider local) are priced at zero in the book.
  • A call that never connected charges nothing.

GET /billing/price-book returns the rates that apply to your workspace, including any custom prices the operator gave you ("custom": true). GET /get_current_price quotes a per-minute total from the same book.

The ledger

Each wallet movement is one immutable row. The database refuses updates and deletes on it.

bash
curl "https://api.sevennadi.si/billing/ledger?kind=call&from=2026-10-01&to=2026-10-31" \
  -H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"

One entry looks like this:

json
{"id": 9912, "kind": "call", "amount": -0.03, "balance_after": 42.47, "currency": "INR",
 "description": "Call to +91******0123, 42 s", "reference": "call:b8f0c9a2-3e5d-4c1b-9f7a-2d6e8c4b1a90",
 "execution_id": "b8f0c9a2-3e5d-4c1b-9f7a-2d6e8c4b1a90", "payment_id": null, "invoice_id": null,
 "minutes": 0.7, "included_minutes": 0.0, "created_at": "2026-10-13T09:31:05Z"}
kindMeaning
callA finished call
number_rentalA phone number's monthly rental
plan_feeA monthly plan's fee
taxTax on an invoice (exclusive mode)
setup_assistantA setup assistant message, if the operator priced it (free in the shipped price book)
topup, creditMoney added
refund, adjustment, opening_balanceCorrections by the operator

Filters: kind, execution_id, from, to; format=csv downloads the result; page_size up to 200.

Each entry has a unique reference. Posting the same thing twice changes nothing, so a call cannot be charged twice however often its job is retried. GET /billing/usage?days=30 returns spend, minutes and calls per day, built from the ledger.

Plans

GET /billing/plans lists the plans in the price book and your current one. Plans come in three kinds:

KindBehaviour
trialWhat new sign-ups start on: the sign-up credit and a small concurrency limit
paygNo monthly fee; calls are charged per minute
monthlyA monthly fee, included call minutes and a higher concurrency limit; extra minutes at per-minute rates
bash
curl -X POST https://api.sevennadi.si/billing/plan \
  -H "Authorization: Bearer vs-0123456789abcdef0123456789abcdef" \
  -H "Content-Type: application/json" \
  -d '{"plan_id": "payg"}'

Only plans marked self_serve can be chosen this way. Choosing a plan sets your concurrency limit. A monthly plan's fee is debited immediately and then each month; without enough balance the plan is past_due and gives no included minutes until a top-up lets the fee through.

Top-ups

bash
curl -X POST https://api.sevennadi.si/billing/topups \
  -H "Authorization: Bearer vs-0123456789abcdef0123456789abcdef" \
  -H "Content-Type: application/json" \
  -d '{"amount": 500}'

Response, 201:

json
{"id": "2f1e0d9c-8b7a-4f6e-9d5c-4b3a2f1e0d9c", "provider": "mock", "amount": 500.0, "currency": "INR",
 "status": "paid", "checkout_url": null, "refunded_amount": 0.0, "error": null,
 "created_at": "2026-10-13T11:00:00Z", "paid_at": "2026-10-13T11:00:00Z", "balance": 542.5}

With a real payment provider the status is created and checkout_url is where to send the payer. The wallet is credited when the provider's webhook confirms the payment: the signature is verified, the amount and currency must match, and the credit is posted once. A receipt is emailed.

The default provider is mock, which pays at once. It is for development and is refused in production. amount must be within the operator's minimum and maximum. A sub-account cannot top up: A sub-account spends its parent's wallet: add funds on the parent account.

Invoices

A daily job charges plan fees and number rentals for the month that has started, then issues last month's invoice for every workspace with billable usage. Owners are emailed.

  • GET /billing/invoices lists them; GET /billing/invoices/{invoice_id}/pdf downloads one.
  • Tax uses a configured rate with a split by state code. In exclusive mode the invoice's tax is debited from the wallet as its own ledger entry.

Low balance and zero balance

  • One email when the balance drops under your threshold, and one when it runs out. No more until a top-up.
  • PUT /billing/alerts with {"low_balance_threshold": 100} sets your own threshold; 0 turns the low-balance email off; null returns to the platform default.
  • A single call with no balance ends balance-low.
  • A batch at zero balance keeps status: running with paused_reason: "balance-low". Its calls stay queued and continue after a top-up.
  • A workflow campaign becomes paused with the same reason and resumes after a top-up.

Limitations

  • No payment has gone through the Razorpay or Stripe adapters; they have only been tested against local fakes.
  • Invoices have not been reviewed by an accountant. There are no credit notes and no e-invoicing, and the PDF cannot print non-Latin scripts.
  • A call is not cut off when the balance runs out mid-call, so the wallet can go below zero. Exclusive tax can also take it negative.
  • A phone number is not released when the wallet cannot pay its rental.

For every endpoint see Billing reference.