The Corti Embedded Assistant API enables seamless integration of Corti Assistant into host applications, such as Electronic Health Record (EHR) systems, web-based clinical portals, or native applications using embedded WebViews. The implementation provides a robust, consistent, and secure interface for parent applications to control and interact with embedded Corti Assistant.
The details outlined below are for you to embed the Corti Assistant “AI scribe solution” natively within your application. To lean more about the full Corti API, please see more here

Overview

This implementation provides a robust, consistent, and secure interface for parent applications to control and interact with the embedded Corti Assistant. The API supports both asynchronous (postMessage) and synchronous (window object) integration modes.

Integration Modes

1. PostMessage API

This method is recommended for iframe/WebView integration
  • Secure cross-origin communication
  • Works with any iframe or WebView implementation
  • Fully asynchronous with request/response pattern

Basic example

<!-- Load the embedded Corti iframe -->
<iframe id="corti-iframe" src="/embedded" width="100%" height="600px"></iframe>

<script>
const iframe = document.getElementById('corti-iframe');
let isReady = false;

// Listen for the ready event
window.addEventListener('message', async (event) => {
  if (event.data?.type === 'CORTI_EMBEDDED_EVENT' && event.data.event === 'ready') {
    isReady = true;
    console.log('Corti embedded app is ready');

    // Start the integration flow
    await authenticateUser();
  }
});

async function authenticateUser() {
  // Send authentication request
  iframe.contentWindow.postMessage({
    type: 'CORTI_EMBEDDED',
    version: 'v1',
    action: 'auth',
    requestId: 'auth-1',
    payload: {
      mode: 'stateful',
      accessToken: 'your-access-token',
      refreshToken: 'your-refresh-token'
    }
  }, '*');
}
</script>

2. Window API

This method is recommended for direct integration
  • Synchronous typescript API via window.CortiEmbedded
  • Promise-based methods
  • Ideal for same-origin integrations

Basic example

// Wait for the embedded app to be ready
window.addEventListener('message', async (event) => {
  if (event.data?.type === 'CORTI_EMBEDDED_EVENT' && event.data.event === 'ready') {
    // Use the window API directly
    const api = window.CortiEmbedded.v1;
    const user = await api.auth({
      mode: 'stateful',
      accessToken: 'your-access-token',
      refreshToken: 'your-refresh-token'
    });

    console.log('Authenticated user:', user);
  }
});

Authentication

Authenticate the user session with the embedded app.

PostMessage

iframe.contentWindow.postMessage({
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: 'auth',
  requestId: 'unique-id',
  payload: {
    mode: 'stateless' | 'stateful', // we currently do not take this value into account and will always refresh the token internally
    access_token: string,
    refresh_token?: string,
    id_token?: string,
    expires_in?: number,
    token_type?: string
  }
}, '*');

Window API

const api = window.CortiEmbedded.v1;
const user = await api.auth({
  mode: 'stateful',
  access_token: 'token',
  refresh_token: 'refresh-token'
});

Create interaction

