Skip to main content

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}/settingsRead the project settings: setup commands, services, environment and sandbox size.
PATCH/api/projects/{projectId}/settingsChange setup commands, branches, working directory, sandbox tier or the Project QA Guide.
POST/api/projects/{projectId}/settings/env-varsAdd environment variables. The body is a JSON array of { key, value, isSecret? }.
DELETE/api/projects/{projectId}/settings/env-varsRemove environment variables. The body is a JSON array of variable ids.
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.
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/servicesAdd a service. A port already used by another service is rejected by the handler.
PATCH/api/projects/{projectId}/settings/services/{serviceId}Change one service. Every field is optional; only the ones sent change.
DELETE/api/projects/{projectId}/settings/services/{serviceId}Remove one service.

Any member of the workspace can read and change these settings. 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 [email protected] with the password in QA_PASSWORD.",
"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" }
]
}
}
warning

For a member's browser session or API key, the response holds every environment variable in plain text, secrets included. Every other credential receives [masked] for each secret value: a chat session's sandbox token, a Cron Run's token, Sensei's token and a Connector Token. The web app masks secrets. Keep the response out of logs.

A [masked] value sent back is never saved over a secret. Adding environment variables refuses it with 400. The settings import keeps the saved value, and refuses the file with 400 when the project has none.

A chat session's sandbox token and a Cron Run's token reach a project's settings, and its live app, only in their own project: the session's project, or a project the Cron Run covers. Another project of the same workspace answers 403.

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
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>.

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.
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​