# 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

```json
{ "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):

```bash
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"
```

```json
{
  "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`.

```bash
curl -s "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions?limit=50" \
  -H "Authorization: Bearer $KOSUKE_API_KEY"
```

```json
{
  "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

```json
{ "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](https://kosuke.ai/docs/api/errors.md#limits). |
| 403 | `API_KEY_SCOPE` | Your API key does not have the access this operation needs. `error` names the area it lacks, or says the operation needs a Full access key. See [What a key can reach](https://kosuke.ai/docs/api/authentication.md#what-a-key-can-reach). | Create a key with that access in Settings > You > API keys, or do it in the web app. |
| 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](https://kosuke.ai/docs/api/what-an-api-key-cannot-do.md). | Do not retry. |
| 403 | `SETTINGS_ADMIN_ONLY` | The request changes a project's setup, services or environment, imports a config, or starts or answers a Deploy. Only a workspace admin does that, in the web or desktop app. An API key and a Connected App are refused, an admin's own included, until API keys get a project settings scope. See [What only the web app can do](https://kosuke.ai/docs/api/what-an-api-key-cannot-do.md#project-settings-and-deploys). | Do not retry. Ask a workspace admin to make the change in the app. |
| 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](https://kosuke.ai/docs/api/sandboxes.md), 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](https://kosuke.ai/docs/help/get-help.md). |
| 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:

```json
{
  "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](https://kosuke.ai/docs/workspace/plans-and-billing.md).

### Other limits

| Limit | Value | What you see |
| --- | --- | --- |
| Concurrent sandboxes | Set by the plan. See [Plans and billing](https://kosuke.ai/docs/workspace/plans-and-billing.md). | `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` |

## Next steps

- [Follow a turn](https://kosuke.ai/docs/api/turns.md)
- [Sandboxes and previews](https://kosuke.ai/docs/api/sandboxes.md)
- [Endpoint reference](https://kosuke.ai/docs/endpoint-reference.md)