iframe.contentWindow.postMessage({
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: 'createInteraction',
  payload: {
    assignedUserId: null,
    encounter: {
      identifier: `encounter-${Date.now()}`,
      status: "planned",
      type: "first_consultation",
      period: {
        startedAt: new Date().toISOString(),
      },
      title: "Initial Consultation",
    },
    patient: {
      identifier: "brief-clinical-note-en",
    },
}, '*');

Add Facts

Add contextual facts to the current interaction.

PostMessage

iframe.contentWindow.postMessage({
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: 'addFacts',
  requestId: 'unique-id',
  payload: {
    facts: [
      { text: "Chest pain", group: "other" },
      { text: "Shortness of breath", group: "other" },
      { text: "Fatigue", group: "other" },
      { text: "Dizziness", group: "other" },
      { text: "Nausea", group: "other" },
    ]
  }
}, '*');

Window API

const api = window.CortiEmbedded.v1;
await api.addFacts({
  facts: [
    { text: "Chest pain", group: "other" },
    { text: "Shortness of breath", group: "other" },
    { text: "Fatigue", group: "other" },
    { text: "Dizziness", group: "other" },
    { text: "Nausea", group: "other" },
  ]
});

Configure Session

Set session-level defaults and preferences.

PostMessage

iframe.contentWindow.postMessage({
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: 'configureSession',
  requestId: 'unique-id',
  payload: {
    defaultLanguage: 'en',
    defaultOutputLanguage: 'en',
    defaultTemplateKey: 'soap_note',
    defaultMode: 'virtual'
  }
}, '*');

Window API

const api = window.CortiEmbedded.v1;
await api.configureSession({
  defaultLanguage: 'en',
  defaultOutputLanguage: 'en',
  defaultTemplateKey: 'soap_note',
  defaultMode: 'virtual'
});
Navigate to a specific path within the embedded app.

PostMessage

iframe.contentWindow.postMessage({
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: 'navigate',
  requestId: 'unique-id',
  payload: {
    path: '/session/interaction-123'
  }
}, '*');

Window API

const api = window.CortiEmbedded.v1;
await api.navigate({
  path: '/session/interaction-123'
});

Recording controls

Start and stop recording within the embedded session.

PostMessage

// Start recording
iframe.contentWindow.postMessage({
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: 'startRecording',
  requestId: 'unique-id'
}, '*');

// Stop recording
iframe.contentWindow.postMessage({
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: 'stopRecording',
  requestId: 'unique-id'
}, '*');

Window APIs

const api = window.CortiEmbedded.v1;
await api.startRecording();
await api.stopRecording();

Events

The embedded app sends events to notify the parent application of important state changes.

Event Types

readyEmbedded app is loaded and ready
loadedNavigation to a specific path completed
recordingStartedRecording has started
recordingStoppedRecording has stopped
documentGeneratedA document has been generated
documentUpdated A document has been updated
documentSyncedA document has been synced to EHR

Listening for Events

window.addEventListener('message', (event) => {
  if (event.data?.type === 'CORTI_EMBEDDED_EVENT') {
    switch (event.data.event) {
      case 'ready':
        console.log('Embedded app ready');
        break;
      case 'documentGenerated':
        console.log('Document generated:', event.data.payload.document);
        break;
      case 'recordingStarted':
        console.log('Recording started');
        break;
      // ... handle other events
    }
  }
});

Complete Integration Flow

Here’s a complete example showing the recommended integration steps:
// State management
let iframe = null;
let isReady = false;
let currentInteractionId = null;
let pendingRequests = new Map();

// Initialize the integration
function initializeCortiEmbeddedIntegration(iframeElement) {
  iframe = iframeElement;
  isReady = false;
  currentInteractionId = null;

  setupEventListeners();
}

function setupEventListeners() {
  window.addEventListener('message', (event) => {
    if (event.data?.type === 'CORTI_EMBEDDED_EVENT') {
      handleEvent(event.data);
    }
  });
}

function handleEvent(eventData) {
  switch (eventData.event) {
    case 'ready':
      isReady = true;
      startIntegrationFlow();
      break;
    case 'documentGenerated':
      onDocumentGenerated(eventData.payload.document);
      break;
    // ... handle other events
  }
}

async function startIntegrationFlow() {
  try {
    // 1. Authenticate
    await authenticate();

    // 2. Configure session
    await configureSession();

    // 3. Create interaction
    const interaction = await createInteraction();

    // 4. Add relevant facts
    await addFacts();

    // 5. Navigate to interaction UI
    await navigateToSession(interaction.id);

    console.log('Integration flow completed successfully');
  } catch (error) {
    console.error('Integration flow failed:', error);
  }
}

async function authenticate() {
  return new Promise((resolve, reject) => {
    const requestId = generateRequestId();

    pendingRequests.set(requestId, { resolve, reject });

    iframe.contentWindow.postMessage({
      type: 'CORTI_EMBEDDED',
      version: 'v1',
      action: 'auth',
      requestId,
      payload: {
        mode: 'stateful',
        accessToken: 'your-accesstoken',
        refreshToken: 'your-refreshtoken',
        ...
      }
    }, '*');
  });
}

// Usage example:
const iframeElement = document.getElementById('corti-iframe');
initializeCortiEmbeddedIntegration(iframeElement);

Error Handling

All API methods can throw errors. Always wrap calls in try-catch blocks:
try {
  const api = window.CortiEmbedded.v1;
  const user = await api.auth(authPayload);
  console.log('Authentication successful:', user);
} catch (error) {
  console.error('Authentication failed:', error.message);
  // Handle authentication failure
}

Demo Page

Visit /embedded-demo to see a working integration example with interactive controls. To authenticate - grab the full auth response from sessionStorage and paste it into the input field. The rest of the actions use static data for the messages to create resources i.e. interaction, facts.
This is also used for manual testing/validation/development

Observing postMessages

There is a chrome extension that you can install to observe the messages coming to/from the application. Link: postMessage debugger

Testing Utilities

Use the embedded-demo page for manual testing. There’s some utility methods set up for potential e2e test, but this is not configured yet, as it also expects a full auth-response that is inserted manually. We could potentially use this page later for e2e testing.
import { EmbeddedAPITester, runIntegrationTest } from './utils';

const tester = new EmbeddedAPITester('/embedded');
await tester.loadEmbeddedFrame(container);
await runIntegrationTest(tester); // TODO

Please contact us for help or questions.