Skip to content

Errors

Every non-2xx response, on every endpoint, is exactly one shape. There are no exceptions — including 413, 429 and 503. No HTML error page ever escapes to a customer.

The error envelope
{
"error": {
"type": "invalid_request_error",
"code": "page_range_reversed",
"message": "Page range \"7-3\" is reversed. Ranges must be ascending; write \"3-7\".",
"param": "options.pages",
"retryable": false,
"retry_after_ms": null,
"doc_url": "https://docs.kaho.ai/errors/page_range_reversed",
"request_id": "req_01JQ8Z5T7B9KX2W4M6N0P3R5S7",
"details": { "value": "7-3" }
}
}
FieldNotes
typeCoarse class. Ten values, listed below
codeMatch on this. Never match on message
messageHuman-readable. Names the offending input, what was wrong, and the fix
paramPath of the offending field, e.g. options.pages. Null when not field-specific
retryableWhether retrying this exact request could succeed
retry_after_msHow long to wait, when we know. Null otherwise
doc_urlA page on this site for this code
request_idPresent on every response, including successes. Quote it to support
detailsCode-specific, additive. Ignore keys you do not recognise

Nothing here bills. Every 4xx and every 5xx is billed 0 credits, and any credit hold is released within 60 seconds.

type is a coarse class, so a client can branch sensibly on a code it has never seen before.

typeHTTPWhat it means for you
invalid_request_error400, 405, 406, 409, 413, 415, 422You sent something we cannot accept. Fix and resend
authentication_error401The key is missing, malformed, unknown, revoked or expired
permission_error403The key authenticated but is not allowed to do this
not_found_error404, 410The thing does not exist, or no longer does
payment_required_error402Out of credits, or spending is frozen
rate_limit_error429Slow down. Retry-After says how much
document_error422The document is the bad input, not the request
timeout_error408, 504Something took too long
overloaded_error503We are saturated. Retry with backoff
api_error500Our bug. Always carries request_id, never bills

document_error sharing a status code with invalid_request_error is deliberate: both are 422, and type is what tells you whether to fix your call or to look at the file.

retryable is a first-class boolean because “should I retry this?” is otherwise a status-code guessing game. Read the field. As a rule:

  • internal_error and overloaded are retryable, and overloaded carries Retry-After.
  • Every 429 is retryable after the Retry-After interval.
  • idempotency_in_progress is retryable after Retry-After: 1 — your first attempt is still running.
  • Everything else that is a 4xx will fail the same way until you change the request.
CodeMeaning
malformed_jsonThe JSON body did not parse
multipart_malformedThe multipart body did not parse
missing_sourceNo PDF was supplied
multiple_sourcesMore than one encoding was used at once. The three may not be mixed
unsupported_source_typebase64 (refused permanently) or url (not in v1) source type
unknown_parameterAn unrecognised field. param names its exact path
invalid_parameter_typeA field was the wrong type — for example pages as an array
query_options_not_allowedQuery parameters sent alongside a multipart or JSON body
metadata_too_largeClient metadata exceeded its size limit

All four carry WWW-Authenticate: Bearer realm="kaho", error="invalid_token".

CodeMeaning
missing_api_keyNo bearer credential, or a key sent in a query parameter or other header
invalid_api_keyMalformed, checksum failed, or unknown
revoked_api_keyThe key was revoked. Revocation is irreversible
expired_api_keyPast expires_at. The message names the replacement key’s hint
CodeMeaning
insufficient_creditsdetails carries credits_required, credits_available and top_up_url
account_delinquentAn open dispute or chargeback froze spending. details always carries debt_credits and credits_required_to_resume
CodeMeaning
insufficient_scopeThe key lacks the scope this endpoint needs
ip_not_allowedThe caller’s address is outside the key’s allowlist
account_suspendedThe organization is suspended
pdf_extraction_not_permittedThe document’s permission bits forbid text extraction. Billed 0
debug_not_permitteddebug: true on an organization whose debug policy is disabled — the default for any organization with a signed DPA
CodeStatusMeaning
file_not_found404No such file, or it belongs to another organization or environment
engine_not_found404No such engine identifier
unknown_endpoint404No such route under /v1
file_expired410The file existed and its retention window has passed
result_expired410The cached result existed and its 24-hour window has passed

