> ## Documentation Index
> Fetch the complete documentation index at: https://developer.suki.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create an Interoperable Headless Session

> Pass emrEncounterId on Headless create, store noteId, and clear a remote session conflict before retry

export const CbRecipeMeta = ({items = []}) => <div className="cb-recipe-meta">
    {items.map(item => <span className={`cb-recipe-pill cb-recipe-pill--${item.type}${item.tone ? ` cb-recipe-pill--${item.tone}` : ""}`} key={`${item.type}-${item.label}`}>
        {item.type === "time" ? <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <circle cx="12" cy="12" r="10" />
            <polyline points="12 6 12 12 16 14" />
          </svg> : item.type === "level" ? <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <path d="M2 20h.01" />
            <path d="M7 20v-4" />
            <path d="M12 20v-8" />
            <path d="M17 20V8" />
            <path d="M22 20V4" />
          </svg> : item.type === "surface" ? <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <polyline points="16 18 22 12 16 6" />
            <polyline points="8 6 2 12 8 18" />
          </svg> : <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <path d="M12 3v3" />
            <path d="M12 18v3" />
            <path d="M3 12h3" />
            <path d="M18 12h3" />
            <path d="M5.6 5.6l2.1 2.1" />
            <path d="M16.3 16.3l2.1 2.1" />
            <path d="M5.6 18.4l2.1-2.1" />
            <path d="M16.3 7.7l2.1-2.1" />
          </svg>}
        {item.label}
      </span>)}
  </div>;

export const CbRecipePage = ({title, description, meta, recipeId, sidebar = {}, children}) => {
  const {tutorialHref, tutorialLabel = "Open Full Tutorial", apiHref, apiLabel = "View API Reference", authSections = [], glance = [], headers = []} = sidebar;
  return <div className="hp-wrap api-overview-wrap docs-frame-wrap cb-recipe-wrap" data-cb-recipe data-disable-read-time="true">
      <header className="cb-recipe-hero tut-hub-intro">
        <h1 className="sdk-overview-main-title api-overview-hero-page-title">{title}</h1>
        {description ? <div className="wse-prose">{description}</div> : null}
        {meta ? <div className="cb-recipe-meta-slot">{meta}</div> : null}
      </header>

      <div className="cb-recipe-body">
        <div className="cb-recipe-layout">
          <div className="cb-recipe-main prose prose-gray dark:prose-invert">{children}</div>

          <aside className="cb-recipe-sidebar" aria-label="Recipe actions and metadata">
            {recipeId ? <div className="cb-recipe-sidebar-card cb-recipe-sidebar-card--run">
                <div className="cb-run" data-cb-run data-recipe-id={recipeId} data-recipe-title={title}>
                  <button type="button" className="cb-run-trigger" aria-expanded="false">
                    <svg className="cb-run-trigger-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
                      <path d="M8 5v14l11-7L8 5z" />
                    </svg>
                    <span>Run This Recipe</span>
                    <svg className="cb-run-trigger-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
                      <polyline points="6 9 12 15 18 9" />
                    </svg>
                  </button>
                </div>
              </div> : null}

            <div className="cb-recipe-sidebar-card cb-recipe-sidebar-card--actions">
              <div className="cb-recipe-sidebar-actions">
                {tutorialHref ? <a className="cb-recipe-action cb-recipe-action--primary" href={tutorialHref}>
                    <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
                      <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
                      <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
                    </svg>
                    <span>{tutorialLabel}</span>
                  </a> : null}
                {apiHref ? <a className="cb-recipe-action cb-recipe-action--secondary" href={apiHref}>
                    <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
                      <path d="M8 4H6a2 2 0 0 0-2 2v4" />
                      <path d="M8 20H6a2 2 0 0 1-2-2v-4" />
                      <path d="M16 4h2a2 2 0 0 1 2 2v4" />
                      <path d="M16 20h2a2 2 0 0 0 2-2v-4" />
                    </svg>
                    <span>{apiLabel}</span>
                  </a> : null}
              </div>
              <div className="cb-recipe-sidebar-caption">
                Step-by-step tutorial and API reference for this workflow.
              </div>
            </div>

            {authSections.length ? <div className="cb-recipe-sidebar-card">
                <div className="cb-recipe-sidebar-kicker">Auth</div>
                {authSections.map(section => <div className="cb-recipe-auth-block" key={section.title}>
                    <div className="cb-recipe-auth-title">{section.title}</div>
                    <div className="cb-recipe-sidebar-copy">
                      {section.text}{" "}
                      {section.href ? <a href={section.href}>{section.linkLabel || "Learn more"}</a> : null}
                    </div>
                    {section.scopes?.length ? <div className="cb-recipe-scope-pills cb-recipe-auth-scopes">
                        {section.scopes.map(scope => <span className="cb-recipe-scope-pill" key={scope}>
                            {scope}
                          </span>)}
                      </div> : null}
                  </div>)}
              </div> : null}

            {glance.length ? <div className="cb-recipe-sidebar-card">
                <div className="cb-recipe-sidebar-kicker">At a glance</div>
                <dl className="cb-recipe-glance">
                  {glance.map(row => <div className="cb-recipe-glance-row" key={row.label}>
                      <dt>{row.label}</dt>
                      <dd>{row.value}</dd>
                    </div>)}
                </dl>
              </div> : null}

            {headers.length ? <div className="cb-recipe-sidebar-card">
                <div className="cb-recipe-sidebar-kicker">Required headers</div>
                <div className="cb-recipe-auth-title">Partner API</div>
                <div className="cb-recipe-scope-pills">
                  {headers.map(h => <span className="cb-recipe-scope-pill" key={h}>
                      {h}
                    </span>)}
                </div>
              </div> : null}
          </aside>
        </div>
      </div>
    </div>;
};

