Canopy
Operations of the Canopy API. Paths are relative to your deployment's API host.
GET /api/canopy-api/canopy/autonomy
Read the autonomy level in force for this user.
Answers with the level in force, what the user asked for, and the org ceiling that capped it — three different facts. A dial that shows only the clamped value looks broken to the user who set the other one.
Authentication: bearer token (Authorization: Bearer <token>)
| Status | Description | Body |
|---|---|---|
| 200 | application/json AutonomyDtoDataEnvelope |
PATCH /api/canopy-api/canopy/autonomy
Set this user’s autonomy level (clamped by the org ceiling).
The stored preference is kept as chosen; the ceiling is applied when the level is READ. So a user can never raise themselves above the org ceiling, and lowering the ceiling takes effect immediately without destroying anyone’s preference.
Authentication: bearer token (Authorization: Bearer <token>)
Request body: application/json SetAutonomyDto
| Status | Description | Body |
|---|---|---|
| 200 | application/json AutonomyDtoDataEnvelope |
GET /api/canopy-api/canopy/autonomy/org-ceiling
Read the org-wide autonomy ceiling.
Authentication: bearer token (Authorization: Bearer <token>)
| Status | Description | Body |
|---|---|---|
| 200 | application/json OrgAutonomyCeilingDtoDataEnvelope |
PATCH /api/canopy-api/canopy/autonomy/org-ceiling
Set or clear the org-wide autonomy ceiling (org admins).
A ceiling can only RESTRICT. It caps every user in the org at read time — including users who never set a preference — and it never grants authority: what a subject may DO is the PDP’s decision, not this dial’s.
Authentication: bearer token (Authorization: Bearer <token>)
Request body: application/json SetOrgAutonomyCeilingDto
| Status | Description | Body |
|---|---|---|
| 200 | application/json OrgAutonomyCeilingDtoDataEnvelope |
GET /api/canopy-api/canopy/consent/grants
Every capability this user has said “always” to.
A standing grant stops the autonomy dial asking about one capability, at or below the risk the user was shown. It is listed and revocable because a permanent consent nobody can see or undo is a trapdoor, not a preference.
Authentication: bearer token (Authorization: Bearer <token>)
| Status | Description | Body |
|---|---|---|
| 200 | application/json StandingConsentGrantDtoDataArrayEnvelope |
POST /api/canopy-api/canopy/consent/grants/revoke
Start asking again about a capability.
Scoped to the caller: a grant belongs to the person who gave it, so this can only ever withdraw your own. 404 when you have no grant for that capability — “revoked” and “there was nothing to revoke” must not look alike, or a user ends up believing they turned something off.
Authentication: bearer token (Authorization: Bearer <token>)
Request body: application/json RevokeStandingConsentDto
| Status | Description | Body |
|---|---|---|
| 204 |
GET /api/canopy-api/canopy/consent/pending
Every ask still waiting on this user.
The ask is durable, so it survives a reload, a pod restart and a device change. Answered asks drop out of this list immediately — which is what stops a notification going phantom after it was answered somewhere else.
Authentication: bearer token (Authorization: Bearer <token>)
| Status | Description | Body |
|---|---|---|
| 200 | application/json PendingConsentDtoDataArrayEnvelope |
POST /api/canopy-api/canopy/consent/{requestId}
Answer an ask, and continue the turn it parked.
Streams the SAME frame vocabulary as a turn, because it IS the same turn: the tool result is appended in place and the loop carries on. A denial comes back as a tool result rather than prose, so the model can adapt instead of retrying. Answering twice is safe — the second answer is reported as already-decided and resumes nothing.
Authentication: bearer token (Authorization: Bearer <token>)
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
requestId | path | string | yes |
Request body: application/json AnswerConsentDto
| Status | Description | Body |
|---|---|---|
| 201 |
POST /api/canopy-api/canopy/sessions
Start an org-scoped Canopy conversation.
Creates an org-layer session in agent-session-api. No projectId is required or accepted — project is switchable context inside the conversation.
Authentication: bearer token (Authorization: Bearer <token>)
Request body: application/json CreateCanopySessionDto
| Status | Description | Body |
|---|---|---|
| 201 | application/json CanopyThreadDtoDataEnvelope |
GET /api/canopy-api/canopy/sessions/current
Resume the caller's most recent Canopy conversation.
Returns the newest thread on the org-layer singleton session, or 404 when the caller has never started one. Surfaces MUST call this on mount rather than POST /sessions: creating is defined as STARTING A NEW CONVERSATION (agent-session-api mints a fresh thread on the singleton every call), so mounting through it silently abandoned the previous transcript and any turn still streaming into it.
Authentication: bearer token (Authorization: Bearer <token>)
| Status | Description | Body |
|---|---|---|
| 200 | application/json CanopyThreadDtoDataEnvelope |
POST /api/canopy-api/canopy/sessions/{sessionId}/threads
Open another thread on a Canopy conversation.
Tier A has no per-session thread cap: the cap of 4 exists because threads cost pod multiplexing, which a workspaceless org-layer session does not pay.
Authentication: bearer token (Authorization: Bearer <token>)
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
sessionId | path | string | yes |
Request body: application/json CreateCanopyThreadDto
| Status | Description | Body |
|---|---|---|
| 201 | application/json CanopyThreadDtoDataEnvelope |
GET /api/canopy-api/canopy/threads
Every Canopy conversation this caller owns, newest first.
Answers a question GET /sessions/current structurally cannot: it returns the NEWEST thread, so a surface built on it cannot learn that others exist. POST /sessions/:id/threads has always been able to open another — there is no Tier A cap — so conversations accumulate and only the most recent was reachable. Scoped to the CALLER, never the organisation: a Canopy conversation is the caller's own.
Authentication: bearer token (Authorization: Bearer <token>)
| Status | Description | Body |
|---|---|---|
| 200 | application/json CanopyThreadDtoDataEnvelope |
GET /api/canopy-api/canopy/threads/{threadId}
Resume ONE named Canopy conversation of this caller's.
The switcher counterpart to GET /sessions/current. 404 for a thread that is not there AND for one that belongs to somebody else — deliberately the same answer, so this cannot be used to discover that a conversation exists.
Authentication: bearer token (Authorization: Bearer <token>)
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
threadId | path | string | yes |
| Status | Description | Body |
|---|---|---|
| 200 | application/json CanopyThreadDtoDataEnvelope |
POST /api/canopy-api/canopy/threads/{threadId}/escalate
Escalate the thread to Tier B (a real agent session).
Provisions a normal project-scoped session through the existing machinery and relays its events into this same conversation. Requires a project context — a workspace cannot exist without one.
Authentication: bearer token (Authorization: Bearer <token>)
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
threadId | path | string | yes |
Request body: application/json EscalateDto
| Status | Description | Body |
|---|---|---|
| 201 | application/json EscalationDtoDataEnvelope |
DELETE /api/canopy-api/canopy/threads/{threadId}/escalate
Return the thread to Tier A. The conversation continues.
Authentication: bearer token (Authorization: Bearer <token>)
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
threadId | path | string | yes |
| Status | Description | Body |
|---|---|---|
| 204 |
PATCH /api/canopy-api/canopy/threads/{threadId}/project-context
Point the conversation at a project, or return it to org-wide.
Authentication: bearer token (Authorization: Bearer <token>)
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
threadId | path | string | yes |
Request body: application/json SetProjectContextDto
| Status | Description | Body |
|---|---|---|
| 200 | application/json CanopyThreadDtoDataEnvelope |
POST /api/canopy-api/canopy/threads/{threadId}/relay
Pump the Tier B deep session into this conversation.
Held open by the client. Relayed events are written into the Canopy thread ledger, so the client reads them from the same durable stream as everything else — one conversation. The relay cursor is persisted per escalation, so disconnecting and calling again resumes exactly where it stopped, without duplicating or dropping an event.
Authentication: bearer token (Authorization: Bearer <token>)
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
threadId | path | string | yes |
| Status | Description | Body |
|---|---|---|
| 204 |
POST /api/canopy-api/canopy/threads/{threadId}/turns
Run one Canopy turn, streaming the answer.
Tier A: the agent loop runs in-service — no worker pod, no PVC, no warm pool. Frames stream on this response for the lowest possible time-to-first-token; the same turn is written durably into the session ledger, so a client that disconnects resumes with GET /sessions/:id/events/stream?afterSequence=N.
Authentication: bearer token (Authorization: Bearer <token>)
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
threadId | path | string | yes |
Request body: application/json SendCanopyMessageDto
| Status | Description | Body |
|---|---|---|
| 201 |
POST /api/canopy-api/canopy/threads/{threadId}/vista/{callId}/ack
Report what a Vista panel did with a relayed vista_* call.
The agent relays vista_open/update/close/list over the session event ledger; the browser applies the call against its tab controller and posts the typed result here. Canopy appends it to the same ledger, where the replica running the turn is waiting, and returns it to the model verbatim. Every browser showing the conversation acks; the first ack wins and later ones are ignored.
Authentication: bearer token (Authorization: Bearer <token>)
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
callId | path | string | yes | |
threadId | path | string | yes |
Request body: application/json VistaAckDto
| Status | Description | Body |
|---|---|---|
| 204 |