Endpoint reference / Project settings

Project settings.

Project settings decide what runs in a project's sandbox. They hold the setup commands, the branches, the sandbox size, the services and the environment variables. To change one field, send PATCH /api/projects/{projectId}/settings. To replace everything at once from a kosuke.config.json, send POST /api/projects/{projectId}/settings/import.

MethodPathDescription
GET/api/projects/{projectId}/settings?card=&values=Read the project settings: setup commands, services, environment and sandbox size. Every project member reads them. --values false leaves every environment variable value empty: their names only. A secret's value reaches only a member's own browser session or API key, never Sensei or another agent. canEdit says whether the caller may change them; Sensei passes --card with the card its member approved.
PATCH/api/projects/{projectId}/settings?card=Change setup commands, branches, working directory, sandbox tier, the Project QA Guide or deviceWritePaths, the device folders a Chat Session may write. Only a workspace admin, in the browser, changes setup commands, branches, working directory or sandbox tier: an API key or a connected app gets 403 until keys carry a project settings scope, and so does every other agent token. Sensei changes the setup commands (host setup, setup, warmup, rerun on checkout move) for a workspace admin, with --card naming the card about this project they approved, from the Turn that approval started; without it, 403. Any member changes the QA Guide, and only a member's own API key or browser session changes deviceWritePaths. Each change is recorded in Change history, Sensei's as Sensei for its member.
POST/api/projects/{projectId}/settings/env-vars?card=Add environment variables. The body is a JSON array of { key, value, isSecret? }. Only a workspace admin, in the browser: an API key, a connected app and every other agent token get 403. Sensei does it for a workspace admin, with --card naming the card about this project they approved, from the Turn that approval started; without it, 403. Sensei may write a secret, and never reads one back. Each one is recorded in Change history, its value masked. A loader or shell name (NODE_OPTIONS, LD_PRELOAD, PATH, GIT_*, and the like) reaches the app only, never Kosuke's agents.
DELETE/api/projects/{projectId}/settings/env-vars?card=Remove environment variables. The body is a JSON array of variable ids. Only a workspace admin, in the browser: an API key, a connected app and every other agent token get 403. Sensei does it for a workspace admin, with --card naming the card about this project they approved, from the Turn that approval started; without it, 403. Each removal is recorded in Change history.
GET/api/projects/{projectId}/settings/historyRead the project's Change history, newest first: each change to its setup, services, environment and deploys that went through, with who made it, how (browser, API key, connected app, Sensei or the deploy agent) and when. A member's restart of the main preview lists as the Recovery Deploy it starts. Every project member reads it. An environment variable's value is never kept: only whether it was set and secret.
POST/api/projects/{projectId}/settings/importReplace the services and environment from a kosuke.config.json document. Cross-service rules (unique ports, after references, one preview endpoint) are checked by the handler, not by the schema. Only a workspace admin, in the browser: an API key, a connected app and every agent token get 403. Recorded in Change history.
GET/api/projects/{projectId}/settings/qa-guideRead the project's saved Project QA Guide and its revision, the SHA-256 that projects qa-guide set --base-revision names. It can be newer than the one a running session started with.
PUT/api/projects/{projectId}/settings/qa-guideSave the project's Project QA Guide. Chat sessions created after the save get it; running sessions keep the one they started with. With baseRevision it is refused with 409 GUIDE_CHANGED when the saved guide changed since; Sensei must pass it.
GET/api/projects/{projectId}/settings/servicesList the project services, in the order they start. Carries no environment variables.
POST/api/projects/{projectId}/settings/services?card=Add a service. A port already used by another service is rejected by the handler. Only a workspace admin, in the browser: an API key, a connected app and every other agent token get 403. Sensei does it for a workspace admin, with --card naming the card about this project they approved, from the Turn that approval started; without it, 403. Recorded in Change history.
PATCH/api/projects/{projectId}/settings/services/{serviceId}?card=Change one service. Every field is optional; only the ones sent change. Only a workspace admin, in the browser: an API key, a connected app and every other agent token get 403. Sensei does it for a workspace admin, with --card naming the card about this project they approved, from the Turn that approval started; without it, 403. Recorded in Change history.
DELETE/api/projects/{projectId}/settings/services/{serviceId}?card=Remove one service. Only a workspace admin, in the browser: an API key, a connected app and every other agent token get 403. Sensei does it for a workspace admin, with --card naming the card about this project they approved, from the Turn that approval started; without it, 403. Recorded in Change history.

