8. Endpoint targeting (FROM ENDPOINT and the endpoint header)

A query can be pinned to named nodes in two ways, and this specification supports both on purpose (§8.4): in the AQL (the FROM ENDPOINT directive, §8.1) and beside the AQL (the openEHR-federation-endpoint request header, §8.4). They are semantically equivalent.

8.1 Semantics

  • FROM ENDPOINT p [ "node_1", "node_2", … ] is a federation-only, optional directive. It selects the node set for the query. When present, the node set is exactly the listed endpoints; when absent, the node set comes from localization (N4/N10).

  • The directive is orthogonal to patient resolution. Per-node scope is always the resolved ehr_id (N7/N11). A directed + subject query means "find this patient, but only in these systems." List members where the patient is not-resolved are simply reported in meta.federation.endpoints[] with status not-resolved, not errored. Registry members the directive did not name SHOULD be reported excluded, meaning a decision was made about them. That differs from not-localized, which belongs to the undirected case where localization simply did not name a node (§11.1).

  • Endpoint identifiers in the directive MUST be the stable registry identifiers of Endpoints (N19), not URLs. This keeps the client decoupled from physical addresses, which the registry owns (§15).

  • A coarser selector ORGANISATION [ … ] MAY be used; the gateway expands each organization to its Endpoints via the registry (an Organization manages 1..* Endpoints; each Endpoint has exactly one managing Organization, N20).

8.2 Directed clients and the transparency trade-off

A client using either targeting mechanism has opted out of transparency, knowingly: it now knows about nodes. Undirected clients, which are the default, never see this.

8.3 Example (directed, with provenance columns)

SELECT
  p/id  AS endpoint_id,
  p/system_id AS system_id,
  c/uid/value AS composition_id
FROM ENDPOINT p [ "node_1", "node_2", "node_3" ]
  CONTAINS EHR e CONTAINS COMPOSITION c
WHERE e/ehr_status/subject/external_ref/id/value = '12345'

The gateway resolves subject = '12345' to a local ehr_id per listed endpoint, runs standard AQL keyed on each ehr_id, and annotates rows with endpoint_id and system_id (both drawn from the registry / the resolving node, §9).

8.4 Targeting outside the AQL: the endpoint header (normative)

Putting the node set inside the query text carries a cost that only shows up in real deployments: it changes the query. That matters in two situations, both of which happen in practice.

  • Stored / persisted AQL. A stored query ({base}/v1/definition/query/…), a query saved in a report definition, or a query held in an application’s source is a fixed artefact. If targeting lives in the AQL text, retargeting means rewriting and re-storing the artefact, and a stored query that is federated for one caller and pinned for another needs two copies. With the header, one stored query serves both.

    The argument turned from a convenience into a necessity once a gateway could hold stored queries itself (§12.7). Under a registry that also distributes definitions to nodes, a definition carrying FROM ENDPOINT cannot be fanned out at all: the targeting means nothing at a node, and §12.7 requires the gateway to refuse it (§12.7). The header is therefore the only way a distributable stored query can be pinned by its caller. Targeting that sits beside the query survives distribution, because it never travels with the definition.

  • Interactive tools. An EHR-explorer-style application lets a user compose AQL by hand and then tick which nodes to run it against. Splicing FROM ENDPOINT […​] into user-authored AQL means parsing and rewriting the user’s text, which is fragile and shows up to the user as a query they did not write.

Therefore:

  • A gateway MUST support both mechanisms. It MUST accept the node set carried outside the AQL - as the openEHR-federation-endpoint request header - and it MUST accept the in-AQL FROM ENDPOINT directive of §8.1. A client MUST support at least one and MAY support either. (N35.)

  • The out-of-AQL mechanism is a header, and only a header. The node set MUST NOT travel as a query parameter, and a gateway MUST NOT define ?endpoint= or ?organisation= as a targeting mechanism. Keeping it in the header means targeting never varies the request URI, so it cannot silently change cache keys, stored-query URLs, or the ITS-REST paths of §7a.

  • Both carry the same values: stable registry endpoint identifiers (N19), never URLs (§8.1). The header value is a comma-separated list of those identifiers.

    POST {base}/v1/query/aql
    openEHR-federation-endpoint: node_1, node_2, node_3
    Content-Type: application/json
    
    { "q": "SELECT c/uid/value AS composition_id FROM EHR e CONTAINS COMPOSITION c
            WHERE e/ehr_status/subject/external_ref/id/value = '12345'" }

    The equivalent organization-level selector is the openEHR-federation-organisation header, expanded through the registry exactly as in §8.1.

  • The header form applies to any federated request, not only AQL - including the single-node routing of §7a and the explicit write target required by §12.4. The AQL directive, by construction, applies only to AQL. That reach is a second reason the header cannot simply be dropped in favour of the directive.

  • Because support for both is mandatory, a gateway does not declare which mechanisms it accepts: the only conformant answer is "both", so there is nothing for a client to discover (§7a.2).

8.4.1 Precedence and conflict

  • "Both" here means the AQL directive and the header, the only two mechanisms that exist (§8.4). If both appear in one request, the gateway MUST NOT silently merge them or silently prefer one. If the two node sets are identical, the request proceeds. If they differ, the gateway MUST reject with 400 and an error naming both sets, so that it never guesses which one the client meant. (N35.)

  • An endpoint identifier unknown to the registry MUST be rejected with 400, under either mechanism. An endpoint that is known but where the patient is not-resolved is reported, not errored (§8.1).

8.5 Why not standardise on one

Both mechanisms stay in the specification because they fail in opposite directions:

FROM ENDPOINT (in AQL) openEHR-federation-endpoint header (outside AQL)

Self-contained

Yes - the query text is the whole request; it can be logged, replayed and diffed as one artefact.

No - the request is the query plus its context.

Requires AQL grammar extension

Yes - this is a federation-only extension to AQL, and a node would reject it.

No - the AQL stays exactly conformant, which also lets the gateway forward it unchanged.

Works for stored / user-authored queries

Poorly - retargeting rewrites the artefact (§8.4).

Yes - one stored query, many targetings.

Works for non-AQL requests (writes, ITS-REST)

No.

Yes (§7a, §12.4).

The gateway therefore carries the cost of supporting both, and the client picks the one that suits it.