VIEWPORT
Reference

API

REST endpoints, WebSocket protocol, and the auth model that ties them together.

Viewport exposes two protocol surfaces:

  • REST at https://api.getviewport.com for control-plane reads and writes (workspaces, machines, pairing, plans, inbox, audit, vault, workflows).
  • WebSocket for both the daemon's local control surface and the relay-mediated transport between daemons and browser clients.

There is no OpenAPI document today. The canonical wire shapes live in code:

Auth model

Two principals talk to the platform: human users via a browser session, and daemons via a per-install issue token.

Browser sessions

The control plane uses WorkOS AuthKit for SSO/login (platform/apps/api/routes/auth.php). Once authenticated, the user gets a Laravel session bridged into the Sanctum guard. All auth:sanctum routes accept this session cookie.

There is no config/sanctum.php override in the repo. Sanctum runs on framework defaults.

Daemon → platform

Daemons pair via the pairing flow and receive a per-install issue token (bcrypt-hashed in installs.daemon_issue_token_hash). The daemon presents this token to runtime endpoints (/api/runtime/*, throttled). The token grants a relay JWT via POST /api/runtime/relay-token, which the daemon then presents on its WebSocket upgrade to the relay.

Personal access tokens

Users can mint personal API tokens at /settings/api-tokens:

MethodPathPurpose
GET/api/me/api-tokensList
POST/api/me/api-tokensCreate (returns plaintext once)
DELETE/api/me/api-tokens/{tokenId}Revoke

These are Sanctum personal tokens and authorize the same API surface as a session.

REST surface

Grouped by feature. Authentication is auth:sanctum unless noted. {workspace} is a ULID; /api/resources/{workspace}/... is an alias for /api/workspaces/{workspace}/... (via apiResource(parameters: ['resources' => 'workspace'])).

Public

MethodPathPurpose
GET/api/healthHealth probe
GET/api/.well-known/jwks.jsonJWKS for relay-token JWT verification
POST/api/integrations/webhooks/{endpoint}Inbound webhook delivery (HMAC-verified)

Auth & profile

MethodPathPurpose
GET/api/auth/loginWorkOS login URL
GET/api/auth/callbackWorkOS callback
POST/api/auth/logoutEnd the session
GET/api/meCurrent user
PUT/api/meUpdate profile
PUT/api/me/preferencesUpdate preferences

Workspaces & members

MethodPathPurpose
(all)/api/workspaces (apiResource)CRUD on workspaces
GET / POST / PATCH / DELETE/api/workspaces/{workspace}/membersMembership
POST/api/workspaces/{workspace}/members/transfer-ownerTransfer ownership

Pairing

MethodPathAuthPurpose
POST/api/workspaces/{workspace}/pairing-codessanctumMint a web-initiated pairing code
POST/api/pairing-codesthrottle:pairing-publicDaemon-initiated pairing code (no auth)
POST/api/pairing-codes/{code}/claimthrottle:pairing-publicDaemon claims a web-initiated code
GET/api/pairing-codes/{code}/statusthrottle:pairing-publicDaemon polls for approval
POST/api/pairing-codes/{code}/approvesanctumUser approves a claimed code
POST/api/pairing-codes/{code}/denysanctumUser denies

Machines & installs

MethodPathPurpose
GET/api/installsAll installs across the user's workspaces
GET/api/workspaces/{workspace}/installsScoped to workspace
POST/api/workspaces/{workspace}/installs/{install}/heartbeatDaemon liveness
DELETE/api/workspaces/{workspace}/installs/{install}Revoke pair (the "unpair" surface)
GET / DELETE/api/resources/{workspace}/machines[/{machine}]Machines (user-owned identities)

Relay token

MethodPathPurpose
POST/api/workspaces/{workspace}/relay-tokenBrowser-side relay JWT

Plans, inbox, vault, workflows

For the full feature CRUDs, see:

Sharing & ACLs

MethodPathPurpose
GET/api/resources/{workspace}/shared-resourcesResource ACL listing
GET/api/resources/{workspace}/share-principalsUsers/teams selectable for sharing
POST/api/resources/{workspace}/shared-resources/{resource}/aclShare
DELETE/api/resources/{workspace}/shared-resources/{resource}/acl/{aclEntry}Unshare
(CRUD)/api/resources/{workspace}/share-groupsNamed groups (Reviewers, On-call, etc.)

Audit

MethodPathPurpose
GET/api/workspaces/{workspace}/auditAudit feed (filterable; 90-day default retention)

Runtime (daemon-facing, JWT-authed)

MethodPathPurpose
POST/api/runtime/relay-tokenDaemon obtains a relay JWT
POST/api/runtime/workspaces/{workspace}/daemon-keyDaemon registers its public key
POST/api/runtime/workspaces/{workspace}/agent-hooks/plansRuntime-side plan-hook ingest
GET / POST/api/runtime/workspaces/{workspace}/context-vaultsRuntime vault read/write
POST/api/runtime/workspaces/{workspace}/context-vault/events/pushPush signed encrypted events (server never decrypts)
POST/api/runtime/workspaces/{workspace}/context-vault/events/pullPull signed encrypted events
PATCH/api/runtime/workspaces/{workspace}/workflow-runs/{run}/syncDaemon → control-plane run sync
POST/api/heartbeatDaemon health ping (anon, throttled)

Relay internal

These are called by the relay itself, gated by a shared RELAY_INTERNAL_KEY:

MethodPathPurpose
POST/api/runtime/internal/relay/validateRelay validates a JWT before admitting a WS upgrade
POST/api/runtime/internal/relay/presence/upsertMulti-relay backplane: record presence
POST/api/runtime/internal/relay/presence/resolveFind which relay holds a given workspace
POST/api/runtime/internal/relay/bus/publish and /pullServer-mode cross-relay bus

WebSocket protocol

Daemon ↔ client (incoming, validated by Zod)

Source: ws-protocol.ts, defined as IncomingMessageSchema = z.discriminatedUnion('type', [...]).

typeRequired fields
launchdirectoryId, optional prompt, model, thinkingMode, images, configOverrides, requestId
killsessionId
promptsessionId, text, optional images
respond-permissionsessionId, permissionRequestId, decision.behavior (one of allow, deny, allow-always), optional decision.message
subscribesessionId, optional lastSeq
unsubscribesessionId
rollbacksessionId, toSha
branch-retrysessionId, fromSha
squash-mergesessionId, targetBranch, commitMessage
list-sessionsdirectoryId, optional limit, offset
read-session-messagesdirectoryId, sessionId, optional limit, offset, `delivery: 'ack'
resumesessionId, directoryId, optional prompt, model
watch-discovered-sessionsessionId, optional directoryId
unwatch-discovered-sessionsame
sync-requestoptional requestId
workflow-rundirectoryId, workflowPath or workflowYaml, optional inputs, runtimeTargetId, platformRunId, rerunOfWorkflowRunId, executionPolicy{mode, branch?}, dataCapturePolicy{transcripts, logs, artifacts}
workflow-list-runsoptional limit
workflow-show-runrunId
workflow-approverunId, nodeId, approved, optional message, actor
workflow-cancelrunId, optional message, actor
supervisesessionId, active
respond-hook-permissionhookRequestId, decision.behavior (one of allow, deny), optional decision.message

Daemon ↔ client (outgoing)

typeNotes
ackReply with `status: ok
session-update{sessionId, seq, update: {updateType, ...}}
session-started{sessionId, directoryId}
session-alertSession-level alert
session-ended{sessionId, reason?}
discovered-sessions-updatedSnapshot of out-of-band agent sessions
discovered-session-tailTail event for a discovered session
discovered-session-waitingDiscovered session is awaiting input
workflow-run-updatedWorkflow run state update
hook-session-start / hook-session-endClaude Code hook bridged
hook-permission-requestHook-mediated permission gate
hook-notificationHook notification
hook-tool-completed / hook-tool-failedTool lifecycle
hook-stopStop signal
hook-subagent-start / hook-subagent-stopSub-agent lifecycle
hook-plan-proposedPlan proposed via the hook bridge

update.updateType (inside session-update frames)

agent-message, agent-message-chunk, agent-thought-chunk, user-message, tool-call, tool-call-update, permission-request, permission-resolved, state-change, step-committed, token-usage, system-status, attention, streaming-state.

Full per-update Zod schemas: platform/apps/web/src/lib/protocol-types.ts.

Relay WS upgrade

GET wss://relay.getviewport.com/ws
    ?role=workspace-daemon|client
    &workspaceId=ULID
    &runtimeTargetId=ULID           # optional, scopes to one machine

Auth: a JWT in Authorization: Bearer …, the Sec-WebSocket-Protocol subprotocol header, or ?token=. The relay calls POST /api/runtime/internal/relay/validate to admit the upgrade.

The relay does not decrypt frames. It identifies envelopes (workspace, runtime target) and routes daemon ↔ client. End-to-end encryption is handled by the daemon-side Noise-IK / Noise-IKpsk2 session inside the envelope.

Pairing flow (end-to-end)

  ┌──────────┐    1. POST /api/workspaces/{ws}/pairing-codes      ┌────────────┐
  │ Browser  ├────────────────────────────────────────────────────►            │
  │          │◄───────────────────────── code ─────────────────────│ Platform  │
  └────┬─────┘                                                     │  API      │
       │  2. shows code                                            └─────┬──────┘
       │                                                                 │
  ┌────▼────┐    3. POST /api/pairing-codes/{code}/claim                 │
  │ Daemon  ├─────────────────────────────────────────────────────────────►
  │ vpd pair│◄──────────────────── status_token ──────────────────────────
  │  <code> │
  │         │    4. GET /api/pairing-codes/{code}/status (poll, with token)
  │         ├─────────────────────────────────────────────────────────────►
  └─────────┘
       ▲                                                                 │
       │              5. User clicks Approve in browser                  │
       │             ┌───────────────────────────────────────────────────┘
       │             │  POST /api/pairing-codes/{code}/approve
       │             ▼
       │       The platform binds machine → workspace, retires any
       │       prior install for that pair, and issues a fresh relay
       │       credential for the daemon (token hash stored, plaintext
       │       returned once in the approve response).

       │  6. status returns approved + relay_credentials
       └─── 7. daemon writes config, restartDaemon() ────► connects to relay

Same flow for daemon-initiated pairing, just swap steps 1 and 3.

Protocol-matrix CI

packages/daemon/scripts/check-protocol-matrix.mjs is the cross-package consistency check between ws-protocol.ts and platform/apps/web/src/lib/protocol.ts. Reference: viewport/packages/daemon/docs/protocol-matrix.json. Run via npm run check:protocol-matrix in the daemon package.

What's NOT here today

PlannedThere is no separately published `@viewportai/protocol` or `@viewportai/client-sdk` package. The protocol types live in the daemon repo and are hand-mirrored into the web app. A standalone protocol package is on the roadmap but not shipped.
  • Sessions for what session-update frames mean
  • CLI for the surface that consumes the local-daemon WS
  • Self-host for running your own relay

On this page