Skip to main content

Previews

GET /api/projects/{projectId}/preview returns a preview's URLs, and starts its sandbox when it is paused or missing. Pass sessionId for a chat session's preview, and leave it out for the project's main preview. Call it before you read a session's files, diff or git state. From here you can also check the preview's health, restart its sandbox and share it with a public link.

MethodPathDescription
GET/api/projects/{projectId}/preview?sessionId=Read the preview URLs of the project's main sandbox, or of one chat session's. Starts a paused or missing sandbox in the background; poll health until it is up.
GET/api/projects/{projectId}/preview/health?sessionId=Read the health of every service behind a preview, and whether a background start is running or failed.
POST/api/projects/{projectId}/preview/sandbox/resetKill a sandbox that is stuck or crashed and does not recover by itself (not a slow preview), paused or unreachable included; the platform creates a new one. Uncommitted work and the agent's memory of the chat are lost, so an agent first asks the member on a card saying so and runs it only once they approve; an Event Turn or a teammate's message is never that approval.
GET/api/projects/{projectId}/preview/share?sessionId=Read the public share link of a preview, if one exists.
POST/api/projects/{projectId}/preview/share?sessionId=Create the public share link of a preview. The body sessionId wins over the query one.
DELETE/api/projects/{projectId}/preview/share?sessionId=Revoke the public share link of a preview.
GET/api/shared-previews/{slug}Resolve a public share link. No credential needed; rate limited per caller.
GET/api/shared-previews/{slug}/artifacts/{artifactId}Read one artifact behind a public share link, by its id or its slug. No credential needed.

Get the preview and wake the sandbox​

ParameterWhat it does
sessionIdOptional. The chat session whose preview you want. Left out, the call reads the project's main preview.

A paused sandbox wakes in the background, and a missing chat session sandbox is created in the background. The call returns at once with provisioning: true, an empty frontendUrls and a startAttemptId. Check health while it reports startInFlight: true. A missing main sandbox is rebuilt by a deploy, and the call returns before that deploy finishes. Sandboxes and previews explains when a sandbox sleeps and what waking it costs.

curl -s "https://app.kosuke.ai/api/projects/$PROJECT_ID/preview?sessionId=$SESSION_ID" \
-H "Authorization: Bearer $KOSUKE_API_KEY"
{
"data": {
"frontendUrls": { "web": "https://…" },
"projectId": "5a1e9c47-83b2-4d6f-a0e8-2b7c9d14f36a",
"sandboxKey": "c3f8a215-6e0d-4b97-9a42-81d5e7f02c6b",
"awayHoldMs": 600000
}
}

frontendUrls maps each service that serves the preview to its URL. When nothing could start, the answer is still 200, with an empty frontendUrls and one of these flags:

FlagWhat it meansWhat to do
creditLimitReached: trueThe workspace is blocked on billing, so nothing started.Sort out the workspace's billing. Retrying does not help.
provisioning: trueThis call started a wake or a create, or another request is already starting or changing this sandbox. startAttemptId names the start when it is a preview start.Check health until the services are up, or until it reports startFailure.

A start that runs in the background can still be refused, for example at the workspace's concurrent sandbox limit. Health reports that as startFailure.

An empty frontendUrls with no flag has four readings. The session may be Backlog, Completed or Archived. A deploy may be rebuilding the project's main preview. No service in the project may serve a preview. Or another operation held the session's sandbox for over five seconds, so call again. When the project's main sandbox is gone, asking for the main preview starts that deploy.

The CLI command is kosuke preview get <projectId> --session-id <sessionId>.

Check health​

GET .../preview/health reports whether the services behind a preview answer. It takes the same sessionId and never wakes a sandbox.

{
"data": {
"state": "healthy",
"frontendUrls": { "web": "https://…" },
"services": [
{ "name": "agent", "httpResult": "healthy" },
{ "name": "web", "httpResult": "healthy" }
],
"sandboxAvailable": true,
"sandboxId": "i8k2m4q6r1t3v5x7z9b0d",
"canRetainPreview": false
}
}
FieldWhat it holds
statehealthy when every service answers, starting otherwise.
servicesEach probed endpoint's name, which is its route key, and httpResult, which is healthy, not_healthy or null. When the check reached a running sandbox, the list starts with agent, the agent inside the sandbox. A project with public endpoints but none that serves a preview lists only those endpoints, each with a null httpResult. Otherwise the list is empty.
sandboxAvailabletrue when the check reached a running sandbox.
sandboxIdThe sandbox the check reached. Present only when it reached a running sandbox.
canRetainPreviewtrue when that sandbox was created with auto-resume off, so a request to its preview does not wake it once it pauses. Present with sandboxId.
pausedtrue when the sandbox is paused. Wake it with GET /api/projects/{projectId}/preview, unless startInFlight is true.
needsStarttrue when nothing is running or starting, and no start failure is recorded. Start it with GET /api/projects/{projectId}/preview.
startInFlighttrue while a start brings the sandbox up. Keep checking health.
startFailureThe last start that ran in the background failed, and none has run since. It holds reason (sandbox_limit or failed), operation (wake or create), the attemptId and a message. It stays for up to five minutes.
creditLimitReachedtrue when the workspace is blocked on billing.
sandboxLimitReachedtrue when waking the paused sandbox would go over the workspace's concurrent sandbox limit, or when the last start was refused at it.
indeterminatetrue when the check could not read the sandbox this time. A pause or resume held it, or it did not answer. Ignore the rest of the answer and ask again in a few seconds. Do not wake it with GET /api/projects/{projectId}/preview.