Any member of the workspace can read these settings and their Change history. Only a workspace admin changes the setup commands, branches, working directory, sandbox tier, services and environment variables, in the web app: an API key, a Connected App and every agent get 403 SETTINGS_ADMIN_ONLY until keys carry a project settings scope. Any member still changes the QA Guide. In the web app they are the tabs of the project's settings view. See Project settings. The routes under /settings/agentic-deploy are on Deploys.

Saving changes to commands, services, environment variables or the sandbox tier only stores them. The main sandbox takes them at the project's next Deploy. A new chat session takes them at once when Sensei builds its sandbox after the save. That is the case when no pool sandbox is waiting, or when the waiting one was prepared after the save. A session can instead take a pool sandbox prepared before the save. Sensei then writes the saved settings into its files. That sandbox still keeps its old sandbox tier and the services it already started. A Deploy does not replace a sandbox a session took, so the session keeps that tier and those services. A Deploy makes the main sandbox current before it reports deployed. It replaces the waiting pool sandboxes after that, in the background. Until the new ones are ready, a new session does not take an old one. Sensei discards it and builds the session's sandbox. In the web app, the Deploy Chat's Warming N more sandboxes line finishes once they are ready. When it shows Warming N more sandboxes failed instead, no pool sandbox is usable until a session start refills the pool. Saving never refreshes the waiting pool sandboxes. Sessions already running keep their old settings until a member delivers them with kosuke sessions env sync <projectId> <sessionId>, described under Chat sessions.

Read settings

Read every setting of a project, services and environment variables included.

curl -s https://app.kosuke.ai/api/projects/$PROJECT_ID/settings \
  -H "Authorization: Bearer $KOSUKE_API_KEY"
{
  "data": {
    "hostSetupCommand": "apt-get install -y postgresql-16",
    "setupCommand": "bun install --frozen-lockfile",
    "warmupCommand": null,
    "rerunSetupOnCheckoutMove": false,
    "agentWorkDir": null,
    "sessionBaseBranch": null,
    "prTargetBranch": "develop",
    "qaGuide": "## Open the app\nThe web service serves the Preview. Sign in as qa@example.com with the password in QA_PASSWORD.",
    "deviceWritePaths": [],
    "sandboxTier": "MEDIUM",
    "services": [
      {
        "id": "8e2b4c6d-1a3f-4b5e-9c7d-0f1e2d3c4b5a",
        "projectId": "5a1e9c47-83b2-4d6f-a0e8-2b7c9d14f36a",
        "name": "web",
        "directory": ".",
        "kind": "process",
        "endpoints": [{ "name": "web", "port": 3000, "exposure": "preview", "healthRoute": "/" }],
        "startCommand": "bun run dev -- -p 3000",
        "stopCommand": null,
        "after": ["postgres"],
        "envFiles": [],
        "sortOrder": 1,
        "createdAt": "2026-09-12T08:41:03.517Z",
        "updatedAt": "2026-09-28T14:06:52.904Z"
      }
    ],
    "envVars": [
      { "id": "5c1d9e7a-3b2f-4a6c-8d0e-7f9a1b2c3d4e", "projectId": "5a1e9c47-83b2-4d6f-a0e8-2b7c9d14f36a", "key": "DATABASE_URL", "value": "postgres://app@localhost:5432/app", "isSecret": true, "createdAt": "2026-09-12T08:41:03.517Z", "updatedAt": "2026-09-12T08:41:03.517Z" }
    ]
  }
}

sandboxTier is null until the project has a tier saved. A first Deploy then runs on XLARGE.

In the CLI, run kosuke projects settings get <projectId>.

Change settings

Send only the fields you want to change. The route answers 200 with the nine fields below.

