Managing Kapelle through its API¶
For an agent that manages a Kapelle deployment on a person's behalf, and for the person who wants to know what it was told. Kapelle runs teams of coding and other agents in sandboxes; the Console API defines and operates them: agents and their versions, team templates, teams, egress, documents, integrations, runs and spend. The onboarding prompt points here; the contract itself is GET /api/v1/openapi.yaml (or .json) on the deployment, and docs/design.md holds the concepts.
Authentication¶
Send Authorization: Bearer <token> on every request; the base URL is the one in the prompt (.../api/v1). The first call is GET /me: it answers who the token is. A request the role may not make is a 403: do not retry it, tell the person which role it needs. The roles:
- viewer: reads only; every write answers 403.
- operator: reads, and may create, change and archive teams, link them, set budgets, read runs, manage a team's documents and MCP servers, and test integrations. Agents, templates, environments, destinations, secrets, users and integration settings answer 403.
- admin: everything an operator may, and agents, team templates, environments, destinations, secrets, platform skills, users and integration settings.
Conventions¶
- JSON everywhere. Errors are
application/problem+json: readdetailanderrors[](each names the field). - Every write of an existing resource needs
If-Matchwith the ETag of a fresh GET of it: 428 without, 412 when stale (re-read and retry once). - An edit of an agent or a team template adds a new version and makes it current; the old version stays and can be made current again.
- Archiving is the delete for teams. Deleting an agent, template, environment or destination is refused with 409 while something uses it.
- Lists answer
{items, next_cursor}; passlimitandcursorto page. - Times are UTC, ISO 8601. Money is US dollars.
X-Kapelle-CSRFis for browsers with a session cookie, never for a token.
Read before you write¶
Call these first so you know what exists: GET /me, GET /agents, GET /teams, GET /team-templates, GET /environments, GET /destinations, GET /integrations, GET /runs?limit=20. Never guess a name; read it.
Safety rules¶
- Never print, log or repeat a secret value, including your own token. Secrets go in once with
PUT /secrets/{name}and are never read back. - Do not set an agent's or a team's egress to
allow_allwithout telling the person what it costs: any public host is reachable through the credential gateway; the deny CIDRs still apply; credentials are injected only on their own hosts and never reach the sandbox; CONNECT to any port is allowed. - Never archive a team with running or waiting runs without asking.
- Ask before raising a budget above what the person named, or acting in a way that spends more than a team's daily budget.
- Confirm destructive changes (archive, delete, disable a user, remove a secret or a destination) with the person first.
- A route that is not in the list below does not exist for you: say so instead of inventing one.
Routes¶
Every route a token can call, by area, relative to the API base URL: the method, the path, the role it needs and what it does. The contract has the request and response shapes. Not listed: log in and out, the account's own tokens and minting an onboarding token, which need a browser session.
agents¶
GET /agents(viewer): List agentsPOST /agents(admin): Create an agent with its first version (admin)DELETE /agents/{name}(admin): Delete an agent no template or team uses (admin)GET /agents/{name}(viewer): One agent with its current versionPATCH /agents/{name}(admin): Edit an agent's display fields (admin)GET /agents/{name}/avatar(viewer): The agent's avatar imagePUT /agents/{name}/avatar(admin): Set the agent's avatar image, at most 512 KiB (admin)GET /agents/{name}/documents(viewer): The agent's context documents, without their contentDELETE /agents/{name}/documents/{doc}(admin): Remove a context document of the agent (admin)GET /agents/{name}/documents/{doc}(viewer): One context document of the agent, with its contentPUT /agents/{name}/documents/{doc}(admin): Create (201) or replace (needs If-Match; 200) a context document of the agent: read-only text the worker puts under /workspace/context in the guest ...GET /agents/{name}/versions(viewer): An agent's versions, newest firstPOST /agents/{name}/versions(admin): Add an agent version and make it current.GET /agents/{name}/versions/{version}(viewer): One agent version
destinations¶
GET /destinations(viewer): List destinationsPOST /destinations(admin): Create a destination.DELETE /destinations/{name}(admin): Delete a destination no current agent version and no team role override names (not a built-in one) (admin)GET /destinations/{name}(viewer): One destinationPUT /destinations/{name}(admin): Replace a destination (not a built-in one).
environments¶
GET /checkpoints(viewer): Checkpoint ids in useGET /environments(viewer): List environmentsPOST /environments(admin): Create an environment; template versions and teams made afterwards copy it (admin)DELETE /environments/{name}(admin): Delete an environment no current agent version or template version names (admin)GET /environments/{name}(viewer): One environmentPUT /environments/{name}(admin): Replace an environment's settings.GET /images(viewer): Agent images from the build manifests, newest first
import¶
POST /import(admin): Import roles, team templates and credgw policies from the repository (dry run by default) (admin)
integrations¶
GET /integrations(viewer): The five integrations with their statusGET /integrations/mattermost/role-bots(viewer): The Mattermost role bots: one per role of an active team, against the bots the gateway holds, with the display name and picture each shows and what ...GET /integrations/{kind}(viewer): One integrationPUT /integrations/{kind}(admin): Replace an integration's record: enabled and its settings.GET /integrations/{kind}/deliveries(viewer): Recent webhook deliveriesPOST /integrations/{kind}/test(operator): Test an integration's connection: the gateway probes Mattermost, Slack, Linear and Jira with the credentials it runs with, the controller probes ...
mcp-servers¶
GET /mcp-servers(viewer): List MCP servers: the platform ones first (scope platform, no team), then each team'sPOST /mcp-servers(operator): Add an MCP server to a team.GET /mcp-servers/{id}(viewer): One MCP server; the id is platform:or / DELETE /mcp-servers/{team}/{name}(operator): Remove a team MCP server (operator)PUT /mcp-servers/{team}/{name}(operator): Replace a team MCP server; a proposed one becomes curated.POST /mcp-servers/{team}/{name}/accept(operator): Promote an MCP server an agent proposed to curated; idempotent (operator)
meta¶
GET /openapi.json(viewer): This document as JSON (any logged-in role; it lists routes only and holds no data)GET /openapi.yaml(viewer): This document as YAML, from the generator that writes console/api/openapi.yaml (any logged-in role)
onboarding¶
GET /onboarding(viewer): The two-sentence onboarding prompt and AGENTS.md snippet for the caller's role and this deployment, with no token in them: what a person gives their ...
runs¶
GET /runs(viewer): List runs, newest firstGET /runs/{id}(viewer): One runPOST /runs/{id}/cancel(operator): Cancel a run; the same command as /cancel in chat.GET /runs/{id}/posts(viewer): The conversation of one run in order (the gateway's console posts: tasks, replies, questions, answers, notices, completion).POST /runs/{id}/replies(operator): Answer a run's open question, or send a follow-up task in the same run.GET /runs/{id}/trace(viewer): The spans of one run, read from Jaeger by the run's trace id (empty when Jaeger is not configured, unreachable or has no such trace)GET /teams/{name}/runs(viewer): A team's runs, newest firstPOST /teams/{name}/tasks(operator): Give a team a task from the Console: a new run on the console surface, the lead role gets the text.
secrets¶
GET /secrets(viewer): Secret reference metadata (never a value)DELETE /secrets/{name}(admin): Remove a secret's value: a destination secret's reference is removed with it, an integration secret's stays, marked unset; a destination secret is ...GET /secrets/{name}(viewer): One secret reference (never a value)PUT /secrets/{name}(admin): Write a secret's value, once; it is never returned.
session¶
GET /me(viewer): Who the credentials are: the user (id, name, role) and how they were presented,sessionortoken.GET /session(viewer): The logged-in user and the CSRF token
skills¶
GET /skills(viewer): The platform skill catalogue, without bodiesDELETE /skills/{name}(admin): Remove a platform skill; a file under roles/skills/ comes back at the next import (admin)GET /skills/{name}(viewer): One platform skill with the text of SKILL.md and its filesPUT /skills/{name}(admin): Create (201) or replace (needs If-Match; 200) a platform skill; the catalogue revision of the skill becomes the highest plus one (admin)
team-templates¶
GET /team-templates(viewer): List team templatesPOST /team-templates(admin): Create a team template with its first version (admin)DELETE /team-templates/{name}(admin): Delete a team template no active team uses (admin)GET /team-templates/{name}(viewer): One team template with its current versionPATCH /team-templates/{name}(admin): Edit a team template's description (admin)GET /team-templates/{name}/versions(viewer): A template's versions, newest firstPOST /team-templates/{name}/versions(admin): Add a team template version and make it current (admin)GET /team-templates/{name}/versions/{version}(viewer): One template version
teams¶
GET /spend(viewer): Spend of all active teams per UTC day, with each team's series (cached 60 s)GET /teams(viewer): List teamsPOST /teams(operator): Create a team from a template; answers when the team is ready (operator)DELETE /teams/{name}(operator): Archive a team; answers when the archive is done (operator)GET /teams/{name}(viewer): One team with role settings and membersPATCH /teams/{name}(operator): Change a team's budget, rework rounds, sleep times, or a role's model, iteration cap, replicas, concurrent runs and confirmation policy.GET /teams/{name}/budget(viewer): A team's budget and its LiteLLM spend per UTC day (cached 60 s; 503 without LiteLLM, 502 when LiteLLM fails; 404 for an unknown team and 404 with ...GET /teams/{name}/documents(viewer): The team's context documents, without their contentDELETE /teams/{name}/documents/{doc}(operator): Remove a context document of the team (operator)GET /teams/{name}/documents/{doc}(viewer): One context document of the team, with its contentPUT /teams/{name}/documents/{doc}(operator): Create (201) or replace (needs If-Match; 200) a context document of the team: read-only text the worker puts under /workspace/context in the guest ...GET /teams/{name}/links(viewer): A team's surface linksPUT /teams/{name}/links(operator): Link a team to a repository, chat channel, Linear team or Jira project; idempotent.DELETE /teams/{name}/links/{surface}/{external_id}(operator): Remove a team's link to a surface;external_idmay contain slashes (owner/repo).DELETE /teams/{name}/roles/{role}/egress(admin): Remove the team's egress override for a role; the agent definition's applies (admin)GET /teams/{name}/roles/{role}/egress(viewer): A team role's egress override (null when the agent definition's own applies)PUT /teams/{name}/roles/{role}/egress(admin): Set the team's egress for one role, replacing the agent definition's.GET /teams/{name}/skills(viewer): The team's own stored skills (curated or proposed), read-only, without bodiesPOST /teams/{name}/upgrade(operator): Move a team to another version of its template; role fields it set by hand are kept (operator)
tools¶
GET /models(viewer): The LiteLLM model aliases and the model each points atGET /tools(viewer): The built-in tool catalogue
users¶
GET /users(admin): List Console users (admin)POST /users(admin): Create a Console account without a password; the answer carries a one-time setup token to hand to the person (there is no mail) (admin)GET /users/{id}(admin): One user (a user may read their own) (admin)PATCH /users/{id}(admin): Change a user's name, role or disabled flag; never leaves the Console without an enabled admin (409); disabling revokes the user's sessions (admin)POST /users/{id}/setup-token(admin): Issue a fresh one-time setup token for a user; earlier unused ones stop working (admin)
Playbooks¶
Twelve things a person asks an agent to do, as the routes to call in order (paths relative to /api/v1; every write of an existing resource needs If-Match with the ETag of a fresh GET). Generated from console/playbooks.py; the agents page carries the same text.
1. Create a team from a template with a repository and a chat room¶
Goal: A new team of agents that works on the person's repository and talks in a chat room.
GET /team-templates: pick a template; readGET /team-templates/{name}for its rolesPOST /teams: operator.{name, template: {name}, repo_url, home: {surface: "mattermost"|"slack"|"none"}, budget_usd_per_day}; answers when the team is ready (up to a minute); a template that needs a repository is a 422 withoutrepo_urlPUT /teams/{name}/links: operator.{surface: "github", external_id: "owner/repo"}links the repository (422 when the GitHub App is not installed on it); the same route links a Linear team or a Jira projectGET /teams/{name}: readstate,home(the room) androle_settings
Check: the team's links list the repository as verified and the answer's home names the room: report the team's name and the room to the person.
2. Change an agent's system prompt and see which teams pick it up¶
Goal: A new prompt for one agent; the old version stays and can be made current again.
GET /agents/{name}: admin to write; notecurrent_versionand the ETagPOST /agents/{name}/versions: admin, withIf-Matchof that ETag.{from_version: <current>, system_prompt}changes only what you send; the new version becomes currentGET /teams: thenGET /teams/{name}for each:role_settings[].agentnames the agent
Check: every team whose roles run that agent starts its next run on the new version (a run that is already going keeps the old one). To undo, post a version from_version the old one.
3. Set a daily budget and read today's spend¶
Goal: A team stops overspending and the person sees what it costs.
GET /teams/{name}: note the ETag and the currentbudget_usd_per_dayPATCH /teams/{name}: operator, withIf-Match:{budget_usd_per_day: 5}GET /teams/{name}/budget:spent_today_usd,state(normal|warning|exceeded),seriesper UTC dayGET /spend: all active teams per day, with each team's series
Check: state is normal and today's spend is below the limit; ask the person before raising a budget above what they named.
4. Read what a team is doing now and answer a waiting question¶
Goal: Find out whether a team is working, stuck or failed, and why.
GET /teams/{name}/runs: newest first;stateis running|waiting|succeeded|failed|cancelledGET /runs/{id}:waiting_for({role, kind: question|confirmation, since}) for a waiting run,failure_reasonfor a failed one,chat_thread_urlandjaeger_urlGET /runs/{id}/trace: the spans: which role did what, the cost of each
Check: no route answers a waiting run: tell the person who is waiting and for what, and give them chat_thread_url, where they answer in the room.
5. Give an agent a document or a skill¶
Goal: Context the agent reads at its next run start, or a skill it can use.
PUT /teams/{name}/documents/{doc}: operator.{media_type: "text/markdown", description, content}; 201 on create, 200 withIf-Matchon replace. Text only, at most 256 KiB each, 20 documents and 384 KiB per owner; it appears under /workspace/contextPUT /agents/{name}/documents/{doc}: admin: the same for every team that runs the agentPUT /skills/{name}: admin.{body: <SKILL.md text>, resources: {path: text}}, text only; a platform skill any role can nameGET /teams/{name}/skills: a team's own skills are proposed by its agents and curated in chat; read-only
Check: read the document or skill back with its GET route; an agent sees it from its next run.
6. Restrict or open an agent's egress¶
Goal: Choose which hosts an agent may reach through the credential gateway.
GET /destinations: the named places (host, methods, paths,inject_secret)POST /agents/{name}/versions: admin.{from_version, egress: {mode: "allowlist"|"allow_all", destinations: [names], upstream_deny_cidrs}}for every team on that agentPUT /teams/{name}/roles/{role}/egress: admin, withIf-Matchof its GET: the same body for one team's role;DELETEreturns to the agent's own
Check: tell the person what allow_all costs before you set it: any public host is reachable through the gateway; the deny CIDRs still apply; named destinations' credentials are injected only on their own hosts and no secret is ever in the guest; CONNECT to any port is allowed. llm, mcp and otel are always included.
7. Connect a destination that needs a secret and use it from an MCP server¶
Goal: An agent calls a tool of a server that needs a credential, without the guest ever holding it.
PUT /secrets/{name}: admin.{value}once, never returned; never print itPOST /destinations: admin.{name, host, methods, paths, header_allowlist, inject_secret: "secret:<name>"}; a loopback or private host needsdev_only: truePOST /mcp-servers: operator.{team, name, url, destination, auth: "bearer", roles}; the answer'swarningslists roles whose egress lacks the destinationPUT /teams/{name}/roles/{role}/egress: admin, ifwarningsnamed a role: add the destination to its egressPOST /agents/{name}/versions: admin. A PLATFORM server is attached with{from_version, mcp_servers: [names]}
Check: GET /mcp-servers/{id} shows secret.status set and no warnings; a team that needs its own credential gets its own destination.
8. Connect an integration and link a team to a repository or a tracker¶
Goal: Mattermost, Slack, GitHub, Linear or Jira working, and a team linked to it.
PUT /secrets/{name}: admin. One call per secret of the kind, namedintegrations/<kind>/<setting>PUT /integrations/{kind}: admin, withIf-Match:{enabled, settings}; enabling needs the required settings and secrets (the 422 names what is missing)POST /integrations/{kind}/test: operator.stateok|error|unknown with a one-linedetailGET /integrations/{kind}:status.applied:pendingmeans the gateway reads the change when it restartsPUT /teams/{name}/links: operator: link the team to the repository, Linear team or Jira project
Check: the test says ok and status.applied is current: an agent cannot restart the gateway, so when it is pending tell the person that it must be restarted.
9. Invite a colleague and change their role¶
Goal: A new person can log in to the Console with the role they need.
POST /users: admin.{name, email, role}; the answer holdssetup_tokenonce. Give the person the link<console URL>/#setup?token=<setup_token>POST /users/{id}/setup-token: admin: a fresh link when the first expiredPATCH /users/{id}: admin, withIf-Match:{role}; never leaves the Console without an admin
Check: GET /users/{id} shows the role; confirm with the person before disabling anyone.
10. Archive a team and clean up¶
Goal: A team that is no longer needed stops and releases what it holds.
GET /teams/{name}/runs: nothing running or waiting, or ask the person firstDELETE /teams/{name}: operator, withIf-Match; answers when the archive is doneGET /teams/{name}:stateis archived
Check: archiving cancels open work, destroys the team's sandboxes, revokes its LiteLLM keys and removes its roles, links and members, which frees the name; the runs, the spend history and the team's memory stay.
11. Give a team a task and read the answer¶
Goal: A task for a team from the Console, with no chat surface in between.
POST /teams/{name}/tasks: operator.{text}; answers 202 with{run_id, work_item_id, state}and aLocation; 409 when the team is archived or lives on a chat surface, 429 when its budget is spent, 503 when no gateway answersGET /runs/{id}: poll with therun_id:stateis running, waiting, succeeded or failed; the lead's task appears undertasksa moment after the 202GET /runs/{id}/posts: the conversation in order; pass the lastseqseen ascursorto read only what is new. Theanswerandcompletedposts hold what the team produced;deliveryon the run names a branch and pull request
Check: the run is succeeded and the last completed or answer post is what you report; on failed, report failure_reason. To stop early, POST /runs/{id}/cancel.
12. Answer a question a run is waiting on¶
Goal: A run that paused for a person goes on with the person's answer.
GET /runs:state=waiting: each such run hasopen_question({seq, role, text}); the same field is onGET /runs/{id}POST /runs/{id}/replies: operator.{text, in_reply_to: <open_question.seq>}; withoutin_reply_tothe text is a follow-up task in the same run. 202 on success; 409 for a run that lives on a chat surface (answer it there)GET /runs/{id}/posts: read on from the question'sseq(cursor) to see the run continue
Check: the run's open_question is null and its state is running again; ask the person before answering anything that commits them (a confirmation, a merge).
When something fails¶
- Read the problem's
detailanderrors[]and fix the named field. - A run's
failure_reasonandwaiting_for(GET /runs/{id}) say why it stopped; itsjaeger_urlopens the trace. POST /integrations/{kind}/testsays whether an integration's credentials work;GET /integrations/{kind}/deliverieslists its recent webhook deliveries;status.appliedsays whether the gateway runs the stored settings.- There is no audit log route yet; do not look for one.
- A 5xx or a 502 is Kapelle or a service behind it: say so, retry once, then report it instead of trying other routes.
A Kapelle MCP server¶
A Kapelle MCP server (kapelle-mcp) that exposes these routes as typed tools is planned. Until it exists, use the HTTP API as described here.