Skip to main content

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.

MethodPathDescription
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/chatSend 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}/cancelStop 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.

FieldTypeWhat it does
contentstringRequired. The message. It can be empty when you send diffCommentsContext or attachments.
modelstringOptional. 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.
previewContextobjectOptional. The preview page the message is about, as { "frontendName": "web", "path": "/settings" }. path starts with /.
diffCommentsContextarrayOptional. Up to 50 review comments from the diff, handed to the agent as feedback.
retryFailedJobIduuidOptional. Retries a turn that overflowed the context window. See below.
runningJobIduuidOptional. 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:

deliveryAlso returnedWhat happened
jobchatJobId, jobNumberThe message started a turn.
queuedqueuedMessageIdA 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 stateWhat happens
Active, sandbox runningThe message is delivered.
Active, sandbox pausedThe send wakes the sandbox first.
Active, sandbox still starting409 with SANDBOX_STARTING. Send again in a few seconds.
Active, no sandboxThe 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.
Backlog400. Start it with POST .../activate first.
Under Review, sent as a memberThe 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 relay400. Resume it by setting status to active on Chat sessions, then send.
Completed or Archived400. 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"
}
}
}
statusWhat it means
pendingThe turn waits to start.
runningThe agent is working.
awaiting_responseThe agent asked a question. The turn has ended.
completedThe turn finished.
failedThe turn stopped on an error. error and errorCode say why.
cancelledA 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 withWhat happens
/name or $nameRuns 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-prOpens the pull request, or updates the open one. See Pull requests and sync.
/kosuke:sync-branch or $kosuke:sync-branchBrings the Target Branch into the session's branch. Same page.
/clear or /compactStarts 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​

StatusCodeWhen
400VALIDATION_ERRORA field has the wrong type or shape.
400BAD_REQUESTThe 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.
400SESSION_UNDER_REVIEWThe 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.
402CREDIT_LIMIT_REACHEDThe workspace is blocked on billing.
403FORBIDDENThe job belongs to another session.
404NOT_FOUNDThe project, session or job does not exist.
409SANDBOX_STARTINGThe sandbox is still starting. Send again in a few seconds.
409SANDBOX_LIMIT_REACHEDWaking the paused sandbox, or starting a missing one, would pass the workspace's concurrent sandbox limit. Stop another session first.
409DEPLOY_ACTIVEThe session's sandbox is gone and cannot be recreated while the project deploys. Send again once the deploy ends.
409JOB_IN_PROGRESSA turn started while the message was on its way. Send it again and it waits Up next.
409JOB_STOPPEDThe turn named in runningJobId was stopped.
409QUEUE_FULLUp next already holds 20 messages.
409COMMAND_IN_FLIGHTA Runtime Command is running on the session. Send again once it ends.
409SESSION_UPDATING, SESSION_STATUS_CHANGED, SESSION_CHANGEDThe session changed while the message was on its way. Read it again and retry.

Responses and errors has the full table.

Next steps​