FieldPurposeValuesIn the web app
hostSetupCommandInstalls runtimes and system packages. Runs on the first Deploy, and again after you change it.Up to 2,000 characters, or null for noneSandbox tab, Host setup script
setupCommandRuns at the repository root on every Deploy, before any service starts.Up to 2,000 characters, or nullServices tab, App setup script
warmupCommandRuns once the services are healthy.Up to 10,000 characters, or nullAdvanced tab, Warmup Script
rerunSetupOnCheckoutMoveRuns setupCommand every time a new chat session starts on a pool sandbox at a newer commit. Off, Sensei skips it when the newer commit changed no lockfile, dependency manifest, toolchain version file, env file, migration, seed, schema file or file: dependency. Turn it on when the setup also generates code from the source.true or false. Starts false.Not in the web app
agentWorkDirThe folder the agent works in, relative to the repository root.Up to 255 characters, without ... null means the root.Advanced tab, Agent Working Directory
sessionBaseBranchThe branch new sessions start from.A branch name up to 100 characters. null means the repository's default branch.Advanced tab, Branch tasks from
prTargetBranchThe Target Branch, which pull requests open against and Sync brings in.A branch name up to 100 characters. null means the repository's default branch.Advanced tab, Open PRs against
sandboxTierCPU and memory of the project's sandboxes.SMALL, MEDIUM, LARGE or XLARGE. Leave it out to keep the current tier.Sandbox tab, Sandbox Tier
qaGuideThe Project QA Guide: how to test the project in a sandbox. See Save the QA Guide.Markdown up to 102,400 characters, or null for noneAdvanced tab, QA Guide
deviceWritePathsFolders on a device that a Chat Session's shell and file calls may write, besides its own. See Let sessions write device folders.Up to 64 paths. [] for none.Not shown in Project settings
sandboxTierCPUMemory
SMALL24 GB
MEDIUM48 GB
LARGE816 GB
XLARGE1632 GB

What each tier costs per hour is on Plans and billing.

curl -s -X PATCH https://app.kosuke.ai/api/projects/$PROJECT_ID/settings \
  -H "Authorization: Bearer $KOSUKE_API_KEY" \
  --json '{ "sandboxTier": "LARGE", "prTargetBranch": "develop" }'

Sensei saves a branch name without checking that the branch exists. Check it first with GET /api/projects/{projectId}/branches.

In the CLI, run kosuke projects settings update <projectId> --sandbox-tier LARGE --pr-target-branch develop.

Save the QA Guide

The QA Guide tells /kosuke:automated-qa how to test the project: which service to open, how to sign in, how to read data, and the traps to avoid. QA keeps the guide it writes under 15,000 characters. A sandbox cannot save it. When QA creates the guide or proves a step in it wrong, QA sends the guide as a message to the chat session's Assignee's Sensei (kosuke sensei messages send, key qa-guide), and the guide is saved when they accept it. A member can also save it in Project settings. PUT /api/projects/{projectId}/settings/qa-guide replaces the whole guide.

FieldMeaning
contentThe guide in Markdown. 1 to 102,400 characters, and not only whitespace.
curl -s -X PUT https://app.kosuke.ai/api/projects/$PROJECT_ID/settings/qa-guide \
  -H "Authorization: Bearer $KOSUKE_API_KEY" \
  --json '{ "content": "## Open the app\nThe web service serves the Preview." }'
{ "data": { "qaGuide": "## Open the app\nThe web service serves the Preview." } }

The route takes an API key, a browser session, or the token of a Cron Run or Sensei. A chat session's sandbox token is refused, and so is a qaGuide in its body on PATCH /api/projects/{projectId}/settings and on the settings import. To clear the guide, send { "qaGuide": null } to PATCH /api/projects/{projectId}/settings.

A saved guide reaches the chat sessions created after the save, with no redeploy. Sessions already running keep the guide they started with. The deploy agent never changes it.

GET /api/projects/{projectId}/settings/qa-guide returns the saved guide in the same shape, with qaGuide set to null when there is none. A chat session's sandbox token can read it too, so a running session can see a guide saved after it started.

In the CLI, run kosuke projects qa-guide set <projectId> --content "<markdown>", or --body-file with a JSON body. To read the guide, run kosuke projects qa-guide get <projectId>.

Let sessions write device folders

