Responses and errors
Every successful response wraps its payload in data. A failed request returns an HTTP status and a body with error, a message for people, and code, a constant for your code. Branch on code, and use the tables below to decide whether to retry.
Successful responses
{ "data": { "session": { "id": "9b2e4f60-…", "status": "active" } } }
| Status | When |
|---|---|
| 200 OK | Reads, and most changes. |
| 201 Created | A request that creates something, such as a project, a session, a comment or a cron. Sending a message answers 201 too. |
| 204 No Content | Most deletes. The body is empty. |
A few responses break the pattern:
| Request | Status | Body |
|---|---|---|
DELETE /api/projects/{projectId}/settings/env-vars | 200 | { "data": { "deleted": 3 } }, the number of variables removed. |
DELETE /api/projects/{projectId}/preview/share | 200 | { "data": { "share": null } }, whether or not a link existed. |
https://app.kosuke.ai/api/openapi.json | 200 | The OpenAPI document itself, without data. |
Pagination
Lists that page do it in one of two ways. Both add meta.pagination next to data, and both set hasMore while more results remain. Other lists, such as projects, models and GitHub repositories, come in one response.
Page-based lists
The session's jobs and the project's deploys take page (from 1) and pageSize (default 25, at most 100):
curl -s "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/jobs/chat?page=2&pageSize=25" \
-H "Authorization: Bearer $KOSUKE_API_KEY"
{
"data": [{ "id": "…", "status": "completed", "jobNumber": 26 }],
"meta": {
"pagination": { "page": 2, "totalPages": 4, "pageSize": 25, "total": 87, "hasMore": true }
}
}
Ask for the next page while hasMore is true. A workspace's crons and a cron's runs take the same page and pageSize, and return their total inside data instead.
Cursor-based lists
A project's chat sessions, your sessions across workspaces, and Activity take limit and cursor. A project's session list pages only when you send limit.
curl -s "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions?limit=50" \
-H "Authorization: Bearer $KOSUKE_API_KEY"
{
"data": [{ "id": "…", "title": "Add a healthz endpoint", "status": "active" }],
"meta": {
"pagination": { "pageSize": 50, "total": 132, "hasMore": true, "nextCursor": "eyJ…" }
}
}
Send nextCursor back as cursor, with the same limit and filters, until nextCursor is null. The repository's branch list is the one exception. It returns nextCursor inside data, next to branches.
Error shape
{ "error": "Validation error", "code": "VALIDATION_ERROR", "details": { "_errors": [], "name": { "_errors": ["Name is required"] } } }
details carries the field errors of a VALIDATION_ERROR, the amounts of a CREDIT_LIMIT_REACHED, and the steerId of a JOB_STOPPED_TEXT_RETURNED.
Status and error codes
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | BAD_REQUEST | The request is malformed, or the resource's state refuses it. error says which, for example a message to an Archived or Completed session. | Fix the request or the state, then send again. |
| 400 | SESSION_UNDER_REVIEW | The session is Under Review. Only a member's own message resumes it, so a message from a Cron or another session is refused. A member's message is refused too when the session went Under Review while it was on its way. | Resume the session, or send the message yourself. |
| 400 | VALIDATION_ERROR | The body or query failed validation. | Fix the fields named in details. |
| 401 | UNAUTHORIZED | The API key is missing, unknown, revoked or expired. | Create a new API key. |
| 401 | GITHUB_RECONNECT_REQUIRED | GitHub stopped accepting your GitHub Connection. Your API key is fine. | Reconnect GitHub in Settings > You > Integrations. |
| 402 | CREDIT_LIMIT_REACHED | The workspace is blocked on billing. | Stop until an admin sorts out billing. See Limits. |
| 403 | FORBIDDEN | Your API key is valid, but the action is not allowed. You are not a member of the workspace, the action needs an owner or admin, or it needs the web app. | Do not retry. |
| 404 | NOT_FOUND | No such resource, or no access to it. Project routes answer 404 for both. | Check the ids. Do not retry. |
| 409 | JOB_IN_PROGRESS | A turn is running, so a message being sent from Up next must wait. A fresh chat-session message waits Up next instead, even when a turn starts while it is on its way. | Leave it Up next. It goes when the turn ends. |
| 409 | QUEUE_FULL | Up next already holds 20 messages. | Wait for one to go, or delete one. |
| 409 | QUEUED_MESSAGE_CHANGED | The Up next message changed or was sent while you edited it. | Read Up next again, then save. |
| 409 | QUEUED_MESSAGE_GONE | The message already left Up next. | Read Up next again. |
| 409 | STEER_UNAVAILABLE | No turn of yours can take the steer right now, or the Up next message is a runtime command, is being edited, has just changed or is already on its way. The message stays Up next. | Let it start its own turn. |
| 409 | JOB_STOPPED | A person stopped the turn or deploy you named. | Do not start another on your own. |
| 409 | JOB_STOPPED_TEXT_RETURNED | A person stopped the turn before it took your steer. The message stays Up next. | Leave it there. Up next is paused. |
| 409 | JOB_ACTIVE | A turn is running, so a file write, commit or push must wait. | Try again when the turn ends. |
| 409 | TURN_IN_FLIGHT | A runtime command was sent to your Sensei Chat while its turn runs. In a chat session the command waits Up next instead. | Wait for the turn to end. |
| 409 | COMMAND_IN_FLIGHT | A runtime command is still running, so a message being sent from Up next, a steer, a move to Backlog or another Sensei Chat command must wait. Fresh chat-session messages wait Up next instead. | Wait for it to finish. |
| 409 | SESSION_UPDATING | Another change to the session is in progress. | Send the same request again. |
| 409 | SESSION_BUSY | Another request is changing the session right now, such as a start, a send or a runtime command. | Send the same request again. |
| 409 | SESSION_STATUS_CHANGED | The session changed under the request. | Read the session again before you send. |
| 409 | SESSION_CHANGED | The session changed while the request ran. | Read the session again, then retry. |
| 409 | SANDBOX_LIMIT_REACHED | The workspace runs as many sandboxes as its plan allows. | Free a slot, then send again. |
| 409 | SANDBOX_STARTING | The session's sandbox is still starting. | Send the same request again shortly. |
| 409 | SKILL_NAME_TAKEN | A skill with this name already exists. | Pick another name. |
| 409 | DEPLOY_IN_PROGRESS | The project is deploying. Only a Backlog session can be created, and none can be started. | Wait for the deploy to end. |
| 409 | DEPLOY_ACTIVE | A deploy is already running, or still stopping. | Wait for it to end. |
| 409 | DEPLOY_RESUME_UNAVAILABLE | The stopped deploy can no longer be resumed. | Start a new deploy. |
| 409 | NOT_DEPLOYED | The project is not deployed yet, so a Deploy Chat message has no deploy to continue. | Start a deploy. |
| 429 | TOO_MANY_REQUESTS | Too many requests to a public share link or an invitation preview. | Back off and retry. |
| 500 | SERVER_ERROR | Something broke on Sensei's side. | Retry once after a pause. If it repeats, get help. |
| 503 | SERVICE_UNAVAILABLE | A step failed and is worth repeating, or the feature is off. | Retry with backoff. If error says the feature is off, stop. |
Retry safely
Retry the same request, with a growing pause, after SESSION_UPDATING, SESSION_BUSY, SANDBOX_STARTING and 503. Start at one second and double up to about 30 seconds.
Stop and read the situation after 402, JOB_STOPPED, SANDBOX_LIMIT_REACHED and QUEUE_FULL. Retrying them unchanged fails the same way.
A send whose response you never received may still have landed. Before you resend, list the session's jobs with GET /api/projects/{projectId}/chat-sessions/{sessionId}/jobs/chat and Up next with GET /api/projects/{projectId}/chat-sessions/{sessionId}/queue. A resent session create makes a second session, so search the project's sessions by title first.
Limits
Credits
When a workspace is blocked on billing, requests that start work or read a sandbox answer 402:
{
"error": "This workspace is blocked on billing.",
"code": "CREDIT_LIMIT_REACHED",
"details": { "usedUsd": 25.41, "limitUsd": 25 }
}
The 402 covers creating a session with a first message, starting a Backlog session, sending a message, running the compact runtime command and starting a deploy. It also covers reading files, the diff and git state, and committing and pushing. Filing a Backlog session and reading sessions and messages still work. A turn already running finishes.
The preview request answers 200 with creditLimitReached: true instead. GET /api/organizations/{workspaceId}/credits returns isOverLimit, so you can check before you start work. An admin clears the block under Plans and billing.
Other limits
| Limit | Value | What you see |
|---|---|---|
| Concurrent sandboxes | Set by the plan. See Plans and billing. | startFailure with reason: "sandbox_limit" on the preview's health, 409 SANDBOX_LIMIT_REACHED on a send, and 400 when you start a Backlog session. |
| Up next | 20 messages per session, and 20 in the Deploy Chat | 409 QUEUE_FULL |
| Attachments | 20 files per message, 10 MB each | 400 BAD_REQUEST |
| Attachment types | JPEG, PNG, GIF and WebP images. PDF, plain text, CSV, TSV, Markdown, XML, YAML, JSON, Excel and Word documents. | 400 BAD_REQUEST. Some models read no images or PDFs, and the message says so. |
| Diff comments sent with one message | 50 | 400 VALIDATION_ERROR |
| Page size | 100 for most lists, 200 for Activity | Larger values are cut to the maximum. |
| Cron schedule | At most one fire every 15 minutes | 400 BAD_REQUEST |
| Rate limits | 429 applies to the public share-link routes and the invitation preview | 429 TOO_MANY_REQUESTS |