On this page

Imaging equipment does not speak DICOMweb. Modalities, workstations, and legacy PACS speak **DIMSE** —
the DICOM message service over raw TCP — and they speak it on a hospital network with no route to the
public internet. The [Medplum Agent](/content/docs/agent/index.html) bridges that gap: it runs inside the firewall,
presents itself to the modality as an ordinary DICOM Storage SCP, and forwards each received instance
to Medplum over an outbound HTTPS connection.

C-STORE over TCP

HTTPS (outbound only)

notification

Modality
(CT, MR, CR, US)

Medplum Agent

dicom://0.0.0.0:8104

Medplum Server

Bot

The Agent needs no inbound firewall rule. From the network's perspective it is a device that listens
on a local port and makes outbound HTTPS calls, which is what makes it deployable in environments
that will not expose a PACS to the internet.

## Supported DIMSE operations [​](/content/docs/dicom/agent-dimse#supported-dimse-operations "Direct link to Supported DIMSE operations"/index.html)

| Operation  | Supported | Notes |
| --- | --- | --- |
| `C-ECHO` | Yes | Always answers success. Use it to verify connectivity first. |
| `C-STORE` | Yes | Stores the instance and notifies a Bot. |
| `C-FIND` | No |  |
| `C-GET` | No |  |
| `C-MOVE` | No |  |

The Agent accepts every presentation context and transfer syntax the calling AE proposes, and
negotiates a maximum PDU length of 64 KB. In practice this means a modality's default configuration
usually associates successfully without transfer syntax tuning.

## Configuring a DICOM channel [​](/content/docs/dicom/agent-dimse#configuring-a-dicom-channel "Direct link to Configuring a DICOM channel"/index.html)

A DICOM channel is configured the same way as any other Agent channel — an
[`Endpoint`](/content/docs/api/fhir/resources/endpoint/index.html) describing what to listen on, referenced from a
channel entry on the [`Agent`](/content/docs/api/fhir/medplum/agent/index.html) resource. See
[Intro to Medplum Agent](/content/docs/agent/index.html) for the full setup, including the `Bot` and `ClientApplication`
that go with it.

What makes the channel a DICOM channel is the `dicom://` scheme on `Endpoint.address`:

```json
{

"resourceType": "Endpoint",

"status": "active",

"name": "CT Scanner",

"connectionType": {

"system": "http://terminology.hl7.org/CodeSystem/endpoint-connection-type",

"code": "dicom-stow-rs"

},

"payloadType": [
  
    {
  
      "coding": [
  
        {
  
          "system": "http://terminology.hl7.org/CodeSystem/endpoint-payload-type",
  
          "code": "any"
  
        }
  
      ]
  
    }
  
  ],

"address": "dicom://0.0.0.0:8104?storage=dicomweb"

}
```

The Agent selects the channel implementation from the address scheme alone — `connectionType` is
descriptive metadata for humans and reporting, not something the Agent interprets. Port 104 is the
registered DICOM port, but it is privileged on Linux; 8104 and 11112 are the usual unprivileged
choices.

Configure the modality to send to the Agent host at that port. The Agent accepts any Called AE Title,
and records both the calling and called AE titles on the notification it sends to the Bot.

## Storage modes [​](/content/docs/dicom/agent-dimse#storage-modes "Direct link to Storage modes"/index.html)

The `storage` query parameter controls where a received instance ends up.

| Mode | What happens | Requires |
| --- | --- | --- |
| `binary` _(default)_ | The instance is uploaded as a FHIR [`Binary`](/content/docs/api/fhir/resources/binary/index.html), and a reference to it is included in the Bot payload. | Any server version |
| `dicomweb` | The instance is sent to the server's [STOW-RS endpoint](/content/docs/dicom/dicomweb-api#stow-rs-store-instances/index.html), which files it into `DicomStudy`, `DicomSeries`, and `DicomInstance` resources. No `Binary` is created by the Agent. | Medplum Server > 5.1.27, Agent > 5.1.28 |

`binary` remains the default so that a DICOM channel configured before DICOMweb existed keeps working
unchanged. **New deployments that want studies in the DICOM data model should set**  **`storage=dicomweb`.** In that mode the payload delivered to the Bot has no `binary` field — the study
is addressed through the DICOM resources the server created instead.