A Chat Session's shell and file calls on a device write only in its scratch folder ~/KosukeQA/<session8>/, the Xcode and Simulator folders, and the temporary folders. See A Chat Session's writes stay in its folders. deviceWritePaths adds folders, such as the data folders of an app the session builds and runs.

  • Each path starts with ~/ and names a folder inside a folder of your home folder, such as ~/Projects/scratch. Under ~/Library it goes one level deeper, such as ~/Library/Caches/com.example.app.
  • <session8> in a path stands for the first 8 characters of the session id, so each session gets its own folder.
  • A path cannot be under ~/KosukeQA or ~/.Trash, be a whole ~/Library folder, be under ~/Library folders that hold your accounts, mail, keys or messages, or belong to the Sensei desktop app. It cannot hold *, ?, quotes, $ or other characters a shell reads, nor an empty, . or .. part.
  • Only your own API key or browser session can change it. Any other caller, a session's sandbox, a Cron Run or Sensei, gets 403 FORBIDDEN. A kosuke.config.json import without it keeps the saved paths, and the deploy agent never changes them.
kosuke projects settings update <projectId> \
  --device-write-paths '["~/Library/Application Support/com.example.app.<session8>"]'

Services

A service is anything that runs in the sandbox, such as an app, a worker, a database or a Docker stack. Services start together. A service waits for the ones named in its after to accept connections.

FieldMeaningValues
nameThe service's name. Placeholders such as {{web.url}} use it.Lowercase letters, digits and inner hyphens, up to 63 characters. Required.
startCommandStarts the service. It must bind the first endpoint's port. For a docker-stack, this is the up command.1 to 500 characters. Required.
directoryWhere the service runs, relative to the repository root.Default .
kindprocess for a command Sensei supervises, such as an app, Postgres or Redis. docker-stack for a docker-compose stack, such as Supabase.Default process
stopCommandStops the service, such as docker compose down.Up to 500 characters. Optional.
endpointsThe ports the service listens on. The first one is the primary endpoint.Array of endpoints. Empty for a background worker.
afterThe services this one waits for.Array of service names
envFilesFor a docker-stack, the files Sensei writes the environment into, relative to directory.Array of paths. Empty means one .env.
sortOrderOrder in which the services are listed.Integer, default 0
Endpoint fieldMeaning
nameUnique within the service, in the same format as a service name.
port1 to 65535, and unique across the whole project. Port 61000 is reserved.
exposureinternal (the default) is reachable inside the sandbox only. public gets a URL. preview gets a URL and shows in the Preview.
healthRouteAn HTTP path, such as /api/health, that must answer before the service counts as ready. Set it on HTTP services only, and leave it out for databases.

Add a service:

curl -s https://app.kosuke.ai/api/projects/$PROJECT_ID/settings/services \
  -H "Authorization: Bearer $KOSUKE_API_KEY" \
  --json '{
    "name": "api",
    "directory": "apps/api",
    "startCommand": "PORT=4000 bun run start",
    "endpoints": [{ "name": "api", "port": 4000, "exposure": "public", "healthRoute": "/health" }],
    "after": ["postgres"],
    "sortOrder": 2
  }'

The route answers 201 with the new service and its id. A port another service already uses answers 400.

RouteWhat it does
GET /api/projects/{projectId}/settings/servicesLists the services by sortOrder.
PATCH /api/projects/{projectId}/settings/services/{serviceId}Changes the fields you send. Answers 200 with the service.
DELETE /api/projects/{projectId}/settings/services/{serviceId}Removes one service. Answers 204.

Adding or changing a single service checks ports only. The import below also checks that every after names a real service without a cycle, and that some endpoint has preview exposure.

In the CLI, run kosuke projects services list, add, update or remove. For endpoints, pass JSON, as in --endpoints '[{"name":"api","port":4000}]'.

Environment variables

Environment variables reach every service when the sandbox starts. Values are encrypted at rest. A session sandbox that is already running takes a variable added later once a member runs kosuke sessions env sync.

POST /api/projects/{projectId}/settings/env-vars takes an array. It adds each variable, or replaces the value of a key that already exists, and answers 201 with the count.

FieldMeaning
keyLetters, digits and underscores, not starting with a digit. Up to 255 characters.
valueThe value. It may hold the placeholders described below.
isSecrettrue masks the value in the web app. Default false.
curl -s https://app.kosuke.ai/api/projects/$PROJECT_ID/settings/env-vars \
  -H "Authorization: Bearer $KOSUKE_API_KEY" \
  --json '[
    { "key": "API_URL", "value": "{{api.url}}" },
    { "key": "STRIPE_SECRET_KEY", "value": "sk_test_...", "isSecret": true }
  ]'
{ "data": { "created": 2 } }

A value can point at a service instead of a fixed address. Sensei fills the placeholder in when the sandbox starts.

PlaceholderBecomes
{{name.url}}The public HTTPS URL of service name's primary endpoint. The endpoint needs public or preview exposure.
{{name.url_int}}The address inside the sandbox, such as http://localhost:3000.
{{name.endpoint.url}}, {{name.endpoint.url_int}}The same for the service's endpoint called endpoint.

A placeholder that matches no endpoint stops the sandbox from starting. So does url on an internal endpoint.

To remove variables, send their id values from the settings response:

curl -s -X DELETE https://app.kosuke.ai/api/projects/$PROJECT_ID/settings/env-vars \
  -H "Authorization: Bearer $KOSUKE_API_KEY" \
  --json '["5c1d9e7a-3b2f-4a6c-8d0e-7f9a1b2c3d4e"]'

The route answers 200 with { "data": { "deleted": 1 } }. Ids from other projects are ignored.

In the CLI, run kosuke projects env-vars create <projectId> --body-file vars.json and kosuke projects env-vars delete <projectId> --body '["<id>"]'.

Import a kosuke.config.json

Replace a project's whole configuration from one document. The web app's Export config button writes this format. The import replaces these in one step:

  • Every service and every environment variable.
  • hostSetupCommand, setupCommand, warmupCommand, agentWorkDir, sessionBaseBranch and prTargetBranch. A field the file leaves out becomes null.
  • rerunSetupOnCheckoutMove. Left out, it becomes false.

sandboxTier and qaGuide are the exceptions. Left out, they keep their current value. A qaGuide of null clears the guide.

FieldShape
servicesAn object keyed by service name. Each value holds the service fields above, without name and sortOrder. Key order becomes sortOrder.
environmentAn object keyed by variable name. Each value is { "value", "isSecret" }.
hostSetupCommand, setupCommand, warmupCommand, rerunSetupOnCheckoutMove, agentWorkDir, sessionBaseBranch, prTargetBranch, sandboxTier, qaGuideAs in Change settings.

A small kosuke.config.json:

{
  "setupCommand": "bun install --frozen-lockfile",
  "sandboxTier": "MEDIUM",
  "services": {
    "postgres": {
      "startCommand": "postgres -D /var/lib/postgresql/data -p 5432",
      "endpoints": [{ "name": "postgres", "port": 5432 }]
    },
    "web": {
      "startCommand": "bun run dev -- -p 3000",
      "endpoints": [{ "name": "web", "port": 3000, "exposure": "preview", "healthRoute": "/" }],
      "after": ["postgres"]
    }
  },
  "environment": {
    "DATABASE_URL": { "value": "postgres://app@localhost:5432/app", "isSecret": true },
    "APP_URL": { "value": "{{web.url}}" }
  }
}
curl -s https://app.kosuke.ai/api/projects/$PROJECT_ID/settings/import \
  -H "Authorization: Bearer $KOSUKE_API_KEY" \
  --json @kosuke.config.json
{ "data": { "message": "Config imported" } }

The whole file is refused with 400 VALIDATION_ERROR in these cases:

  • Two endpoints use the same port.
  • An after names the service itself or a service that does not exist.
  • The after lists form a cycle.
  • The file has services, and none of their endpoints has preview exposure.

details names the field at fault. Nothing is saved until the whole file passes.

In the CLI, run kosuke projects settings set <projectId> --body-file kosuke.config.json.

Errors

StatusCodeWhen
400BAD_REQUESTA port is already used by another service in the project.
400VALIDATION_ERRORThe body fails the schema or an import rule above. details names the field.
403FORBIDDENA body with deviceWritePaths came from anything but your own API key or browser session.
404NOT_FOUNDNo such project or service, or the project belongs to a workspace you are not in.

The full list is in Responses and errors.

Next steps