# Basic Iframe Integration Guide

This guide provides instructions for embedding RADPAIR within your application using the simplified iframe integration. This integration is designed for new partners who want to get started quickly with minimal complexity.

{% hint style="info" %}
This guide covers basic, single-author workflows only. For multi-author workflows, advanced event handling, or complex organizational structures, please refer to the Advanced Integration Guide.
{% endhint %}

## Overview

The Basic Integration is designed to:

* Minimize complexity — Reports flow in via HL7 integration, then open them by MRN and accession number
* Use industry-standard identifiers — MRN and accession number ensure report uniqueness (no internal report IDs needed)
* Handle user management internally — RADPAIR manages user metadata, roles, and authentication
* Provide essential status updates — Five core events: opened, closed, saved, discarded, and signed

## Integration Workflow (Summary)

{% stepper %}
{% step %}
**HL7 Integration (Handled by Interoperability Partner)**

Reports are created and pre-cached via HL7/DICOM before users need them.
{% endstep %}

{% step %}
**Embed Iframe**

Add RADPAIR iframe to your application to render the reader UI.
{% endstep %}

{% step %}
**Authenticate**

* Wait for the `app_ready` event from the iframe.
* Send the access token.
* Wait for `sign_in_successful`.
  {% endstep %}

{% step %}
**Open Report**

Send the MRN and accession number to open the pre-cached report in the iframe.
{% endstep %}

{% step %}
**Listen for Events**

Handle core reporting events (opened, saved, signed, discarded, closed).
{% endstep %}
{% endstepper %}

***

## Step 1: HL7 Integration (Handled by Interoperability Partner)

Report data flows into RADPAIR through our HL7/DICOM interoperability partner. This integration handles the creation and pre-caching of reports before users access them, ensuring instant load times and proper metadata association.

{% hint style="info" %}
You do not need to implement this step directly. Your RADPAIR representative will coordinate the HL7 integration setup with your interoperability partner.
{% endhint %}

### Report Delivery

When reports are signed, RADPAIR sends report events to the interoperability partner, who transforms them into HL7 ORU (Observation Result) messages for delivery to your downstream systems (PACS, EMR). This includes support for:

* Standard reports — Delivered upon signature (or after recall window if enabled)
* Linked reports — Multiple studies can be linked to a single report; when signed, events are sent for all linked studies

### Data Flow Overview

Endpoint:

```
POST https://api.radpair.com/integrations/reports
```

Headers:

```
Content-Type: application/json
Authorization: Bearer <your-api-key>
```

Request Body:

```json
{
  "mrn": "MRN123456",
  "accession_number": "ACC789012",
  "metadata": {
    "studyDescription": "CT Chest with Contrast",
    "modality": "CT",
    "patient_age": "65",
    "patient_gender": "M",
    "clinical_notes": "Shortness of breath"
  }
}
```

Required Fields:

| Field              | Type   | Description                                |
| ------------------ | ------ | ------------------------------------------ |
| `mrn`              | string | Medical Record Number — patient identifier |
| `accession_number` | string | Accession number — unique study identifier |

{% hint style="info" %}
Together, MRN and accession number ensure report uniqueness within your organization. In rare cases where these two identifiers may not guarantee uniqueness (e.g., complex multi-site organizations), you can use the `org_path` parameter.
{% endhint %}

### Optional Metadata Fields

| Field              | Type   | Description                                                                               |
| ------------------ | ------ | ----------------------------------------------------------------------------------------- |
| `studyDescription` | string | Description of the imaging study                                                          |
| `clinical_notes`   | string | Clinical history or reason for exam                                                       |
| `laterality`       | string | Side of body (e.g., "Left", "Right", "Bilateral")                                         |
| `views`            | string | Imaging views obtained                                                                    |
| `comparison`       | string | Prior studies available for comparison                                                    |
| `symptom`          | string | Patient's presenting symptoms                                                             |
| `modality`         | string | Imaging modality (e.g., "CT", "MR", "XR", "US")                                           |
| `contrast`         | string | Contrast administration details                                                           |
| `patient_age`      | string | Patient's age                                                                             |
| `patient_gender`   | string | Patient's gender                                                                          |
| `pre_findings`     | string | DICOM SR data or AI-detected findings                                                     |
| `findings`         | string | Pre-populated findings text                                                               |
| `org_path`         | string | Organization path (e.g., "parent\_org/sub\_org") — rarely needed for uniqueness assurance |

***

## Step 2: Embed the Iframe

Add the RADPAIR iframe to your application:

```html
<iframe
  id="radpair-iframe"
  allow="microphone; hid"
  credentialless
  src="https://external.radpair.com?headless=true"
  style="width: 100%; height: 600px; border: none;"
></iframe>
```

### Iframe Attributes

