API errors
Every error code the Koutei PDF API can answer with: what it means, whether a credit was used, and what to do next. The docs_url in an error opens its section on this page.
The shape of an error
An error is always JSON, whatever the request asked for. This is the answer to a request with two mistakes (a missing client name and a date that does not exist):
{
"error": {
"code": "validation_failed",
"message": "2 fields need attention.",
"details": [
{
"field": "fields.client_name",
"code": "required",
"message": "client_name is required."
},
{
"field": "fields.date",
"code": "invalid_date",
"message": "date must be a real date such as 2026-10-12."
}
],
"request_id": "req_…",
"docs_url": "https://kouteisystems.com/docs/api/errors#validation_failed"
},
"credit_used": false
}codesays what happened. Codes do not change;messagetexts may, so write your code against the codes.details, when present, lists each problem with its own code and, where it applies, thefieldit is about, as a path from the top of the body. It holds at most 50 entries.request_ididentifies the request. It is also in theKoutei-Request-Idheader. Quote it if you write to support@kouteisystems.com.credit_usedis on every JSON answer to a document request.falsemeans no credit was used.nullmeans we could not confirm it either way: send the same request again with the same Idempotency-Key and you are charged once at most.- New codes may be added. Treat a code you do not know by its HTTP status.
The API reference covers the routes, fields and limits these errors refer to.
400 invalid_request
The request itself is malformed: the body is not valid JSON, a top-level name is unknown or missing, or a header, query value or option has a value the API does not accept.
What to do: Read details: each entry names the part of the request and why it was refused. Fix it and send the request again.
credit_used: false.
| Detail code | What it means |
|---|---|
malformed_json | The body is not valid JSON, is not valid UTF-8, or repeats a name inside one object. |
wrong_type | A value has the wrong JSON type, such as text where an object is expected. A null for an optional top-level parameter counts too. |
unknown_parameter | A top-level name the API does not know. |
required | Something required is missing, null or blank: template, mode or fields at the top level, or a required field. |
unsupported_value | A value outside the accepted list for mode, paper, locale or output. |
unsupported_version | The Koutei-Version header names a version the API does not have. |
invalid_idempotency_key | The Idempotency-Key header is empty, too long or has characters outside visible ASCII. |
invalid_reference | The reference label is empty, too long, has characters outside printable ASCII, or starts or ends with a space. |
invalid_template_version | template_version is not a major version written as digits, such as "2". |
invalid_query | A query value of GET /api/v1/documents is not accepted, or starting_after is not one of your documents. |
400 reserved_field
The request uses a name that is held for a later version of the API. It wins over any field problem, so you get this error alone.
What to do: Remove the name. The API reference lists the reserved names.
credit_used: false.
| Detail code | What it means |
|---|---|
reserved | The name is held for a later version. |
402 insufficient_credits
The account has no credit left for a final document. The body adds credits_remaining and period_end, the end of the current credit period.
What to do: Wait for the next credit period or change your plan. Previews and validation still work without credits.
credit_used: false.
403 forbidden_scope
The key is valid, but it is not allowed to do this. A Preview only key cannot make a final document.
What to do: Use a key with the scope the route needs, or create a Full access key in Settings.
credit_used: false.
| Detail code | What it means |
|---|---|
missing_scope | The key lacks the scope for this route or mode. |
404 not_found
There is no such path under /api/v1. Every path answers this while the API is switched off.
What to do: Check the path against the API reference.
credit_used: false.
404 template_not_found
There is no API template with that name. Templates that exist only in the web app are not offered here.
What to do: GET /api/v1/templates lists the templates you can use.
credit_used: false.
404 document_not_found
There is no such document in your history: the id is wrong, the document belongs to another account, or it is older than the history keeps.
What to do: Check the id. GET /api/v1/documents lists the documents you can read.
credit_used: not stated (this is not a document request).
404 file_not_available
Koutei does not store documents, so this route always answers this.
What to do: The file is only in the response to POST /api/v1/documents. To get it again, repeat that request with the same Idempotency-Key while the key is still remembered.
credit_used: not stated (this is not a document request).
405 method_not_allowed
The path exists, but not for this method. The Allow header lists the methods it takes.
What to do: Use one of the methods in the Allow header.
credit_used: false.
409 idempotency_in_progress
A request with the same Idempotency-Key is still running.
What to do: Wait for the number of seconds in Retry-After and send the same request again. You get the finished document, or the request runs.
credit_used: false. The answer carries a Retry-After header, in seconds.
413 payload_too_large
The request body is larger than the API accepts.
What to do: Send a smaller body. The API reference lists the size limit.
credit_used: false.
413 response_too_large
The PDF came out larger than the API can send back. Nothing was charged.
What to do: Make the document smaller, or ask for the file itself instead of JSON: the file form has the larger limit.
credit_used: false.
415 unsupported_media_type
The body is not sent as JSON.
What to do: Send "Content-Type: application/json", in UTF-8.
credit_used: false.
422 validation_failed
One or more fields were refused. Every problem is listed at once, not only the first.
What to do: Read details: each entry gives the field, a code and a message. Fix them all and send the request again.
credit_used: false.
| Detail code | What it means |
|---|---|
wrong_type | A value has the wrong JSON type, such as text where an object is expected. A null for an optional top-level parameter counts too. |
required | Something required is missing, null or blank: template, mode or fields at the top level, or a required field. |
unknown_field | The template has no field with that name. A misspelt name is refused, never dropped. |
too_long | The value has more characters than its type allows. |
line_break_not_allowed | A one-line value contains a line break. |
invalid_characters | The value contains control characters or other characters the API refuses. |
invalid_date | Not a real calendar date written as YYYY-MM-DD. |
not_one_of | Not one of the values the field lists. The match is exact and case-sensitive. |
not_a_number | Not a number the API can read. |
too_many_digits | The number has more digits than the API accepts. |
too_many_decimals | The amount has more decimal places than its currency has. |
out_of_range | The number is below or above what the field accepts, such as a negative amount or a percentage that is too high. |
must_be_positive | The amount must be more than 0. |
unsupported_currency | Not one of the currency codes the API accepts: three capital letters, no symbol. |
no_items | The list of line items is empty. |
too_many_items | There are more line items than the API accepts. |
items_or_amount | A quote needs line items or a fixed amount: exactly one of the two. |
requires_field | The field only makes sense with another one that is missing: tax_label needs tax_rate. |
conflicting_fields | A field and its older name were both sent. Send one. |
discount_too_large | The discount is more than the subtotal. |
amount_paid_too_large | The amount paid is more than the total. |
negative_total | The line items add up to less than 0. |
total_too_large | The subtotal is too large to print. |
too_many_fields | fields has more names than the API accepts. |
invalid_url | Not a plain https:// web address: another scheme such as http:, spaces, characters outside ASCII, a user name or password, or a host name that is not written plainly. The message says which. |
422 idempotency_key_reused
The Idempotency-Key was already used for a different request. A changed reference counts as a different request.
What to do: Send the original request again unchanged, or use a new key for the new request.
credit_used: false.
422 render_rejected
The template refused input that validation had accepted. That is a fault on our side, and it is logged.
What to do: Nothing was charged. Tell us the request_id so we can find it.
credit_used: false.
429 rate_limited
A limit was reached. details names which one.
What to do: Wait for the number of seconds in Retry-After, then try again. The RateLimit headers show how much of your limit is left.
credit_used: false. The answer carries a Retry-After header, in seconds.
| Detail code | What it means |
|---|---|
final_key_limit | This key made too many new final documents in the current minute. |
request_key_limit | This key sent too many requests in the current minute. |
preview_user_limit | The account made too many previews this hour or today, across every key and the web app. |
preview_key_limit | This key made too many previews this hour or today. |
auth_failure_limit | Too many requests with a missing or wrong key came from this network. A valid key is never refused for this. |
replay_limit | This key, or its account, replayed stored documents too often in this window: a replay renders the document again. Nothing was charged; the Idempotency-Key still replays after Retry-After. |
500 commit_status_unknown
The document was made, but we could not confirm whether the credit was taken, so credit_used is null.
What to do: Wait 35 seconds and send the same request with the same Idempotency-Key. If the credit was taken, you get the document without another one; if not, it is charged once.
credit_used: null (unknown: retry with the same Idempotency-Key).
500 internal_error
Something failed on our side. credit_used is false unless the charge had already started, in which case it is null.
What to do: Try again with the same Idempotency-Key. If it keeps happening, tell us the request_id.
credit_used: false, or null (unknown: retry with the same Idempotency-Key).
503 engine_busy
The service that makes the PDF is at capacity. Nothing was charged.
What to do: Wait for the number of seconds in Retry-After, then try again.
credit_used: false. The answer carries a Retry-After header, in seconds.
503 service_busy
A shared limit was reached, or a part of the service that the request needs could not be reached. The request was not processed and nothing was charged.
What to do: Wait for the number of seconds in Retry-After, then try again.
credit_used: false. The answer carries a Retry-After header, in seconds.
| Detail code | What it means |
|---|---|
preview_global_limit | Too many previews are being made by everyone together. |
limiter_unavailable | The rate limiter could not be reached, so the request was not processed. |
504 render_timeout
The document took too long to make. Nothing was charged.
What to do: Try again. If the same document keeps timing out, make it smaller.
credit_used: false.