Quickstart
Three steps, in this order: get a key, put credits on the account, convert a document. Signup to key takes under a minute and needs no card.
-
Get an API key
Section titled “Get an API key”Sign in at kaho.ai/app with your email address. There is no password — authentication is a magic link, so there is nothing to store, nothing to reset and nothing to stuff.
Create a key under API keys → Create. Naming is required, not optional: one form field is the difference between an incident where you know which key leaked and one where you do not. Names are 1–64 characters and unique within a project.
The plaintext key is shown exactly once, in the creation response. We store only a peppered HMAC of it, so there is no reveal button, because there is nothing to reveal. If you lose it, rotate it.
Put the key somewhere your shell can see it export KAHO_API_KEY="kaho_sk_live_JhK2mQ9xT4vBn7RcW1sYdF6gPzL8aE3uH5iO0jN2kM4_x7Kp2q"Confirm it works.
/v1/accountis free and needs only theaccount:readscope, which the default Full access preset includes.Check the key and see the account curl -sS https://api.kaho.ai/v1/account \-H "Authorization: Bearer $KAHO_API_KEY"Response — a new free-tier account, abridged {"balance_credits": 200,"held_credits": 0,"debt_credits": 0,"credits_required_to_resume": 0,"currency_unit": "page","trust_tier": "T0","limits": {"requests_per_min": 5,"concurrency": 1,"max_pages_per_document": 10,"max_upload_bytes": 33554432,"storage_limit_bytes": 0,"storage_used_bytes": 0},"features": { "remote_fetch": false, "ocr": false, "provenance": false, "jobs": false }}A verified email alone puts you on the F0 free tier: 200 credits a month, documents up to 10 pages, 5 requests a minute, one concurrent request. That is enough to evaluate the API without paying anything. See Pricing and credits for F1, which is 1,000 credits a month and 20-page documents.
-
Buy credits
Section titled “Buy credits”Credits are prepaid. There is no subscription, no per-request fee and no minimum charge per document — you buy a balance and spend it a page at a time.
Top up from Billing → Add credits in the console. Checkout is hosted by Stripe, so no card data touches us. Four packs, and the bonus is real credits rather than a discount code:
Price Base credits Bonus Total credits Effective per 1,000 pages $25 50,000 — 50,000 $0.5000 $100 200,000 10% 220,000 $0.4545 $500 1,000,000 15% 1,150,000 $0.4348 $2,500 5,000,000 20% 6,000,000 $0.4167 Paid and bonus credits never expire. The $25 floor exists because card fees are 9% on a $5 charge and 4.1% on $25, and because removing sub-$5 charges removes the cheap card-testing probe entirely.
Watch the balance move without opening the console:
Balance, after the purchase settles curl -sS https://api.kaho.ai/v1/account \-H "Authorization: Bearer $KAHO_API_KEY" | jq '.balance_credits, .trust_tier' -
Convert your first PDF
Section titled “Convert your first PDF”The shortest possible call: raw PDF bytes in, Markdown out, nothing else.
One curl, Markdown on stdout curl -sS https://api.kaho.ai/v1/convert \-H "Authorization: Bearer $KAHO_API_KEY" \-H "Accept: text/markdown" \-H "Content-Type: application/pdf" \--data-binary @annual-report.pdfstdout ## Chairman's letterTrading conditions in the second half were materially better than we guided in March.| Segment | Revenue | Change || ---------- | ------- | ------ || Industrial | 412.9 | +8.1% || Consumer | 188.3 | -2.4% |With
Accept: text/markdownyou get the body and nothing else; every field of the JSON response is still present, inx-kaho-*response headers. Drop theAcceptheader — JSON is the default — to get the full object:The full JSON response, five pages only curl -sS https://api.kaho.ai/v1/convert \-H "Authorization: Bearer $KAHO_API_KEY" \-H "Idempotency-Key: $(uuidgen)" \-F 'options={"pages":"1-5","page_markers":"rule"};type=application/json'Response, abridged {"id": "conv_01JQ8Z5T7B9KX2W4M6N0P3R5S7","object": "conversion","engine": "kaho-md-1.0.0","created_at": "2026-08-17T18:22:31.114Z","document": {"page_count": 42,"pages_selected": "1-5","pages_selected_count": 5,"tagged": false,"language": "en-GB","content_sha256": "3b1f0a9c…"},"markdown": "## Chairman's letter\n\n…","pages": null,"warnings": [],"usage": {"pages_processed": 5,"pages_billed": 5,"pages_not_billed": [],"credits_charged": 5,"credit_balance_after": 219995,"line_items": [{ "kind": "page_convert", "quantity": 5, "credits": 5 }],"cache": "miss"},"timings_ms": { "parse": 41, "convert": 388, "encode": 12, "total": 441 }}Five pages, five credits, a quarter of a cent. There is no
statusfield on a conversion: a200means we produced a result, and anything we could not do well is inwarnings.
Three ways to supply the PDF
Section titled “Three ways to supply the PDF”Exactly three encodings, and they may not be mixed. There are no precedence rules, by
construction — precedence rules are where contracts rot. Sending query parameters alongside
multipart or JSON is 400 query_options_not_allowed; any other content type is
415 unsupported_content_type.
Simplest, and the right choice for a one-shot conversion. Scalar options only — nested
objects such as normalize need one of the other two encodings. A password cannot be sent
this way.
curl -sS "https://api.kaho.ai/v1/convert?pages=1-5&page_markers=rule" \ -H "Authorization: Bearer $KAHO_API_KEY" \ -H "Content-Type: application/pdf" \ --data-binary @annual-report.pdfThe general case. The full options object goes in an options part typed as
application/json, and a document password goes in its own part — never in a query
parameter or a header, both of which are routinely logged by proxies.
curl -sS https://api.kaho.ai/v1/convert \ -H "Authorization: Bearer $KAHO_API_KEY" \ -F "password=hunter2" \ -F 'options={ "pages": "1-5,9", "headers_footers": "strip", "normalize": { "dehyphenate": true, "unicode": "nfc" } };type=application/json'Upload once, convert many times — and the recommended path if you convert the same document more than once, because an uploaded file has a known page count and therefore takes an exact credit hold instead of the 200-credit ceiling.
curl -sS https://api.kaho.ai/v1/files \ -H "Authorization: Bearer $KAHO_API_KEY" \{ "id": "file_01JQ8Z5T7B9KX2W4M6N0P3R5S7", "object": "file", "bytes": 4718592, "page_count": 42, "expires_at": "2026-08-18T18:22:31.114Z"}curl -sS https://api.kaho.ai/v1/convert \ -H "Authorization: Bearer $KAHO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "source": { "type": "file_id", "id": "file_01JQ8Z5T7B9KX2W4M6N0P3R5S7" }, "options": { "pages": "12-18", "output": "pages" } }'Uploads are capped at 32 MiB on /v1/convert and 128 MiB on /v1/files. Stored files are deleted
after 24 hours by default and 7 days at most, or immediately with DELETE /v1/files/{id}.
Know the price before you pay it
Section titled “Know the price before you pay it”/v1/inspect answers “what is this document, and what will converting it cost?” without converting
anything. With the default scan: "none" it is free: it reads the catalog, the page tree, the
tagged flag, the permission bits, the form type and the bookmark tree, and loads no page.
curl -sS https://api.kaho.ai/v1/inspect \ -H "Authorization: Bearer $KAHO_API_KEY" \{ "object": "inspection", "page_count": 312, "tagged": true, "encrypted": false, "extraction_permitted": true, "outline": { "status": "present", "entry_count": 137, "max_depth": 3 }, "quote": { "pages": "1-200", "billable_pages_max": 200, "credits_max": 200, "exceeds_sync_ceiling": true, "line_items": [{ "kind": "page_convert", "quantity": 200, "credits": 200 }] }}The quote never quotes a conversion the API would refuse. /v1/convert caps at 200 selected pages,
so a 312-page document is quoted as its first 200 pages with exceeds_sync_ceiling: true, rather
than as a 312-credit number that would produce 413 too_many_pages if you acted on it.
Two deeper scans exist and are selected with the JSON body encoding: scan: "sample" text-loads up
to 20 evenly spaced pages and is free against a 2,000 pages/day budget, and scan: "full"
text-loads every page and costs 1 credit per 100 pages, rounded up. A zero-balance account may run
scan: "none"; it may not run the two that load pages.
Make retries safe
Section titled “Make retries safe”Send an Idempotency-Key on every POST. It is optional and strongly recommended, and it is the
difference between a network blip costing you nothing and costing you a second conversion.
curl -sS https://api.kaho.ai/v1/convert \ -H "Authorization: Bearer $KAHO_API_KEY" \ -H "Idempotency-Key: invoice-2026-Q2-84120" \Keys are scoped to your organization rather than to the API key, so rotating a key in the middle of
a retry cannot double-bill you. A replay of a completed request returns the original response with
Idempotency-Replayed: true and is not billed again. A retry that arrives while the first
attempt is still in flight is 409 idempotency_in_progress with Retry-After: 1. The same key with
a different request body is 409 idempotency_conflict. Records live for 25 hours.
What to read next
Section titled “What to read next”- Authentication — key format, scopes, rotation, and leak response.
- Pricing and credits — exactly what bills, and how holds and settlement work.
- Page ranges — the
pagesgrammar and how a selection turns into a bill. - Rate limits — the headers, and how to pace against them.
- Errors — the full taxonomy, and what
retryablemeans.