| Attribute                 | Purpose                                                               |
| ------------------------- | --------------------------------------------------------------------- |
| `allow="microphone; hid"` | Required for dictation and hardware devices (e.g., SpeechMike)        |
| `credentialless`          | Isolates session storage to prevent conflicts with multiple instances |
| `?headless=true`          | Hides RADPAIR navigation UI for embedded use                          |

***

## Step 3: Authentication

### Obtaining an API Key

1. Navigate to your organization's settings page in RADPAIR
2. Click the options menu (⋮) and select "API Keys"
3. Click "Create New API Key"
4. Copy and securely store the generated key — it will only be shown once

### Creating an Access Token

Endpoint:

```
POST https://api.radpair.com/integrations/auth/token
```

Headers:

```
Content-Type: application/json
Authorization: Bearer <your-api-key>
```

Request Body:

```json
{
  "user_email": "radiologist@example.com",
  "first_name": "Jane",
  "last_name": "Smith"
}
```

| Parameter    | Type   | Required | Description       |
| ------------ | ------ | -------- | ----------------- |
| `user_email` | string | ✅        | Email of the user |
| `first_name` | string | ❌        | User's first name |
| `last_name`  | string | ❌        | User's last name  |

Response:

```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```

{% hint style="warning" %}
Access tokens expire after 24 hours. User metadata (NPI, roles, unique IDs) is managed within RADPAIR — you do not need to pass these during authentication.
{% endhint %}

### Authenticating the Iframe

```javascript
const iframe = document.getElementById('radpair-iframe');
const RADPAIR_URL = 'https://external.radpair.com';

// Wait for app_ready, then authenticate
window.addEventListener('message', (event) => {
  if (event.origin !== RADPAIR_URL) return;

  if (event.data?.event === 'app_ready') {
    iframe.contentWindow.postMessage(
      { authentication: `Bearer ${accessToken}` },
      RADPAIR_URL
    );
  }

  if (event.data?.event === 'sign_in_successful') {
    console.log('Ready to open reports');
  }
});
```

***

## Step 4: Open Reports

Once authenticated, open a report by sending both the MRN and accession number:

```javascript
iframe.contentWindow.postMessage({
  open_report: {
    mrn: 'MRN123456',
    accession_number: 'ACC789012'
  }
}, RADPAIR_URL);
```

**Required Fields:**

| Field              | Type   | Description                                |
| ------------------ | ------ | ------------------------------------------ |
| `mrn`              | string | Medical Record Number — patient identifier |
| `accession_number` | string | Accession number — unique study identifier |

**Optional Fields:**

| Field      | Type   | Description                                                                                                                      |
| ---------- | ------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `org_path` | string | Organization path (e.g., "parent\_org/sub\_org") — used in rare cases to ensure the report is routed to the correct organization |

{% hint style="info" %}
**Ensuring Report Uniqueness:** MRN and accession number together uniquely identify the report within your organization. In unlikely cases where these two identifiers may not guarantee uniqueness (e.g., complex multi-site organizations with overlapping identifiers), you can provide the `org_path` parameter to explicitly specify the organization/sub-organization path.
{% endhint %}

### Example with Optional org\_path:

```javascript
iframe.contentWindow.postMessage({
  open_report: {
    mrn: 'MRN123456',
    accession_number: 'ACC789012',
    org_path: 'main_hospital/imaging_center_east'
  }
}, RADPAIR_URL);
```

### Closing Reports

To close the currently open report:

```javascript
iframe.contentWindow.postMessage({ close_report: true }, RADPAIR_URL);
```

***

## Step 4a: Linking Multiple Reports

RADPAIR supports linking multiple studies from the same patient into a single combined report. This is useful when a radiologist needs to read and report on multiple related studies together (e.g., multiple body parts from the same examination, or comparison studies).

### How Report Linking Works

To link multiple reports, send the same MRN with multiple accession numbers. RADPAIR will automatically link these studies together in the system, allowing the radiologist to view and report on them as a unified report.

### Opening and Linking Multiple Reports

```javascript
iframe.contentWindow.postMessage({
  open_report: {
    mrn: 'MRN123456',
    accession_numbers: ['ACC001', 'ACC002', 'ACC003']
  }
}, RADPAIR_URL);
```

**Required Fields for Linked Reports:**

| Field               | Type      | Description                                                     |
| ------------------- | --------- | --------------------------------------------------------------- |
| `mrn`               | string    | Medical Record Number — must be the same for all linked reports |
| `accession_numbers` | string\[] | Array of accession numbers to link together                     |

**Optional Fields:**

| Field      | Type   | Description                                                                                                                      |
| ---------- | ------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `org_path` | string | Organization path (e.g., "parent\_org/sub\_org") — used in rare cases to ensure the report is routed to the correct organization |

