Skip to main content

Provisioning a workspace via the API

The full path from nothing to a running playbook, in order, with no UI. Use this when you are scripting the setup of a new team, a new client, or a reproducible demo.

Every call carries Authorization: Bearer <token> — a personal access token, or a JWT from POST /api/v1/auth/login. JWTs expire; handle a 401 by logging in again.

1. Create the workspace

POST /api/v1/workspaces
{"name": "<workspace name>"}

Returns workspace_uuid.

2. Create the projects

One per repository or base image:

POST /api/v1/workspaces/{workspace_uuid}/projects
{"name": "<project name>"}

Returns project_uuid for each.

The upsert is PUT, not POST

POST on this path returns 405. The route is GET / PUT / DELETE.

PUT /api/v1/projects/{project_uuid}/git-link
{
"git_connection_uuid": "<an existing Git connection>",
"repo_url": "https://github.com/<owner>/<repo>",
"default_branch": "main",
"auto_clone_on_launch": true,
"commit_identity_ref": "user_email",
"github_repo_id": <numeric repository id>
}

Reusing an existing git_connection_uuid works only if its GitHub App installation already covers the new repositories. An account-wide installation covers them automatically; a selected-repositories installation does not, and the symptom is not an error here — it is a clone failure at first launch. Add the repositories on GitHub's side first.

Skip this step entirely for base-image projects.

4. Create the environment

POST /api/v1/workspaces/{workspace_uuid}/environments
{"name": "<name>", "topology": "docker_pods", "is_default": true}

Returns environment_uuid. Its controller_uuid starts null — a controller binds itself in step 6.

topology takes docker_pods (a Host with Docker) or k8s_byoc (a Kubernetes cluster you bring) — these are the wire values behind the two choices the UI offers. The install flow differs for Kubernetes; see BYOC Kubernetes.

5. Mint a workspace token

Use the workspace-token route
POST /api/v1/workspaces/{workspace_uuid}/tokens
{"name": "<controller name>"}

This returns the raw pst_… token once — only its hash is stored. There is no way to retrieve it later.

6. Start the controller

Run this where you want containers to run:

docker run -d --name ps-controller-<workspace> --restart unless-stopped \
-p 127.0.0.1:<PORT>:<PORT> \
-v /var/run/docker.sock:/var/run/docker.sock \
-e RUST_LOG=info \
-e CONTROLLER_WS_PORT=<PORT> \
-e ORCHESTRATOR_WS_URL=wss://<your-platformsmith-host>/hub/ws \
-e CONTROLLER_NAME=<controller name> \
-e CONTROLLER_TOKEN=<pst_…> \
-e ENVIRONMENT_UUID=<environment_uuid> \
<controller image>

Notes that save time:

  • Pick a unique host port per controller. CONTROLLER_WS_PORT is the single source of truth for both the bind and the runtime's connect-back.
  • The Docker socket mount is essential — driving Docker is the controller's whole job.
  • On connect the controller auto-binds to the environment. Verify with GET /api/v1/workspaces/{workspace_uuid}/environments and check that controller_uuid is no longer null.
  • Coding-agent credentials do not ride on the controller. They are resolved from the organization's integrations at instance launch. → Credentials

The UI's Attach controller action renders this same command for you with the values filled in — use it if you would rather not assemble it.

7. Create an agent definition and a playbook

POST /api/v1/workspaces/{workspace_uuid}/agent-definitions
{"name": …, "coding_agent_type": …, "default_file_policy": …}
The files route is scope-agnostic

Add files with POST /api/v1/agent-definitions/{definition_uuid}/files.

There is no /workspaces/{ws}/agent-definitions/{def}/files route — that path returns 404.

POST /api/v1/playbooks
{
"workspace_uuid": …,
"name": …,
"primary_project_uuid": …,
"agent_definition_uuid": …,
"default_runtime_kind": "sandbox"
}

8. Run it

POST /api/v1/playbook-runs
{"workspace_uuid": …, "playbook_uuid": …, "branch": "<work branch>", "base_branch": "main"}

The branch and base branch ride the run: every container it starts clones or creates the branch from the base and pushes to it.

Wait for the controller to be connected before firing a run

Firing a run while the environment's controller is still registering fails with a 502 and auto-cancels the run. Do not retry the same POST — create a fresh run.

Route gotchas, in one table

SymptomCauseUse instead
405 on POST …/git-linkThe upsert route is PUTPUT /api/v1/projects/{uuid}/git-link
404 on POST …/workspaces/{ws}/agent-definitions/{def}/filesThe files route is scope-agnosticPOST /api/v1/agent-definitions/{def}/files
502 on POST /api/v1/playbook-runs, run cancelledThe controller had not finished registeringWait for controller_uuid on the environment, then create a new run
Clone fails at first launch, everything else fineThe Git App installation does not cover that repositoryAdd the repository to the installation on GitHub