VOLT

REST & realtime API

How to call the VOLT server from your own code - base path, authentication, response envelopes, pagination and errors, the route groups, and the Socket.IO channel.

The VOLT server exposes a JSON API under /api and a Socket.IO endpoint at /socket.io on the same origin. The web app uses both. Your programs use the API with a team secret key, directly or through the TypeScript and Python SDKs.

The routes are declared in packages/shared/contracts/src/modules/<module>/routes.ts. The server mounts exactly those, and the client calls them, so that directory is the authoritative list, with each route's input and output types next to it.

The server publishes no OpenAPI document, and it has no /api-docs endpoint.

Authentication

Protected routes take a bearer token in the Authorization header. The API reads credentials from that header only.

Authorization: Bearer <token>

There are two kinds of token, and the server tells them apart by the vsk_ prefix:

TokenFormUsed byHow you get one
Secret keyvsk_…Programs, scripts, notebooks, the SDKsCreate it for a team; see Secret keys. It acts in that team with the permissions of the role it was given.
Session tokenA JWTThe web appPOST /api/auth/sessions with { "email", "password" } returns { token, user }. Each token is a session, listed by GET /api/sessions and revoked by DELETE /api/sessions/:sessionId.

A secret key works only on its own team's routes; any other /api/teams/:teamId/… answers 403 Team::AccessDenied. Every request made with a key is logged for its usage metrics (GET /api/teams/:teamId/secret-keys/:secretKeyId/usage). GET /api/teams/secret-keys/me returns the key you call it with, including its team, role, name and keyPrefix; it is how a program learns its team ID.

VOLT=https://volt.example.com
KEY=vsk_...

TEAM=$(curl -s -H "Authorization: Bearer $KEY" "$VOLT/api/teams/secret-keys/me" | jq -r .data.team)
curl -s -H "Authorization: Bearer $KEY" "$VOLT/api/teams/$TEAM/trajectories?page=1&limit=20"

A few routes take no token: GET /api/system/config (the deployment mode), the sign-in and sign-up routes, and the /api/public/… routes, which serve public trajectories and accept a token when there is one. The server also answers GET /healthz outside /api.

Responses

A successful response is an envelope with the data under data:

{ "status": "success", "data": { "...": "..." } }

A route that returns nothing answers 204 with no body. A list answers with a page of results and its pagination:

{
  "status": "success",
  "data": [{ "_id": "...", "...": "..." }],
  "pagination": { "page": 1, "limit": 20, "total": 57, "totalPages": 3, "hasMore": true },
  "_meta": {}
}

Lists take page (1-based, default 1) and limit query parameters. Each route sets its default limit and caps it. _meta carries extra information some lists add, such as the columns of a listing.

An error has the same envelope with status: "error", a stable code of the form Module::Reason and the HTTP status:

{ "status": "error", "code": "Authentication::Required", "message": "Authentication::Required", "statusCode": 401 }
CodeStatusMeaning
Authentication::Required401No bearer token.
Authentication::Unauthorized401The session token is invalid, expired, revoked, or older than the user's last password change.
SecretKey::Invalid401The secret key does not exist or is revoked.
Team::AccessDenied403A secret key used on another team's route.

A path the server does not have answers Express's own 404 page, which is HTML.

Route groups

Team-scoped resources live under /api/teams/:teamId/. The groups below follow the contract modules.

GroupRoutes
SystemGET /api/system/config, GET /api/system/rbac
Account/api/auth/sessions (sign in), /api/auth/users (sign up), /api/auth/me (read, update, delete the account), /api/auth/me/password, /api/auth/oauth/providers; OAuth sign-in at /api/auth/github, /api/auth/google and /api/auth/microsoft with their /callback routes
Sessions/api/sessions, /api/sessions/activity, /api/sessions/:sessionId
Notifications/api/notifications, /api/notifications/read-status
Teams/api/teams, /api/teams/:teamId, and under it members, roles, invitations, invite-codes, self/permissions, self/membership, default-membership; /api/teams/invite-codes/:code, /api/teams/invitations/:invitationId
Secret keys/api/teams/:teamId/secret-keys (list, create, /:secretKeyId/revocations, delete), /secret-keys/metrics, /secret-keys/:secretKeyId/usage, and /api/teams/secret-keys/me
Trajectories/api/teams/:teamId/trajectories and /:trajectoryId (read, update, delete); upload-sessions and their commits; clones; /:trajectoryId/download, /preview, /frames/:timestep/atoms, /scene-artifacts, /folder, /color-codings, /particle-filters, /simulation-cell, /analyses/download; /api/teams/:teamId/trajectory-folders, /trajectory-metrics
Simulation cells/api/teams/:teamId/simulation-cells, /simulation-cells/:simulationCellId
Analyses/api/teams/:teamId/analyses, /analyses/:analysisId, /analyses/:analysisId/logs/:timestep, /analyses/:analysisId/failed-frames/retries; /api/teams/:teamId/provenance-records and /:provenanceId/reproduction
Plugins/api/teams/:teamId/plugins and /:pluginId (CRUD), /:pluginId/binary (upload target, commits, download, delete), /:pluginId/clones, /:pluginId/export, imports, registry/search, registry/installations, workflow-validations, node-types/schema
Pipelines/api/teams/:teamId/plugins/trajectories/:trajectoryId/pipeline-executions (run), /pipeline-runs and /pipeline-runs/:pipelineRunId
Results/api/teams/:teamId/plugins/listings/analyses/:analysisId and its export, sub-listings/:exposureId/:timestep/:subListingName; /plugins/:pluginId/listings and its export; /plugins/exposures/:trajectoryId/:analysisId/:exposureId/:timestep/glb, /plugins/exposures/artifacts/:artifactId/chart, /plugins/exposures/analyses/:analysisId/export, /plugins/exposures/analyses/:analysisId/panels/:timestep
Jobs/api/teams/:teamId/jobs, /jobs/retries
Clusters/api/teams/:teamId/clusters and /:teamClusterId, with runtime-snapshot, queue-concurrency, role, transfers, resource-limits, remote-access/…, enrollment-tokens, credentials, delete-requests; /api/teams/:teamId/cluster-objects/:token
Daemon/api/team-clusters/:teamClusterId/healthcheck (enrollment), /api/team-clusters/:teamClusterId/install-manifest
Notebooks/api/teams/:teamId/notebooks, /:notebookId/content, /:notebookId/kernel; /api/teams/:teamId/notebook-sessions
Whiteboards/api/teams/:teamId/whiteboards, /whiteboard-folders
VOLT AI/api/teams/:teamId/ai-conversations and /:conversationId/messages, /compact; /api/teams/:teamId/ai-integrations and /:provider, /ai-integrations/models
Dashboard/api/teams/:teamId/search, /daily-activities, /scene-artifacts, /sample-simulations
Public/api/public/teams/:teamId/trajectories; /api/public/trajectories/:trajectoryId and its bootstrap, preview, analyses, dumps/:timestep, frames/:timestep/glb, frames/:timestep/atoms, plugin listings, exposures, logs and panels

