Kinoseed

Behaviour

Errors

Every error from the API, REST and MCP alike, is the same envelope. Branch on code, which never changes; show or log message, which may.

The envelope

Response body
{
  "error": {
    "code": "insufficient_credits",
    "message": "This run costs N credits and the account has M. Top up to continue.",
    "details": {
      "needed": "N",
      "balance": "M",
      "topUpUrl": "https://kinoseed.app/pricing"
    }
  }
}

details is there when the client can act on it: the balance and a link to the plans, the seconds to wait, the fields that failed validation. A request refused with an error was never charged, and a run that fails after it started is refunded on the job (see credits.refunded).

Codes

CodeStatusWhenWhat to doRetry
unauthorized401No Authorization header, a token that is not a key, a revoked key, or a key whose account is gone. details.reason says missing, invalid or revoked.Send Authorization: Bearer ks_live_... with a live key from the account page.no
forbidden_scope403The key is valid but was created without the scope this operation needs.Create a key with that scope (a key's scopes are fixed when it is created) and revoke the old one.no
insufficient_credits402The balance does not cover the quoted price. Nothing was charged. details has needed, balance and topUpUrl.Start a plan at topUpUrl, then send the same request again.yes
rate_limited429Over a request or generation budget for this key, this account or this address.Wait for the Retry-After header (also details.retryAfterSeconds) and retry.yes
invalid_request400The body or query failed validation, the JSON did not parse, or an upload was too large (413) or of an unsupported type (415). details.issues lists the fields.Fix the request; sending it again unchanged gets the same answer.no
unknown_model400model is not an id in the catalog for this kind of run.Pick an id from GET /models (filter with ?kind=image or ?kind=video).no
unsafe_url400An input URL is not public https: a private address, localhost, an IP literal, a root-relative path or plain http.Upload the file with POST /files and pass the URL it returns, or use a public https URL.no
not_found404No job (or webhook endpoint) with this id belongs to the account behind the key.Check the id. Jobs and endpoints of other accounts are never visible, so this is also the answer for them.no
limit_reached409The account already has as many of something as it may have: 5 webhook endpoints. details.limit says how many.Delete one you no longer use (DELETE /webhooks/{id}), then create the new one.no
idempotency_in_flight409Another request with the same Idempotency-Key is still being processed.Wait a moment and send it again: you will get the first request's response.yes
idempotency_mismatch422This Idempotency-Key was used in the last 24 hours with a different body.Use a new key for a new request.no
too_many_running429The account already has the maximum number of runs queued or running through the API.Wait for one to finish (poll GET /jobs/{id}), then start the next. Retry-After suggests when.yes
provider_unavailable503A service behind the API (storage, or the model provider) is not answering right now.Retry with backoff.yes
internal500Something failed on our side. It was recorded and we are alerted.Retry with backoff; with an Idempotency-Key the retry cannot start a second run.yes

Retrying safely

Retry the codes marked yes with exponential backoff, honouring Retry-After when it is set. Send an Idempotency-Key on every POST and a retry after a timeout returns the first response instead of starting a second run. A 5xx without an envelope (a proxy timing out) is safe to retry the same way.