Skip to content
Discord

Wallet

Mirai Voice is prepaid. Every workspace has one INR wallet. Calls debit it when they end; ops credits it when you top up. If the wallet cannot cover a minute, POST /v2/calls returns 402 and no call is placed.

For rates and rounding, see Billing & tiers.

GET /v2/wallet
curl https://api.voice.miraiminds.co/v2/wallet \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
200 OK
{
  "balance_inr": 500,
  "currency": "INR",
  "updated_at": "2026-07-26T09:12:44Z"
}
FieldTypeDescription
balance_inrnumberSpendable balance. Never negative.
currencystringAlways INR.
updated_atstringWhen the balance last changed.
GET /v2/wallet/transactions?limit=&cursor=

The ledger. One row per credit or debit, newest first.

curl "https://api.voice.miraiminds.co/v2/wallet/transactions?limit=50" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
200 OK
{
  "data": [
    {
      "id": "txn_01JZQ9D6P2R5S8T4V7W9X1Y3ZB",
      "object": "wallet_transaction",
      "type": "debit",
      "amount_inr": 2,
      "balance_after_inr": 498,
      "call_id": "call_01JZQ9B4M8N3P6R2S5T7V9W1YA",
      "description": "call 96s @ t1",
      "created_at": "2026-07-26T09:16:39Z"
    },
    {
      "id": "txn_01JZQ8E2M4N6P8R1S3T5V7W9XA",
      "object": "wallet_transaction",
      "type": "credit",
      "amount_inr": 500,
      "balance_after_inr": 500,
      "call_id": null,
      "description": "pilot top-up",
      "created_at": "2026-07-26T08:40:11Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
FieldTypeDescription
typecredit | debitDirection.
amount_inrnumberAlways positive; read type for direction.
balance_after_inrnumberBalance immediately after this row.
call_idstring | nullSet on call debits, null on top-ups and adjustments.
descriptionstringHuman-readable. Do not parse it.

Reconcile against call_id: every billable call produces exactly one debit row.

Top-ups are ops-issued during the pilot — there is no self-serve endpoint yet. Email sneh@miraiminds.co; the credit appears as a credit row within minutes and takes effect immediately.

Before dialling, we check the wallet can cover at least one minute at the call’s tier. If it cannot:

402 Payment Required
{
  "error": {
    "code": "insufficient_balance",
    "message": "wallet balance 0.40 INR is below the minimum for one minute at t1"
  }
}
  • The gate runs pre-dial. No phone rings, nothing is charged, no call object is created.
  • Retry after topping up, with the same Idempotency-Key — the failed request did not consume it.
  • Calls already in flight are not killed. A call that started with credit runs to its natural end, then debits. A wallet can therefore end a busy minute slightly lower than the pre-dial check implied.

Handle it explicitly — it is the one error that is a business condition rather than a bug:

r = httpx.post(f"{API}/v2/calls", headers=auth, json=payload, timeout=30)
if r.status_code == 402:
    pause_campaign()
    alert_ops(r.json()["error"]["message"])
else:
    r.raise_for_status()