> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vendo.run/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP routes: threads, approvals, MCP door, and OAuth

> Reference for every HTTP route Vendo mounts under the base path, including methods, body shapes, responses, and streaming for the wire.

The default base is `/api/vendo`. Every request passes through
`principal(req)`. Bodies use core types encoded as JSON and conversation turns
stream over SSE.

| Route                                              | Method                      | Body → Response                                                                                                                                                                                                                                                                                                                                                                                    |
| -------------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/threads`                                         | POST                        | `{ threadId?, message }` → AI SDK UI message stream (SSE), one conversational turn. The response carries `X-Vendo-Thread-Id: <thr_…>` — the effective requested or server-minted id — so hosts can persist the identity of a default thread without a follow-up round trip                                                                                                                         |
| `/threads` · `/threads/:id`                        | GET · GET/DELETE            | thread summaries · thread                                                                                                                                                                                                                                                                                                                                                                          |
| `/approvals`                                       | GET                         | pending `ApprovalRequest[]`                                                                                                                                                                                                                                                                                                                                                                        |
| `/approvals/decide`                                | POST                        | `{ ids, decision }` → `{}` (batch-capable)                                                                                                                                                                                                                                                                                                                                                         |
| `/grants` · `/grants/:id`                          | GET · DELETE                | grants · revoke                                                                                                                                                                                                                                                                                                                                                                                    |
| `/apps`                                            | GET · POST                  | list · `{ prompt }` → `AppDocument`                                                                                                                                                                                                                                                                                                                                                                |
| `/apps/:id`                                        | GET · DELETE                | app · delete                                                                                                                                                                                                                                                                                                                                                                                       |
| `/apps/:id/open`                                   | GET                         | `OpenSurface`                                                                                                                                                                                                                                                                                                                                                                                      |
| `/apps/:id/ship-diff`                              | GET                         | `ShipDiff` — the reviewable code diff against the last approved baseline, hash-pinned to the current version. Owner-scoped. See [In-client venue](/concepts/in-client-venue).                                                                                                                                                                                                                      |
| `/apps/:id/call`                                   | POST                        | `{ ref: "fn:<name>" \| "<tool>", args }` → `ToolOutcome` (tree actions + fn: calls)                                                                                                                                                                                                                                                                                                                |
| `/apps/:id/edit`                                   | POST                        | `{ instruction }` → `EditResult`                                                                                                                                                                                                                                                                                                                                                                   |
| `/apps/:id/history`                                | GET · POST                  | versions · `{ op: "undo" }`                                                                                                                                                                                                                                                                                                                                                                        |
| `/apps/:id/export`                                 | GET                         | `.vendoapp` bytes                                                                                                                                                                                                                                                                                                                                                                                  |
| `/apps/import`                                     | POST                        | bytes → `AppDocument` (fresh id minted)                                                                                                                                                                                                                                                                                                                                                            |
| `/apps/:id/fork`                                   | POST                        | → `AppDocument`                                                                                                                                                                                                                                                                                                                                                                                    |
| `/apps/:id/pin-drift`                              | GET                         | `PinDrift[]` — pins whose captured baseline no longer matches                                                                                                                                                                                                                                                                                                                                      |
| `/apps/:id/rebase-pin`                             | POST                        | `{ slot }` → `PinRebaseResult` — re-fork from the new baseline and replay recorded intents, all-or-nothing                                                                                                                                                                                                                                                                                         |
| `/automations`                                     | GET                         | list                                                                                                                                                                                                                                                                                                                                                                                               |
| `/automations/:id/enable` · `/disable`             | POST                        | `{ enabled, missing }` · `{}`                                                                                                                                                                                                                                                                                                                                                                      |
| `/automations/:id/dry-run`                         | POST                        | `RunPlan`                                                                                                                                                                                                                                                                                                                                                                                          |
| `/runs` · `/runs/:id`                              | GET                         | run records                                                                                                                                                                                                                                                                                                                                                                                        |
| `/runs/:id/stop`                                   | POST                        | `{}`                                                                                                                                                                                                                                                                                                                                                                                               |
| `/tick`                                            | POST                        | scheduler tick (serverless cron target; requires `Authorization: Bearer <secret>`)                                                                                                                                                                                                                                                                                                                 |
| `/webhooks/:source`                                | POST                        | trigger ingress (Composio, host, plain), verified as described below                                                                                                                                                                                                                                                                                                                               |
| `/activity`                                        | GET                         | `AuditEvent[]`, self-scoped with `guard.audit.query({ principal })`                                                                                                                                                                                                                                                                                                                                |
| `/connections`                                     | GET                         | connected accounts owned by the caller                                                                                                                                                                                                                                                                                                                                                             |
| `/connections/initiate`                            | POST                        | `{ toolkit, connector?, callbackUrl? }` → `{ id, connector, redirectUrl }`; refused for ephemeral and synthetic (`webhook:` / `vendo:`) subjects                                                                                                                                                                                                                                                   |
| `/connections/:id?connector=`                      | GET · DELETE                | status (poll while connecting) · disconnect through the broker                                                                                                                                                                                                                                                                                                                                     |
| `/orgs` · `/orgs/:id`                              | GET · POST · DELETE         | list · create · delete an organization (Cloud-gated)                                                                                                                                                                                                                                                                                                                                               |
| `/orgs/:id/members` · `/orgs/:id/members/:subject` | GET · POST · PATCH · DELETE | list members · invite · change role · remove                                                                                                                                                                                                                                                                                                                                                       |
| `/orgs/:id/apps`                                   | POST                        | transfer a user-owned app into the org                                                                                                                                                                                                                                                                                                                                                             |
| `/status`                                          | GET                         | `{ posture, version, blocks: {...} }`, includes `blocks.mcp: true` when the door is open, `blocks.orgs: true` when a Cloud key with the `orgs` capability is configured, `blocks.sandbox` reporting the execution venue (`"e2b"`, `"modal"`, `"custom"`, or `false` when none is wired), and `blocks.connections` reporting the connected-accounts broker posture (`"byo"`, `"cloud"`, or `false`) |
| `/sync/impact`                                     | POST                        | dev-only. `{ tools: string[] }` → per-tool reference counts across enabled apps, enabled automations, and active standing grants. Accepts up to 200 tool names per call and returns `404` in production                                                                                                                                                                                            |

## Development routes

These routes are only mounted in development compositions. Production
deployments return `404` for each of them. They require a host-resolved
principal — anonymous callers receive `401`.

| Route                    | Method | Body → Response                                                                                                                                                                                                                                                                         |
| ------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/dev/inclient-approval` | POST   | `{ appId }` → `{}`. Pins the app's *current* `versionHash` as an in-client approval for the resolved principal. The Cloud console mints approvals in production; this route is the documented stand-in for local demos. See [In-client venue and approvals](/concepts/in-client-venue). |

## MCP door

With `createVendo({ mcp: true, oauth })`, the door serves its transport under
the wire plus four origin-root discovery documents through the same handler.
Bodies follow the MCP transport spec, not the wire's JSON envelope.

| Path                                                                 | Method              | Purpose                                                                                                   |
| -------------------------------------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------- |
| `/api/vendo/mcp`                                                     | POST · GET · DELETE | MCP Streamable HTTP transport                                                                             |
| `/api/vendo/mcp/authorize`                                           | GET · POST          | OAuth authorization endpoint; POST receives the door-owned consent form submission                        |
| `/api/vendo/mcp/token`                                               | POST                | OAuth token endpoint (form-encoded)                                                                       |
| `/api/vendo/mcp/revoke`                                              | POST                | RFC 7009 token revocation (form-encoded); always empty `200`                                              |
| `/api/vendo/mcp/register`                                            | POST                | OAuth dynamic client registration (JSON body)                                                             |
| `/api/vendo/mcp/federate`                                            | GET                 | signed login handshake for an external authorization server; only mounted when `federation` is configured |
| `/.well-known/oauth-protected-resource/api/vendo/mcp`                | GET                 | RFC 9728 protected-resource metadata                                                                      |
| `/.well-known/oauth-authorization-server/api/vendo/mcp`              | GET                 | RFC 8414 authorization-server metadata                                                                    |
| `/.well-known/mcp/server-card.json` · `/.well-known/mcp-server-card` | GET                 | server card describing the door                                                                           |

With `remoteAs` configured, `/api/vendo/mcp/authorize`, `/token`, `/register`,
and the RFC 8414 authorization-server metadata all return `404`; the external
issuer owns those endpoints and RFC 9728 metadata advertises it. See [MCP
door](/capabilities/mcp) for the delegation model.

Door paths bypass the wire's principal resolver and CSRF JSON gate. The door
re-resolves `oauth.principal(subject)` on every bearer-authenticated transport
request, so returning `null` revokes live sessions; the OAuth and discovery
endpoints are not principal-resolved. The origin-root discovery documents sit
outside `/api/vendo`; opting into MCP during `vendo init` generates the sibling
Next.js route that forwards them to the same handler. See [MCP
door](/capabilities/mcp) for the generated placement and manual fallback.

## Webhook verification

Every source registers verification during wiring. Connector sources use their
own signed headers. Self-minted sources use HMAC-SHA256 over
`id.timestamp.rawBody`, with signature, timestamp, and delivery id in headers.
The accepted clock window is five minutes. Delivery ids are deduplicated.

A verification failure returns 401, resolves no principal, starts no run, and
writes one audit event. The secret never appears in a URL. No wire route is
unauthenticated.

## Organization context

Approvals and grants take an optional `?org=<orgId>` query parameter (or
`body.org` on POSTs) to operate under an organization's subject instead of
the caller's user subject. Members of the org may list; admins and owners
may decide, grant, or revoke. Non-members see `404`; members without the
required role see `403`. Without an entitled `VENDO_API_KEY`, the entire
`/orgs` route family and every `?org=` variant return `cloud-required`
(`402`). See [Principals and organizations](/deploy/principals-and-orgs).

## Errors and CSRF

Every non-2xx response uses `{ "error": { "code", "message" } }`. Statuses are
400 validation, 404 not found, 403 blocked, 409 conflict, 402 cloud required,
and 501 sandbox unavailable or not implemented.

Cookie-authenticated mutations require `Content-Type: application/json`.
Binary app import, signed webhooks, and bearer-authenticated tick are the only
exceptions. MCP door paths sit outside both rules; they speak MCP and OAuth
response shapes, not the wire envelope.
