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.
3. Link each project to a repository
PUT, not POSTPOST 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
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_PORTis 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}/environmentsand check thatcontroller_uuidis 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": …}
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.
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
| Symptom | Cause | Use instead |
|---|---|---|
405 on POST …/git-link | The upsert route is PUT | PUT /api/v1/projects/{uuid}/git-link |
404 on POST …/workspaces/{ws}/agent-definitions/{def}/files | The files route is scope-agnostic | POST /api/v1/agent-definitions/{def}/files |
502 on POST /api/v1/playbook-runs, run cancelled | The controller had not finished registering | Wait for controller_uuid on the environment, then create a new run |
| Clone fails at first launch, everything else fine | The Git App installation does not cover that repository | Add the repository to the installation on GitHub |