Write routes follow the same shape: a resource's sub-collection names the action, as in POST …/secret-keys/:secretKeyId/revocations or POST …/plugins/registry/installations with { "name": "@voltlabs/opendxa", "version": "2.5.1" }.

Realtime: Socket.IO

The server runs a Socket.IO server on its HTTP port at the default path, /socket.io, with the websocket and polling transports. The web app uses it for live job lists, analysis logs, presence, collaboration on the canvas and whiteboards, VOLT AI chats and cluster metrics.

Authenticate in the handshake with a session token. Secret keys are not accepted here. A connection without a token is a guest; one with an invalid token is refused.

import { io } from 'socket.io-client';

const socket = io('https://volt.example.com', { auth: { token: sessionToken } });
socket.emit('subscribe_to_team', { teamId });
socket.on('analysis.status.changed', (event) => console.log(event));

Team events

Emit subscribe_to_team with { teamId, previousTeamId? } to join the room team:<teamId>; the server checks that you are a member, and leave_team leaves it. In that room the server forwards these domain events, each with its payload plus timestamp and eventName:

trajectory.created, trajectory.updated, trajectory.deleted, analysis.created, analysis.deleted, analysis.status.changed, analysis.stage.changed, scene-artifact.upserted, plugin.created, plugin.deleted, team.created, team.deleted, team-member.deleted, team-role.created, team-role.updated, team-role.deleted, secret-key.created, secret-key.deleted, notebook.deleted, whiteboard.deleted.

Your socket receives team.jobs.initial, a snapshot of the team's jobs, when you subscribe; the room receives team.job.updated each time a job changes status, and a new team.jobs.initial after running jobs are removed. The room also carries the presence events user:online, user:offline and user:list, which team:heartbeat keeps current.

Other channels

ChannelClient emitsServer emits
Analysis logssubscribe_to_analysis_log and unsubscribe_from_analysis_log, with { analysisId, timestep, afterCursor? }analysis-log:chunk: { analysisId, timestep, cursor, segments, sealed, status, truncated }
Clusterssubscribe_to_team_cluster with { teamClusterIds }metrics:all, metrics:history, team-cluster.updated
Canvastrajectory.presence.join, trajectory.presence.leave, canvas.lobby.join, canvas.lobby.leave, canvas.workspace.visit, canvas.workspace.leave, canvas.workspace.publish_snapshot, canvas.workspace.patch, canvas.workspace.cursor, canvas.workspace.model_dragcanvas.lobby.update, canvas.workspace.viewers, canvas.workspace.sync_state, canvas.workspace.apply_patch, canvas.workspace.cursor, canvas.workspace.model_drag, canvas.workspace.closed, trajectory.presence.update
Whiteboardssubscribe_to_whiteboard, unsubscribe_from_whiteboard, whiteboard_patchwhiteboard_users_update, whiteboard_apply_delta
VOLT AIai-chat:join, ai-chat:leave, ai-chat:send, ai-chat:stopai-chat:chunk, ai-chat:saved, ai-chat:tool-invoke
Plugin debuggingdebug:start, debug:step, debug:continue, debug:stopdebug:session:created, debug:session:completed, debug:session:error, debug:node:started, debug:node:completed, debug:node:skipped, debug:node:error, debug:node:log-chunk

A handler that rejects a request emits error with { code, details } to your socket.

Cluster daemons connect to the same server with their own protocol; see @voltstack/daemon-cluster-client.

On this page