Skip to content

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.

$0.0005one credit, one converted page
0per-request fee and minimum charge
neverpaid and bonus credits expire
integersevery credit quantity in the API

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.

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.

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 itemRateApplies to
page_convert1 credit per billable pagePOST /v1/convert
page_probe1 credit per 100 pages, rounded upPOST /v1/inspect with scan: "full"
toc_outline1 credit per requestPOST /v1/toc, outline path
toc_infer1 credit per billable pagePOST /v1/toc, inference path (from M2)
Per-request fee0everywhere
Minimum charge0everywhere

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.

The cost function, in full
billable_pages = pages_selected
− { p : some warning w has severity "critical", scope "page", and p in w.pages }
cost_credits = |billable_pages| × 1

Not 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.

OutcomeBilled
2xxPages converted, minus every page in usage.pages_not_billed
Any 4xx, including 402 and 4290
Any 5xx, any timeout, any converter crash0
Cache hitFull rate — you received the product
A page with a critical page-scope warning0 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 critical page-scope warning appears in usage.pages_not_billed, and every page in usage.pages_not_billed appears in a critical page-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.

A 19-page selection with three image-only pages
{
"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 }]
}
}

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 documentHeld
A file_id — page count already known from the uploadExact
Raw body or multipart with a bounded pages, e.g. 1-5,9Exact, the selected count
Raw body or multipart with pages: null or 10-last200 credits, the sync ceiling
/v1/toc with fallback: "none"1 credit
/v1/toc with fallback: "headings" from a file_idThe 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.

Three places, for three different questions.

GET /v1/account
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.

Every conversion response carries the balance in headers, so pacing needs no extra call:

x-credits-charged: 5
x-credits-balance: 219995
x-credits-unit: page

x-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.

The authoritative credit history
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_after

Every ledger and usage row carries api_key_id, so “which key spent this?” is an indexed query rather than an investigation.

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.

402 insufficient_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.

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.

LevelRequirementCredits/monthMax pages/docReq/minConcurrency
F0Verified email2001051
F1GitHub OAuth aged ≥ 90 days with ≥ 1 public repo or ≥ 5 contributions, or a corporate email domain1,0002051

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.

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.

TierEntryMax single top-upMax 24 hMax balanceBurn cap
T0 FreeVerified emailMonthly allowance
T1 NewFirst settled payment$100$100$20020,000 credits/24 h
T2 Funded≥ 7 days since first settled payment, no risk events$500$1,000$2,000200,000 credits/24 h
T3 Seasoned≥ 30 days, ≥ $250 lifetime settled, no risk events$2,500$5,000$10,000Rate limits only
T4 ContractManual approval; ACH, wire or invoice onlyNegotiatedRate 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.

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.

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.