7a. The REST façade (openEHR ITS-REST)
The federation surface is not limited to AQL. A gateway presents the openEHR ITS-REST API as a whole, reads and writes, so that an application written against a single CDR can be pointed at the gateway unchanged (N1). AQL (§7) is the part of that surface where fan-out happens. The rest is routed to a single node (§12).
Transparency is only credible if the boundaries are stated, so this section says both what a gateway MUST expose and what it is not expected to federate.
7a.1 What the façade federates
| ITS-REST area | Federated? | Behaviour at the gateway |
|---|---|---|
|
Yes - fan-out |
The subject → |
|
Yes - single-node routing |
Routed to exactly one node by the rules of §12. |
|
Partially - not federated in v1 |
A gateway MAY expose these, but MUST route each request to a single, explicitly chosen node (§12.6). It MUST NOT silently pick a node, and MUST NOT present a merged view of templates across nodes as if it were one repository. Fan-out of template upload is OPTIONAL and discussed in §12.6. |
|
Either - single-node-routed, or gateway-held |
Two behaviours, and a gateway declares which. By default, as for templates: routed to a single explicitly chosen node (§12.6). Alternatively, a gateway MAY offer a federated stored-query registry (§12.7) in which the gateway is authoritative for the definition, versions it immutably, and expands it into an ordinary fan-out when it is invoked by name. The registry is the only way a federated query becomes invocable by name at all. Distributing the definition to nodes is a further option again. All of it is declared under N30. |
|
No - out of scope |
The federation model keeps demographics outside the CDR on purpose (§5.1); identity is resolved by an MPI / demographic service through the identity binding (§5.2, Annex A). A gateway MUST NOT federate the openEHR DEMOGRAPHIC API. If it does not expose it at all, it MUST answer |
7a.2 Declaring the surface: OPTIONS
A client (and a test harness) needs a machine-readable answer to "what does this gateway actually do, and what is behind it?".
|
This body has no ITS-REST counterpart. Unlike the AQL result envelope, which is an openEHR The body is therefore versioned with this specification, and |
-
A gateway MUST implement
OPTIONS {base}/returning a description of the federation surface: the ITS-REST areas it supports (per §7a.1 above), which are federated vs. single-node-routed, which are unsupported, the dedup mode (§10), and the gateway’s timeout policy (§11.5). (N30.) -
The response MUST include the member endpoints behind the gateway - the same identifiers usable in a directive (§8) - each with at least
id,organisation, andstatus, and SHOULD include the owningnode_id(§ The four identifiers). It SHOULD includeproductandversionas separate fields, matching §9.5, and a representative latency where the gateway tracks one. -
An endpoint’s
statushere is membership and health information, meaning whether this member is in service. It is not the per-query status vocabulary of §11.1. The two answer different questions: this one describes the federation’s standing configuration,meta.federation.endpoints[]describes what happened to one query. A value such asnot-localizedmeans nothing here, andactivehere does not predictactivethere. -
This specification does not define a closed vocabulary for the membership status. It is a free-form string whose values a deployment chooses, and a client MUST NOT assume a fixed set.
activeis the conventional value for a member in service, and the schema treats the field as an unconstrained string so that no implementer reads the §11.1 enum into it by default. Fixing a vocabulary here is deferred (§18), because it is a membership-lifecycle question and this release does not specify that lifecycle (§12b). -
Latency reported here is an aggregate over recent requests, not the per-query measurement of §9.5; a gateway reporting one SHOULD name the statistic in the field (e.g.
latency_ms_p50) so the two are not confused. -
The response MUST additionally declare every behaviour this specification requires be declared under N30: whether the opt-in best-effort completeness mode is offered, and how it is selected (N37; all-or-nothing is the default and needs no declaration beyond
completeness.default), theOFFSET > 0strategy (N39), which decomposable aggregates are supported if any (§11.6.3), whether fan-out template upload is offered (N43), whether the stored-query registry and the fan-out of stored-query definitions are offered (N44, §12.7), the behaviour when localization is unavailable (§14.1), and the JWKS location (§13.1). -
The accepted patient-resolution input predicates are not declared here, on purpose. Both carriers,
EHR_STATUS.subject.external_refand anENTRY-levelsubjectPARTY_IDENTIFIED/DV_IDENTIFIERpredicate, are mandatory at every conformant gateway (§5.4.3, N33). There is nothing for a deployment to vary and nothing for a client to discover, and a key declaring them would invite the variation that the requirement removes. -
The accepted targeting mechanisms are omitted for the same reason. Both the in-AQL
FROM ENDPOINTdirective and theopenEHR-federation-endpointheader are mandatory at every conformant gateway (§8.4, N35), so the only conformant declaration is "both" and a client learns nothing by reading it. Theaqlobject therefore carries noendpoint_directiveorendpoint_headerkey.fan_outremains, because it describes what the gateway does with an untargeted query and says nothing about which mechanisms it accepts. -
The endpoint list is federation membership information and contains no patient data. It MUST NOT be gated on a patient identifier, and it MUST be subject to the gateway’s normal authentication (§13). A deployment MAY restrict the detail returned to unauthenticated callers.
-
OPTIONSon a sub-path (e.g.OPTIONS {base}/v1/ehr/{ehr_id}) SHOULD return the HTTP methods allowed for that resource, per ordinary HTTP semantics. -
Note that
its_rest.definitiondescribes the area as a whole whiledefinition.stored_query_registrydescribes one artefact within it, and the two can legitimately differ. The example below declares templates single-node-routed and stored queries held at the gateway registry, which is the expected shape for a gateway that adopted §12.7 without changing template handling. Theits_restvalues are free-form descriptions of behaviour and a client MUST NOT parse them as a closed vocabulary. A client tests thedefinitionobject’s booleans programmatically. -
federation.spec_versioncarries the version of this specification the gateway implements, inmajor.minorform. Patch releases are editorial and do not change the wire contract, so a gateway implementing 0.9.0 reports"0.9"and would still report"0.9"at 0.9.1. A client MUST NOT match on a patch component. A minor release may change the wire contract and so does move this value: 0.4.0 did, by correctingrowsto the ITS-REST array form (Changes). 0.9.0 changes no wire structure, but it is a minor release and so moves the value too, from"0.4"to"0.9". The field identifies which release a gateway was built against, and a client pinning"0.4"should be told the document has moved on (Changes).
OPTIONS / response body{
"federation": {
"id": "rso-zl",
"spec_version": "0.9",
"aql": { "fan_out": true },
"dedup": {
"default": "none",
"modes": ["none", "version-identity"],
"request_header": "openEHR-federation-dedup"
},
"timeout": { "per_node_ms": 5000, "overall_ms": 15000, "policy": "all-or-nothing" },
"completeness": {
"default": "all-or-nothing",
"best_effort": true,
"opt_in": { "header": "openEHR-federation-completeness", "value": "partial" }
},
"paging": { "offset_strategy": "reject" },
"aggregates": { "decomposable": ["COUNT", "SUM", "MIN", "MAX"] },
"definition": {
"fan_out_template_upload": false,
"stored_query_registry": true,
"stored_query_fan_out": false
},
"localization": { "on_failure": "closed" },
"auth": { "jwks_uri": "https://gw.rso.nl/.well-known/jwks.json" },
"its_rest": {
"query": "federated",
"ehr": "routed",
"definition": "routed-single-node; stored queries at the gateway registry",
"demographic": "unsupported"
}
},
"endpoints": [
{ "id": "node_1", "node_id": "node_1", "system_id": "cdr1.rso.nl", "organisation": "Org A",
"status": "active", "product": "VendorX CDR", "version": "3.4.1", "latency_ms_p50": 120 },
{ "id": "node_2", "node_id": "node_2", "system_id": "cdr2.rso.nl", "organisation": "Org B",
"status": "active", "product": "VendorY", "version": "2026.1", "latency_ms_p50": 310 }
]
}
7a.3 Write responses carry provenance in headers
An AQL result set carries provenance in meta.federation.endpoints[] (§9). A write has no such envelope. POST {base}/v1/ehr/{ehr_id}/composition returns a 201 with Location and ETag, and nothing else.
-
On every request the gateway routed or dispatched to a single node - including
POST,PUTandDELETE- the gateway MUST return the acting endpoint’s identifier in theopenEHR-federation-endpointresponse header, and SHOULD return the node’ssystem_idinopenEHR-federation-system-id. (N31, §9.6.) -
The gateway MUST pass the node’s
LocationandETagthrough unmodified, because openEHR uids are never rewritten (N22). TheETagof a commit is anOBJECT_VERSION_IDand so already carries thecreating_system_id. The headers above are a convenience and an audit aid; they do not replace it. -
These headers SHOULD also be set on federated AQL responses, listing the endpoints that contributed, but
meta.federation.endpoints[]stays the normative carrier there (§11.1).