Messages and turns
Send a message to a chat session with POST .../jobs/chat. If the agent is idle, the message starts a turn and you get its chatJobId. If the agent is working, the message waits Up next and you get a queuedMessageId. Follow the turn until it ends, then read the reply from the session's messages. Follow a turn explains how deliveries, job statuses and waiting fit together.
| Method | Path | Description |
|---|---|---|
| GET | /api/projects/{projectId}/chat-sessions/{sessionId}/jobs/chat?page=&pageSize= | List a chat session's jobs, one per Turn, newest first: each one's status, error and timestamps, with the total in meta.pagination. |
| POST | /api/projects/{projectId}/chat-sessions/{sessionId}/jobs/chat | Send a message: starts a turn, or waits Up next while one is running. To steer the running turn with it, steer the Queued Message it becomes. Attachments ride along as multipart. |
| GET | /api/projects/{projectId}/chat-sessions/{sessionId}/jobs/chat/{jobId} | Read one turn's job: its status, error and timestamps. |
| POST | /api/projects/{projectId}/chat-sessions/{sessionId}/jobs/chat/{jobId}/cancel | Stop a running turn. Steers it had not taken go back to the top of Up next, which pauses. |
Send a message
POST /api/projects/{projectId}/chat-sessions/{sessionId}/jobs/chat sends a message into an Active session.
| Field | Type | What it does |
|---|---|---|
content | string | Required. The message. It can be empty when you send diffCommentsContext or attachments. |
model | string | Optional. A value from GET /api/models. The session keeps it from this turn on. Ignored when the message waits Up next, or when the value is unknown. |
previewContext | object | Optional. The preview page the message is about, as { "frontendName": "web", "path": "/settings" }. path starts with /. |
diffCommentsContext | array | Optional. Up to 50 review comments from the diff, handed to the agent as feedback. |
retryFailedJobId | uuid | Optional. Retries a turn that overflowed the context window. See below. |
runningJobId | uuid | Optional. The turn you saw running when you sent. If a member stopped it meanwhile, you get 409 with JOB_STOPPED instead of a new turn. |
A model from another provider starts the agent fresh. The turn opens with the conversation handed over as text.
Each diffCommentsContext item holds a comment's id, filePath, side, startLineNumber, endLineNumber, body and lineSnapshot. Kosuke reads each comment again by id and leaves out resolved ones. Send review comments to the agent walks through it.
curl -s -X POST "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/jobs/chat" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "Make the toggle remember the choice after a reload"}'
The answer is 201. Its delivery says what happened to the message:
delivery | Also returned | What happened |
|---|---|---|
job | chatJobId, jobNumber | The message started a turn. |
queued | queuedMessageId | A turn was running. The message waits Up next and starts its own turn after the ones ahead of it. |
{ "data": { "delivery": "job", "chatJobId": "b7e41d09-2c5a-4f3e-8a61-0d9c7b2e5f14", "jobNumber": 3 } }
{ "data": { "delivery": "queued", "queuedMessageId": "0a6f93c2-41d8-4e7b-b5a0-7c2d18e9f365" } }
A queued message has no job until its turn starts. Change, move or delete it with the Up next endpoints on Chat sessions.
To attach files, send multipart/form-data with content, attachmentCount and one field per file, attachment_0 to attachment_9. A message takes up to 10 files of 10 MB each. In a multipart body, previewContext and diffCommentsContext are JSON strings.
kosuke sessions send $PROJECT_ID $SESSION_ID --content "Match this design" --attach mockup.png
Sending into an unassigned session makes you its Assignee. A session Kosuke is the Assignee of stays Kosuke's until a member reassigns it.
What the session needs
| Session state | What happens |
|---|---|
| Active, sandbox running | The message is delivered. |
| Active, sandbox paused | The send wakes the sandbox first. |
| Active, sandbox still starting | 409 with SANDBOX_STARTING. Send again in a few seconds. |
| Active, no sandbox | The send starts a sandbox, and the turn runs once it is ready. 400 when the session has no branch, 409 with DEPLOY_ACTIVE during a deploy, 409 with SANDBOX_LIMIT_REACHED at the limit. |
| Backlog | 400. Start it with POST .../activate first. |
| Under Review, sent as a member | The pull request turns back into a draft, the session becomes Active, and the message is then delivered by the rows above. |
| Under Review, sent by a Cron, another session or a Slack relay | 400. Resume it by setting status to active on Chat sessions, then send. |
| Completed or Archived | 400. The session takes no new messages. |
Follow a turn
GET .../jobs/chat/{jobId} reads one turn. Read it until status is no longer pending or running.
curl -s "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/jobs/chat/$JOB_ID" \
-H "Authorization: Bearer $KOSUKE_API_KEY"
{
"data": {
"job": {
"id": "b7e41d09-2c5a-4f3e-8a61-0d9c7b2e5f14",
"status": "completed",
"jobNumber": 3,
"error": null,
"errorCode": null,
"createdAt": "2026-09-30T09:12:04.118Z",
"startedAt": "2026-09-30T09:12:09.502Z",
"completedAt": "2026-09-30T09:14:41.077Z"
}
}
}
status | What it means |
|---|---|
pending | The turn waits to start. |
running | The agent is working. |
awaiting_response | The agent asked a question. The turn has ended. |
completed | The turn finished. |
failed | The turn stopped on an error. error and errorCode say why. |
cancelled | A member stopped the turn. |
Instead of polling, kosuke sessions watch <projectId> <sessionId> <jobId> streams the turn and exits when it ends. GET .../jobs/chat lists a session's turns, newest first, with page and pageSize (default 25, up to 100). The CLI commands are kosuke sessions jobs get and kosuke sessions jobs list.
When the turn ends, GET .../chat-sessions/{sessionId}/messages returns the conversation. The reply is the last message with role set to assistant, in its blocks. Its content is null for an ordinary reply.
Stop a turn
POST .../jobs/chat/{jobId}/cancel stops a turn that is pending or running. It takes no body.
curl -s -X POST "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/jobs/chat/$JOB_ID/cancel" \
-H "Authorization: Bearer $KOSUKE_API_KEY"
The answer is { "data": { "cancelled": 1, "message": "Chat job cancelled" } }. Steers the turn had not taken go back to the top of Up next, and Up next pauses. Send its top message with POST .../chat-sessions/{sessionId}/queue/resume. A turn that ended completed, failed or cancelled answers 400. The CLI command is kosuke sessions jobs cancel <projectId> <sessionId> <jobId>.
Answer the agent's question
A turn that ends awaiting_response has asked you something. The last assistant message in GET .../chat-sessions/{sessionId}/messages carries a block with type set to clarification. Its clarificationData.questions each hold a question and two to four options, each with a title, a description and isRecommended.
Send your answer as an ordinary message to POST .../jobs/chat. It starts the next turn.
curl -s -X POST "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/jobs/chat" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "Keep the version from this branch."}'
Run a skill or a command from a message
A message that starts with the session's invocation character runs a skill. The character is $ when the session's model starts with openai:, and / for Claude, GLM and Kimi sessions.
| Message starts with | What happens |
|---|---|
/name or $name | Runs the skill name when the session's sandbox has it. Otherwise the agent reads the text as an ordinary message. |
/kosuke:open-pr or $kosuke:open-pr | Opens the pull request, or updates the open one. See Pull requests and sync. |
/kosuke:sync-branch or $kosuke:sync-branch | Brings the Target Branch into the session's branch. Same page. |
/clear or /compact | Starts an ordinary turn. To run them as runtime commands, use POST .../chat-sessions/{sessionId}/runtime-commands on Chat sessions. |
GET /api/projects/{projectId}/skills with provider and sessionId lists the skills the session can run. provider is anthropic for Claude, GLM and Kimi sessions and openai for Codex sessions.
Retry after the context window overflows
A turn that outgrows the model's context window fails with errorCode set to context_window_exceeded. Send the message again with retryFailedJobId set to that job's id.
curl -s -X POST "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/jobs/chat" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"content\": \"Continue where you stopped\", \"retryFailedJobId\": \"$JOB_ID\"}"
The new turn starts the agent fresh and hands it the conversation as text. The retry works only when the failed job is the session's latest. Any other id is ignored, and the message goes out as a plain message.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | A field has the wrong type or shape. |
| 400 | BAD_REQUEST | The session is Backlog, Completed or Archived, a multipart message is empty, an attachment is refused, or the Assignee cannot run the model. The message says which. An empty JSON message answers VALIDATION_ERROR. |
| 400 | SESSION_UNDER_REVIEW | The session is Under Review and the message cannot resume it. The sender is a Cron, another session or a Slack relay, or the session went Under Review while the message was on its way. A member sends again to resume. |
| 402 | CREDIT_LIMIT_REACHED | The workspace is blocked on billing. |
| 403 | FORBIDDEN | The job belongs to another session. |
| 404 | NOT_FOUND | The project, session or job does not exist. |
| 409 | SANDBOX_STARTING | The sandbox is still starting. Send again in a few seconds. |
| 409 | SANDBOX_LIMIT_REACHED | Waking the paused sandbox, or starting a missing one, would pass the workspace's concurrent sandbox limit. Stop another session first. |
| 409 | DEPLOY_ACTIVE | The session's sandbox is gone and cannot be recreated while the project deploys. Send again once the deploy ends. |
| 409 | JOB_IN_PROGRESS | A turn started while the message was on its way. Send it again and it waits Up next. |
| 409 | JOB_STOPPED | The turn named in runningJobId was stopped. |
| 409 | QUEUE_FULL | Up next already holds 20 messages. |
| 409 | COMMAND_IN_FLIGHT | A Runtime Command is running on the session. Send again once it ends. |
| 409 | SESSION_UPDATING, SESSION_STATUS_CHANGED, SESSION_CHANGED | The session changed while the message was on its way. Read it again and retry. |
Responses and errors has the full table.