.. _restful: RESTful APIs ================ .. note:: Swagger API overview is available on base openFHIR context. If one is set up locally on port 8080 without and context, this would be http://localhost:8080. For sandbox example see https://sandbox.open-fhir.com Overview -------- openFHIR Engine is a standalone component that exposes FHIR Operations-framework endpoints for mapping (``/$tofhir`` and ``/$toopenehr``, as defined by the FHIRconnect REST API specification, plus the legacy ``/openfhir/tofhir`` and ``/openfhir/toopenehr``) and 3 restful APIs for state configuration (``/opt``, ``/fc/context`` and ``/fc/model``). Additional APIs are exposed for :ref:`terminology` and :ref:`multitenancy`. FHIRconnect operations ---------------------- Once operational templates, context mappers and model mappers are correctly set up (i.e. state of the engine has been configured), you can run your mappings by invoking the FHIR operations. Requests and responses use the R4 FHIR envelope (``application/fhir+json``); errors are returned as an ``OperationOutcome``. .. http:post:: /$tofhir Map an openEHR Composition to FHIR. Body is a FHIR (R4) ``Parameters`` resource with a ``composition`` parameter (stringified openEHR Composition, flat or canonical), an optional ``templateId`` parameter (required when the composition is flat) and an optional nested ``context`` parameter (parts: ``ehr_id``, ``patient``, ``who``, ``onBehalfOf``). Short context fields may also be passed as query parameters; the body takes precedence on conflict. **Example request**: .. sourcecode:: http POST /$tofhir?templateId=Growth%20chart HTTP/1.1 Content-Type: application/fhir+json Body: { "resourceType": "Parameters", "parameter": [ { "name": "composition", "valueString": "" } ] } :query templateId: Required when the composition is flat (may alternatively be passed as a ``templateId`` parameter in the body). The response is a FHIR Bundle with the mapped resources, an engine-generated ``Provenance`` entry and, when some elements could not be mapped, an ``OperationOutcome`` entry with warnings. The same entry carries an ``error`` issue for every mapping whose execution failed (see `Error responses`_ below); the other mappings are still present in the Bundle. .. http:post:: /$toopenehr Map a FHIR Bundle to an openEHR Composition. Body is the FHIR Bundle itself (no ``Parameters`` wrapper; a single resource is also accepted). **Example request**: .. sourcecode:: http POST /$toopenehr?templateId=Growth%20chart&format=flat HTTP/1.1 Content-Type: application/fhir+json Body: :query templateId: Template id used to select the mapping context; when omitted, the engine tries to deduce it from the available contexts. :query format: ``canonical`` (default) or ``flat``. The response is a FHIR (R4) ``Parameters`` resource with a ``composition`` parameter (``valueString``) and, when gaps were reported during mapping, an ``outcome`` parameter holding an ``OperationOutcome`` — a partial result may coexist with issues. Besides warnings, the outcome carries an ``error`` issue for every mapping whose execution failed (see `Error responses`_ below). Error responses ^^^^^^^^^^^^^^^ Failures are returned as an ``OperationOutcome``. The status code tells you whether the request is yours to correct: * ``400`` — the request or the engine state it refers to needs correcting. This covers a malformed body, a missing ``templateId`` for a flat composition, no context mapper matching the input, and an operational template that is referenced by a context mapper but has never been uploaded. In the last case the ``diagnostics`` names the template, so a context mapper pointing at ``Growth chart1`` when ``Growth chart`` is loaded is reported as such rather than as a server error. * ``500`` — the engine failed. Either the stored operational template no longer parses (``diagnostics`` names it), or an unexpected error occurred, in which case ``diagnostics`` carries a reference id to quote when reporting the problem and the detail is written to the engine log rather than returned. Input that maps to nothing is not an error: if the payload contains none of the resource type the template starts from, the response is a success with an empty result and a warning explaining that nothing was mapped. A failure inside a single mapping does not fail the request either. The engine records it, carries on with the remaining mappings and returns the partial result with a ``200``; the ``OperationOutcome`` (the Bundle entry for ``$tofhir``, the ``outcome`` parameter for ``$toopenehr``) then holds an issue with ``severity`` ``error`` whose ``diagnostics`` name the model mapper, archetype, mapping and the openEHR/FHIR paths being processed, so the failing mapping can be located without reproducing the request. The issue ``code`` tells you whose problem it is: * ``processing`` or ``structure`` — the cause is yours to correct (an unparseable value in the input, an argument the mapper feeds the engine that it rejects); the cause's message is included * ``exception`` — the engine failed; ``diagnostics`` carry the exception's class name and a reference id, and the detail is written to the engine log under that reference A FHIRPath expression in a model mapping that cannot be evaluated against the input (an unknown function, a malformed condition path, a ``resolve()`` that finds nothing) is not an error but a ``warning`` with code ``incomplete`` naming the mapping, the expression and the FHIRPath engine's message; that mapping is skipped and the rest is mapped. OperationOutcome verbosity ^^^^^^^^^^^^^^^^^^^^^^^^^^ An input that skips many elements can produce many warnings, and every one of them lands in the response. How much the ``OperationOutcome`` carries is configurable: .. list-table:: :header-rows: 1 :widths: 40 15 45 * - Environment variable - Default - Description * - ``OPENFHIR_OPERATIONS_OUTCOME_VERBOSITY`` - ``all`` - ``all``: errors and warnings. ``errors``: only mappings whose execution failed; warnings about skipped or unmappable elements are dropped. ``none``: no ``OperationOutcome`` is produced at all (neither the Bundle entry nor the ``outcome`` parameter). Issues below the configured level are never collected, so the payload does not grow with them. The setting does not change what the engine logs, and it does not apply to the error responses above (a ``400`` / ``500`` is always an ``OperationOutcome``). Mappings (legacy) ----------------- The following endpoints predate the FHIRconnect operations above and remain available: .. http:post:: /openfhir/tofhir Map an openEHR Composition to a FHIR Resource. Body can either be a FLAT representation of the openEHR Composition or a Canonical JSON. FLAT examples: https://github.com/ehrbase/openEHR_SDK/tree/develop/test-data/src/main/resources/composition/flat/simSDT/conformance Canonical JSON examples: https://github.com/ehrbase/openEHR_SDK/tree/develop/test-data/src/main/resources/composition/canonical_json **Example request**: .. sourcecode:: http POST /openfhir/tofhir HTTP/1.1 Content-Type: application/json Body: .. sourcecode:: http POST /openfhir/tofhir?templateId=dataset_poc_rso-zl_acp HTTP/1.1 Content-Type: application/json Body: :query templateId: If an engine can not deduce a template it (i.e. if body is NOT a canonical json), then you need to provide a template id in a query parameter. When body of the request is a canonical json, this information is present within the canonical json and a templateId query param is not required. Response is a FHIR Resource. .. http:post:: /openfhir/toopenehr Map a FHIR Resource to an openEHR Composition. Body needs to be a valid FHIR Resource in a JSON format. Response is an openEHR Composition in either a FLAT format or a Canonical JSON. **Example request**: .. sourcecode:: http POST /openfhir/toopenehr HTTP/1.1 Content-Type: application/json Body: :query flat: If you'd like to receive response in a FLAT format instead of a canonical JSON. default is false. :query templateId: For the purpose of using the correct context for mapping, the engine needs to know a template id. Template id can either come as an input query parameter or it can be deduced based on all available context mappings. If no templateId is provided in the request, the engine will check all available contexts (that were persisted as part of the state configuration) and try to find one that is able to map the incoming FHIR Resource. If more than 1 is available, it will log a message and take the first one it finds. State configuration ------------------- .. note:: An alternative state configuration option is via a Bootstrapping mechanism. See more at: :ref:`state-configuration` Context mappers ^^^^^^^^^^^^^^^ Context mappers are yaml files representing context on which a mapping takes place. See more at :doc:`fhirconnect`. Structure needs to comply to a schema available at https://github.com/better-care/fhir-connect-mapping-spec. Body when creating/updating a context mapper is plain text, directly yaml itself. .. http:post:: /fc/context Create a new context mapper. **Example request**: .. sourcecode:: http POST /fc/context HTTP/1.1 Content-Type: application/x-yaml Body: grammar: FHIRConnect/v0.0.1 type: context metadata: name: "Growth chart" version: 1.0.0 spec: system: FHIR version: R4 context: profile: url: "Observation" template: id: "Growth chart" archetypes: - "openEHR-EHR-OBSERVATION.body_weight.v2" - "openEHR-EHR-OBSERVATION.height.v2" start: "openEHR-EHR-OBSERVATION.body_weight.v2" .. http:put:: /fc/context/(uuid:context_mapper_unique_id) Update an existing context mapper. **Example request**: .. sourcecode:: http PUT /fc/context/6b7fe776-e4de-49ce-849e-406b280e5338 HTTP/1.1 Content-Type: text/plain Body: grammar: FHIRConnect/v0.0.1 type: context metadata: name: "Growth chart" version: 1.0.0 spec: system: FHIR version: R4 context: profile: url: "Observation" template: id: "Growth chart" archetypes: - "openEHR-EHR-OBSERVATION.body_weight.v2" - "openEHR-EHR-OBSERVATION.height.v2" start: "openEHR-EHR-OBSERVATION.body_weight.v2" Model mappers ^^^^^^^^^^^^^ Model mappers are yaml files representing model mappings. See more at :doc:`fhirconnect`. Structure needs to comply to a schema available at https://github.com/better-care/fhir-connect-mapping-spec. Body when creating/updating a model mapper is plain text, directly yaml itself. .. http:post:: /fc/model Create a new model mapper. **Example request**: .. sourcecode:: http POST /fc/model HTTP/1.1 Content-Type: text/plain Body: grammar: FHIRConnect/v0.0.1 type: model metadata: name: "openEHR-EHR-OBSERVATION.body_weight.v2" version: 1.0.0 spec: system: FHIR version: R4 openEhrConfig: archetype: "openEHR-EHR-OBSERVATION.body_weight.v2" fhirConfig: structureDefinition: http://hl7.org/fhir/StructureDefinition/Observation condition: - targetRoot: "$resource" targetAttribute: "code.coding.code" operator: "one of" criteria: "29463-7" mappings: - name: "weight" with: fhir: "$resource.value" openehr: "$archetype/data[at0002]/events[at0003]/data[at0001]/items[at0004]" type: "QUANTITY" fhirCondition: targetRoot: "$resource" targetAttribute: "code.coding.code" operator: "one of" criteria: "[$loinc.29463-7, $snomed.27113001]" - name: "time" with: fhir: "$resource.effective" openehr: "$archetype/data[at0002]/events[at0003]/time" type: "DATETIME" - name: "comment" with: fhir: "$resource.note.text" openehr: "$archetype/data[at0002]/events[at0003]/data[at0001]/items[at0024]" type: "STRING" .. http:put:: /fc/model/(uuid:model_mapper_unique_id) Update an existing model mapper. **Example request**: .. sourcecode:: http PUT /fc/model/9ae2278a-c02b-4165-96fc-79dd91495db3 HTTP/1.1 Content-Type: text/plain Body: grammar: FHIRConnect/v0.0.1 type: model metadata: name: "openEHR-EHR-OBSERVATION.body_weight.v2" version: 1.0.0 spec: system: FHIR version: R4 openEhrConfig: archetype: "openEHR-EHR-OBSERVATION.body_weight.v2" fhirConfig: structureDefinition: http://hl7.org/fhir/StructureDefinition/Observation condition: - targetRoot: "$resource" targetAttribute: "code.coding.code" operator: "one of" criteria: "29463-7" mappings: - name: "weight" with: fhir: "$resource.value" openehr: "$archetype/data[at0002]/events[at0003]/data[at0001]/items[at0004]" type: "QUANTITY" fhirCondition: targetRoot: "$resource" targetAttribute: "code.coding.code" operator: "one of" criteria: "[$loinc.29463-7, $snomed.27113001]" - name: "time" with: fhir: "$resource.effective" openehr: "$archetype/data[at0002]/events[at0003]/time" type: "DATETIME" - name: "comment" with: fhir: "$resource.note.text" openehr: "$archetype/data[at0002]/events[at0003]/data[at0001]/items[at0024]" type: "STRING" Operational Templates ^^^^^^^^^^^^^^^^^^^^^ If openFHIR Engine is not integrated (see :doc:`integrations`) with an existing openEHR repository where it's fetching/searching templates for, then they need to exist within the database of the engine itself. .. note:: When the engine *is* integrated with an openEHR CDR (``OPENFHIR_CDR_ENABLED=true``), templates are owned by the CDR and the endpoints below change behaviour: ``POST``, ``PUT`` and ``DELETE`` return ``409 Conflict``, ``GET /opt`` returns an empty array and ``GET /opt/{id}`` returns ``404``. Load templates into the CDR directly instead. See :doc:`integrations`. .. http:post:: /opt Create a new operational template **Example request**: .. sourcecode:: http POST /opt HTTP/1.1 Content-Type: application/xml Body: