Project Provisioning API (Control Plane)
Everything else in the SDK reference describes the data plane: the realtime WebSocket endpoint that clients connect to. This page describes the control plane — a separate, out-of-band HTTP REST API through which developers provision Projects ahead of time, declaring the durable configuration (matchmaking policies, credential bindings, lockdown) that the data plane then enforces.
For the concept, see Projects. For a task-oriented walkthrough, see Matchmaking as a Policy.
| Plane | Transport | Audience | Lifetime |
|---|---|---|---|
| Data plane | WebSocket (ws://…/starfish) + optional WebRTC | Clients | Ephemeral — sessions/pools die when empty |
| Control plane | HTTP REST (/admin) | Developers / operators | Durable — Project records persist |
The control plane is exposed only when the server is started with a management key (below). A pure zero-config deployment runs without it, in which case every referenced Project is implicit.
Endpoints
All paths are under the base path /admin and use JSON request/response bodies.
| Method | Path | Purpose |
|---|---|---|
POST | /admin/projects | Create a Project (body = Project config). 409 if the id already exists. |
GET | /admin/projects | List Projects. |
GET | /admin/projects/:id | Read one Project. 404 if not found. |
PATCH | /admin/projects/:id | Update config (policies, keys, lockdown, displayName). 404 if not found. |
DELETE | /admin/projects/:id | Delete a Project. Live sessions/pools under it are unaffected until they drain. |
A Project's id is immutable after creation. "default" is reserved and MUST NOT be created — a POST that names it is rejected.
Authentication
The control plane is guarded by a management (admin) key, presented as an HTTP header:
Authorization: Bearer <mgmt-key>A request with a missing or invalid management key is rejected with 401. The management key is distinct from any client credential and MUST never be handed to data-plane clients. The server owns all validation logic — how the key is issued, rotated, or validated is not prescribed.
Starting a server with a management key
The control plane mounts only when a management key is set.
# /admin is served on the data-plane port (:8080)
go run . -mgmt-key <key> # from servers/golang# /admin is served on the data-plane port (:8080)
node servers/typescript/dist/main.js --mgmt-key <key>
# or: STARFISH_MGMT_KEY=<key> node servers/typescript/dist/main.js# The Python server serves the control plane on a SEPARATE management port
python -m starfish_server --management-key <key> --management-port 8081Project config schema
The body of POST /admin/projects (and the shape returned by GET) is a Project config object.
{
"id": "studio-42",
"displayName": "Studio 42",
"lockdown": false,
"keys": [
{ "type": "shared-secret", "secret": "s3cr3t-…" }
],
"policies": [
{ "name": "duos", "mode": "auto", "groupSize": 2, "default": true, "filter": { "language": "@self" } }
]
}| Field | Type | Description |
|---|---|---|
id | string | The Project key clients declare at handshake as project. Opaque, ≤128 chars. Immutable after creation. "default" is reserved and cannot be created. |
displayName | string? | Human-readable label. Optional. |
lockdown | boolean | Default false. When true, clients in this Project may only use declared policies — ad-hoc create: true on sessions or pools is rejected with project.lockdown. When false, ad-hoc creation is still permitted (additive). |
keys | ProjectKey[] | Optional credential bindings that resolve to this Project. A credential that maps to a Project via keys is authoritative and overrides the client's declared project; a mismatch is rejected with project.forbidden. Normalized to [] when omitted. |
policies | MatchmakingPolicy[] | Zero or more named matchmaking policies. Normalized to [] when omitted. |
Create
POST /admin/projects
Authorization: Bearer <mgmt-key>
Content-Type: application/json{
"id": "studio-42",
"displayName": "Studio 42",
"policies": [
{ "name": "duos", "mode": "auto", "groupSize": 2, "default": true }
]
}A 201 Created echoes the stored config (with keys/policies/lockdown normalized). A POST whose id already exists returns 409; a POST naming the reserved "default" id returns 400.
curl -X POST http://localhost:8080/admin/projects \
-H "Authorization: Bearer $STARFISH_MGMT_KEY" \
-H "Content-Type: application/json" \
-d '{"id":"studio-42","displayName":"Studio 42","policies":[{"name":"duos","mode":"auto","groupSize":2,"default":true}]}'The repo ships a helper that provisions the example Project used by the pool-matchmaking examples: scripts/provision-example-project.sh.
Status codes
| Status | Meaning |
|---|---|
200 OK | Read / list / update succeeded |
201 Created | Project created |
204 No Content | Project deleted |
400 Bad Request | Malformed JSON, invalid body, or the reserved "default" id |
401 Unauthorized | Missing or invalid management key |
404 Not Found | No Project with that id |
405 Method Not Allowed | Unsupported method on a valid path |
409 Conflict | A Project with that id already exists |
MatchmakingPolicy schema
A matchmaking policy is a pre-declared pool: it captures exactly the config a client used to pass on pool entry, declared server-side ahead of time. Clients reference it by name instead of supplying pool config.
{ "name": "duos", "mode": "auto", "groupSize": 2, "default": true, "filter": { "language": "@self" } }| Field | Type | Description |
|---|---|---|
name | string | Policy name, unique within the Project. What a client references to join. |
mode | string | Reuses the pool modes: auto | claim | mutual | propose | delegated. |
groupSize | number | Number of clients per match. |
default | boolean? | At most one policy per Project may set default: true. Used when a client enters without naming a policy. |
filter | object? | Optional server-side attribute filter template for auto mode. See Attribute Filtering. |
When a client enters by policy name, it references the policy only and cannot override mode, groupSize, or filter — that server-ownership is the whole point of declaring a policy. Joining by policy is covered in Matchmaking as a Policy; the relevant errors are pool.policy_not_found and pool.policy_required.
Persistence
Project records are durable and outlive any session: a server backing declared Projects persists Project config so that a Project provisioned today is still declared after a restart. The store is implementation-defined — in-memory is fine for local development; a durable, pluggable store is expected for production. This is the one place Starfish holds long-lived state; sessions and pools minted under a Project remain ephemeral (they die when empty). Deleting a Project removes its config but does not tear down live sessions/pools, which drain naturally.