HTTP API
The Vendo HTTP API surface at vendo.run/api — what the SDKs, CLI, and dashboard call under the hood.
The Vendo SDKs, CLI, and dashboard all talk to the platform over the HTTP API documented in this section. You usually don't call these endpoints directly — the Python and TypeScript SDKs wrap the SDK-facing surface — but they're worth knowing for debugging, building thin clients, scripting deploys, or writing in a language without a first-party SDK.
Base URL
https://vendo.runAll endpoints documented here are rooted at that origin. Three top-level surface groupings:
/api/v1/*— the legacy CORS-enabled tenant surface. Today only/api/v1/balancelives here./api/deployments/me/*— runtime-facing endpoints called by deployed tools using their App Key.connections,events.- Everything else (
/api/billing/*,/api/connections/*,/api/integrations/*,/api/apps/*,/api/tools/*,/api/tenants/*,/api/keys,/api/oauth/*,/api/deploy) — dashboard surface, authenticated by thevendo.runsession cookie, with the SDK accepting App Keys on a subset (e.g./api/billing/balance).
Auth model
Vendo accepts four distinct credentials, with strict rules about which endpoints accept which:
| Credential | Header | Issued by | Scope |
|---|---|---|---|
App Key (vendo_sk_*) | Authorization: Bearer … | Deploy flow, one per deployment | One app — multiple connections via bindings |
Connection Key (vendo_sk_*) | Authorization: Bearer … | POST /api/connections/{id}/keys | One connection, one provider |
| Identity token (JWT) | Authorization: Bearer … | Supabase OAuth at auth.vendo.run | User identity, 1 hour |
| Session cookie | Browser cookies | vendo.run sign-in flow | Dashboard user |
Two different format-detection strategies live in the codebase:
/api/v1/balanceand/api/billing/balanceuse dot-segment counting: a token with exactly three base64url segments is treated as a JWT; anything else is looked up as an opaque proxy key./api/deployments/me/connectionsand/api/deployments/me/eventshard-checktoken.startsWith("vendo_sk_"). Anything else — including a valid identity JWT — is rejected before any verification work happens.
Endpoint auth matrix
| Endpoint | App Key | Connection Key | Identity JWT | Session cookie |
|---|---|---|---|---|
GET /api/v1/balance | yes | yes | yes | no |
GET /api/billing/balance | yes | yes | yes | no |
GET /api/deployments/me/connections | yes | no | no | no |
GET /api/deployments/me/events | yes | no | no | no |
GET /api/cli/balance | yes | yes | no | no |
POST /api/deploy | no | no | no | yes |
POST /api/connections (cookie) | yes | no | no | yes |
GET /api/billing/* (dashboard) | no | no | no | yes |
GET /api/integrations, /api/apps, /api/keys, /api/tenants/me, /api/user/me | no | no | no | yes |
GET /api/tools/*, /api/pricing/* (public catalog) | n/a | n/a | n/a | optional |
GET /api/auth/me | no | no | no | yes (or anonymous) |
/api/deployments/me/* only accepts an App Key. Connection-scoped keys are rejected even though they share the same vendo_sk_* prefix — these endpoints need an app_id to resolve bindings.
Error envelopes
Vendo currently emits three distinct error shapes across its surface. New code uses the canonical helper (web/src/lib/errors/response.ts), but legacy routes ship their own shapes. Check each endpoint's Errors section for the exact body it returns.
Shape 1 — canonical (errorResponse())
Used by every route that imports @/lib/errors/response. Sets a Vendo-Error-Code HTTP header and emits:
{
"error": {
"code": "rate_limit_sse_streams",
"message": "Too many concurrent SSE streams."
}
}Optional fields (slug, retry_after, connect_url, suggested_fix) appear inside error when the route passes them. The canonical code registry lives at web/src/lib/errors/codes.ts. Today only /api/deployments/me/events (rate-limit branch) uses this helper.
Shape 2 — nested without code
Used by /api/v1/balance, /api/billing/balance, and /api/deployments/me/connections:
{ "error": { "message": "Missing Bearer token" } }Shape 3 — flat (no wrapper)
Used by /api/deploy, /api/cli/balance, and most of the dashboard surface:
{ "message": "Rate limit exceeded" }Deploy and a few dashboard routes add a code field next to message:
{ "message": "Add credits to your wallet before deploying.", "code": "INSUFFICIENT_CREDITS" }A handful of dashboard mutations use a third variant with a single error string property:
{ "error": "unauthorized" }Common status codes
| Status | Meaning |
|---|---|
400 | Validation failure or unknown integration slug |
401 | Missing, malformed, revoked, or expired Bearer token / no session |
402 | Tenant billing gate (BILLING_NOT_SETUP, INSUFFICIENT_CREDITS) or out of credits on a metered call |
403 | Authenticated but not permitted (App Key has no binding for the requested provider, tenant ownership mismatch, tool unreleased) |
404 | Resource or tenant not found |
409 | Conflict (duplicate deployment slug, duplicate workspace slug, exclusive connection bound elsewhere, idempotency conflict) |
429 | Rate limit exceeded |
502 | Upstream (deploy-worker, provider API, OAuth exchange) failed |
5xx | Vendo bug — please report |
CORS
/api/v1/balance and /api/billing/balance reflect *.vendo.run origins with Vary: Origin; other origins get *. Allowed methods: GET, OPTIONS. Allowed headers: Authorization, Content-Type. Safe because every endpoint requires a Bearer token.
Other surfaces (/api/deployments/me/*, /api/deploy, the dashboard endpoints) do not enable cross-origin browser access — call them from your server or via the SDK.
Conventions
- JSON only. Requests and responses are
application/jsonexcept SSE channels, image uploads (multipartformData), and HTML postMessage responses (OAuth callbacks). - Timestamps are ISO 8601 with
Zsuffix unless otherwise noted. - Money is reported as USD (
number, two-decimal precision) on legacy routes (/api/v1/balance,/api/cli/balance, dashboard charts) and as integer micros (USD × 10⁶) on SDK-facing routes (/api/billing/balance,/api/billing/spend-caps). Field names tell you which:*_usdvs*_micros. - Field naming.
/api/v1/*and/api/deployments/me/*responses use snake_case./api/deploy,/api/auth/me, and a number of dashboard mutations use camelCase. Cross-language SDK consumers will see both — Vendo is gradually standardizing on snake_case but never breaks an existing field. - Single-shot, not polled. Connection state changes surface to a deployment when it restarts and re-reads, or via the events SSE channel.
- Idempotency.
POST /api/deployhonorsIdempotency-Keyrequest header — repeated calls with the same key replay the cached response.
Rate limits
Per-IP limiters apply to a handful of public endpoints:
| Endpoint | Limit |
|---|---|
GET /api/v1/balance | 60 req/min |
GET /api/billing/balance | 60 req/min |
GET /api/cli/balance | 30 req/min |
POST /api/deploy | 5 req/hour |
POST /api/integrations/{slug} and /api/integrations/{slug}/regenerate | shared integrationLimiter |
GET /api/deployments/me/events | max 5 concurrent streams per app (advisory) |
Other endpoints rely on key-level revocation and proxy-level spend caps.
See also
- vendo.yaml schema — deploy-time manifest
- SDK reference — Python, TypeScript, Swift
- CLI reference — the
vendocommand