{% hint style="info" %}
**Important:** All reports being linked must belong to the same patient (same MRN) and must have been pre-cached via HL7 integration. The studies will be linked together in RADPAIR's system, and when the radiologist signs the combined report, events will be sent for all linked accession numbers.
{% endhint %}

### Example: Linking Three Studies

```javascript
// Link chest, abdomen, and pelvis CT studies for the same patient
iframe.contentWindow.postMessage({
  open_report: {
    mrn: 'MRN123456',
    accession_numbers: ['ACC_CHEST_001', 'ACC_ABDOMEN_002', 'ACC_PELVIS_003']
  }
}, RADPAIR_URL);
```

### Example with Optional org\_path:

```javascript
iframe.contentWindow.postMessage({
  open_report: {
    mrn: 'MRN123456',
    accession_numbers: ['ACC001', 'ACC002'],
    org_path: 'main_hospital/imaging_center_east'
  }
}, RADPAIR_URL);
```

### Events for Linked Reports

When a linked report is signed, RADPAIR will send separate `report.signed` events for each linked accession number. Your application should handle these events individually:

```javascript
// When signing a linked report with 3 accessions, you'll receive 3 events:
{
  "type": "reporting_event",
  "event": "report.signed",
  "payload": {
    "mrn": "MRN123456",
    "accession_number": "ACC_CHEST_001",
    "report_status": "signed",
    ...
  }
}
{
  "type": "reporting_event",
  "event": "report.signed",
  "payload": {
    "mrn": "MRN123456",
    "accession_number": "ACC_ABDOMEN_002",
    "report_status": "signed",
    ...
  }
}
{
  "type": "reporting_event",
  "event": "report.signed",
  "payload": {
    "mrn": "MRN123456",
    "accession_number": "ACC_PELVIS_003",
    "report_status": "signed",
    ...
  }
}
```

***

## Step 5: Listen for Events

RADPAIR sends events to notify your application of report state changes. For basic integrations, you only need to handle five core events.

### Core Events

| Event                         | Description           | When It Fires                                                           |
| ----------------------------- | --------------------- | ----------------------------------------------------------------------- |
| `report.opened`               | Report was opened     | User opens a report                                                     |
| `report.closed`               | Report was closed     | User closes or navigates away from report                               |
| `report.saved`                | Report saved as draft | User saves work in progress                                             |
| `report.discarded_completely` | Report was discarded  | User discards the report (unassigns it, making it available for others) |
| `report.signed`               | Report was signed     | User signs the report (triggers downstream delivery)                    |

{% hint style="info" %}
**Note on `report.published`:** A `report.published` event also exists, but you can ignore it — report delivery to downstream systems (PACS, EMR) is handled automatically by our HL7 interoperability partner.
{% endhint %}

{% hint style="info" %}
**Note on Report Recall:** If Report Recall is enabled, you will receive a `report.signed` event at the beginning of the recall period, and then again when the recall window has elapsed (or if the user clicks "Sign Now"). If a user recalls a report during that time, you will receive a `report.opened` event with a report status of `draft`.
{% endhint %}

### Event Listener Example

```javascript
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://external.radpair.com') return;

  if (event.data?.type === 'reporting_event') {
    const { event: eventType, payload } = event.data;

    switch (eventType) {
      case 'report.opened':
        console.log('Report opened:', payload.mrn, payload.accession_number);
        break;

      case 'report.closed':
        console.log('Report closed:', payload.mrn, payload.accession_number);
        break;

      case 'report.saved':
        console.log('Report saved (draft):', payload.mrn, payload.accession_number);
        console.log('Status:', payload.report_status);
        break;

      case 'report.discarded_completely':
        console.log('Report discarded:', payload.mrn, payload.accession_number);
        break;

      case 'report.signed':
        console.log('Report signed:', payload.mrn, payload.accession_number);
        console.log('Report status:', payload.report_status);
        handleSignedReport(payload);
        break;
    }
  }
});

function handleSignedReport(payload) {
  // Update your UI or internal state
  console.log('Report signed by:', payload.user_info?.full_name);
  console.log('Status:', payload.report_status); // 'signed', 'preliminary', or 'addendum'
}
```

### Event Payload Structure

All events include both MRN and accession number for identification:

```javascript
{
  "type": "reporting_event",
  "event": "report.signed",
  "payload": {
    "timestamp": 1743518340903,
    "mrn": "MRN123456",
    "accession_number": "ACC789012",
    "report_status": "signed",
    "user_info": {
      "email": "radiologist@example.com",
      "full_name": "Jane Doe"
    }
  }
}
```

**Key Payload Fields:**

| Field              | Description                                            |
| ------------------ | ------------------------------------------------------ |
| `mrn`              | The Medical Record Number                              |
| `accession_number` | The accession number                                   |
| `report_status`    | Signature type: `preliminary`, `signed`, or `addendum` |
| `user_info`        | Information about the user who signed                  |
| `timestamp`        | Unix timestamp of the event                            |

