Skip to content

Guides

Credit

You read the balance, movement history and tax documents through both API and MCP. Paying by card - one-off or automatic - always goes through a Stripe Checkout link that a human completes; the API itself never charges a card on its own.

Money without VAT, hellers, the language of descriptions

Every amount in this API is an integer in HELLERS and exclusive of VAT - volai is VAT-registered, but VAT is only computed when you top up credit by card (see /pricing). Movement descriptions (GET /v1/credit/ledger) are composed by the API in English from the movement type - the stored Czech note never goes out, in any field.

Balance, runway and notices

GET /v1/balance returns the current balance plus two additional computations ADDITIVELY (existing clients that only read balanceHal/balanceCzk/currency are not broken): runway (dailyAverageHal - the recent average daily spend, runwayDays - how many days the balance lasts at this pace, null when it can't be computed) and notice - the same notice you see on the portal's overview page, null when there is none. Four possible notice shapes: {kind:"unknown"} (not enough data to estimate), {kind:"empty"} (zero balance), {kind:"number_fee",feeHal,dueAt} (a monthly number fee is coming up) and {kind:"runway",days} (the balance runs out in days days at the current pace).

Movement history

GET /v1/credit/ledger?limit - most recent first, WITHOUT a cursor (just limit, 1-200, default 50) - there is no cursor at all, so only the newest 200 movements are reachable through the API; the portal's /credit page and the tax documents have the full history. Every entry carries type (bonus, topup, call_out, call_in, sms, number_fee, agent, refund, admin), amountHal (positive for a credit, negative for a debit), grossHal (the VAT-inclusive amount for a card top-up, null otherwise) and an English description composed from the type - for number_fee, with the number the fee is for as well.

Top-up by card

POST /v1/credit/topup {amountCzk} returns {checkoutUrl} - a Stripe Checkout link that a HUMAN opens in a browser. Nothing is charged until they complete the payment themselves. A complete billing address (PUT /v1/account/billing) is required BEFORE the call - without it a valid tax document can't be issued (billing_address_required). Supports Idempotency-Key - safe to retry after a timeout without creating two payments.

Auto top-up

Through the API, auto top-up can only be READ, TURNED OFF and have its THRESHOLD changed - turning it on and changing the AMOUNT go exclusively through Stripe Checkout, never a direct write.

Why not a plain "enabled: true"

When a card is already saved, a bare {"enabled": true} would trigger charging WITHOUT A HUMAN at the card - exactly what we want to avoid ("money only moves with a human at the card"). So PATCH /v1/credit/auto-topup rejects both enabled: true and amountHal with its own message, not a generic unknown-field error.

Reading and turning off

GET /v1/credit/auto-topup returns {enabled, thresholdHal, amountHal, card: {brand, last4} | null, spentThisMonthHal, monthlyCapHal, disabledReason} - WITHOUT the card number, just the brand and last four digits. PATCH {enabled: false} turns it off immediately, PATCH {thresholdHal} only changes the threshold (it must be one of the offered values and lower than the saved amount, otherwise the balance would drop back below the threshold right after a top-up).

Turning on and changing the amount

POST /v1/credit/auto-topup/setup {thresholdHal, amountHal} returns {checkoutUrl} - completing Checkout IMMEDIATELY charges amountHal as the first top-up, and only that saves the card for future auto top-ups. It works even with a card already saved from before - it's the only way to turn auto top-up on or change its amount. After Checkout, the browser lands back on the CZECH portal page (/kredit?automat=zapnuto) even for an English-speaking customer - a known gap, not a bug in your integration.

Card

POST /v1/credit/auto-topup/card returns a Checkout link (setup mode, nothing is charged) to replace the saved card. DELETE /v1/credit/auto-topup/card detaches the card IMMEDIATELY - the whole configuration is deleted (threshold and amount included), not just the card; turning it on again only works through .../setup, which charges a first top-up right away. After Checkout, the browser lands back on the CZECH portal page (/kredit?karta=zmenena) regardless of the account's language - same known gap as above.

Caps

Two INDEPENDENT caps: at most 10,000 CZK of credited amount per calendar month, and at most 4 top-ups per calendar DAY - no matter how many times the balance drops below the threshold. Exceeding either one turns auto top-up off automatically (disabledReason monthly_cap or daily_cap); spentThisMonthHal/monthlyCapHal in the response show how much of the monthly cap is left.

MCP: get_auto_topup (read) and disable_auto_topup (turn off) - turning it on, changing the amount and the card are not in MCP, for the same reason as enabled: true in REST above.

Tax documents

A document is issued automatically on every card top-up of credit (one-off or automatic) - no manual step, no issuing through the API.

GET /v1/billing/documents?limit&before - most recent first, the before cursor (Unix ms, exclusive) is the nextBefore from the previous page. The underlying storage never returns more than 100 records per call, even when limit asks for more.

GET /v1/billing/documents/{id} returns {id, number, issuedAt, taxableAt, netHal, vatHal, grossHal, vatRatePercent, reverseCharge, description, customer, paymentRef, pdfUrl} - reverseCharge: true for payers from another EU country with a VIES-verified DIC, who pay without Czech VAT.

GET /v1/billing/documents/{id}/pdf downloads the same document as application/pdf - it's rendered fresh on every download, not stored. A foreign or missing id reports the same error (invoice_not_found) on both endpoints, so the response can't reveal that someone else's document exists.

MCP: list_billing_documents and get_billing_document have the same shape as REST. The PDF is REST-only - MCP has no binary channel to transfer a file.

Errors

  • ledger_unavailable - The movement history or balance could not be read right now - try again shortly.
  • invalid_amount - The top-up, threshold or auto top-up amount isn't one of the offered fixed values.
  • billing_address_required - The billing address is missing or incomplete - add it in the portal (/en/settings) before topping up, otherwise a valid document can't be issued.
  • stripe_not_configured - Payments are switched off for this volai deployment - not specific to your request.
  • stripe_error - Stripe refused to create a payment session - try again shortly.
  • autotopup_not_configured - Auto top-up has never been through .../setup - there is nothing to turn off, change or replace the card for.
  • invoice_not_found - No document with this id exists on the account (or it belongs to another account - the response is the same).
  • validation - Specifically on PATCH /v1/credit/auto-topup: an attempt to turn it on (enabled: true) or change amountHal directly - use POST .../setup instead.

The full error table for every endpoint is in the Credit section of the REST API reference.