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

# Attaching External Info

> Enrich tickets with structured data from external systems like CRMs, EHRs, or payment platforms.

Integrations can attach structured external information to a ticket, which is then displayed in the Qminder Service View alongside the visitor's details. This is useful for showing contextual data from CRMs, EHRs, payment systems, or any other external platform — giving service clerks the information they need without switching between tools.

<Note>
  External data is for **server-side integrations** that display read-only information from external systems. For visitor-provided input fields that are collected during sign-in, see the `extra` fields in the [Tickets overview](/reference/tickets).
</Note>

## API Endpoint

```
POST /v1/tickets/{ticketId}/external
```

Both `POST` and `PUT` are accepted with identical behavior.

### Parameters

Parameters are sent as **form-encoded** fields (not a JSON body). The `data` field contains a JSON string, which must be URL-encoded.

| Parameter | Type | Required | Description |
| - | - | - | - |
| `provider` | string | Yes | Unique identifier for your integration (e.g., `"salesforce"`, `"epic"`). Max 100 characters. |
| `title` | string | Yes | Display title shown as the section header in the Service View. Max 100 characters. |
| `data` | string | Yes | JSON string conforming to the data schema described below. |

### Example Request

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.qminder.com/v1/tickets/12345/external \
    -H "X-Qminder-REST-API-Key: YOUR_SECRET_API_KEY" \
    -d "provider=my-crm" \
    -d "title=Patient Info" \
    --data-urlencode 'data={
      "fields": [
        {
          "type": "message",
          "content": "ALLERGY ALERT: Penicillin — Severe",
          "importance": "warning"
        },
        { "type": "text", "title": "Patient Name", "value": "Maria Schmidt" },
        { "type": "email", "title": "Contact Email", "value": "maria.schmidt@example.com" },
        {
          "type": "list",
          "title": "Recent Visits",
          "items": [
            {
              "title": "Annual Checkup",
              "text": "Dr. Patel",
              "url": "https://portal.example.com/doc/7391",
              "footer": "Internal Medicine",
              "timestamp": "2026-01-10"
            }
          ]
        }
      ]
    }'
  ```

  ```typescript TypeScript theme={null}
  import { Qminder } from 'qminder-api';
  Qminder.setKey('YOUR_SECRET_API_KEY');

  const data = {
    fields: [
      {
        type: 'message',
        content: 'ALLERGY ALERT: Penicillin — Severe',
        importance: 'warning',
      },
      { type: 'text', title: 'Patient Name', value: 'Maria Schmidt' },
      { type: 'email', title: 'Contact Email', value: 'maria.schmidt@example.com' },
      {
        type: 'list',
        title: 'Recent Visits',
        items: [
          {
            title: 'Annual Checkup',
            text: 'Dr. Patel',
            url: 'https://portal.example.com/doc/7391',
            footer: 'Internal Medicine',
            timestamp: '2026-01-10',
          },
        ],
      },
    ],
  };

  await Qminder.Ticket.setExternalData('12345', 'my-crm', 'Patient Info', data);
  ```
</CodeGroup>

**Response (200 OK):**

```json theme={null}
{"statusCode": 200}
```

## Data Structure

The `data` parameter must be a JSON string containing an object with a `fields` array:

```json theme={null}
{
  "fields": []
}
```

Three categories of fields are supported: **message**, **textual** (`text`, `email`, `phoneNumber`), and **list**. You can mix and combine them freely within the `fields` array.

### Message Fields

Displays a colored alert banner. Use messages to highlight important information that clerks should see immediately.

| Property | Type | Required | Description |
| - | - | - | - |
| `type` | string | Yes | Must be `"message"` |
| `content` | string | Yes | The message text to display |
| `importance` | string | Yes | `"error"`, `"warning"`, or `"info"` |

The `importance` level controls the visual styling:

**`error`** — red background, for critical alerts:

```json theme={null}
{ "type": "message", "content": "CRITICAL: Elevated troponin levels detected in Feb 14 labs. Cardiology follow-up overdue by 17 days.", "importance": "error" }
```

<img src="https://mintcdn.com/qminder/sf92mEsDaThHZFDP/images/reference/external-data/01-message-error.png?fit=max&auto=format&n=sf92mEsDaThHZFDP&q=85&s=72a78ed6ab1b76c77dbd2dd62a39329f" alt="Error message banner" width="437" height="98" data-path="images/reference/external-data/01-message-error.png" />

**`warning`** — orange background, for important notices:

```json theme={null}
{ "type": "message", "content": "ALLERGY ALERT: Penicillin — Severe (Anaphylaxis). Consult prescriber before any new medications.", "importance": "warning" }
```

<img src="https://mintcdn.com/qminder/sf92mEsDaThHZFDP/images/reference/external-data/02-message-warning.png?fit=max&auto=format&n=sf92mEsDaThHZFDP&q=85&s=95228bdc56c2a3b4f23f675fecb86312" alt="Warning message banner" width="437" height="98" data-path="images/reference/external-data/02-message-warning.png" />

**`info`** — gray background, for general information:

```json theme={null}
{ "type": "message", "content": "Annual flu vaccination completed on 2025-10-05. Next recommended checkup: Apr 2026.", "importance": "info" }
```

<img src="https://mintcdn.com/qminder/sf92mEsDaThHZFDP/images/reference/external-data/03-message-info.png?fit=max&auto=format&n=sf92mEsDaThHZFDP&q=85&s=acac109ff2cd96cbdc822c43248c8f68" alt="Info message banner" width="437" height="98" data-path="images/reference/external-data/03-message-info.png" />

### Textual Fields

Displays a key-value row. Three subtypes are available: `text` for plain text, `email` for email addresses, and `phoneNumber` for phone numbers.

| Property | Type | Required | Description |
| - | - | - | - |
| `type` | string | Yes | `"text"`, `"email"`, or `"phoneNumber"` |
| `title` | string | Yes | Label displayed on the left side |
| `value` | string | Yes | Value displayed on the right side |

```json theme={null}
{ "type": "text", "title": "Patient Name", "value": "Maria Schmidt" }
```

```json theme={null}
{ "type": "email", "title": "Contact Email", "value": "maria.schmidt@example.com" }
```

```json theme={null}
{ "type": "phoneNumber", "title": "Emergency Contact", "value": "+15551234567" }
```

<img src="https://mintcdn.com/qminder/sf92mEsDaThHZFDP/images/reference/external-data/04-textual-fields-block.png?fit=max&auto=format&n=sf92mEsDaThHZFDP&q=85&s=667c6c00733e914f30ff13468631c96e" alt="Textual fields displayed as key-value rows" width="437" height="192" data-path="images/reference/external-data/04-textual-fields-block.png" />

### List Fields

Displays a titled section with a collection of items. Each item has a required title and optional metadata.

| Property | Type | Required | Description |
| - | - | - | - |
| `type` | string | Yes | Must be `"list"` |
| `title` | string | Yes | Section header (displayed in uppercase) |
| `items` | array | Yes | Array of list item objects |

**List item properties:**

| Property | Type | Required | Description |
| - | - | - | - |
| `title` | string | Yes | Bold item title |
| `text` | string | No | Description text below the title |
| `url` | string | No | Clickable link displayed below the title |
| `footer` | string | No | Displayed as a tag/badge pill |
| `timestamp` | string | No | Date in `YYYY-MM-DD` format, displayed below other content |

A list item with all optional properties:

```json theme={null}
{
  "title": "Complete Record",
  "text": "Comprehensive entry with all optional fields populated",
  "url": "https://records.example.com/entry/48291",
  "footer": "Category: Full Review",
  "timestamp": "2026-03-01"
}
```

<img src="https://mintcdn.com/qminder/sf92mEsDaThHZFDP/images/reference/external-data/12-list-item-all-properties.png?fit=max&auto=format&n=sf92mEsDaThHZFDP&q=85&s=8baafa82500c1880c32a223c5f2d8999" alt="List item showing all optional properties" width="437" height="144" data-path="images/reference/external-data/12-list-item-all-properties.png" />

A complete list field with multiple items demonstrates different combinations:

```json theme={null}
{
  "type": "list",
  "title": "Detailed Records",
  "items": [
    {
      "title": "Complete Record",
      "text": "Comprehensive entry with all optional fields populated",
      "url": "https://records.example.com/entry/48291",
      "footer": "Category: Full Review",
      "timestamp": "2026-03-01"
    },
    { "title": "Entry with Date", "timestamp": "2026-02-15" },
    { "title": "Entry with Description", "text": "This item demonstrates the text property showing a description below the title" },
    { "title": "Entry with Link", "url": "https://portal.example.com/doc/7391" },
    { "title": "Entry with Tag", "footer": "Status: Pending Approval" }
  ]
}
```

<img src="https://mintcdn.com/qminder/sf92mEsDaThHZFDP/images/reference/external-data/13-complete-list-field-detailed.png?fit=max&auto=format&n=sf92mEsDaThHZFDP&q=85&s=d75eb980bd6bfd117cd0f833b31ce808" alt="Complete list field with multiple varied items" width="437" height="524" data-path="images/reference/external-data/13-complete-list-field-detailed.png" />

A minimal list with just a title and one item:

```json theme={null}
{ "type": "list", "title": "Pending Tasks", "items": [{ "title": "Schedule follow-up appointment" }] }
```

<img src="https://mintcdn.com/qminder/sf92mEsDaThHZFDP/images/reference/external-data/14-complete-list-field-minimal.png?fit=max&auto=format&n=sf92mEsDaThHZFDP&q=85&s=6d1841c98406e2300488a008c7e84d01" alt="Minimal list field" width="437" height="84" data-path="images/reference/external-data/14-complete-list-field-minimal.png" />

## Key Behaviors

* **Upsert semantics:** One record per `(ticketId, provider)` pair. Calling the endpoint again with the same provider replaces the previous data entirely.
* **Multiple providers:** A single ticket can have external data from many providers (e.g., both `"salesforce"` and `"epic"`). Each appears as a separate section in the Service View.
* **Schema validation:** The `data` JSON is validated server-side. Invalid structures or unknown properties return a 400 error.
* **Real-time updates:** After storing, the Service View updates immediately — no page refresh needed.

## Reading External Data

External data attached to a ticket can be read back via the GraphQL API using the `external` field on the `Ticket` type. The `data` field returns a [`JSON` scalar](/reference/graphql/scalars/json) — a parsed JSON object matching the structure you originally sent:

```graphql theme={null}
query {
  ticket(id: "12345") {
    external {
      provider
      title
      data
      created
    }
  }
}
```

## Error Responses

| Status | Description | Example |
| - | - | - |
| 400 | Invalid JSON | `"Data is not valid JSON object"` |
| 400 | Schema validation failed | `"Data does not match the schema"` |
| 403 | Non-SECRET API key used | Forbidden |
| 404 | Ticket not found | `"Ticket with id 12345 not found"` |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.