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.
| Method | Path | Description |
|---|---|---|
| GET | /api/projects/{projectId}/settings | Read the project settings: setup commands, services, environment and sandbox size. |
| PATCH | /api/projects/{projectId}/settings | Change setup commands, branches, working directory, sandbox tier or the Project QA Guide. |
| POST | /api/projects/{projectId}/settings/env-vars | Add environment variables. The body is a JSON array of { key, value, isSecret? }. |
| DELETE | /api/projects/{projectId}/settings/env-vars | Remove environment variables. The body is a JSON array of variable ids. |
| POST | /api/projects/{projectId}/settings/import | Replace 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-guide | Read 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-guide | Save 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/services | List the project services, in the order they start. Carries no environment variables. |
| POST | /api/projects/{projectId}/settings/services | Add 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" }
]
}
}
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.
| Field | Purpose | Values | In the web app |
|---|---|---|---|
hostSetupCommand | Installs runtimes and system packages. Runs on the first Deploy, and again after you change it. | Up to 2,000 characters, or null for none | Sandbox tab, Host setup script |
setupCommand | Runs at the repository root on every Deploy, before any service starts. | Up to 2,000 characters, or null | Services tab, App setup script |
warmupCommand | Runs once the services are healthy. | Up to 10,000 characters, or null | Advanced tab, Warmup Script |
rerunSetupOnCheckoutMove | Runs 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 |
agentWorkDir | The folder the agent works in, relative to the repository root. | Up to 255 characters, without ... null means the root. | Advanced tab, Agent Working Directory |
sessionBaseBranch | The branch new sessions start from. | A branch name up to 100 characters. null means the repository's default branch. | Advanced tab, Branch tasks from |
prTargetBranch | The 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 |
sandboxTier | CPU and memory of the project's sandboxes. | SMALL, MEDIUM, LARGE or XLARGE. Leave it out to keep the current tier. | Sandbox tab, Sandbox Tier |
qaGuide | The 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 none | Advanced tab, QA Guide |
sandboxTier | CPU | Memory |
|---|---|---|
SMALL | 2 | 4 GB |
MEDIUM | 4 | 8 GB |
LARGE | 8 | 16 GB |
XLARGE | 16 | 32 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.
| Field | Meaning |
|---|---|
content | The 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.
| Field | Meaning | Values |
|---|---|---|
name | The service's name. Placeholders such as {{web.url}} use it. | Lowercase letters, digits and inner hyphens, up to 63 characters. Required. |
startCommand | Starts the service. It must bind the first endpoint's port. For a docker-stack, this is the up command. | 1 to 500 characters. Required. |
directory | Where the service runs, relative to the repository root. | Default . |
kind | process for a command Sensei supervises, such as an app, Postgres or Redis. docker-stack for a docker-compose stack, such as Supabase. | Default process |
stopCommand | Stops the service, such as docker compose down. | Up to 500 characters. Optional. |
endpoints | The ports the service listens on. The first one is the primary endpoint. | Array of endpoints. Empty for a background worker. |
after | The services this one waits for. | Array of service names |
envFiles | For a docker-stack, the files Sensei writes the environment into, relative to directory. | Array of paths. Empty means one .env. |
sortOrder | Order in which the services are listed. | Integer, default 0 |
| Endpoint field | Meaning |
|---|---|
name | Unique within the service, in the same format as a service name. |
port | 1 to 65535, and unique across the whole project. Port 61000 is reserved. |
exposure | internal (the default) is reachable inside the sandbox only. public gets a URL. preview gets a URL and shows in the Preview. |
healthRoute | An 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.
| Route | What it does |
|---|---|
GET /api/projects/{projectId}/settings/services | Lists 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.
| Field | Meaning |
|---|---|
key | Letters, digits and underscores, not starting with a digit. Up to 255 characters. |
value | The value. It may hold the placeholders described below. |
isSecret | true 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.
| Placeholder | Becomes |
|---|---|
{{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,sessionBaseBranchandprTargetBranch. A field the file leaves out becomesnull.rerunSetupOnCheckoutMove. Left out, it becomesfalse.
sandboxTier and qaGuide are the exceptions. Left out, they keep their current value. A qaGuide of null clears the guide.
| Field | Shape |
|---|---|
services | An object keyed by service name. Each value holds the service fields above, without name and sortOrder. Key order becomes sortOrder. |
environment | An object keyed by variable name. Each value is { "value", "isSecret" }. |
hostSetupCommand, setupCommand, warmupCommand, rerunSetupOnCheckoutMove, agentWorkDir, sessionBaseBranch, prTargetBranch, sandboxTier, qaGuide | As 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
afternames the service itself or a service that does not exist. - The
afterlists form a cycle. - The file has services, and none of their endpoints has
previewexposure.
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
| Status | Code | When |
|---|---|---|
| 400 | BAD_REQUEST | A port is already used by another service in the project. |
| 400 | VALIDATION_ERROR | The body fails the schema or an import rule above. details names the field. |
| 404 | NOT_FOUND | No 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
- Apply your changes with a Deploy through Deploys.
- See the same settings in the web app on Project settings.