An identifier belonging to another organization, or to the other environment, returns 404 and never 403. A 403 there would be an existence oracle.

CodeStatusMeaning
method_not_allowed405Known path, wrong method. Carries an Allow header
unsupported_accept406Accept was neither application/json nor text/markdown
idempotency_conflict409Same idempotency key, different request
idempotency_in_progress409The first attempt is still running. Carries Retry-After: 1
unsupported_content_type415Not one of the three accepted encodings
CodeMeaning
file_too_largeAbove 32 MiB on /v1/convert, or 128 MiB on /v1/files
too_many_pagesThe selection exceeds the endpoint’s ceiling — 200 pages on /v1/convert
storage_quota_exceededThe organization’s stored-bytes limit is full
pdf_too_complexThe document’s structure exceeds what we will parse

There is no result_too_large. An oversized result is a 200 carrying result_truncated and the pages we did deliver: killing the conversion mid-write would throw away work you would have accepted and bill nothing for work we performed.

type is invalid_request_error. Fix the request.

CodeMeaning
page_range_invalidThe pages expression does not parse. See Page ranges
page_range_zeroPage 0. Pages are 1-based
page_range_reversedA descending range such as 7-3
page_range_out_of_boundsThe selection lies entirely beyond the document. Billed 0
page_range_too_complexOver 2,048 characters or over 1,000 ranges
unsupported_option_valueA valid option with a value not available in v1 — flavor: "commonmark", tables: "auto", fallback: "headings"
option_conflictTwo options that cannot both be honoured
ocr_not_availableocr was set to anything other than "off"
feature_not_enabledThe feature exists but is not enabled for this organization

type is document_error. The request was fine; look at the file.

CodeMeaning
pdf_invalidNot a PDF
pdf_corruptA PDF whose structure is damaged beyond recovery
pdf_read_errorThe bytes could not be read to completion
pdf_parse_failedParsing failed on a structure we understand but the file gets wrong
pdf_encryptedThe document needs a password and none was supplied
pdf_password_incorrectA password was supplied and it is wrong
pdf_unsupported_securityAn encryption handler we do not support
pdf_no_pagesA valid PDF with zero pages
pdf_is_xfaAn XFA form, which is a different document model
document_has_no_text_layerNo page in the document carries extractable text. There is no OCR in v1
document_too_complexThe document exhausted its work budget

All carry Retry-After and the full x-ratelimit-* header set. All bill 0. See Rate limits for what each meter measures and how to pace against it.

CodeThe meter that denied you
rate_limit_requestsRequests per minute
rate_limit_pagesPages per minute
rate_limit_concurrencyConcurrent in-flight requests for your organization
rate_limit_uploadsUpload bytes per minute
rate_limit_inspect/v1/inspect requests per minute, or its daily sample page budget
rate_limit_toc/v1/toc requests per minute
rate_limit_no_value_conversionsConversions per hour that produced nothing at all
rate_limit_unbilled_workCompute per hour consumed by requests that billed zero
rate_limit_new_accountThe new-account burn cap
CodeStatusMeaning
request_timeout408Your upload did not finish inside the 20-second total-body deadline
conversion_timeout504Conversion exceeded the wall-clock limit. details.pages_completed says how far it got

Both bill 0. A request_timeout usually means a slow link rather than a large file — the deadline is on the whole body from the first byte, so a 30 MiB upload on a slow connection can hit it. Upload once with POST /v1/files and convert from the file_id afterwards.

CodeStatusMeaning
internal_error500Our bug. Retryable, always carries request_id, never bills
overloaded503The fleet is saturated. Retryable, carries Retry-After

503 overloaded and 429 rate_limit_concurrency are different situations and the split matters: 429 means your organization exceeded its own concurrency limit and the fix is on your side; 503 means the fleet is full and the fix is to back off and retry.

Quote the request_id when you contact support. It is on every response, including successful ones.

Error messages name the offending input, state what was wrong, and state the fix. They never contain internal paths, hostnames, addresses, stack traces or SQL. Any string you supplied that appears in a message is capped at 128 characters with control characters stripped, so an error message can never be used to inject a header or a log line.