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

# Handling events & results

> Subscribe to the consultation lifecycle and read the generated artifacts.

Everything TORTUS produces flows back to your application through **events**. You subscribe with `client.on(event, callback)`, which returns an unsubscribe function.

```ts theme={null}
const unsubscribe = client.on('consultation:started', ({ consultation }) => {
  console.log('Started:', consultation.reference);
});

unsubscribe(); // stop listening
```

## Events

<AccordionGroup>
  <Accordion title="Consultation events">
    | Event                    | When it fires                                                         |
    | ------------------------ | --------------------------------------------------------------------- |
    | `consultation:started`   | A consultation has started                                            |
    | `consultation:resumed`   | An in-progress consultation was re-opened (e.g. after client restart) |
    | `consultation:completed` | A consultation completed successfully                                 |
    | `consultation:error`     | A consultation failed with an error                                   |
    | `consultation:cancelled` | A consultation was cancelled                                          |
    | `consultation:closed`    | A consultation was closed                                             |
  </Accordion>

  <Accordion title="Meeting events">
    | Event               | When it fires                                                                             |
    | ------------------- | ----------------------------------------------------------------------------------------- |
    | `meeting:started`   | A meeting has started                                                                     |
    | `meeting:resumed`   | An in-progress meeting was re-opened                                                      |
    | `meeting:updated`   | Meeting metadata (notes/audio) was updated                                                |
    | `meeting:completed` | A meeting completed successfully (suppressed when `disableFinishMeetingButton` is `true`) |
    | `meeting:cancelled` | A meeting was cancelled                                                                   |
    | `meeting:closed`    | A meeting was closed                                                                      |
  </Accordion>
</AccordionGroup>

For completion events, the payload separates **metadata** from **output**:

* `consultation` (or `meeting`): all the metadata, including `reference`, `mode`, `patient`, `customFields`, and `integration`.
* `result`: the generated artifacts only.

## Completing a consultation

When a consultation completes you **must** call `finish()` to acknowledge receipt. Optionally pass a status to report whether your processing succeeded.

```ts theme={null}
client.on('consultation:completed', async ({ result, finish }) => {
  try {
    await processConsultation(result);
    await finish({ status: 'success' }); // default if no parameter is provided
  } catch (error) {
    await finish({ status: 'failed' }); // TORTUS shows retry options to the user
  }
});
```

| Status                  | Meaning                                                   |
| ----------------------- | --------------------------------------------------------- |
| `{ status: 'success' }` | Processing succeeded. Default when no parameter is given. |
| `{ status: 'failed' }`  | Processing failed; the user can retry from the TORTUS UI. |

Calling `finish()` with no arguments is equivalent to `finish({ status: 'success' })`:

```ts theme={null}
await finish(); // these are
await finish({ status: 'success' }); // equivalent
```

## Reading artifacts

`result.artifacts` holds the structured medical output. Each artifact type is an **array**, so always filter by `contentType` rather than relying on array order.

```ts theme={null}
const { medicalCodes, medicalNotes, letters, transcriptions } = result.artifacts;

const htmlNote = medicalNotes.find((n) => n.contentType === 'text/html');
const markdownNote = medicalNotes.find((n) => n.contentType === 'text/markdown');
const letter = letters.find((l) => l.contentType === 'text/html');
const transcription = transcriptions.find((t) => t.contentType === 'text/plain');
```

### Medical codes

`medicalCodes` includes diagnoses and observations. Observational codes carry a measurement, and every code carries an `evidence` array (which may be empty).

```ts theme={null}
medicalCodes.forEach((code) => {
  console.log(code.code); // e.g. 'I10' or '72313002'
  console.log(code.display); // e.g. 'Essential hypertension'

  if (code.value && code.unit) {
    console.log(code.value, code.unit); // e.g. '120 mmHg'
  }

  if (code.evidence.length > 0) {
    console.log(code.evidence); // e.g. ['Patient reported elevated BP']
  }
});
```

## Other lifecycle events

```ts theme={null}
client.on('consultation:error', ({ consultation, error }) => {
  console.error(`Consultation ${consultation.reference} failed:`, error);
});

client.on('consultation:resumed', ({ consultation }) => {
  console.log('Resumed:', consultation.reference, consultation.patient);
});
```

## Putting it together

A complete integration that loads the client, subscribes, then starts a consultation on demand:

```ts theme={null}
import { loadTortus } from '@tortus-ai/embed-client';

async function setupTortusClient() {
  const client = await loadTortus({
    publishableKey: 'pk_live_...',
    container: '#tortus-container',
    environment: 'production',
    fetchClientSecrets: async () => {
      const response = await fetch('/api/tortus/client-secrets');
      const data = await response.json();
      return { launchToken: data.launchToken };
    },
  });

  client.on('consultation:completed', async ({ consultation, result, finish }) => {
    try {
      await processConsultation(consultation, result);
      await finish({ status: 'success' });
    } catch (error) {
      await finish({ status: 'failed' });
    }
  });

  client.on('consultation:error', ({ consultation, error }) => {
    // show a notification, log, etc.
  });

  return client;
}

const client = await setupTortusClient();

document.getElementById('start-consultation').addEventListener('click', async () => {
  await client.consultations.start(
    { mode: 'FACE_TO_FACE', patient: await getPatientData(), integration: { system: 'EMIS' } },
    { returnTo: 'home' }
  );
});

async function processConsultation(consultation, result) {
  const { medicalNotes, letters, medicalCodes, transcriptions } = result.artifacts;

  const note =
    medicalNotes.find((n) => n.contentType === 'text/html') ??
    medicalNotes.find((n) => n.contentType === 'text/markdown');
  const letter =
    letters.find((l) => l.contentType === 'text/html') ??
    letters.find((l) => l.contentType === 'text/markdown');
  const transcription = transcriptions.find((t) => t.contentType === 'text/plain');

  await saveToEhr({
    reference: consultation.reference,
    note: note?.content,
    letter: letter?.content,
    codes: medicalCodes,
    transcription: transcription?.content,
  });
}
```

## Cleanup

Unsubscribe from individual listeners, close a specific consultation, or destroy the whole client.

```ts theme={null}
// Remove a single listener
const unsubscribe = client.on('consultation:completed', handleConsultationComplete);
unsubscribe();

// Close a specific consultation
await consultation.close();

// Destroy the entire client (cleans up all resources)
await client.destroy();
```

<Note>
  `destroy()` cleans up all resources but won't remove historical data saved in the browser.
</Note>