An unrecognized `storage` value logs a warning and falls back to `binary`, so a typo cannot silently
point a channel at an endpoint the server may not have. Against a server without DICOMweb support,
the STOW-RS request 404s and the `C-STORE` fails with a processing failure status.

Changing the storage mode takes effect on [`Agent/$reload-config`](/content/docs/agent/reload-config/index.html) without a restart, and without rebinding the port.

## What the Bot receives [​](/content/docs/dicom/agent-dimse#what-the-bot-receives "Direct link to What the Bot receives"/index.html)

Every `C-STORE` produces a notification to the channel's target `Bot`, regardless of storage mode:

```json
{

"association": {

"callingAeTitle": "CT_SCANNER_1",

"calledAeTitle": "MEDPLUM"

},

"dataset": {

"00080018": { "vr": "UI", "Value": ["1.2.840.113619.2.55.3.12345"] },

"00080060": { "vr": "CS", "Value": ["MR"] },

"0020000D": { "vr": "UI", "Value": ["1.2.840.113619.2.55.3.99999"] }

},

"binary": { "reference": "Binary/0195f2c1-..." }

}
```

- `association` carries the AE titles from the DIMSE association, which is how you tell one modality
from another when several send to the same channel.
- `dataset` is the instance's DICOM JSON with `PixelData``(7FE0,0010)` removed — enough to route,
match a patient, or build an order reconciliation, without shipping the image through the Bot.
- `binary` is present only in `binary` storage mode.

A minimal Bot that logs each arrival:

```ts
import { BotEvent, MedplumClient } from '@medplum/core';

interface DicomNotification {

association: { callingAeTitle?: string; calledAeTitle?: string };

dataset: Record<string, { vr: string; Value?: unknown[] }>;

binary?: { reference?: string };

}

export async function handler(medplum: MedplumClient, event: BotEvent<DicomNotification>): Promise<void> {

const { association, dataset } = event.input;

const sopInstanceUid = dataset['00080018']?.Value?.[0];

console.log(`Received ${sopInstanceUid} from ${association.callingAeTitle}`);

}
```

In `dicomweb` mode this is the natural place to reconcile imaging against the chart — look up the
`DicomStudy` the server just created, match `patientId` against your MRN identifier system, and link
the two. See [Relationship to FHIR ImagingStudy](/content/docs/dicom/data-model#relationship-to-fhir-imagingstudy/index.html).

## Testing a channel [​](/content/docs/dicom/agent-dimse#testing-a-channel "Direct link to Testing a channel"/index.html)

Verify connectivity before involving the modality. With
[dcmtk](https://dicom.offis.de/dcmtk.php.en) installed:

```bash
# C-ECHO — verifies the association handshake only

echoscu -v localhost 8104

# C-STORE — sends a file

storescu -v localhost 8104 MRBRAIN.DCM
```

A successful `C-STORE` in `dicomweb` mode should be followed by a new `DicomStudy` in your project.
If the `C-STORE` returns a processing failure, check the Agent's **channel log** — the main Agent log
deliberately excludes message content, so DICOM transfer detail lands in the channel log, which
[may contain PHI](/content/docs/agent/configuration#channel-logger/index.html).

To test without a modality or dcmtk at all, skip DIMSE entirely and use
[`medplum dicomweb stow`](/content/docs/dicom/cli/index.html).

## See also [​](/content/docs/dicom/agent-dimse#see-also "Direct link to See also"/index.html)

- [Intro to Medplum Agent](/content/docs/agent/index.html)
- [Agent Features](/content/docs/agent/features/index.html) — version matrix for `storage=dicomweb` and other channel options
- [Agent Troubleshooting](/content/docs/agent/troubleshooting/index.html)
- [DICOMweb API](/content/docs/dicom/dicomweb-api/index.html)

- [Supported DIMSE operations](/content/docs/dicom/agent-dimse#supported-dimse-operations/index.html)
- [Configuring a DICOM channel](/content/docs/dicom/agent-dimse#configuring-a-dicom-channel/index.html)
- [Storage modes](/content/docs/dicom/agent-dimse#storage-modes/index.html)
- [What the Bot receives](/content/docs/dicom/agent-dimse#what-the-bot-receives/index.html)
- [Testing a channel](/content/docs/dicom/agent-dimse#testing-a-channel/index.html)
- [See also](/content/docs/dicom/agent-dimse#see-also/index.html)
