Skip to main content

HTTP API Reference

The vibed control plane serves an HTTP API under /v1, alongside the MCP endpoint, the dashboard, and the operational probes. This page is the operator/developer reference for those routes.

The API is defined by an OpenAPI spec committed at api/openapi.yaml. The Go server stubs are generated from it by oapi-codegen (make openapi-gen); the spec is the source of truth for routing. A browsable Swagger UI is served at /api/docs/, and the raw spec at /api/docs/openapi.yaml.

Base URL

Everything below is relative to the server's base URL — http://localhost:8080 in local dev, or https://vibed.<your-domain> in production. Set it via server.baseURL so generated share links are correct.

Authentication

All /v1/* routes require a bearer token unless auth is disabled (dev only). Pass it in the Authorization header:

Authorization: Bearer <token>

The token is either an OIDC-issued JWT or a configured API key, depending on auth.mode. See Authentication & HTTPS for how tokens are issued and validated. When auth.enabled is false, every request is treated as an admin — convenient for a local Kind cluster, never acceptable when the API is exposed.

Requests without a valid token get 401 with an error body. The operational probes (/healthz, /readyz, /metrics) are public — they carry no auth so a load balancer or Prometheus can scrape them.

Apps are owner-scoped: GET /v1/apps and the per-app routes only return apps whose owner matches the authenticated user. A caller asking for an app it doesn't own gets 404, not 403 — vibeD deliberately does not confirm that another user's app exists. The governance audit trail (GET /v1/audit) is the one admin-only route.

Operational endpoints

These carry no auth and are meant for probes, load balancers, and metrics scrapers.

MethodPathPurposeSuccessFailure
GET/healthzLiveness probe200503
GET/readyzReadiness probe200503
GET/metricsPrometheus exposition200

/readyz returns 503 until Kubernetes clients, storage, the artifact store, and the HTTP server have all come up. Wire it as your readiness probe so traffic isn't routed before dependencies are live. /metrics returns the standard Prometheus text format; see Monitoring.

curl -fsS http://localhost:8080/healthz
curl -fsS http://localhost:8080/readyz
curl -fsS http://localhost:8080/metrics | head

Deploy & lifecycle

POST /v1/deploy

Deploy a new app. This is a multipart upload with two parts:

PartTypeRequiredNotes
sourcefile (binary)yesgzipped tarball of the source tree, ≤ 50 MB
metadataJSON string fieldyesat least name; see metadata

The classifier inspects the source file names to pick a lane and template; an explicit runtime.lane or runtime.template in metadata is an override. There is no container build on this path — the source is injected into a pre-booted warm sandbox.

curl -X POST http://localhost:8080/v1/deploy \
-H "Authorization: Bearer $VIBED_TOKEN" \
-F 'metadata={"name":"my-tool"};type=application/json' \
-F 'source=@my-tool.tar.gz;type=application/gzip'

Responses. The endpoint is bound by a latency contract — it returns within ~10 s with one of:

StatusMeaning
200App is Ready; the warm pool absorbed the request. Body has a live url.
202Accepted onto a slower path; app not yet Ready. Poll status_url.
400Bad multipart, invalid metadata JSON, or missing name/source.
401Missing or invalid bearer token.
403A deploy-time policy gate denied the request.
413Tarball exceeds the 50 MB limit.
429A per-owner or per-department quota was exceeded.
500Internal error.

The 200/202 body is a DeployResponse:

{
"app_id": "my-tool",
"url": "https://my-tool.vibed.example.com",
"status_url": "/v1/apps/my-tool"
}

app_id is always present. On 200, url is set and the app is live. On 202, status_url points at GET /v1/apps/{id} — poll it until phase reaches Ready or Failed.

Deploy metadata

The metadata field is a JSON object:

{
"name": "my-tool",
"runtime": { "lane": "general", "template": "node-24", "entrypoint": "server.js" },
"egress": { "allowed_hosts": ["api.example.com", "*.internal.example.com"] },
"env": [{ "name": "LOG_LEVEL", "value": "info" }],
"ttl": "30m"
}
FieldRequiredDescription
nameyesURL-safe slug (^[a-z0-9]([a-z0-9-]*[a-z0-9])?$, ≤ 63 chars). Becomes the app_id and subdomain.
runtimenoClassifier override. lane (fast | general), template, and entrypoint are each optional.
egressnoPer-app outbound allow-list. Omitting it (or an empty list) means no external egress. See Egress Control.
envnoEnvironment variables injected into the runner.
ttlnoIdle TTL before suspend (Go duration, default 30m).

GET /v1/apps

List apps owned by the authenticated user.

curl -H "Authorization: Bearer $VIBED_TOKEN" http://localhost:8080/v1/apps
{
"items": [
{
"app_id": "my-tool",
"name": "my-tool",
"owner": "alice@example.com",
"phase": "Ready",
"url": "https://my-tool.vibed.example.com",
"runtime": { "lane": "general", "template": "node-24" },
"last_deployed_at": "2026-07-03T10:12:00Z"
}
]
}

phase is one of Pending, Claiming, Starting, Ready, Suspended, Failed (see App Lifecycle). Returns 401 without a token.

GET /v1/apps/{id}

Get a single app's status and URL. The path parameter matches ^[a-z0-9-]{1,63}$.

curl -H "Authorization: Bearer $VIBED_TOKEN" http://localhost:8080/v1/apps/my-tool

Returns the same App shape as a list item. Responses: 200 (found), 401 (unauthenticated), 404 (not found or not owned by the caller). This is the endpoint a client polls after a 202 from deploy.

DELETE /v1/apps/{id}

Tear down an app. Deletes the VibedApp (the controller reaps the bound sandbox pod) and best-effort removes the stored source tarball.

curl -X DELETE -H "Authorization: Bearer $VIBED_TOKEN" http://localhost:8080/v1/apps/my-tool

Responses: 204 (deleted, empty body), 401, 404.

GET /v1/apps/{id}/logs

Read logs from the app's runner as a Server-Sent Events (SSE) stream. Ownership is checked up front, so a 404 is returned cleanly before the connection is upgraded. By default the endpoint returns a snapshot of recent lines and then closes; pass ?follow=true to keep the connection open and stream new lines as they arrive.

# snapshot (returns, then closes)
curl -N -H "Authorization: Bearer $VIBED_TOKEN" \
http://localhost:8080/v1/apps/my-tool/logs

# live tail
curl -N -H "Authorization: Bearer $VIBED_TOKEN" \
"http://localhost:8080/v1/apps/my-tool/logs?follow=true"

The response is text/event-stream; each log line arrives as one event:

data: 2026-07-03T10:12:01Z starting server on :8080

data: 2026-07-03T10:12:02Z listening

If the stream fails mid-flight, a terminal event: error frame is emitted. A client disconnect (cancelled request) is not an error. Concurrent streams per user are capped by limits.maxConcurrentLogStreamsPerUser; exceeding it returns 429 with a Retry-After header. Responses: 200 (stream), 401, 404, 429.

GET /v1/apps/{id}/versions

List an app's deploy history, newest first (the first item is the current deployment). Each successful deploy snapshots the source under a monotonic version number, so any listed version can be restored via rollback. Ownership is checked; a caller that doesn't own the app gets 404.

curl -H "Authorization: Bearer $VIBED_TOKEN" \
http://localhost:8080/v1/apps/my-tool/versions
{
"items": [
{ "version": 3, "timestamp": "2026-07-08T09:41:00Z", "template": "node-24", "lane": "general", "rolled_back_from": 1 },
{ "version": 2, "timestamp": "2026-07-07T18:02:00Z", "template": "node-24", "lane": "general" },
{ "version": 1, "timestamp": "2026-07-07T11:20:00Z", "template": "node-24", "lane": "general" }
]
}

Each entry also carries a source_hash; rolled_back_from is present only on a version a rollback created (above, v3 restored v1's source). History is retained up to a fixed cap (older versions are pruned as new ones land). Responses: 200, 401, 404.

POST /v1/apps/{id}/rollback

Redeploy an app from a previous version's snapshotted source. The rollback itself becomes a new version at the head of the history (it does not rewrite it). Ownership is checked.

curl -X POST -H "Authorization: Bearer $VIBED_TOKEN" \
-H "Content-Type: application/json" -d '{"version": 1}' \
http://localhost:8080/v1/apps/my-tool/rollback

Returns the same App shape as a deploy: 200 when the app is Ready within the latency budget, 202 with a status_url to poll otherwise. Responses: 200, 202, 400 (unknown version), 401, 404.

Governance

GET /v1/audit

The admin-only governance audit trail: an append-only record of mutating actions (deploy, delete, suspend, resume), newest first. Non-admin callers get 403. This route is not part of the generated OpenAPI surface; it is mounted directly and gated on the admin role.

Query parameters (all optional) filter the result:

ParamFilters on
actorauthenticated user ID that performed the action
actiondeploy | delete | suspend | resume
apptarget app / artifact name
limitmax number of events to return
curl -H "Authorization: Bearer $VIBED_TOKEN" \
"http://localhost:8080/v1/audit?action=deploy&limit=50"
{
"events": [
{
"id": "01J...",
"time": "2026-07-03T10:12:00Z",
"actor": "alice@example.com",
"action": "deploy",
"target": "my-tool",
"outcome": "ok",
"tenant_id": "default",
"source_hash": "sha256:..."
}
]
}

Whether events persist depends on the store backend — see Audit Trail. With the SQLite backend the trail is durable; with the memory/configmap backends it is in-memory and does not survive a restart.

Internal source blobs

GET /internal/sources/{id}.tar.gz

The source blob that the in-sandbox agent (vibed-agent) pulls on startup. This route is served only when the tarball store uses the served backend (dev). Requests must be GET/HEAD for a path ending in .tar.gz; anything else is 404 or 405. There are no directory listings.

It sits behind the same auth middleware as /v1, so only the shared agent token can pull. In production the served backend is not used: sandboxes have no cluster DNS or cluster-internal egress under the restrictive NetworkPolicy, so the agent instead pulls from a pre-signed S3 URL and vibeD serves nothing here. See Storage for served vs s3.

MCP endpoint

The Model Context Protocol server is exposed over streamable HTTP at /mcp (and /mcp/). MCP clients (Claude Desktop, Cursor, Goose, …) connect there to call vibeD's tools; the same bearer-token auth applies. The MCP tools cover the same deploy/list/status/logs surface as this API — see the MCP Tools reference.

Swagger UI

A self-contained Swagger UI is served at /api/docs/ (a bare /api/docs redirects to it), reading the spec from /api/docs/openapi.yaml. Use it to browse schemas and try requests interactively against a running instance.

Errors

Error responses share a stable JSON shape:

{
"code": "deploy_failed",
"message": "human-readable description",
"details": {}
}

code is a stable, machine-readable string (e.g. unauthenticated, missing_name, not_found, policy_denied, quota_exceeded, too_many_log_streams); message is human-readable; details is optional structured context.