Pricing and credits
1 credit = 1 standard converted page. List price $0.50 per 1,000 pages.
Credits are prepaid and spent a page at a time. There is no subscription, no per-request fee, no minimum charge per document, and no page-count rounding on conversion.
Every credit quantity in the API is a non-negative integer. There are no fractional credits anywhere, ever. Money is tracked in integers end to end — no floating point touches the billing path.
Why credits and not dollars
Section titled “Why credits and not dollars”A credit is an abstract unit whose standard-page price happens to be 1. Pricing in credits rather than in micro-dollars means a price cut cannot silently make an existing balance buy more pages than it was sold to buy, a bonus ladder lands on exact integers with no dust, and no response ever has to perform a division — and divisions are where rounding bugs live.
The rate card
Section titled “The rate card”Rate card version 1 is immutable. It is pinned into every credit hold, so the price you were quoted is the price you are charged.
| Line item | Rate | Applies to |
|---|---|---|
page_convert | 1 credit per billable page | POST /v1/convert |
page_probe | 1 credit per 100 pages, rounded up | POST /v1/inspect with scan: "full" |
toc_outline | 1 credit per request | POST /v1/toc, outline path |
toc_infer | 1 credit per billable page | POST /v1/toc, inference path (from M2) |
| Per-request fee | 0 | everywhere |
| Minimum charge | 0 | everywhere |
page_convert and toc_infer are never rounded: one page, one credit. page_probe and
toc_outline are the only two line items that round, neither is a conversion charge, and the
“no page-count rounding” promise is scoped to conversion for exactly that reason.
What a conversion costs
Section titled “What a conversion costs”billable_pages = pages_selected − { p : some warning w has severity "critical", scope "page", and p in w.pages }
cost_credits = |billable_pages| × 1Not input bytes, not output bytes, not wall clock — pages. “No page-count surprise” is a promise, and the other three are not things you can predict before you send the request. The billable set is computed by the converter from the document; nothing you send in the request can influence it.
Each unique page is billed exactly once regardless of how many ranges cover it. 1-5,3-7 selects
seven pages and bills seven credits.
What bills and what does not
Section titled “What bills and what does not”| Outcome | Billed |
|---|---|
2xx | Pages converted, minus every page in usage.pages_not_billed |
Any 4xx, including 402 and 429 | 0 |
Any 5xx, any timeout, any converter crash | 0 |
| Cache hit | Full rate — you received the product |
A page with a critical page-scope warning | 0 for that page; the rest of the request bills normally |
This is contractual, and it is machine-checkable from the response alone:
Every page appearing in a
criticalpage-scope warning appears inusage.pages_not_billed, and every page inusage.pages_not_billedappears in acriticalpage-scope warning.
Assert both in your CI. We do.
The three page-scope critical codes in v1 are page_has_no_text_layer, page_render_failed and
page_budget_exceeded. A genuinely blank page is not one of them: a blank page correctly
converts to nothing, so we did our job and we bill for it, and it reports page_empty (info). An
image-only page produced nothing of value, so we did not, and we do not.
{ "warnings": [ { "code": "page_has_no_text_layer", "severity": "critical", "scope": "page", "pages": [7, 8, 12], "billed": false } ], "usage": { "pages_processed": 19, "pages_billed": 16, "pages_not_billed": [7, 8, 12], "credits_charged": 16, "line_items": [{ "kind": "page_convert", "quantity": 16, "credits": 16 }] }}Holds and settlement
Section titled “Holds and settlement”We cannot know what a conversion costs until the PDF is parsed, so a request takes a credit hold on admission and settles the real number when it completes. The amount held is never more than it has to be:
| How you supply the document | Held |
|---|---|
A file_id — page count already known from the upload | Exact |
Raw body or multipart with a bounded pages, e.g. 1-5,9 | Exact, the selected count |
Raw body or multipart with pages: null or 10-last | 200 credits, the sync ceiling |
/v1/toc with fallback: "none" | 1 credit |
/v1/toc with fallback: "headings" from a file_id | The document’s page count |
The 200-credit ceiling is a $0.10 hold that is released within one round trip when the request settles. Charged is always less than or equal to held, by construction and by a database constraint. Any hold is released within 60 seconds of a terminal outcome that does not bill.
If you convert the same document more than once, upload it with POST /v1/files first. The upload
is free, it computes the page count without loading a single page, and every conversion afterwards
takes an exact hold rather than the ceiling.
Checking your balance
Section titled “Checking your balance”Three places, for three different questions.
Right now, before you call
Section titled “Right now, before you call”curl -sS https://api.kaho.ai/v1/account \ -H "Authorization: Bearer $KAHO_API_KEY"Returns balance_credits, held_credits, debt_credits, credits_required_to_resume, your trust
tier, your low-balance threshold, the published limits that apply to you, and which optional
features are enabled. The limits in this response are generated from the same source as this site
and the OpenAPI document; no other number anywhere is authoritative.
On every response, for free
Section titled “On every response, for free”Every conversion response carries the balance in headers, so pacing needs no extra call:
x-credits-charged: 5x-credits-balance: 219995x-credits-unit: pagex-credits-unit is page on a conversion and document on the /v1/toc outline path. It changes
value between those two paths, which is exactly why it exists as a header rather than as a constant
in the documentation.
Afterwards, authoritatively
Section titled “Afterwards, authoritatively”curl -sS -G https://api.kaho.ai/v1/credits/ledger \ -H "Authorization: Bearer $KAHO_API_KEY" \ --data-urlencode "since=2026-08-01T00:00:00Z" \ --data-urlencode "limit=100"GET /v1/credits/ledger reads the account’s ledger directly and is the authoritative record of
every credit movement. GET /v1/usage and GET /v1/usage/records are indexed reporting copies:
faster to query, groupable by day, key or endpoint, and able to lag the ledger slightly. Use the
ledger to reconcile money and usage to analyse behaviour.
For a spreadsheet or a warehouse, POST /v1/usage/export produces CSV or NDJSON at a signed URL
valid for one hour. Columns are append-only — new ones arrive on the right, so a positional parser
keeps working:
request_id, ts_utc, api_key_id, api_key_name, mode, endpoint, engine, rate_card_version,page_count, pages_selected, pages_processed, pages_billed, pages_not_billed, credits_charged,line_items, cache, outcome, error_code, duration_ms, balance_afterEvery ledger and usage row carries api_key_id, so “which key spent this?” is an indexed query
rather than an investigation.
Before you run out
Section titled “Before you run out”Set a low-balance threshold in the console and you get a credits.low notification when the
balance crosses it. The threshold has hysteresis: it fires once, then re-arms only after the
balance recovers to twice the threshold, so an account hovering at the line does not emit a
notification per request.
Running out of credits
Section titled “Running out of credits”{ "error": { "type": "payment_required_error", "code": "insufficient_credits", "message": "This request needs 200 credits and the account has 43.", "retryable": false, "doc_url": "https://docs.kaho.ai/errors/insufficient_credits", "request_id": "req_01JQ8Z5T7B9KX2W4M6N0P3R5S7", "details": { "credits_required": 200, "credits_available": 43, "top_up_url": "https://kaho.ai/app/billing" } }}A 402 is billed 0, like every other 4xx. Note the required figure: if you sent an unbounded page
range, this is the 200-credit ceiling rather than the true cost of your document. Sending a bounded
pages expression, or converting from an uploaded file_id, makes the requirement exact.
The other 402 is account_delinquent, which means an open dispute or chargeback has frozen
spending. It always carries debt_credits and credits_required_to_resume, so you are never told
to pay without being told how much.
Free tier
Section titled “Free tier”No card is required for free access, and that is deliberate: requiring a card for free access turns a signup form into a card-testing endpoint.
| Level | Requirement | Credits/month | Max pages/doc | Req/min | Concurrency |
|---|---|---|---|---|---|
| F0 | Verified email | 200 | 10 | 5 | 1 |
| F1 | GitHub OAuth aged ≥ 90 days with ≥ 1 public repo or ≥ 5 contributions, or a corporate email domain | 1,000 | 20 | 5 | 1 |
Free credits are granted as a promotional lot at 00:00 UTC on the first of the month, expire at month end, and do not accumulate. Free-tier traffic runs on lower-priority infrastructure and is shed first under load, so it never displaces a paying request. The free tier excludes asynchronous jobs, webhooks, result storage, priority queueing and any SLA.
Trust tiers
Section titled “Trust tiers”Purchase and consumption limits rise automatically with account history. Everything here is
published, appears in GET /v1/account, and is visible in the x-ratelimit-* headers.
| Tier | Entry | Max single top-up | Max 24 h | Max balance | Burn cap |
|---|---|---|---|---|---|
| T0 Free | Verified email | — | — | — | Monthly allowance |
| T1 New | First settled payment | $100 | $100 | $200 | 20,000 credits/24 h |
| T2 Funded | ≥ 7 days since first settled payment, no risk events | $500 | $1,000 | $2,000 | 200,000 credits/24 h |
| T3 Seasoned | ≥ 30 days, ≥ $250 lifetime settled, no risk events | $2,500 | $5,000 | $10,000 | Rate limits only |
| T4 Contract | Manual approval; ACH, wire or invoice only | — | — | Negotiated | Rate limits only |
The T1 burn cap is the one to plan around: for the first seven days an account may consume at most 20,000 credits per 24 hours regardless of balance. That is $10 of list value a day, twenty times the monthly free allowance, and it lifts on its own. If you need more on day one, ask support — a two-minute check and you have it within the hour.
Amounts above $2,500 are a contract invoice, never self-serve checkout.
Credit lifetime
Section titled “Credit lifetime”Paid and bonus credits never expire. Free-tier lots expire at the end of the month they were granted in, and goodwill lots expire after 12 months.
A top-up is characterised in the terms of service as a prepayment for a specific service — not stored value, not a gift card, not a general-purpose balance. That is why there is no cash-out.
The house eats the rounding
Section titled “The house eats the rounding”Where a calculation could land between two integers, it lands in your favour: grants round up, charges round down, refunds round up, and disputed delivery bills zero. The maximum the house can be wrong by on any single event is 1 credit — half a thousandth of a cent.