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.
| Method | Path | Description |
|---|---|---|
| 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/reset | Kill 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
| Parameter | What it does |
|---|---|
sessionId | Optional. 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:
| Flag | What it means | What to do |
|---|---|---|
creditLimitReached: true | The workspace is blocked on billing, so nothing started. | Sort out the workspace's billing. Retrying does not help. |
provisioning: true | This 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
}
}
| Field | What it holds |
|---|---|
state | healthy when every service answers, starting otherwise. |
services | Each 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. |
sandboxAvailable | true when the check reached a running sandbox. |
sandboxId | The sandbox the check reached. Present only when it reached a running sandbox. |
canRetainPreview | true 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. |
paused | true when the sandbox is paused. Wake it with GET /api/projects/{projectId}/preview, unless startInFlight is true. |
needsStart | true when nothing is running or starting, and no start failure is recorded. Start it with GET /api/projects/{projectId}/preview. |
startInFlight | true while a start brings the sandbox up. Keep checking health. |
startFailure | The 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. |
creditLimitReached | true when the workspace is blocked on billing. |
sandboxLimitReached | true when waking the paused sandbox would go over the workspace's concurrent sandbox limit, or when the last start was refused at it. |
indeterminate | true 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.
| Field | Type | What it does |
|---|---|---|
sessionId | string | Optional. 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.
| Call | What it does | Answer |
|---|---|---|
POST .../preview/share | Creates the link, or returns the one that already exists. | { "share": { ... } } |
GET .../preview/share | Reads the link. | { "share": { ... } }, or { "share": null } when there is none |
DELETE .../preview/share | Revokes 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.
Public share-link endpoints
These endpoints need no API key. Anyone who holds the link can call them. An unknown or revoked link answers 404.
Resolve a link
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.
status | reason | What it means |
|---|---|---|
ready | None | At least one service answers. |
starting | None | The preview is waking or its services are starting. Call again in a few seconds. |
unavailable | session_ended | The session is Backlog, Completed or Archived. |
unavailable | not_running | The preview cannot start now. The workspace may be at its credit or sandbox limit, or the project's main sandbox is gone. |
unavailable | no_frontends | The 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
| Endpoint | Limit 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
| Status | Code | When |
|---|---|---|
| 400 | BAD_REQUEST | The 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. |
| 400 | VALIDATION_ERROR | A sandbox restart body has the wrong shape. |
| 409 | TURN_RUNNING, SANDBOX_PROVISIONING, DEPLOY_RUNNING | A sandbox restart while a turn runs, a sandbox is being started, or the main is deploying. |
| 404 | NOT_FOUND | The project, session or share link does not exist, or health was asked for a session with no preview. |
| 429 | TOO_MANY_REQUESTS | A public share-link endpoint passed its rate limit. |
Responses and errors has the full table.