{% hint style="info" %}
Events return the same `mrn` and `accession_number` from the HL7 integration. You do not need to track internal report IDs.
{% endhint %}

***

## Complete Integration Example

```javascript
const RADPAIR_URL = 'https://external.radpair.com';
let iframe;
let isAuthenticated = false;

// Initialize
function initRadpair() {
  iframe = document.getElementById('radpair-iframe');
  window.addEventListener('message', handleMessage);
}

// Handle all messages
function handleMessage(event) {
  if (event.origin !== RADPAIR_URL) return;
  const { data } = event;

  // Authentication flow
  if (data.event === 'app_ready') {
    authenticateUser();
  }

  if (data.event === 'sign_in_successful') {
    isAuthenticated = true;
    console.log('Ready to open reports');
  }

  // Report events
  if (data.type === 'reporting_event') {
    handleReportEvent(data.event, data.payload);
  }
}

// Authenticate
async function authenticateUser() {
  const token = await getAccessToken(); // Your backend call
  iframe.contentWindow.postMessage(
    { authentication: `Bearer ${token}` },
    RADPAIR_URL
  );
}

// Open a single pre-cached report
function openReport(mrn, accessionNumber, orgPath = null) {
  if (!isAuthenticated) {
    console.error('Not authenticated');
    return;
  }

  const message = {
    open_report: {
      mrn: mrn,
      accession_number: accessionNumber
    }
  };

  // Add org_path only if provided (rare case)
  if (orgPath) {
    message.open_report.org_path = orgPath;
  }

  iframe.contentWindow.postMessage(message, RADPAIR_URL);
}

// Open and link multiple reports for the same patient
function openLinkedReports(mrn, accessionNumbers, orgPath = null) {
  if (!isAuthenticated) {
    console.error('Not authenticated');
    return;
  }

  const message = {
    open_report: {
      mrn: mrn,
      accession_numbers: accessionNumbers
    }
  };

  // Add org_path only if provided (rare case)
  if (orgPath) {
    message.open_report.org_path = orgPath;
  }

  iframe.contentWindow.postMessage(message, RADPAIR_URL);
}

// Handle report events
function handleReportEvent(eventType, payload) {
  switch (eventType) {
    case 'report.opened':
      updateUI('Report opened');
      break;
    case 'report.saved':
      updateUI('Draft saved');
      break;
    case 'report.signed':
      handleSignedReport(payload);
      updateUI('Report signed');
      break;
    case 'report.discarded_completely':
      updateUI('Report discarded');
      break;
    case 'report.closed':
      updateUI('Report closed');
      break;
  }
}

// Handle signed report
function handleSignedReport(payload) {
  console.log('Report signed:', payload.mrn, payload.accession_number);
  console.log('Signed by:', payload.user_info?.full_name);
  console.log('Status:', payload.report_status);
  // Report delivery to PACS/EMR is handled by HL7 interoperability partner
}

document.addEventListener('DOMContentLoaded', initRadpair);
```

***

## Report Status Values

Events include a `report_status` field indicating the current state:

| Status        | Description                                   |
| ------------- | --------------------------------------------- |
| `draft`       | Report is being worked on, not yet signed     |
| `preliminary` | Preliminary read (resident/trainee signature) |
| `signed`      | Final signature by attending radiologist      |
| `addendum`    | Addendum added to a signed report             |

***

## Troubleshooting

{% hint style="info" %}
Use the expandable items below to view common troubleshooting scenarios.
{% endhint %}

<details>

<summary>"App not ready" errors</summary>

Wait for the `app_ready` event before sending authentication.

</details>

<details>

<summary>"Not authenticated" errors</summary>

* Verify the access token is valid (tokens expire after 24 hours)
* Ensure you received `sign_in_successful` before opening reports

</details>

<details>

<summary>Report not opening</summary>

* Verify the report exists (was received via HL7 integration)
* Check that both MRN and accession number match exactly
* Ensure the user belongs to the organization where the report was created
* In rare cases with complex multi-site organizations, verify the `org_path` is correct
* For linked reports, ensure all accession numbers have been pre-cached and belong to the same MRN

</details>

<details>

<summary>Events not received</summary>

* Confirm event listener is checking the correct origin
* Ensure iframe is loaded before adding the listener

</details>

<details>

<summary>Linked reports not working</summary>

* Verify all accession numbers belong to the same patient (same MRN)
* Ensure all studies have been pre-cached via HL7 integration
* Check that you're using the `accession_numbers` array (plural) for linked reports

</details>

***

## Support

For integration support, contact your RADPAIR representative or email <support@radpair.com>.
