Back to docs
Troubleshooting

Common Error Codes

Every error the Koji Headless API returns: the error envelope, status codes, all error strings with fixes, and a debugging checklist.

Common Error Codes

Every failure in the Headless API is designed to be diagnosable from two things: the HTTP status and the error string in the body. This page collects all of them in one place, with fixes.


The error envelope

  • Every error response is JSON with an error string: { "error": "..." }.
  • Some errors add fields: retry_after (rate limit), message and reason (start-endpoint availability errors), current and limit (quota errors), closed (paused studies).
  • There is no code or details field. Match on the HTTP status and the error string.
  • The message endpoint can also fail mid-stream: the final SSE frame is then {"type":"error","error":"Stream processing failed"} with the HTTP status already committed as 200.

A typical error body:

{
  "error": "Rate limit exceeded",
  "retry_after": 42
}

Status code overview

StatusMeaning
200Success (message: SSE stream; complete/get: JSON)
201Interview created (start)
400Bad request. Missing content, inactive interview, or invalid fields
401Authentication failed. Bad/missing API key or session token
403Authenticated but not allowed. Missing permission, origin blocked, study closed, wrong project, or plan/entitlement
404Interview or project not found
429Rate limit exceeded or interview quota reached
500Server error. Retry with backoff

Every error string

This table consolidates every distinct error the API returns, across the start, message, complete, read, and embed bootstrap endpoints. The four per-permission 403s are collapsed into one row: the real string names the specific scope, for example API key does not have interview:read permission.

StatuserrorWhen it happensHow to fix it
400Interview is not activeThe interview was already completed or abandonedThe interview was already completed or abandoned. Start a new one.
400Message content is requiredcontent was missing or not a stringSend a JSON body with a content string field.
401Missing or invalid authorization headerNo Authorization: Bearer header sentSend Authorization: Bearer pk_live_... on every request.
401Invalid API keyThe key does not exist, is malformed, is inactive, or has expiredCheck the key starts with pk_live_, was copied in full, and has not been revoked or expired. Create a new key if in doubt.
401Missing X-Session-Token headerThe session token header was not sentSend the session_token from the start response in the X-Session-Token header.
401Invalid session tokenThe token does not match this interview's respondentUse the exact session_token issued for this interview id. Tokens are not interchangeable between interviews.
403API key does not have <permission> permissionThe key was created without the permission this endpoint requiresCreate a key that includes the scope the endpoint requires. See API Key Permissions.
403Origin not allowedThe browser's Origin header does not match the key's allowed originsAdd your site's origin (with scheme) to the key's allowed origins, or use a wildcard entry. See API Authentication.
403Headless API access requires an Interviews plan or higher.The study owner's account cannot use the headless API. Rare: API access is included on all current plans, so this appears only for restricted accountsCheck the study owner's plan and credit balance in the dashboard.
403This study is no longer accepting responsesThe study is paused or closedThe study is paused or closed. Reopen it from the dashboard.
403unavailableThe study cannot accept responses right now (owner account state)The study cannot accept responses right now. Inspect the message and reason fields for the specific cause.
403Interview does not belong to this projectThe interview belongs to a different study than the API keyThe interview id and the API key point at different studies. Use the key for the study that owns the interview.
403API key does not match projectThe key belongs to a different studyUse the API key created for the same study as the embed project id.
404Interview not foundUnknown interview id (or the interview was deleted)Check the interview id. It must come from a start response for the same study.
404Project not foundUnknown project idCheck the project id in the embed URL.
429Rate limit exceededThe key exceeded its per-minute request limitWait retry_after seconds, then retry. See Rate Limits and CORS.
429interview_limit_reachedThe study hit its response limit or the owner's interview quotaRaise the study's response limit or the owner's quota. Retrying will not help.
500Internal server errorUnexpected server failure. Safe to retry with backoffRetry with exponential backoff. If it persists, contact support.

Per-endpoint error tables, including which extra fields accompany each error, live on the endpoint pages: Starting Interviews, Sending Messages, and Completing Interviews.


SSE stream errors

The message endpoint streams its reply as Server-Sent Events, which means a failure can happen after the HTTP status is already committed as 200. In that case the last frame of the stream is:

data: {"type":"error","error":"Stream processing failed"}

Your SSE parser must treat an error frame as terminal for that message. Do not rely on the HTTP status alone for the message endpoint; check frame types. See Sending Messages via API for a parser that handles this correctly.


Debugging checklist

  • Key rejected? Confirm it starts with pk_live_, is active (not revoked), and hasn't expired.
  • Invalid session token? Send the exact session_token from the start response in the X-Session-Token header, for the same interview id.
  • Origin not allowed? Your key restricts origins; add your site's origin (with scheme) or use a wildcard entry.
  • Interview is not active? The interview was already completed. Start a new one.
  • Missing permission? The key was created with a narrowed permission set; create a key that includes the scope you need.
  • 429? Check retry_after and the X-RateLimit-* headers; distinguish rate limits from interview_limit_reached quota errors.

FAQ

Is there an error code field I can switch on?

No. There is no code or details field. Match on the HTTP status plus the error string. The strings in the table above are the exact strings the handlers return.

I got a 429. Should I retry?

Depends on the string. Rate limit exceeded resolves after retry_after seconds. interview_limit_reached is a quota error and retrying will not help. See Rate Limits and CORS.

Why do I get 401 on message but 200 on start with the same key?

Start needs only the API key. Message additionally needs the X-Session-Token header with the exact token from the start response. A missing or mismatched token fails with 401 even though the key is valid.

The error says the interview belongs to a different project. What happened?

Interview ids are study-scoped, and so are API keys. You are calling with a key from one study and an interview id from another. Use the key for the study that owns the interview.