Skip to main content
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
PostMessage
<!-- 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
Window API
// 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:
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
  }
}, '*');

Configure interface

The configure command allows you to configure the Assistant interface for the current session, include toggling which UI features are visible, the visual appearance of assistant, and locale settings.
iframe.contentWindow.postMessage(
  {
    type: "CORTI_EMBEDDED",
    version: "v1",
    action: "configure",
    payload: {
      features: {
        interactionTitle: false,
        aiChat: false,
        documentFeedback: false,
        navigation: true,
        virtualMode: true,
      },
      appearance: {
        primaryColor: "#00a6ff",
      },
      locale: {
        interfaceLanguage: "de-DE",
        dictationLanguage: "da-DK",
      },
    },
  },
  "*"
);
The defaults are as follows:
  • features.interactionTitle: true
  • features.aiChat: true
  • features.documentFeedback: true
  • features.navigation: false
  • features.virtualMode: true (regardless of user preferences, the option can be hidden and “disabled”, which means it won’t show up on the UI and even if the session is configured to be virtual, it won’t be if this is toggled after the session configuration message)
  • appearance.primaryColor: null (uses built-in styles, which is blue-ish)
  • locale.interfaceLanguage: null (uses the current user’s specified default language or determined by browser setting if not specified)
  • locale.dictationLanguage: "en"
The configure command can be invoked at any time and will take effect instantly. The command can be invoked with a partial object, and only the specified properties will take effect. The command returns the full currently applied configuration object. Note that if appearance.primaryColor has not been set, it will always return as null indicating default colors will be used, unlike locale.interfaceLanguage or locale.dictationLanguage which will return whatever actual language is currently used for the given setting.

Appearance

Disclaimer - always ensure WCAG 2.2 AA conformance
Corti Assistant’s default theme has been evaluated against WCAG 2.2 Level AA and meets applicable success criteria in our supported browsers. This conformance claim applies only to the default configuration. Customer changes (e.g., color palettes, CSS overrides, third-party widgets, or content) are outside the scope of this claim. Customers are responsible for ensuring their customizations continue to meet WCAG 2.2 AA (including color contrast and focus visibility). When supplying a custom accent or theme, Customers must ensure WCAG 2.2 AA conformance, including:
  • 1.4.3 Contrast (Minimum): normal text ≥ 4.5:1; large text ≥ 3:1
  • 1.4.11 Non-text Contrast: UI boundaries, focus rings, and selected states ≥ 3:1
  • 2.4.11 Focus Not Obscured (Minimum): focus indicators remain visible and unobstructed
Corti provides accessible defaults. If you override them, verify contrast for all states (default, hover, active, disabled, focus) and on all backgrounds you use.

Available interface languages

Updated as per November 2025
Language code Language
enEnglish
de-DEGerman
fr-FRFrench
it-ITItalian
sv-SESwedish
da-DKDanish

Available dictation languages

Updated as per November 2025

EU

Language code Language
enEnglish
en-GBBritish English
deGerman
frFrench
svSwedish
daDanish
nlDutch
noNorwegian

US

Language code Language
enEnglish

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",
    },
  }
}, '*');

Add Facts

Add contextual facts to the current interaction:
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" },
    ]
  }
}, '*');

Configure Session

Set session-level defaults and preferences:
iframe.contentWindow.postMessage({
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: 'configureSession',
  requestId: 'unique-id',
  payload: {
    defaultLanguage: 'en',
    defaultOutputLanguage: 'en',
    defaultTemplateKey: 'soap_note',
    defaultMode: 'virtual'
  }
}, '*');
Navigate to a specific path within the embedded app:
iframe.contentWindow.postMessage({
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: 'navigate',
  requestId: 'unique-id',
  payload: {
    path: '/session/interaction-123'
  }
}, '*');
Navigable URL’s include:
  • / – start a new session
  • /session/<id> - go to an existing session identified by <id>
  • /templates - browse and create templates
  • /settings/preferences - edit defaults like languages and default session settings
  • /settings/input - edit dictation input settings
  • /settings/account - edit general account settings
  • /settings/archive - view items in and restore from archive (only relevant if navigation is visible)

Set credentials

Change the credentials of the currently authenticated user. This can be used both to set the credentials for a user without a password (if only authenticated via identity provider) or to change the password of a user with an existing password. The current password policy must be followed:
  • At least 1 uppercase, 1 lowercase, 1 numerical and 1 special character
  • At least 8 characters long
iframe.contentWindow.postMessage({
  type: 'CORTI_EMBEDDED',
  version: 'v1',
  action: 'setCredentials',
  requestId: 'unique-id',
  payload: { password: 'new-password' }
}, '*');

Recording controls

Start and stop recording within the embedded session:
// 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'
}, '\*');

Get status

The getStatus method allows you to request information about the current state of the application, including: authentication status, current user, current URL and interaction details.
iframe.contentWindow.postMessage(
  {
    type: "CORTI_EMBEDDED",
    version: "v1",
    action: "getStatus"
  },
  "*"
);
The response is an object containing information about the current state of the application. The interaction in particular contains a list of resources combined with their respective metadata.
{
  "auth": {
    "isAuthenticated": "<boolean>",
    "user": {
      "id": "<string>",
      "email": "<string>"
    }, // User OR null if not authenticated
  },
  "currentUrl": "<string>",
  "interaction": {
    "id": "<string>",
    "title": "<string>",
    "state": "<planned | ongoing | paused | disconnected | ending | parsing | ended>",
    "startedAt": "<string>",
    "endedAt": "<string | null>",
    "endsAt": "<string | null>",
    "transcripts": [{
      "utterances": [{
        "id": "<string>",
        "start": "<number>",
        "duration": "<number>",
        "text": "<string>",
        "isFinal": "<boolean>",
        "participantId": "<string | undefined>",
      }],
      "participants": [{
        "id": "<string>",
        "channel": "<number>",
        "role": "<agent | patient | other | multiple>"
      }],
      "isMultiChannel": "<boolean>"
    }],
    "documents": [{
      "id": "<string>",
      "name": "<string>",
      "templateRef": "<string>",
      "isStream": "<boolean>",
      "sections": [{
        "key": "<string>",
        "name": "<string>",
        "text": "<string>",
        "sort": "<number>",
        "createdAt": "<string>",
        "updatedAt": "<string>",
        "markdown": "<string | undefined | null>",
        "htmlText": "<string | undefined>",
        "plainText": "<string | undefined>",
      }],
      "outputLanguage": "<string>",
    }],
    "facts": [{
      "id": "<string>",
      "text": "<string>",
      "group": "<string>",
      "isDiscarded": "<boolean>",
      "source": "<core | system | user>",
      "createdAt": "<string | undefined>",
      "updatedAt": "<string>",
      "isNew": "<boolean>",
      "isDraft": "<boolean | undefined>",
    }],
    "websocketUrl": "<string>"
  }
}

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

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:
Example Embedded Integration
// 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:
Error Handling
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
}

Please contact us for help or questions.