Developers

VOS API & Webhooks

Team plan accounts can integrate VOS sessions into a helpdesk, CRM, or ticketing workflow, create sessions from their systems, and get notified when a session ends or a recording is ready.

Authentication

Generate an API token from Settings, then API (Team plan owners only). Tokens look like vos_live_… and are shown once at creation — store it somewhere safe.

Send it as a bearer token on every request:

Authorization: Bearer vos_live_xxxxxxxxxxxxxxxxxxxxxxxx

Requests without a valid, unrevoked token get a 401 with { "error": "Missing or invalid API token." }. Every token is scoped to one organization — there is no cross-account access.

List sessions

GET /api/v1/sessions
curl https://vos.live/api/v1/sessions \
  -H "Authorization: Bearer vos_live_xxxxxxxxxxxxxxxxxxxxxxxx"
{
  "sessions": [
    {
      "id": "session_8f2c1a90b6d4e123",
      "organizationId": "org_123",
      "name": "Front lobby printer",
      "status": "ended",
      "pin": "VO4829",
      "minutesUsed": 12,
      "startedAt": "2026-06-17T18:42:00.000Z",
      "endedAt": "2026-06-17T18:54:12.000Z",
      "recordingEnabled": true,
      "hostOnly": false,
      "hostCameraShare": false,
      "hasRecording": true,
      "hasScreenshots": true,
      "hasNotes": false,
      "host": {
        "userId": "user_123",
        "displayName": "Morgan Lee",
        "email": "morgan@example.com"
      },
      "hostUrl": "https://vos.live/session/session_8f2c1a90b6d4e123",
      "joinUrl": "https://vos.live/join/VO4829"
    }
  ]
}

Create a session

Useful for launching a support session directly from a ticket or WorkFlow — drop the returned joinUrl into the customer's ticket or message, and the hostUrl into the technician's.

POST /api/v1/sessions
curl https://vos.live/api/v1/sessions \
  -X POST \
  -H "Authorization: Bearer vos_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "Ticket #4821", "recordingEnabled": true}'

All fields are optional:

{
  "name": "Ticket #4821",       // defaults to a timestamp-based name
  "recordingEnabled": true,     // defaults to false
  "hostOnly": false,            // defaults to false
  "hostCameraShare": false,     // host publishes video; guest joins audio-only
  "workflowTemplateId": "workflow_123" // optional Team/Trial WorkFlow
}

Returns 201 with the same session shape as the list endpoint.

Get a session

GET /api/v1/sessions/:id

Returns the complete saved-session package: session metadata, notes, recordings, screenshots, annotations, WorkFlow progress, target assets, active review-link metadata, and a flat assets array for downloads. Returns 404 if the session doesn't exist or belongs to a different organization.

{
  "session": {
    "id": "session_8f2c1a90b6d4e123",
    "organizationId": "org_123",
    "name": "Front lobby printer",
    "status": "ended",
    "pin": "VO4829",
    "minutesUsed": 12,
    "startedAt": "2026-06-17T18:42:00.000Z",
    "endedAt": "2026-06-17T18:54:12.000Z",
    "recordingEnabled": true,
    "hostOnly": false,
    "hostCameraShare": false,
    "hasRecording": true,
    "hasScreenshots": true,
    "hasNotes": true,
    "notesText": "Replaced tray sensor.",
    "hostUrl": "https://vos.live/session/session_8f2c1a90b6d4e123",
    "joinUrl": "https://vos.live/join/VO4829"
  },
  "notes": ["Replaced tray sensor.", "Showed cracked guide tab."],
  "recordings": [
    {
      "id": "recording_rec_123",
      "title": "Front lobby printer",
      "durationLabel": "11m 52s",
      "playbackUrl": "https://vos.live/recordings/front-lobby.mp4",
      "downloadUrl": "https://vos.live/recordings/front-lobby.mp4"
    }
  ],
  "snapshots": [
    {
      "id": "snapshot_123",
      "title": "Tray assembly",
      "noteText": "Cracked guide tab",
      "imageUrl": "https://vos.live/api/session/session_8f2c1a90b6d4e123/assets/snapshot-1.png?sig=...",
      "rawImageUrl": "https://vos.live/api/session/session_8f2c1a90b6d4e123/assets/snapshot-raw.png?sig=...",
      "annotatedImageUrl": "https://vos.live/api/session/session_8f2c1a90b6d4e123/assets/snapshot-1.png?sig=..."
    }
  ],
  "annotations": [
    {
      "id": "annotation_123",
      "targetId": "target_123",
      "revision": 3,
      "status": "active",
      "payload": {
        "targetId": "target_123",
        "strokes": [],
        "markers": [],
        "notes": "Showed cracked guide tab."
      }
    }
  ],
  "targets": [
    {
      "id": "target_123",
      "label": "Printer tray",
      "status": "ready",
      "sourceSnapshotUrl": "https://vos.live/api/session/session_8f2c1a90b6d4e123/assets/target.png?sig=...",
      "compiledTargetUrl": "https://vos.live/api/session/session_8f2c1a90b6d4e123/assets/bundle-abcd.mind?sig=..."
    }
  ],
  "workflowRun": {
    "id": "session_workflow_123",
    "title": "Printer intake",
    "status": "completed",
    "steps": []
  },
  "reviewShare": {
    "id": "review_123",
    "reviewUrl": "https://vos.live/review/abc123",
    "expiresAt": "2026-07-17T18:54:12.000Z",
    "passwordProtected": true
  },
  "assets": [
    {
      "id": "recording:recording_rec_123",
      "type": "recording",
      "label": "Front lobby printer",
      "url": "https://vos.live/recordings/front-lobby.mp4",
      "downloadUrl": "https://vos.live/recordings/front-lobby.mp4",
      "filename": "front-lobby.mp4",
      "contentType": "video/mp4",
      "relatedId": "recording_rec_123"
    }
  ]
}

Screenshot and target URLs are signed asset URLs because those files are stored outside the public web root. Treat them as private customer data and store only what your integration needs.

Webhooks

Register an endpoint from Settings, then API on the Team plan. We'll POST a signed JSON payload to it for these events:

  • session.ended — fired when a session is ended, by a technician or automatically when the room closes.
  • recording.ready — fired once a recording finishes processing and is available to play back.

Payload shape:

{
  "type": "session.ended",
  "createdAt": "2026-06-17T18:54:12.000Z",
  "data": {
    "sessionId": "session_8f2c1a90b6d4e123",
    "sessionName": "Front lobby printer",
    "minutesUsed": 12
  }
}
{
  "type": "recording.ready",
  "createdAt": "2026-06-17T18:54:20.000Z",
  "data": {
    "sessionId": "session_8f2c1a90b6d4e123",
    "sessionName": "Front lobby printer",
    "durationSeconds": 712
  }
}

Endpoints must be https://. Failed deliveries retry automatically with backoff, and account owners can replay recent deliveries from API settings. Webhook payloads stay lightweight; use GET /api/v1/sessions/:id with the event's sessionId when your integration needs the full saved-session package or asset download URLs.

Verifying webhook signatures

Every delivery includes an X-VOS-Signature header: an HMAC-SHA256 of the raw request body, hex-encoded, using the signing secret shown when you created the endpoint. The event type is also sent as X-VOS-Event.

const crypto = require("crypto");

function isValidVosWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader),
  );
}

Compute the HMAC over the exact raw bytes received — re-serializing parsed JSON before verifying will produce a different signature.

Need something the API doesn't cover yet?

SSO/SAML and broader API scopes are on our enterprise roadmap. Tell us what you're building and we'll help you get there.