Verentis

The SDK & bridge

The SDK & postMessage bridge

How your app talks to the workspace host — the postMessage protocol, scoped tokens, and file access via the Verentis SDK.

Your app runs in a sandboxed iframe, isolated from the workspace host. The Verentis SDK (@verentis/sdk) connects the two over a postMessage protocol so your app can receive launch context and call platform APIs with a short-lived token — without ever handling raw credentials.

The bridge in one picture

┌─────────────────────────────┐        postMessage         ┌──────────────────────┐
│  Workspace host (parent)     │ ◄────────────────────────► │  Your app (iframe)    │
│  • injects scoped tokens     │      init / events         │  • @verentis/sdk      │
│  • owns workspace routing    │                            │  • view / edit UI     │
└─────────────────────────────┘                            └──────────────────────┘

The host is the trusted broker. It holds the user session, mints short-lived tokens, and validates each request against the permissions the workspace consented to for your app.

Initialising the SDK

Connect to the host when your app loads:

import { createVerentisClient } from '@verentis/sdk'

const client = createVerentisClient()
await client.whenReady()

const { surface, workspace, entryPoint } = client.context

Tokens are injected, not stored

You never embed a client secret or API key in an iframe app. The host injects and refreshes a short-lived token containing the manifest permissions granted to the app. SDK modules use that token automatically.

File and workspace launch context

The context is a discriminated union:

if (client.context.surface === 'file') {
  const file = client.context.file
  const content = await client.files.readContent(file.path)
} else {
  // A spec.launch app opened without a selected file.
  // client.context.file is unavailable on this surface.
  renderWorkspaceHome(client.context.workspace)
}
Context propertyFile launchspec.launch workspace launch
surface'file''workspace'
workspacePresentPresent
file / hasFileFile metadata / trueThrows if read / false
entryPointMatched file-handler entry pointConfigured launch entry point (or default)
apiUrlPresentPresent

Older hosts sent file context without surface; the SDK treats that legacy shape as file.

Two meanings of standalone

Manifest spec.launch still runs inside the workspace iframe and receives surface: 'workspace'. SDK “standalone mode” means running outside any iframe with explicit apiUrl, credentials and workspaceId; it is a separate local-development mode.

Reading and writing files

Use VFS paths with the SDK:

const content = await client.files.readContent('/reports/q1.json')

await client.files.write('/reports/q1.json', {
  content: updatedJson,
  contentType: 'application/json',
})

These calls go through the platform file/node APIs with the injected token. Declare the corresponding permissions in the manifest; see Working with files & nodes.

WOPI document sessions

For an approved file-bound editor, the same bridge exposes these methods:

MethodResult and responsibility
requestWopiLaunch()WopiLaunch: action URL, operator origin, WOPISrc, document-scoped token, absolute expiry and document state.
requestWopiSave(generation)WopiSaveCheckpoint: safe nonnegative integer generation, correlation and optional revision. Requests a checkpoint, not a document upload.
getWopiStatus()WopiSaveStatus: state, revision, read-only flag, receipt and optional continuationAvailable.
requestWopiContinuation()WopiContinuation: rebind to the target of a completed server-owned derived operation. No new access token in this response.
closeWopiSession()Release session participation after the wrapper has handled pending edits.

These methods require an initialized workspace bridge. The host binds requests to the current iframe session and installed app; callers cannot choose arbitrary document authority. WOPI-only wrappers do not request ordinary backend credentials or use WOPI tokens as the bearer token for client.files calls.

await client.whenReady()
if (!client.bridge || client.context.surface !== 'file' ||
    !client.context.installation?.wopiEnabled) {
  throw new Error('This is not an approved WOPI launch.')
}
const launch = await client.bridge.requestWopiLaunch()
// Pass launch only into the wrapper's validated CODE framing flow.

Track dirty generations, send CODE the checkpoint correlation and wait for a matching durable receipt before reporting a save. Neither the checkpoint request nor CODE's save-response message alone confirms persistence. See WOPI integration for the framing, save, Save As and failure-handling sequence.

Host navigation

Ask the host to navigate instead of assigning the parent window:

client.bridge?.navigate('/reports/q1.json')
client.bridge?.navigate('/reports/q1.json', { newTab: true })
client.bridge?.navigate('https://docs.example.com', { newTab: true })

A VFS path is converted to /files/** by the host. /files/** is also accepted, but old root-level browser URLs are not redirected. Absolute HTTP(S) targets require the navigation:external capability; other schemes and protocol-relative URLs are rejected.

Hosted application actions

The workspace content header is shared by file browsing, application views, and execution views. For file-bound applications it displays the file name first, with application identity beneath it. Register document-level actions through SDK 0.2 rather than rendering a second Save/Copy toolbar:

await client.whenReady()

const descriptor = {
  id: 'document.save',
  label: 'Save',
  icon: 'lucide:save',
  scopes: ['node.file.read', 'node.node.create', 'node.node.update', 'node.journal.create'],
  enabled: false,
}

const action = client.bridge!.registerAction(descriptor, async () => {
  await saveDocument()
})

action.update({ ...descriptor, enabled: true })
// When the owning view is disposed:
action.dispose()

Handlers remain inside your iframe. The host receives only typed descriptors and sends an invocation back to the SDK, which awaits the handler and returns its outcome. Throw failures instead of catching them and returning success. Scope requirements must fit the delegated token; scopes: [] explicitly identifies client-only operations. Supported presentation fields are icon (a Lucide identifier), variant (default, outline, destructive), enabled, visible, busy, and disabledReason.

Use kind: 'clipboard' and return { clipboardText: source } for a host-owned clipboard operation. Return { navigate: { path: '/target' } } when a completed mutation should navigate, so navigation does not destroy the iframe before its result reaches the host.

Actions are bound to the current iframe session and cleared on reload/navigation. Timeouts mean the result is unknown; mutations are not retried automatically. At most 16 actions can be registered. There is no legacy toolbar fallback: coordinate the SDK, host, and app releases. Fullscreen entries hide the host header only when they have no registered actions.

Keep specialized controls such as zoom, preview/source modes, and contextual form buttons inside your application. Platform Run is not an inferred application capability: users open Source to reach the existing execution view, and must save unsaved editor changes before that transition.

Responsive iframe surfaces

The workspace uses a persistent sidebar on large screens and a navigation drawer on smaller screens. Notifications, the inspector and the assistant open as overlay panels. Your iframe therefore changes size without a page reload.

Build against the iframe's available width rather than 100vw, use fluid breakpoints, and test both file and workspace launch surfaces. client.bridge?.resize(height) is available for intrinsic-height content; a full-height application should normally fill the host-provided container.

Standalone development

While iterating outside the platform, create the client with an API URL, API key or bearer token, and workspace ID. whenReady() resolves immediately and bridge is null:

const client = createVerentisClient({
  apiUrl: 'https://api.localtest.me:6500',
  auth: { apiKey: 'vrt_...' },
  workspaceId: 'workspace-uuid',
})

Language SDKs for engines

Execution engines use the same pattern with a language-appropriate SDK (e.g. Python) inside the runtime. The shape of the API — context, scoped token, file read/write — mirrors the TypeScript SDK. See The execution model.

Next

Installation & consent

How your app's OAuth client and scopes are approved on install.