<CbRecipePage
  recipeId="create-interoperable-headless-session"
  title="Create an Interoperable Headless Session"
  description="Share one clinical note across Suki products from Headless: pass `emrEncounterId` on `session.create()`, store `noteId`, and clear remote conflicts before retry. Without `emrEncounterId`, create succeeds but the note stays local to Headless."
  meta={
<CbRecipeMeta
  items={[
    { type: "time", label: "5 min" },
    { type: "level", label: "Beginner" },
    { type: "product", label: "Ambient" },
    { type: "surface", label: "Headless Web SDK", tone: "api" },
  ]}
/>
}
  sidebar={{
tutorialHref: "/documentation/tutorials/headless-ambient-hooks",
apiHref: "/headless-web-sdk/guides/ambient-interoperability",
apiLabel: "View Interoperability Guide",
authSections: [
  {
    title: "Headless Web SDK",
    text: "Authenticate PlatformClient before creating an ambient session. See",
    href: "/headless-web-sdk/authentication",
    linkLabel: "Headless authentication",
  },
],
glance: [
  { label: "Product", value: "Ambient" },
  { label: "Surface", value: "Headless Web SDK" },
  { label: "Time", value: "~5 min" },
  { label: "Level", value: "Beginner" },
],
}}
>
  ## Problem

  A Headless Web SDK ambient session is created, but the clinical note is not shared with Ambient APIs, Mobile SDK, or Web SDK. Without `emrEncounterId`, create still succeeds, but other Suki products cannot find or attach to the same note.

  Interoperable ambient sessions require `emrEncounterId` on create, plus a reusable `encounterId` for re-ambient grouping. Store `ambientSessionId` and `noteId` from the create response, and handle `sessionAlreadyExists` conflicts before retry.

  ## Architecture

  ```mermaid actions={false} theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  %%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#FFFBDE','primaryTextColor':'#1C1C1C','primaryBorderColor':'#FFD147','lineColor':'#6F5410','secondaryColor':'#FFFDF5','tertiaryColor':'#FFEB94','fontSize':'13px'}}}%%
  flowchart LR
    A[Auth PlatformClient] --> B[session.create]
    B --> C[Store IDs]
    C --> D{Conflict}
    D -->|No| E[Stream ambient]
    D -->|sessionAlreadyExists| F[cancelRemote / endRemote]
    F --> B
  ```

  ## Prerequisites

  <Check>App wrapped with `PlatformClientProvider` and `useAuth` completed before calling ambient hooks.</Check>

  <Check>`@suki-sdk/platform-react` **v0.3.0** or later installed. See [Ambient interoperability](/headless-web-sdk/guides/ambient-interoperability).</Check>

  <Check>An `emrEncounterId` (UUID or non-UUID, at most **36 characters**) and a reusable `encounterId` for the visit note.</Check>

  ## Solution

  On `session.create()`, pass an **`emrEncounterId`** (maps to `emr_encounter_id`; **UUID or non-UUID**, at most **36 characters**) and a reusable **`encounterId`** (maps to `encounter_id`). Store **`ambientSessionId`** and **`noteId`**. If create fails with `sessionAlreadyExists`, clear the blocking session with `cancelRemote` or `endRemote`, then retry.

  ```tsx React theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  import { useAmbient } from "@suki-sdk/platform-react";

  export function CreateInteroperableHeadlessSession() {
    const { cancelRemote, endRemote, session } = useAmbient();

    const emrEncounterId = "6ec3920f-b0b1-499d-a4e9-889bf788e5ab";
    const encounterId = "visit-note-1"; // reuse for every re-ambient on this note

    async function createInteroperableSession() {
      try {
        const result = await session.create({
          encounterId,
          emrEncounterId,
        });

        // result.ambientSessionId → pass to useAmbientSession
        // result.noteId → Ambient API note-level endpoints
        return result;
      } catch (error) {
        return resolveConflictAndRetry(error);
      }
    }

    async function resolveConflictAndRetry(error: unknown) {
      if (
        !error ||
        typeof error !== "object" ||
        !("reason" in error) ||
        error.reason !== "sessionAlreadyExists"
      ) {
        throw error;
      }

      const additionalProperties =
        "additionalProperties" in error ? error.additionalProperties : undefined;

      const blockingSessionId =
        additionalProperties &&
        typeof additionalProperties === "object" &&
        additionalProperties !== null &&
        "blockingSessionId" in additionalProperties
          ? String(additionalProperties.blockingSessionId)
          : undefined;

      if (!blockingSessionId) {
        throw new Error(
          "Conflict returned without blockingSessionId. Clear the active session from the owning product, then retry."
        );
      }

      // Discard the blocking session:
      await cancelRemote({ ambientSessionId: blockingSessionId });

      // Or end it so captured audio can be processed:
      // await endRemote({ ambientSessionId: blockingSessionId });

      // No delay is required between a successful remote call and retry.
      return session.create({
        encounterId,
        emrEncounterId,
      });
    }

    return (
      <button type="button" onClick={() => void createInteroperableSession()}>
        Create interoperable session
      </button>
    );
  }
  ```

  When `session.isSuccess` is `true`, `ambientSessionId` and `noteId` are also available on the hook return.

  ```tsx React theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  import { useGetEncounterInfo } from "@suki-sdk/platform-react";

  export function ListEncounterNotes() {
    const { data, isLoading, error } = useGetEncounterInfo({
      emrEncounterId: "6ec3920f-b0b1-499d-a4e9-889bf788e5ab",
    });

    if (isLoading) {
      return <p>Loading encounter notes...</p>;
    }

    if (error) {
      return <p role="alert">{error.message}</p>;
    }

    // data.notes[].note_id → use with Ambient note-level APIs
    return (
      <ul>
        {(data?.notes ?? []).map((note) => (
          <li key={note.note_id}>{note.note_id}</li>
        ))}
      </ul>
    );
  }
  ```

  <Steps>
    <Step title="Pass emrEncounterId on create">
      Call `session.create({ encounterId, emrEncounterId })`. Interop requires `emrEncounterId`; reuse `encounterId` for re-ambient grouping on the same note.
    </Step>

    <Step title="Store ambientSessionId and noteId">
      Persist both IDs from the create result. Headless returns **`noteId`**, not `compositionId`. Use `noteId` with Ambient note-level APIs.
    </Step>

    <Step title="Resolve sessionAlreadyExists conflicts">
      Read `error.additionalProperties.blockingSessionId`, then call `cancelRemote` or `endRemote` before retry. See [Handle ambient session conflicts](/headless-web-sdk/guides/handle-ambient-session-conflicts).
    </Step>

    <Step title="List notes and retrieve content">
      Use `useGetEncounterInfo` to list notes for the EMR encounter. Headless does not expose note content hooks, so call [Get note content](/api-reference/ambient-content/note-content) and related APIs after you have `noteId`.
    </Step>
  </Steps>

  ## Try it

  <Card>
    * **Create returns noteId and ambientSessionId**

      After a successful create, both IDs should be available on the result and on the hook when `session.isSuccess` is `true`.

    * **Notes appear for the EMR encounter**

      `useGetEncounterInfo` with the same `emrEncounterId` should list `note_id` values you can pass to Ambient note-level APIs.

    * **Conflicts clear before retry**

      When another product holds an active session, `sessionAlreadyExists` should resolve after `cancelRemote` or `endRemote`, then a second create succeeds.
  </Card>

  ## Common mistakes

  <Warning>
    * Omit `emrEncounterId`. Create still succeeds, but the note is not interoperable.
    * Pass the EMR visit id as `encounterId` only. Interop requires `emrEncounterId`. Reuse `encounterId` for re-ambient grouping.
    * Expect create to return `compositionId`. Headless returns **`noteId`**.
    * Read conflict details from `error.metadata`. Use **`error.additionalProperties.blockingSessionId`**.
    * Call local `cancel()` or `submit()` when another Suki product cancels or ends the live session. Use `onSessionTerminatedByPeer` and update UI only.
  </Warning>

  ## Related guides

  * [Ambient interoperability](/headless-web-sdk/guides/ambient-interoperability)
  * [Handle ambient session conflicts](/headless-web-sdk/guides/handle-ambient-session-conflicts)
  * [Read shared ambient notes](/headless-web-sdk/guides/read-shared-ambient-notes)

  ## Related cookbooks

  <Columns cols={2}>
    <Card title="Share One Note Across Products" href="/documentation/cookbooks/pass-emr-encounter-id" arrow={true} icon="book-open">
      Share one note with emr\_encounter\_id.
    </Card>

    <Card title="Clear a Remote Session Conflict" href="/documentation/cookbooks/resolve-session-conflict" arrow={true} icon="book-open">
      End remote session, then retry.
    </Card>
  </Columns>
</CbRecipePage>