A Backlog, Completed or Archived session answers 404. The CLI command is kosuke preview health get <projectId> --session-id <sessionId>.

Restart a preview's sandbox​

POST .../preview/sandbox/reset kills the preview's sandbox, a paused or unreachable one too, so Sensei creates a new one. It is for a sandbox that is stuck and does not recover by itself. Uncommitted work in the sandbox is lost, and so is the agent's memory of the chat: the next message opens a fresh agent that reads the chat history, which stays.

FieldTypeWhat it does
sessionIdstringOptional. The chat session whose sandbox to restart. Left out, or with no body at all, the project's main sandbox restarts.
curl -s -X POST "https://app.kosuke.ai/api/projects/$PROJECT_ID/preview/sandbox/reset" \
-H "Authorization: Bearer $KOSUKE_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"sessionId\": \"$SESSION_ID\"}"

The answer comes back at once with { "status": "queued" }, plus the projectId, the sandboxKey and alreadyInFlight. The kill runs in the background, and health reports starting until it is done. A second restart while one is queued or running kills nothing more and answers alreadyInFlight: true. A chat session's new sandbox starts the next time its preview opens or a message is sent. The main sandbox comes back through a Recovery Deploy that the restart starts. A Backlog (Todo), Completed or Archived session answers 400. It answers 409 while something else owns the sandbox: a running turn (TURN_RUNNING, stop it first), a sandbox still being started (SANDBOX_PROVISIONING), or a deploy of the main (DEPLOY_RUNNING). A malformed body answers 400 rather than restarting the main sandbox. Agents other than Sensei cannot call it. The CLI command is kosuke preview sandbox reset <projectId> --session-id <sessionId>.

Share a preview​

A share link lets anyone open a preview without a Sensei account. For a chat session, it also opens the session's artifacts. Any member of the workspace can create or revoke one. GET, POST and DELETE on .../preview/share take the same sessionId, and POST also takes it in the body.

CallWhat it doesAnswer
POST .../preview/shareCreates the link, or returns the one that already exists.{ "share": { ... } }
GET .../preview/shareReads the link.{ "share": { ... } }, or { "share": null } when there is none
DELETE .../preview/shareRevokes the link. It stops resolving at once. A page already open keeps the preview until its Auth Token expires, within 15 minutes.{ "share": null }, also when there was no link
curl -s -X POST "https://app.kosuke.ai/api/projects/$PROJECT_ID/preview/share?sessionId=$SESSION_ID" \
-H "Authorization: Bearer $KOSUKE_API_KEY"
{
"data": {
"share": {
"slug": "Tq3xV9mZkP2rLw7sHc4nB8aQ",
"url": "https://app.kosuke.ai/share/Tq3xV9mZkP2rLw7sHc4nB8aQ",
"createdAt": "2026-09-30T09:20:11.402Z",
"lastViewedAt": null
}
}
}

lastViewedAt is when someone last opened the link, accurate to about five minutes. Opening the link wakes a paused sandbox, and the time it runs counts against the workspace. The CLI group is kosuke preview share, with create, get and delete.

These endpoints need no API key. Anyone who holds the link can call them. An unknown or revoked link answers 404.

GET /api/shared-previews/{slug} returns what the link shows and wakes a paused preview.

curl -s "https://app.kosuke.ai/api/shared-previews/$SLUG"
{
"data": {
"projectName": "storefront",
"sessionTitle": "Add a dark mode toggle to the settings page",
"status": "ready",
"frontends": [{ "name": "web", "url": "https://…", "isServing": true }],
"artifacts": [{ "id": "e2c7…", "slug": "qa-report", "title": "QA Report: kosuke/chat-4kd9wp", "kind": "html", "version": 3, "createdAt": "2026-09-30T09:31:47.208Z", "updatedAt": "2026-09-30T10:02:15.664Z" }]
}
}

sessionTitle is null for the main preview. artifacts lists the session's artifacts without their content, whatever status says. It is empty for the main preview.

statusreasonWhat it means
readyNoneAt least one service answers.
startingNoneThe preview is waking or its services are starting. Call again in a few seconds.
unavailablesession_endedThe session is Backlog, Completed or Archived.
unavailablenot_runningThe preview cannot start now. The workspace may be at its credit or sandbox limit, or the project's main sandbox is gone.
unavailableno_frontendsThe sandbox runs, but no service serves a preview.

Read an artifact​

GET /api/shared-previews/{slug}/artifacts/{artifactId} returns one artifact of the shared session, in the same shape as on Artifacts. It works while the sandbox is paused. An artifact from another session, or any artifact through a main-preview link, answers 404.

The CLI reads both without an API key, with kosuke preview shared get <slug> and kosuke preview shared artifacts get <slug> <artifactId>.

Rate limits​

EndpointLimit per caller
GET /api/shared-previews/{slug}60 requests a minute
GET /api/shared-previews/{slug}/artifacts/{artifactId}30 requests a minute

Past the limit, the answer is 429 with TOO_MANY_REQUESTS.

Errors​

StatusCodeWhen
400BAD_REQUESTThe project was never deployed, a chat session's preview is asked for while a deploy is in progress, or a sandbox restart names a Backlog, Completed or Archived session.
400VALIDATION_ERRORA sandbox restart body has the wrong shape.
409TURN_RUNNING, SANDBOX_PROVISIONING, DEPLOY_RUNNINGA sandbox restart while a turn runs, a sandbox is being started, or the main is deploying.
404NOT_FOUNDThe project, session or share link does not exist, or health was asked for a session with no preview.
429TOO_MANY_REQUESTSA public share-link endpoint passed its rate limit.

Responses and errors has the full table.

Next steps​