Error codes
Every non-2xx response, on every endpoint, is one shape, and every one of them
bills zero credits. Match on code; never match on message. A code is never
repurposed inside /v1 and never changes the HTTP status it is returned with, so a
client that switches on code keeps working. New codes may appear, so treat a
code you do not recognize as its type and move on.
Each code has a page of its own at https://docs.kaho.ai/errors/<code>, which is exactly the
doc_url the error body carries.
type is the coarse class, so a client can branch sensibly on a code it has
never seen. The rung column is the rung of the repository’s failure policy the
class corresponds to at the boundary.
type | Rung | Codes |
|---|---|---|
invalid_request_error | 3 | 27 |
authentication_error | 3 | 4 |
permission_error | 3 | 5 |
not_found_error | 4 | 5 |
payment_required_error | 2 | 2 |
rate_limit_error | 2 | 9 |
document_error | 3 | 11 |
timeout_error | 2 | 2 |
overloaded_error | 2 | 1 |
api_error | 1 | 1 |
Every code
Section titled “Every code”Grouped by HTTP status, in the order the contract declares them. Simulate is
whether X-Kaho-Simulate can induce the code on a test key; on a live key the
header is ignored rather than refused, so a live key can never zero its own
pages.
| Code | type | Retryable | Simulate |
|---|---|---|---|
malformed_json | invalid_request_error | No | No |
multipart_malformed | invalid_request_error | No | No |
missing_source | invalid_request_error | No | No |
multiple_sources | invalid_request_error | No | No |
unsupported_source_type | invalid_request_error | No | No |
unknown_parameter | invalid_request_error | No | No |
invalid_parameter_type | invalid_request_error | No | No |
query_options_not_allowed | invalid_request_error | No | No |
metadata_too_large | invalid_request_error | No | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
missing_api_key | authentication_error | No | No |
invalid_api_key | authentication_error | No | No |
revoked_api_key | authentication_error | No | No |
expired_api_key | authentication_error | No | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
insufficient_credits | payment_required_error | No | Yes |
account_delinquent | payment_required_error | No | Yes |
| Code | type | Retryable | Simulate |
|---|---|---|---|
insufficient_scope | permission_error | No | No |
ip_not_allowed | permission_error | No | No |
account_suspended | permission_error | No | No |
pdf_extraction_not_permitted | permission_error | No | Yes |
debug_not_permitted | permission_error | No | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
file_not_found | not_found_error | No | No |
engine_not_found | not_found_error | No | No |
unknown_endpoint | not_found_error | No | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
file_expired | not_found_error | No | No |
result_expired | not_found_error | No | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
method_not_allowed | invalid_request_error | No | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
unsupported_accept | invalid_request_error | No | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
idempotency_conflict | invalid_request_error | No | No |
idempotency_in_progress | invalid_request_error | Yes | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
unsupported_content_type | invalid_request_error | No | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
file_too_large | invalid_request_error | No | No |
too_many_pages | invalid_request_error | No | Yes |
storage_quota_exceeded | invalid_request_error | No | No |
pdf_too_complex | invalid_request_error | No | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
page_range_invalid | invalid_request_error | No | No |
page_range_zero | invalid_request_error | No | No |
page_range_reversed | invalid_request_error | No | No |
page_range_out_of_bounds | invalid_request_error | No | No |
page_range_too_complex | invalid_request_error | No | No |
unsupported_option_value | invalid_request_error | No | No |
option_conflict | invalid_request_error | No | No |
ocr_not_available | invalid_request_error | No | No |
feature_not_enabled | invalid_request_error | No | No |
pdf_invalid | document_error | No | No |
pdf_corrupt | document_error | No | Yes |
pdf_read_error | document_error | No | No |
pdf_parse_failed | document_error | No | No |
pdf_encrypted | document_error | No | Yes |
pdf_password_incorrect | document_error | No | Yes |
pdf_unsupported_security | document_error | No | No |
pdf_no_pages | document_error | No | No |
pdf_is_xfa | document_error | No | No |
document_has_no_text_layer | document_error | No | Yes |
document_too_complex | document_error | No | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
rate_limit_requests | rate_limit_error | Yes | Yes |
rate_limit_pages | rate_limit_error | Yes | Yes |
rate_limit_concurrency | rate_limit_error | Yes | Yes |
rate_limit_uploads | rate_limit_error | Yes | No |
rate_limit_inspect | rate_limit_error | Yes | No |
rate_limit_toc | rate_limit_error | Yes | No |
rate_limit_no_value_conversions | rate_limit_error | Yes | No |
rate_limit_unbilled_work | rate_limit_error | Yes | No |
rate_limit_new_account | rate_limit_error | Yes | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
request_timeout | timeout_error | Yes | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
conversion_timeout | timeout_error | Yes | Yes |
| Code | type | Retryable | Simulate |
|---|---|---|---|
internal_error | api_error | Yes | No |
| Code | type | Retryable | Simulate |
|---|---|---|---|
overloaded | overloaded_error | Yes | Yes |
Retrying
Section titled “Retrying”retryable is a field rather than a status-code guessing game: it is true when
the identical request, sent again later, can plausibly succeed without you
changing anything. That is the 429 family, 503, 500, both timeouts and
idempotency_in_progress. Everything else needs you to change the request, the
document, the credential or the balance, and retrying it is a way to burn your
own rate limit.
Never retry a POST without an Idempotency-Key: a retry without one is a
second conversion at full price.