Cadasto demographic link — connecting an EHR to a demographic person

Cadasto exposes a vendor-extension header that lets clients link an EHR to a Demographic PERSON in the same call that creates or updates the EHR. The link is stored separately from the openEHR EHR_STATUS.subject field — both can be set independently — and is used internally by the platform to resolve a person to their EHR (and back) without an AQL detour.

Two headers govern the feature:

Header Direction Where Purpose
cadasto-person-uid request POST /ehr, PUT /ehr/{ehr_id}, PUT /ehr/{ehr_id}/ehr_status Write the link between the EHR and the supplied PERSON.
include-cadasto-person-uid request GET /ehr/{ehr_id} Opt in to receiving the linked PERSON UID back in the response.
cadasto-person-uid response GET /ehr/{ehr_id} The linked PERSON UID, when the opt-in header was set and a link exists.

After reading this guide, you should be able to:

  • Link an EHR to a Cadasto PERSON in one call when creating the EHR
  • Backfill the link on an existing unlinked EHR via the EHR_STATUS update endpoint
  • Retrieve the linked PERSON UID from a GET /ehr/{ehr_id} call
  • Understand the append-only semantics and conflict responses

Cadasto extension. The cadasto-person-uid and include-cadasto-person-uid headers described on this page are not part of the openEHR ITS-REST specification. Standard openEHR clients can continue to use the EHR_STATUS.subject.external_ref pattern together with GET /ehr?subject_id=…&subject_namespace=… to identify EHR subjects — the two mechanisms are independent and can coexist on the same EHR.

Both routes write to the same underlying link store and share the same conflict rules.

Entry point Use when
cadasto-person-uid on POST /ehr or PUT /ehr/{ehr_id} A new EHR is being created for a PERSON that already exists in the Demographic API.
cadasto-person-uid on PUT /ehr/{ehr_id}/ehr_status An existing EHR has no link yet and you want to backfill one.

On EHR_STATUS update the operation is append-only: once an EHR has a link, the header cannot be used to re-link it to a different PERSON. See Cadasto-specific behaviour below.

The cadasto-person-uid request header

Request format

A single bare PERSON UID, in the HIER_OBJECT_ID form — the same form returned as uid.value by the Demographic API on create:

cadasto-person-uid: 7d44b88c-4199-4bad-97dc-d78268e01398

The versioned form (<uuid>::<system>::<version>) is not accepted. Strip any version suffix before sending — for example, with PHP Engine24_Helper::extractUid($personUid) or any standard UUID regex.

Endpoints that accept it

Endpoint Effect when the header is set
POST /ehr Creates the EHR and writes the link in the same contribution.
PUT /ehr/{ehr_id} (create-with-id) Same as above, with a client-supplied ehr_id.
PUT /ehr/{ehr_id}/ehr_status Writes the link in the same contribution as the EHR_STATUS version, only if no link exists yet.

The header is ignored on all other endpoints. Omitting it preserves the existing behaviour — none of these endpoints write a link without it.

Example: create an EHR linked to a person in one call

POST /ehr
Authorization: Bearer <token>
Accept: application/json
Prefer: return=representation
cadasto-person-uid: 7d44b88c-4199-4bad-97dc-d78268e01398
HTTP/1.1 201 Created
ETag: W/"3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718"
Location: /ehr/3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718
Content-Type: application/json

{
  "system_id": { "value": "cadasto.io" },
  "ehr_id": { "value": "3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718" },
  "ehr_status": { "...": "..." },
  "time_created": { "value": "2026-06-08T10:30:00.000+00:00" }
}

The EHR is now linked to PERSON 7d44b88c-…. The link does not appear in the response body — see Reading the link back for how to retrieve it on subsequent calls.

PUT /ehr/3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718/ehr_status
Authorization: Bearer <token>
Content-Type: application/json
If-Match: "3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718::cadasto.io::1"
Prefer: return=minimal
cadasto-person-uid: 7d44b88c-4199-4bad-97dc-d78268e01398

