All endpoints under /api/v1/. Auth via either:
- API key:
X-Orva-API-Key: orva_xxx...header. Used by curl, CI, external callers. - Session cookie: set by
POST /api/v1/auth/login. Used by the dashboard.
API keys carry a permission set. The bootstrap admin key has all four:
invoke, read, write, admin. Operator-issued keys can be
narrowed.
Error envelope (every 4xx/5xx):
{
"error": {
"code": "POOL_AT_CAPACITY",
"message": "function pool at capacity for 019df200-7b00-7e00-9c00-aab1cd2e3f40",
"request_id": "req_abc",
"hint": "raise pool_config.max_warm via PUT /api/v1/pool/config",
"retry_after_s": 5,
"details": {"function_id": "019df200-7b00-7e00-9c00-aab1cd2e3f40", "current": 16, "limit": 16}
}
}Retry-After HTTP header set in parallel when retry_after_s is
present. Full code catalog in ERRORS.md.
First-run only. Creates the admin user. Returns 409 if a user already exists.
// request
{"username": "admin", "password": "AdminPass123!Secure"}
// response 201
{"user": {"id": "u_xxx", "username": "admin"}, "expires_at": "..."}Sets the session cookie.
{"username": "admin", "password": "..."}Returns the current user (cookie-authed).
Returns {"has_user": bool} so the UI knows whether to route to
/onboarding or /login.
Rotates the cookie's expiry forward by 7 days.
Invalidates the session.
Create a function record.
{
"name": "my-fn",
"runtime": "node", // node | python
"entrypoint": "handler.js", // optional, defaults match the runtime
"memory_mb": 128,
"cpus": 1,
"timeout_ms": 30000,
"env_vars": {"NODE_ENV": "production"},
"network_mode": "none" // none (default) | egress
}network_mode controls per-function network access:
none(default) — isolated net namespace, loopback only. DNS / TCP / UDP all blocked. Best for pure-compute handlers.egress— userspace TCP/UDP stack via nsjail--user_net. Function can call external HTTPS APIs. Host interfaces stay isolated.
Toggling on an existing function via PUT /api/v1/functions/{id}
drains the warm pool so the next invocation picks up the new mode.
List all functions. Optional ?status=active|inactive, ?runtime=....
Single function record.
Partial update. Whitelisted fields: name, entrypoint, timeout_ms,
memory_mb, cpus, env_vars, network_mode, status.
status accepts only active | inactive. Setting inactive causes
POST /invoke/<id> to return 409 NOT_ACTIVE.
Removes the row + the on-disk versions dir. Irreversible.
Deploy from JSON.
{
"code": "module.exports = async () => ({ok:true});",
"filename": "handler.js",
"dependencies": "lodash@^4.17.21" // optional, becomes package.json or requirements.txt
}Returns 202 with the deployment record. Build runs asynchronously.
Deploy from a tarball (multipart upload).
Roll back to a prior version.
{"deployment_id": "019df210-1234-7000-8000-deadbeef0001"} // or {"code_hash": "abc..."}Returns 200 with a synthetic deployment row of source: "rollback".
Returns 410 VERSION_GCD if the target version was pruned by the GC.
Returns the function's current code + dependencies as JSON. Used by the Editor view.
Compares the handler source + dependency manifest between two past
succeeded deployments. Both from and to must be deployment IDs
(dep_…) belonging to this function.
format=json(default — dashboardCompare versionsview) returns{from, to, files: [{path, kind:"handler"|"manifest", before, after, added, removed}]}.before/aftercarry the raw file bytes so the browser-side merge viewer can compute its own hunks.format=unifiedreturnstext/x-diffwith git-style hunks per file (--- a/path/+++ b/path/@@ …). Consumed byorva diff.
Errors:
- 400
VALIDATIONiffromandtoare equal, belong to different functions, or aren't in statussucceeded. - 404
VERSION_NOT_FOUNDif either deployment ID is unknown; details include the requested ID. - 410
VERSION_GCDif either version's source tree was pruned by the GC.details.available_hasheslists the surviving on-disk versions so the caller can retry against a still-archived target.
Deployment history for a function. Optional ?limit=N (default 50).
Calls the function. id is the function's UUID (the same value returned in the id field by GET /api/v1/functions).
(e.g. function 019df200-7b00-7e00-9c00-aab1cd2e3f40 → URL /fn/ttp836b9x3m1). Method,
headers, body, query, and path (everything after /{id}) are all
passed to the handler as event.
Response is whatever the handler returns. HTTP status is 200 unless
the handler throws or returns an AWS-shape {statusCode, body}.
Custom routes (e.g. /webhooks/stripe) reach the same handler — see
the routes section below.
Single deployment record.
Build logs for that deployment.
Server-sent events stream of build progress. Live tail; closes when
the build reaches a terminal state (succeeded | failed).
List recent invocations. Optional ?function_id=..., ?limit=N.
Single execution row (status, duration, cold_start flag).
The function's stderr from this invocation.
List secret keys for a function. Values are not returned (encrypted at rest; only injected into the sandbox at spawn time).
Upsert. Body: {"key": "STRIPE_KEY", "value": "sk_..."}. Triggers a
pool refresh so the next invocation sees the new value.
Remove. Triggers a pool refresh.
Map a custom URL to a function so external callers don't need the function ID.
List custom routes.
{"path": "/webhooks/stripe", "function_id": "019df200-7b00-7e00-9c00-aab1cd2e3f40", "methods": "POST"}methods accepts * for all methods or comma-separated (GET,POST).
Reserved prefixes (/api/, /fn/, /mcp/, /web/, /_orva/) are rejected.
Remove a route.
Per-function autoscaler tuning.
Read the row.
{
"function_id": "019df200-7b00-7e00-9c00-aab1cd2e3f40",
"min_warm": 2,
"max_warm": 32,
"idle_ttl_seconds": 120,
"target_concurrency": 10,
"scale_to_zero": false
}Fields are partial — unspecified ones keep the prior value (or default for new rows).
List keys. Returns prefixes, names, last_used_at, expires_at. Never returns the plaintext key.
{
"name": "ci-deployer",
"permissions": ["invoke", "read", "write"], // optional, defaults to all 4
"expires_in_days": 90 // or expires_at: "ISO timestamp"
}Returns the plaintext key once. Save it immediately — it's not recoverable.
Revoke a key.
A channel bundles N deployed functions under a name and a static bearer
token. Presenting that token at /mcp exposes ONE MCP tool per
bundled function (invoke-only) and nothing else — no Orva-management
surface. Token format: orva_chn_<32 hex>. Channel tokens are
explicitly rejected at every /api/v1/* endpoint (401); they're
MCP-only.
Auth header at /mcp — channel tokens accept either form, same as
operator API keys:
Authorization: Bearer orva_chn_<token> # spec-standard, recommended
X-Orva-API-Key: orva_chn_<token> # parity with the REST API
The REST endpoints below (CRUD on /api/v1/channels) are operator-
managed and require an API key or session cookie — channel tokens
themselves cannot manage channels.
List channels. Returns {channels: [...]} with name, description,
prefix, function_count, last_used_at, expires_at, created_at.
{
"name": "support-bot",
"description": "Support workflow toolkit", // optional
"function_ids": ["<uuid>", "<uuid>"],
"expires_in_days": 30 // optional; or expires_at: "ISO timestamp"
}Returns the plaintext token once in the token field. Save it
immediately — it's not recoverable. Two functions whose names
snake_case to the same MCP tool name are rejected with 400 / TOOL_NAME_COLLISION.
Detail with the bundled function set + per-function description overrides.
Update name / description / expires_at. Function set is unchanged.
{
"function_ids": ["<uuid>", ...],
"descriptions": {"<uuid>": "tool description override"} // optional
}Replaces the function set wholesale. Junction descriptions on overlapping function IDs are preserved unless explicitly overridden.
Re-issues the bearer token. Returns {token: "orva_chn_..."} once;
the previous token stops working immediately.
Cascade — removes the channel and every junction row.
{"status": "ok"} when orvad is up. Used by Docker HEALTHCHECK and
load balancers.
Prometheus text format.
Same data, JSON shape, used by the dashboard.
Server-sent events stream of:
event: metrics— periodic 5-second snapshotsevent: execution— every new invocationevent: deployment— every status / phase change
Browser EventSource automatically reconnects. Cookie auth (API-key auth not supported on EventSource — browsers can't set custom headers).
List supported runtimes.
The seccomp policy catalog. Useful for the dashboard's "what is this function allowed to do" tooltip.
The in-product agentic chat (the dashboard's AI section). Requires
a configured provider (BYO key). Streaming endpoints emit
text/event-stream; everything else is JSON. All paths require admin.
Send a user message and stream the assistant turn. Body carries
conversation_id (or omit to start one), content, and the selected
provider/model/thinking level. Response is SSE: message_start,
delta (text), thinking, tool_call, tool_result,
awaiting_approval, message_end, error. Long pre-token gaps are
kept alive with : ping comment frames.
List conversations (most-recently-updated first).
Create an empty conversation.
Fetch one conversation with its full message + tool-call timeline.
Rename ({"title": "..."}) or archive ({"archived": true}).
Delete a conversation and all its messages + tool calls (cascade).
List messages, optionally ?since_seq=N for incremental loads.
Truncate the last assistant turn and re-run it. SSE, same frames as
/chat.
Replace a user message's content, truncate everything after it, and re-run the turn. SSE. (There is no branching history — the tail is discarded.)
Delete a message and every message + tool call after it (truncate by
seq).
Resolve a tool call that is awaiting_approval and resume the stream
(approve) or skip it (reject). SSE.
List, upsert, and remove provider configs (provider, label, base URL, API key). Keys are encrypted at rest with the same cipher as function secrets and never returned in responses.
List the models the configured provider/endpoint reports.
Read/update assistant settings: default provider/model, thinking level,
approval policy (all_writes / destructive_only / auto), and the
per-reply tool-step cap.