API · Projects, Teams & Agents¶
CRUD and lifecycle for projects, roles (agents), prompts, templates, team configuration, versioning, and agent association/deployment. All endpoints are unauthenticated (see API conventions).
ProjectsController¶
api/projects — project CRUD, team config, import/export, and Quick Create.
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| GET | /api/projects |
— | 200 { projects } |
yes |
| GET | /api/projects/{id} |
route id:int |
Project / 404 |
yes |
| POST | /api/projects |
body Project |
201 / 400 / 500 |
yes |
| PUT | /api/projects/{id} |
route id:int; body Project |
200 / 404 / 500 |
yes |
| DELETE | /api/projects/{id} |
route id:int |
200 / 404 |
yes |
| GET | /api/projects/{id}/team |
route id:int |
TeamConfig / 404 |
yes |
| POST | /api/projects/{id}/team |
route id:int; body TeamConfig |
200 / 400 / 404 / 500 |
yes |
| PATCH | /api/projects/{id}/team |
route id:int; body TeamDesignPatch |
200 / 400 / 404 |
yes |
| GET | /api/projects/{id}/export |
route id:int |
ProjectExportDto |
yes |
| POST | /api/projects/import |
body ProjectExportDto |
201 / 400 / 500 |
yes |
| POST | /api/projects/quick-create |
body CreateProjectRequest |
ProjectCreationResult / 400 |
yes |
| GET | /api/projects/default-path |
— | 200 { path } |
yes |
| POST | /api/projects/{id}/ensure-folder |
route id:int |
200 / 500 |
yes |
PATCH and POST on /team are not interchangeable
POST replaces the whole TeamConfig. Whatever the caller does not send is gone — including
fields the caller never knew about, which is how a save from one screen can silently drop
settings owned by another.
PATCH takes a TeamDesignPatch and merges, preserving fields the patch does not mention.
Prefer it for edits.
One thing PATCH does not soften: within the patch, nodes is a whole-list replacement, not
a per-node merge. Sending a patch containing one node leaves the team with one agent. The list
is the edit.
Every write to /team records a version, so a mistaken save is recoverable through
api/projects/{id}/versions.
Each node in a TeamDesignPatch may carry role_id, the role-library entry it was made from (the
Designer's from library sets it). It is carried rather than edited: null leaves the stored
value alone and an empty string clears it. Keeping it matters after the save, because the
resolver reads the role's profiles through it; a reporting role saved without it would be granted
edit_file by the content/code/test keyword rule.
DeliverablesController¶
What the run produced, as opposed to what it did. The workspace outlives the container whose ports died with it, so these are the durable view of a finished project.
| Method | Path | Inputs | Returns | Auth |
|---|---|---|---|---|
| GET | /api/projects/{projectId}/deliverables |
route projectId:int |
DeliverableTreeDto — every file in the workspace with its size, plus the detected entryPoint |
no |
| GET | /api/projects/{projectId}/deliverables/raw |
route projectId:int; query path |
the file itself, 400 when the path escapes the workspace, 404 when it does not exist or any part of it is a link |
no |
| GET | /api/projects/{projectId}/preview/{**path} |
route projectId:int, path |
the file rendered under a single prefix so relative assets resolve; an empty path serves the detected entry point; 404 when any part of the path is a link |
no |
Both file routes resolve inside the workspace and refuse anything that escapes it, and both send
Content-Security-Policy: sandbox — agent-written HTML must never execute on this origin. raw
additionally sends X-Content-Type-Options: nosniff.
The difference between them is the whole point: raw hands back one file, while preview serves
the workspace as a site. A stylesheet the page links but nobody wrote is a 404 in the browser
console under preview, where under raw it would be a silently unstyled page.
RolesController¶
api/roles — the roles table: one row per project team member, copied when the project is
created. Runs read the project's team file, not these rows. The role library is
CompositionController. Paginated list and search.
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| GET | /api/roles |
query page:int=1, per_page:int=50, role_type:string?, search:string? |
200 { roles, pagination } |
yes |
| GET | /api/roles/{id} |
route id:int |
200 / 404 |
yes |
| POST | /api/roles |
body Role |
201 / 400 / 409 / 500 |
yes |
| PUT | /api/roles/{id} |
route id:int; body Role |
200 / 404 / 409 / 500 |
yes |
| DELETE | /api/roles/{id} |
route id:int |
200 / 404 |
yes |
| POST | /api/roles/bulk |
body BulkRolesRequest |
201 / 400 |
yes |
| GET | /api/roles/search |
query q?, type?, tech?, specialization?, limit:int=20, page:int=1 |
200 { roles } |
yes |
| GET | /api/roles/{id}/export |
route id:int |
RoleExportDto / 404 |
yes |
| POST | /api/roles/import |
body RoleExportDto |
201 / 400 |
yes |
BulkRolesRequest is declared inline. See Schema · Role for the entity.
CompositionController¶
api/composition — the role library (Templates/composition/roles.json, plus the base roles in roles.library.json beside it), read-only: the roles
templates name by role_id and Quick Create picks from. The Agent Library page and the Designer's
from library read it. The library ships inside the Engine, so a role changes with an update,
not through this API.
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| GET | /api/composition/roles |
— | List<LibraryRoleDto> |
yes |
| GET | /api/composition/roles/{id}/node |
route id:string |
TeamNode / 404 |
no |
LibraryRoleDto gives each role in file order: id, name, kind (supervisor or worker),
family, purpose, description, generatorPickable (the generator is offered it today: catalogued,
and its prompt names no workspace path), tools, profiles, modelSlot and the model that
slot names today (empty: chosen at run time; null: no slot), requiresEvidence, home (the
template it was written for, or null) and usedBy (every loaded template with a node naming it,
core templates first; each { id, name, isCore }).
The node route returns the team node the role becomes, built by the same role expansion a
template node with that role_id gets: name, role, description, system_prompt, tools,
mcp_servers, the model from its slot, and role_id itself. It has no id (the team assigns one
on save) and no topology wiring (delegate and reply come from the team it joins).
PromptsController¶
api/prompts — reusable prompt library.
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| GET | /api/prompts |
— | 200 { prompts } |
yes |
| GET | /api/prompts/{id} |
route id:int |
200 / 404 |
yes |
| POST | /api/prompts |
body Prompt |
201 |
yes |
| PUT | /api/prompts/{id} |
route id:int; body Prompt |
200 / 404 |
yes |
| DELETE | /api/prompts/{id} |
route id:int |
200 / 404 |
yes |
TemplateController¶
api/template — the Workflow Template catalog and template-to-project creation. See §08 — Templates.
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| GET | /api/template |
— | 200 (template summaries) |
yes |
| GET | /api/template/list |
— | alias of GET /api/template |
yes |
| POST | /api/template/reload |
— | 200 { success, count, templates } |
yes |
| GET | /api/template/{id} |
route id:string |
full template / 404 |
yes |
| POST | /api/template/create |
body CreateFromTemplateRequest |
ProjectCreationResult / 400 (including an override that names an unknown slot) / 404 / 500 |
yes |
| POST | /api/template/{id}/resolved |
route id:string; optional body TeamRunOverrides |
ResolvedTeamPreview — the team the template will run as, plus grants, removals, overrides, warnings (including a node placed on a slot the health check has marked offline, or on a slot whose model list does not include its model) / 400 { error } when an override names an unknown slot / 404 |
yes |
| POST | /api/template/resolve |
body TeamConfig |
ResolvedTeamPreview for a team config in hand, with the same placement warnings / 400 |
no |
CreateFromTemplateRequest, RunFromTemplateRequest and CreateProjectRequest accept an optional overrides object (TeamRunOverrides: modelId, nodeModels keyed by node id, preferredInstance, nodeInstances keyed by node id, subagentInstance, verifierInstance, temperature, maxTokens, contextWindow, reasoningEffort, stepRounds, toolRequests). The resolver applies it to the team before it is persisted and logs each field it set, so a benchmark pins a model without writing a template variant. modelId, preferredInstance, maxTokens, contextWindow and reasoningEffort also land on the team's subagent_defaults, so a dynamic-parallel run's spawned children follow the override rather than the operator default. The three instance fields place a run across slots, each a pin: nodeInstances sets one node's preferred_instance (winning over preferredInstance; an unknown node id is a warning), subagentInstance sets subagent_defaults.preferred_instance, and verifierInstance sets the team's verifier_instance, which places the step verifier whether verification comes from the team's block or the global switch, and the trajectory judge unless the team pins its own. POST /api/template/create, POST /api/template/{id}/resolved and POST /api/orchestrator/run-from-template answer 400 when any slot the overrides name is not a configured slot, listing the known ones. The two resolved routes are the preview of that path: nothing is created.
TeamController¶
api/projects/{projectId}/team — persistent-agent status (backed by the persistent-agent service).
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| GET | /api/projects/{projectId}/team/status |
route projectId:int |
200 { projectId, agents[] } |
no |
TeamGeneratorController¶
api/teamgenerator — LLM synthesis of a team or single agent from a natural-language prompt (the Quick Create backend).
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| POST | /api/teamgenerator/generate |
body string (prompt); query seed (int, optional), needs (repeatable or comma-separated: web, audio, images, video, 3d; optional), imageStyle (string, optional, read only with images), roleLibrary (bool, optional: offer the generator the base-role library; absent means yes) |
TeamConfig / 400 / 422 / 500 |
yes |
| POST | /api/teamgenerator/generate-agent |
body string (prompt) |
TeamNode / 400 / 500 |
yes |
422 means no design attempt could be delivered: after the generator's own repair, a node still owed something no tool it holds can make, or a stage that produces nothing could not be dropped. The body is the reasons as plain text, and Quick Create shows them in its error line.
needs is what the project needs, as ticked on the New project form. Each known value is stated in the brief the generator reads (unknown values are ignored), with imageStyle (up to 80 characters) as how every picture is drawn. A design that leaves a ticked need with no role that can make it is handed back once, through the same coverage hand-back as a requested path no role owes: web needs a role owing an .html page, audio a role holding tts_render that owes a recording, images a role holding comfyui_generate that owes pictures, and video a role owing an .mp4. A need still uncovered after the hand-back is not a failure; it is returned in the team's generation_notes. Ticking 3d asks for written briefs instead of model files, since no workspace tool makes 3D formats.
generate-agent returns one node for extending a team. When the generator leaves its model_id empty, it is filled from the same model policy the team generator uses — the planning slot for a supervisor or an agent holding delegate, the code slot for one holding run_shell or run_tests, the content slot otherwise — and stays empty when that slot is empty, which means the model is chosen at run time. A slot that names an instance and a completion cap gives the node its preferred_instance and max_tokens too, unless the node already sets them.
seed is for scripted callers that need a reproducible design: with it every design attempt samples at temperature 0 with that seed, so a rerun of the same prompt designs the same team. Without it (Quick Create sends none) the generator samples at temperature 0.3.
Bare-string bodies
Both endpoints bind a raw JSON string from the body — the request body must be a quoted JSON string ("build me a research team"), not an object.
TeamVersionController¶
api/projects/{projectId}/versions — version history for a project's team config (save, compare, restore, prune).
| Verb | Route | Request | Response | Async |
|---|---|---|---|---|
| GET | /api/projects/{projectId}/versions |
route projectId:int; query limit:int=50 |
List<TeamVersionSummary> |
yes |
| GET | /api/projects/{projectId}/versions/{versionId} |
route ints | TeamConfigVersion / 404 |
yes |
| GET | /api/projects/{projectId}/versions/{versionId}/config |
route ints | TeamConfig / 404 |
yes |
| GET | /api/projects/{projectId}/versions/compare |
route projectId:int; query fromVersionId:int, toVersionId:int |
VersionDiff / 404 |
yes |
| POST | /api/projects/{projectId}/versions/{versionId}/restore |
route ints | TeamConfig / 400 / 404 |
yes |
| POST | /api/projects/{projectId}/versions |
route projectId:int; body SaveVersionRequest |
TeamConfigVersion |
yes |
| DELETE | /api/projects/{projectId}/versions/prune |
route projectId:int; query keepCount:int=50 |
200 { deletedCount } |
yes |
SaveVersionRequest is declared inline.
Where to go next¶
- Schema & Data Model — the
Project,Role, andTeamConfigshapes these endpoints exchange. - Orchestration & Runs — running the team you just created.
- §08 — Templates — authoring the templates
TemplateControllerserves.