# HL7v2 to FHIR - sending bundles to FHIR servers

Post converted bundles to FHIR servers through self.fhir - transaction and batch semantics, reading responses and handling server rejections.

A [converted bundle](https://zato.io/docs/dev/healthcare/hl7/to-fhir/index.html) is ready to post without any further work. An [outgoing FHIR connection](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html) delivers it with the address and credentials configured in the Dashboard, and the server's reply names every resource it created.

## Posting a bundle {#posting-a-bundle}

Transaction and batch bundles are posted to the server's root URL. With an [outgoing FHIR connection](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html) named FHIR.Sample:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

# ###########################################################################
# ###########################################################################

if 0:
    from zato.hl7v2.base import HL7Message

# ###########################################################################
# ###########################################################################

class StoreInFHIR(Service):
    name = 'hl7-api.store-in-fhir'

    def handle(self) -> 'None':

        msg:'HL7Message' = self.request.input
        bundle = msg.to_fhir()

        client = self.fhir['FHIR.Sample']
        response = client.execute('', method='post', data=bundle.to_dict())

        self.logger.info('Server response type -> %s', response['type'])
```

The empty path means the server's base URL, and FHIR servers accept bundles at that URL. Everything the connection is configured with, such as [Basic Auth or OAuth](https://zato.io/docs/dev/healthcare/hl7/fhir/security/index.html), applies to the request automatically.

## What the server sends back {#what-the-server-sends-back}

For a transaction, the server replies with a `transaction-response` bundle - one entry per posted resource, in the same order, each with the HTTP status and the location of the created resource:

```python
for entry in response['entry']:
    self.logger.info('%s -> %s',
        entry['response']['status'], entry['response']['location'])
```

```text
201 Created -> Patient/8391/_history/1
201 Created -> Encounter/8392/_history/1
```

The server assigned real IDs and rewrote all the `urn:uuid:` [references](https://zato.io/docs/dev/healthcare/hl7/to-fhir/references/index.html) between the resources - the stored Encounter's `subject` now points at `Patient/8391`.

## Transaction or batch {#transaction-or-batch}

Which [bundle type](https://zato.io/docs/dev/healthcare/hl7/to-fhir/config/index.html) to produce depends on how the receiving side should treat partial failures:

- A **transaction** is all or nothing. If any entry is invalid, the server rejects the whole bundle and stores nothing - the right choice when the resources only make sense together, a Patient with their Encounter and results.
- A **batch** processes each entry independently. The response includes a per-entry status, so some entries may succeed while others fail, and your service checks each one:

```python
for entry in response['entry']:
    status = entry['response']['status']

    if not status.startswith('2'):
        self.logger.warning('Entry rejected -> %s', entry['response'])
```

## Handling rejections {#handling-rejections}

When a server rejects a transaction, it responds with an error status and an OperationOutcome resource explaining why, and the FHIR client raises an exception that contains it. A service that must not lose messages catches the exception and routes the message to a retry path:

```python
try:
    response = client.execute('', method='post', data=bundle.to_dict())
except Exception as e:
    self.logger.warning('FHIR server rejected the bundle -> %s', e)
    raise
```

Raise the exception again - when the service runs behind an [MLLP channel](https://zato.io/docs/dev/healthcare/hl7v2/mllp/index.html), the channel answers with `AR` and the sending system retransmits the message. When the rejection is the message's own fault - the OperationOutcome names a validation error in the content - raise `HL7ApplicationError` instead, so the channel answers with `AE` and the sender does not resend what would fail again.

Two checks catch most rejections before they happen:

- [Validate](https://zato.io/docs/dev/healthcare/hl7/to-fhir/index.html) the bundle's resources against the R4 schema before posting
- Add your local values to the [code mappings](https://zato.io/docs/dev/healthcare/hl7/to-fhir/codes/index.html), so required coded elements get your real codes instead of `unknown` or a `data-absent-reason`

## See also {#see-also}

- [FHIR connections](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html) - Creating the outgoing connection in the Dashboard
- [FHIR security](https://zato.io/docs/dev/healthcare/hl7/fhir/security/index.html) - Basic Auth, OAuth and TLS for the servers you post to
- [MLLP channels](https://zato.io/docs/dev/healthcare/hl7v2/mllp/index.html) - The channel that receives the messages you convert
- [References and deduplication](https://zato.io/docs/dev/healthcare/hl7/to-fhir/references/index.html) - Why transactions keep the bundle's graph intact on the server

## Learn more {#learn-more}

- [Healthcare interface engine](https://zato.io/docs/dev/healthcare/) - Clinical messages, FHIR, conversion and operations
- [HL7 v2 parsing](https://zato.io/docs/dev/healthcare/hl7/v2/parsing/) - Messages as typed objects, with field access and validation
- [MLLP channels](https://zato.io/docs/dev/healthcare/hl7v2/mllp/) - Receiving and sending HL7 v2 over MLLP sockets
- [FHIR integrations](https://zato.io/docs/dev/healthcare/hl7/fhir/) - Read and write FHIR resources, with paths, bundles and extensions
- [HL7 v2 to FHIR](https://zato.io/docs/dev/healthcare/hl7/to-fhir/) - Converting v2 messages into FHIR bundles, codes and references included
- [EDIFACT](https://zato.io/docs/dev/healthcare/edifact/) - Parsing interchanges, dialects and the transports they arrive on
- [Transformation](https://zato.io/docs/dev/healthcare/transformation/) - One message in, another standard out, in ordinary Python
- [Audit log](https://zato.io/docs/dev/healthcare/audit-log.html) - Every message with its acknowledgment, searchable by patient identifier
- [AI in clinical interfaces](https://zato.io/docs/dev/healthcare/ai/) - Services calling LLMs and AI agents calling clinical services as tools
