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

# Embedding Assistant in React Web App

> Complete guide for integrating Corti Assistant into a React web application

This guide walks you through integrating Corti Assistant into a React web application. You'll learn the key concepts, architecture patterns, and implementation approach using the Web Component API.

<Check>
  **Complete working example available**: This guide references the [basic React example](https://github.com/corticph/corti-examples/tree/main/embedded-assistant/react/basic-example) in our examples repository. Clone it to see a fully functional implementation, then follow this guide to understand how it works and adapt it to your needs.
</Check>

<Info>
  This guide uses ROPC (Resource Owner Password Credentials) authentication, matching the React basic example.
</Info>

***

## What you'll learn

By following this guide, you'll understand:

* How to architect a secure client-server integration
* Why ROPC authentication works well for embedded EHR scenarios
* How to use the Web Component API with React
* How to manage the interaction lifecycle programmatically
* Key customization points and production considerations

## Prerequisites

Before starting, ensure you have:

* **Node.js 18+** installed
* **Corti API access** with valid credentials
* **OAuth client** configured for ROPC (Resource Owner Password Credentials) flow
* **User account** created in Corti Console for authentication
* Basic familiarity with React and Express

<Tip>
  Don't have credentials yet? Visit [Corti Console](https://console.corti.app) to create a user and OAuth client. Ensure your OAuth client has "Direct Access Grants" enabled for ROPC flow.
</Tip>

***

## Architecture overview

The integration follows a client-server architecture:

**Backend (Express Server)**:

* Handles OAuth authentication using ROPC flow via `@corti/sdk`
* Provides configuration endpoint for environment-specific settings
* Keeps sensitive credentials server-side
* Runs on port 8013 by default

**Frontend (React + Vite)**:

* Fetches auth tokens and configuration from backend
* Embeds Corti Assistant using `@corti/embedded-web` Web Component
* Manages interaction lifecycle (create, navigate, handle events)
* Uses React 19's Suspense for async data loading
* Runs on port 8015, with `/api` proxied to the backend

**Why this architecture?**

* **Security**: Credentials stay on the server, never exposed to the browser
* **Separation of concerns**: Auth logic separate from UI logic
* **Development speed**: Simple to run and test locally

***

## Key concepts

### Authentication flow

1. **Backend authenticates** with Corti using stored credentials (ROPC)
2. **Frontend requests** tokens from backend `/api/auth` endpoint
3. **Web Component** receives tokens and authenticates the embedded session
4. **No user interaction** required - seamless experience

**Why ROPC?** Users are already logged into your application. Forcing them to re-authenticate with Corti via browser popup would be confusing and disruptive. ROPC allows seamless embedded authentication.

### Web Component integration

The integration uses `<CortiEmbeddedReact>`, a React wrapper around the standard Web Component:

```tsx theme={null}
import {
  CortiEmbeddedReact,
  useCortiEmbeddedApi,
} from "@corti/embedded-web/react";

// In your component:
<CortiEmbeddedReact
  ref={cortiRef}
  baseURL={baseUrl}
  visibility="visible"
  onReady={handleReady}
  onEvent={handleEvent}
  onError={handleError}
/>;
```

**Key benefits**:

* **Type-safe**: Full TypeScript support with IDE autocomplete
* **React-friendly**: Hooks-based API (`useCortiEmbeddedApi`)
* **Event handling**: Native React event props for Assistant callbacks
* **Lifecycle management**: Automatic cleanup and ref management

### Interaction lifecycle

Every clinical encounter in Corti Assistant is called an "interaction":

1. **Create interaction** - Define encounter metadata (type, identifier, timestamps)
2. **Navigate to session** - Load the session view with recording interface
3. **User records** - Clinician documents the encounter
4. **Handle events** - React to document generation, errors, state changes

**Why create interactions programmatically?** This allows you to:

* Link Corti sessions to your EHR's encounter IDs
* Pre-fill encounter metadata from your system
* Control when and how sessions start
* Track interactions in your database

***

## Implementation guide

### Project structure

```text theme={null}
your-react-app/
├── server.ts              # Backend: Auth + config endpoints
├── src/
│   ├── App.tsx            # Root: Suspense boundary + data fetching
│   ├── components/
│   │   └── EmbeddedAssistant.tsx  # Main integration component
│   ├── lib/
│   │   └── auth.ts        # API calls to backend
│   └── types.ts           # TypeScript definitions
├── .env                   # Credentials (never commit!)
└── package.json
```

### Step 1: Backend setup

**Purpose**: Securely authenticate with Corti and provide tokens to the frontend.

**Key file**: `server.ts`

**What it does**:

* `/api/auth` - Uses `@corti/sdk` to perform ROPC authentication
* `/api/config` - Returns base URL based on environment (EU/US)
* `/api/health` - Provides a simple backend health check
* CORS configured for the local Vite dev server origin
* Backend port defaults to 8013; frontend origin defaults to `http://localhost:8015`

**Implementation approach**:

```typescript theme={null}
// Uses Corti SDK for authentication
const auth = new CortiAuth({
  environment: process.env.CORTI_ENVIRONMENT,
  tenantName: process.env.CORTI_TENANT_NAME,
});

// ROPC flow - exchanges username/password for tokens
const tokenResponse = await auth.getRopcFlowToken({
  clientId: process.env.CORTI_CLIENT_ID,
  username: process.env.CORTI_USER_EMAIL,
  password: process.env.CORTI_USER_PASSWORD,
});

// Return credentials in the shape expected by api.auth()
res.json({
  access_token: tokenResponse.accessToken,
  refresh_token: tokenResponse.refreshToken,
  id_token: "",
  token_type: tokenResponse.tokenType || "Bearer",
  expires_in: tokenResponse.expiresIn,
  mode: "stateful",
});
```

**See the full implementation**: [server.ts in example repo](https://github.com/corticph/corti-examples/blob/main/embedded-assistant/react/basic-example/server.ts)

### Step 2: Frontend data fetching

**Purpose**: Fetch auth tokens and config before rendering the Assistant.

**Key file**: `src/lib/auth.ts`

**What it does**:

* Promise-based API calls to backend
* Simple in-memory caching to prevent duplicate requests
* Separates data fetching logic from UI components

**Why separate?** Keeps components focused on rendering and keeps backend communication in one place.

**See the implementation**: [auth.ts in example repo](https://github.com/corticph/corti-examples/blob/main/embedded-assistant/react/basic-example/src/lib/auth.ts)

### Step 3: App root with suspense

**Purpose**: Coordinate parallel data fetching and handle loading states.

**Key file**: `src/App.tsx`

**Key pattern**: React 19's `use()` hook with Suspense for async data

```tsx theme={null}
function AppWithData({ configPromise, authPromise }) {
  const config = use(configPromise); // Suspends until resolved
  const authData = use(authPromise); // Fetched in parallel

  if (authData.error) {
    return <ErrorDisplay />;
  }

  return <EmbeddedAssistant baseUrl={config.baseUrl} authData={authData} />;
}
```

**Why this pattern?**

* **Parallel fetching**: Config and auth load simultaneously
* **Declarative loading**: Suspense fallback handles loading UI automatically
* **Error boundaries**: Easy to add error handling at the boundary level

**See the implementation**: [App.tsx in example repo](https://github.com/corticph/corti-examples/blob/main/embedded-assistant/react/basic-example/src/App.tsx)

### Step 4: Embedded Assistant component

**Purpose**: Render the Web Component and manage the interaction lifecycle.

**Key file**: `src/components/EmbeddedAssistant.tsx`

**Core responsibilities**:

1. Render `<CortiEmbeddedReact>` with configuration
2. Use `useCortiEmbeddedApi` hook to access API methods
3. Handle `onReady` event to authenticate and configure the embedded app
4. Set interaction-level options before creating the interaction
5. Create the interaction and navigate to the session route
6. Manage status display and error states

**Critical pattern - preventing double initialization**:

```tsx theme={null}
const hasInitialized = useRef(false);

const handleReady = async () => {
  if (hasInitialized.current) return; // Guard against React StrictMode
  hasInitialized.current = true;

  // Safe to authenticate and create interaction
};
```

**Why needed?** React 19's StrictMode intentionally double-invokes effects in development. Without this guard, you'd create two interactions.

**Event handling strategy**:

* `onReady` - Authenticate, configure app UI, set interaction options, create interaction, navigate to session
* `onEvent` - Receive Assistant events for logging or workflow integration
* `onError` - Display errors to user

**API sequence**:

```tsx theme={null}
await api.auth(authData);

await api.configureApp({
  ui: {
    interactionTitle: true,
    aiChat: true,
    documentFeedback: true,
    navigation: true,
  },
});

await api.setInteractionOptions({
  mode: {
    fallback: "in-person",
    options: ["in-person", "virtual"],
  },
  documents: {
    actions: {
      sync: true,
    },
  },
});

const interaction = await api.createInteraction({
  assignedUserId: null,
  encounter: {
    identifier: `encounter-${Date.now()}`,
    status: "planned",
    type: "first_consultation",
    period: { startedAt: new Date().toISOString() },
  },
});

await api.navigate(`/session/${interaction.id}`);
```

**See the implementation**: [EmbeddedAssistant.tsx in example repo](https://github.com/corticph/corti-examples/blob/main/embedded-assistant/react/basic-example/src/components/EmbeddedAssistant.tsx)

***

## Running the example

Clone and run the complete example:

```bash theme={null}
git clone https://github.com/corticph/corti-examples.git
cd corti-examples/embedded-assistant/react/basic-example
npm install
cp .env.example .env
# Edit .env with your credentials
npm run dev
```

Open [http://localhost:8015](http://localhost:8015) to see the integration in action. The backend runs at [http://localhost:8013](http://localhost:8013), and Vite proxies `/api` requests to that backend.

***

## Customization points

### Interaction creation

The example creates an interaction in `EmbeddedAssistant.tsx` after authentication and configuration:

```tsx theme={null}
const interaction = await api.createInteraction({
  assignedUserId: null,
  encounter: {
    identifier: `encounter-${Date.now()}`,
    status: "planned",
    type: "first_consultation",
    period: {
      startedAt: new Date().toISOString(),
    },
  },
});
```

### Assistant configuration

The example calls `api.configureApp()` after authentication for app-level UI settings:

```tsx theme={null}
await api.configureApp({
  ui: {
    interactionTitle: true,
    aiChat: true,
    documentFeedback: true,
    navigation: true,
  },
});
```

The example calls `api.setInteractionOptions()` for interaction-level options:

```tsx theme={null}
await api.setInteractionOptions({
  mode: {
    fallback: "in-person",
    options: ["in-person", "virtual"],
  },
  documents: {
    actions: {
      sync: true,
    },
  },
});
```

See [Configuration Reference](/assistant/configuration) for all options.

### Event and error handling

The example wires `onEvent` and `onError` callbacks on `<CortiEmbeddedReact>`:

```tsx theme={null}
const handleEvent = () => {
  // Events are logged internally by the component
};

const handleError = (event: CustomEvent) => {
  setStatus({
    message: `Error: ${event.detail?.message || "Unknown error"}`,
    type: "error",
  });
};
```

See [Events Reference](/assistant/events/index) for all available events.

***

## Production considerations

### Environment variables

Never commit credentials to version control:

```bash theme={null}
# Use environment-specific .env files
.env.development  # Local dev credentials
.env.production   # Production credentials (injected via CI/CD)

# Add to .gitignore
echo ".env*" >> .gitignore
echo "!.env.example" >> .gitignore
```

### CORS configuration

Update CORS for production domains:

```typescript theme={null}
app.use(
  cors({
    origin: [
      "https://your-production-domain.com",
      process.env.CLIENT_ORIGIN || (process.env.NODE_ENV === "development" ? "http://localhost:8015" : null),
    ].filter(Boolean),
    credentials: true,
  }),
);
```

***

## Next steps

Build on the React basic example with the current Embedded API:

* **Document export** - Handle document events and save generated content to your host application
* **Session management** - Store created interaction IDs and navigate back with `api.navigate("/session/<id>")`
* **App configuration** - Use `api.configureApp()` for UI, appearance, locale, and network settings
* **Interaction options** - Use `api.setInteractionOptions()` for mode, document actions, spoken language, and templates
* **Event monitoring** - Use `onEvent` and `onError` to track Assistant activity in your application

***

## Troubleshooting

### CORS errors

If you see CORS errors, verify Vite's dev server origin matches the `CLIENT_ORIGIN` used by `server.ts`. The example uses strict port 8015 for Vite and port 8013 for the backend.

**Fix**: Set `CLIENT_ORIGIN` to match your frontend URL:

```bash theme={null}
CLIENT_ORIGIN=http://localhost:8015
```

### Authentication fails

* Verify credentials in `.env` match your Corti Console setup
* Ensure OAuth client has "Direct Access Grants" enabled for ROPC
* Check user account exists and is active in your Corti tenant
* Verify `CORTI_ENVIRONMENT` matches your tenant region (eu/us)

### Web Component not loading

* Check browser console for import errors
* Verify `@corti/embedded-web` package is installed correctly
* Ensure `baseURL` matches your region (`https://assistant.eu.corti.app`)
* Confirm microphone permissions are granted (required for recording)

### Double initialization in Dev Mode

This is expected with React 19 StrictMode. The `hasInitialized` ref guard prevents issues. In production builds, StrictMode is disabled and this won't occur.

***

## Related documentation

* [Web Component API Reference](/assistant/web-component-api)
* [Authentication Guide](/assistant/authentication)
* [Configuration Reference](/assistant/configuration)
* [API Reference](/assistant/api-reference)
* [Events Reference](/assistant/events/index)
* [Example Repository](https://github.com/corticph/corti-examples/tree/main/embedded-assistant)
