API reference

One JSON-over-HTTP surface for everything the dashboard can do. Each topic below has its own page with every endpoint's parameters, payload, response, and error codes. New to the concepts? Start with the guide.
AuthenticationProjects & namingQuickstartStreamsPublishConsumeSubscriptionsFailed messages (DLQ)IntrospectionOperationsProjects & API keysAdmin: team, roles, systemErrorsSemantics

Authentication

Every request that sends a body uses Content-Type: application/json; every authenticated request carries one Authorization header. /healthz, /readyz, and /metrics are always open. With no auth configured at all (local dev), the API is open.

Project API key
recommended for services
Created in the portal under a project (vk_… + vs_… secret, shown once). Authorization: Bearer <key>:<secret> (or headers x-api-key / x-api-secret). The key is pinned to its project: every name resolves inside it, other projects are unreachable, and the admin surface is refused.
Static token
VARISAI_TOKENS
Configured server-side as token:scope+scope. Scopes: read (all GETs — any valid token has it), publish, consume, manage (create/change streams, subscriptions, projects, keys; redrive, seek, pause), admin (implies everything). Authorization: Bearer <token>.
Dashboard session
people
Browser calls carry the sign-in cookie. What a person can do is their team role's policy — the same five permissions, assigned on Admin → Team. A 403 names the missing permission and the caller's role.

Projects & naming

Stream and subscription names live inside a project payments/orders and analytics/orders are different streams, and each project can have its own billing-worker. Message keys are scoped by their stream, so they inherit the project.

Quickstart

# Auth: create an API key under your project in the portal, then
AUTH='Authorization: Bearer vk_yourkey:vs_yoursecret'   # omit on an open dev instance

# 1. a subscription with a declared retry policy (project comes from the key)
curl -X POST localhost:7700/v1/subscriptions -H "$AUTH" -d '{
  "name": "billing-worker", "stream": "orders",
  "retry": { "max_attempts": 5, "strategy": "exponential_jitter",
             "initial_backoff": "10s", "max_backoff": "1h" }
}'

# 2. publish — the stream is created implicitly in your project
#    (same key = strict order; different keys = full parallelism)
curl -X POST localhost:7700/v1/streams/orders/messages -H "$AUTH" \
  -d '{"key":"tenant_42","payload":{"order_id":8891},"idempotency_key":"order-8891"}'

# 3. consume: lease -> work -> ack (or nack, and the retry policy takes over)
curl -X POST localhost:7700/v1/subscriptions/billing-worker/receive -H "$AUTH" \
  -d '{"max_messages":10,"wait_seconds":20,"consumer":"worker-1"}'
curl -X POST localhost:7700/v1/subscriptions/billing-worker/ack -H "$AUTH" \
  -d '{"delivery_ids":["dlv_..."]}'

# Without an API key, name the project explicitly:
curl -X POST 'localhost:7700/v1/streams/orders/messages?project=payments' -d '...'

Prefer a client? The zero-dependency Node SDK (sdk/js) wraps all of this — new Varisai({ project: "payments" }) scopes every call, and its consume loop makes resolve = ack, throw = nack.

The message envelope — what GETs, receive, and traces return for a message:

{
  "id": "msg_1m1b9kb5qvvcqn0tc334f",
  "stream": "orders", "project": "payments",
  "key": "tenant_42",
  "payload": { "order_id": 8891 },
  "headers": { "region": "in" },
  "idempotency_key": "order-8891-created",
  "deliver_at": null, "expires_at": null,
  "published_at": "2026-08-31T10:15:00.000Z",
  "offset": 41, "status": "available"
}

Endpoints by topic

Streams
scope: manage · reads: read
Create, configure, browse, and delete streams — named durable logs with per-stream retention and backlog caps.
Publish
scope: publish
Publish messages one at a time or in ordered batches, with idempotency keys, headers, scheduled delivery, and expiry.
Consume
scope: consume
Long-poll receive with per-message leases, ack, nack, and lease extension — per-key ordering enforced broker-side.
Subscriptions
scope: manage · reads: read
Declare retry policy, rate limits, filters, and push delivery on the subscription — no retry or DLQ code in consumers.
Failed messages (DLQ)
scope: manage · reads: read
Browse dead letters grouped by error signature, redrive them back to the queue, or discard them for good.
Introspection
scope: read
Trace any message's full lifecycle, inspect per-key lanes, scheduled messages, retries, and node-wide stats.
Operations
scope: manage
Operational verbs: skip a poison message, fire backing-off retries immediately, deliver a scheduled message now.
Projects & API keys
scope: manage · reads: read
Projects namespace your streams and subscriptions; project API keys are pinned to their project and can never leave it.
Admin: team, roles, system
scope: admin — dashboard sessions only; API keys are refused here
Team members, role policies, effective configuration, and the audit log — dashboard sessions only.

Errors

Every error, on every endpoint, is the same shape with a meaningful status code:

{ "error": "a stream named orders already exists in project payments" }
400Malformed request — the message names the field and the accepted values
401No credentials, or an unknown API key / wrong secret
403Authenticated but not allowed: missing scope/permission, or an API key reaching outside its project
404No such stream / subscription / message / project in that scope
409Conflict: the name already exists, or the state no longer allows the action
413Payload over MAX_MESSAGE_BYTES (default 1 MB)
429Backpressure: the stream's undelivered backlog crossed its cap — back off and retry
503The broker could not reach MongoDB (readyz fails too)

Semantics worth knowing