Errors
The error envelope, every public error code, and how to handle each.
Every failure answers with the same JSON envelope — error.code, a human-readable error.message, and error.meta with details that depend on the code — and an HTTP status that follows from its code.
For example, POST /v1/publish-requests sent without its Idempotency-Key header answers 400:
{ "error": { "code": "validation_error", "message": "The idempotency-key header is required for this operation.", "meta": { "validationErrors": [ { "field": "header.idempotency-key", "message": "Required header." } ] } }}Branch on error.code, never on message. The codes are stable; the table below is generated from the same table the API answers with.
| Code | Status | Retry | Meaning |
|---|---|---|---|
validation_error | 400 | No | The request does not match the operation's input schema or fails an admission check. meta.validationErrors names every problem. |
unauthorized | 401 | No | No API key, or a key Koil does not accept (unknown, revoked, or with a prefix that does not match its mode). |
invalid_token | 401 | No | The provider credential behind the Connected Profile no longer works; the end user must re-authorize. |
quota_exceeded | 402 | No | A free-plan hard cap is reached; GET /v1/billing/account reports the caps. |
scope_insufficient | 403 | No | The provider grant lacks a scope the operation needs; meta names the missing scopes. |
not_found | 404 | No | The resource does not exist in this Organization and mode. |
conflict | 409 | No | The request collides with current state: an idempotency key reused with a different payload or still in flight, a run already in progress, or a publish that has already started. |
rate_limit_exceeded | 429 | After Retry-After | Koil's own rate limit for the key, Organization, or client address is spent; retry after Retry-After seconds. |
platform_rate_limit | 429 | After Retry-After | The provider throttled the call or the provider quota is spent; retry after Retry-After seconds. |
internal | 500 | With backoff | Koil or the provider failed: 500 for Koil, 502 for a provider answer Koil cannot use, or the provider's own 5xx. The message is generic; retry with backoff. |
not_supported | 501 | No | The provider or content type does not offer this operation. |
service_unavailable | 503 | After Retry-After | A service Koil needs to accept the request (API key validation) is unavailable. Nothing was attempted; retry after Retry-After seconds. |
Validation errors
Every request is validated once, against the operation's input schema, and every problem is reported together. A validation_error carries:
meta.validationErrors— each with afieldandmessage. Always present.meta.violations— each with acodeand a JSON-pointerpathinto the input. Present when the input schema or an admission check refused the request; a refusal before the input is assembled (a missing required header, a malformedcursor) has onlyvalidationErrors, as in the example above.
A body field the schema does not declare is refused with the violation code unknown_field rather than silently dropped, so a misspelled optional field never passes as an omitted one. Query-string values are converted to the schema's types before validation ("true" and "1" to a boolean, "10" to a number, a repeated key to an array); path parameters are strings.
Provider failures
When a provider refuses a call, Koil maps it onto the codes above: an expired or revoked grant is invalid_token, a missing permission scope_insufficient, a provider throttle or spent quota platform_rate_limit. Provider response detail may appear in meta when it is safe to show.
A failure on the provider's side is internal, never validation_error: a provider outage answers with the provider's own 5xx status, and a provider answer Koil cannot use (a response missing the id of what it just created, say) with 502. Retry both with backoff. internal errors never carry detail.
Retrying
The table's Retry column says what to do with each code: wait out the Retry-After header's seconds, retry with exponential backoff, or stop — those fail the same way until the request, the grant, or the resource changes. Writes that take an idempotency key are safe to retry with the same key.