{ "_type": "EHR_STATUS", "...": "..." }
HTTP/1.1 204 No Content
ETag: W/"3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718::cadasto.io::2"
Location: /ehr/3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718/ehr_status/3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718::cadasto.io::2

The EHR_STATUS version is incremented and the link is written in the same atomic contribution.

The include-cadasto-person-uid opt-in

GET /ehr/{ehr_id} does not emit the link by default. Add the request header include-cadasto-person-uid to opt in:

GET /ehr/3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718
Authorization: Bearer <token>
Accept: application/json
include-cadasto-person-uid: 1

The header is presence-triggered — the value is ignored, consistent with how Prefer: include_item_tags works for ITEM_TAG. Use any non-empty string (1, true, the literal text yes).

Response

When the EHR has a link, the response carries it as a cadasto-person-uid header:

HTTP/1.1 200 OK
Content-Type: application/json
cadasto-person-uid: 7d44b88c-4199-4bad-97dc-d78268e01398

{ "_type": "EHR", "ehr_id": { "value": "3f9b8aa1-..." }, "...": "..." }

When the EHR has no link, the response header is omitted entirely (not sent with an empty value). When the request did not opt in via include-cadasto-person-uid, the extra link lookup is skipped and the header is also omitted.

Cadasto-specific behaviour

[!warning] The following points constrain how the link behaves on the Cadasto platform. Sending an unsupported value or attempting a forbidden re-link returns 400 Bad Request, 409 Conflict, or 422 Unprocessable Entity depending on the case — see Validation and error responses.

Append-only on EHR_STATUS update

Once an EHR has a link, the cadasto-person-uid header on PUT /ehr/{ehr_id}/ehr_status will not re-link it. The request is rejected with 409 Conflict. To re-link an EHR you must first delete it via DELETE /ehr/{ehr_id} (which also removes the link), then create a new EHR.

Independent of subject.external_ref

The link and the openEHR EHR_STATUS.subject.external_ref.id/namespace fields are independent. You can set:

  • Neither — the EHR has no Cadasto person link and no external subject reference.
  • Subject only — standard openEHR behaviour; GET /ehr?subject_id=…&subject_namespace=… resolves via the EHR_STATUS XML.
  • Cadasto link only — the EHR has no subject.external_ref but is resolvable via the demographic store.
  • Both — independently set, neither overrides the other.

The duplicate-subject check on POST /ehr (which rejects a body whose subject.external_ref matches an existing EHR's subject with 409) is unaffected by this feature and continues to operate on the body content.

PERSON-typed parties only

The header value must resolve to a PERSON in the Demographic API. Organisations, Agents, Groups, and Roles are not accepted and return 422 Unprocessable Entity.

Not part of the openEHR ITS-REST specification

This is a Cadasto-specific extension. Clients that need to remain on the standard spec surface can continue to use EHR_STATUS.subject.external_ref and the standard GET /ehr?subject_id=…&subject_namespace=… lookup.

Validation and error responses

Endpoint Condition Status
Any write endpoint Header value is not a well-formed bare UUID (e.g. versioned form sent) 400 Bad Request
Any write endpoint The PERSON does not exist, or the UID resolves to a non-PERSON actor 422 Unprocessable Entity
Any write endpoint The supplied PERSON is already linked to another EHR 409 Conflict
PUT /ehr/{ehr_id}/ehr_status only The target EHR already has a link to any PERSON 409 Conflict (append-only)
PUT /ehr/{ehr_id}/ehr_status The target EHR does not exist 404 Not Found

On EHR_STATUS update the EHR-exists check runs before the cadasto pre-checks, so a 404 for a non-existent EHR takes precedence over the cadasto 409s — this prevents the response from leaking link state for an unknown EHR.

Sample 409 Conflict body

{
  "title": "Conflict",
  "errors": [
    { "detail": "The supplied PERSON is already linked to another EHR." }
  ]
}
{
  "title": "Conflict",
  "errors": [
    { "detail": "The EHR is already linked to a PERSON. The cadasto-person-uid header is append-only." }
  ]
}

API references

Next steps