# Authentication This guide explains how to authenticate with the Cadasto API using the [OAuth 2.0 Client Credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4) flow. This is the standard approach for server-to-server (machine-to-machine) integrations where no interactive user login is needed. After completing this guide you will be able to obtain an access token and make authenticated API requests. ## Prerequisites - A registered OAuth client (`client_id` and `client_secret`) issued by Cadasto. If you don't have credentials yet, see [Requesting Client Credentials](/docs/guides/faq). - Your Cadasto environment base URLs: - **Auth server:** `https://.auth.prod.cadasto.io` - **API server:** `https://.api.prod.cadasto.io` ## Service discovery Instead of hardcoding endpoint URLs, you can discover them automatically from the SMART configuration endpoint: ```bash curl "https://.auth.prod.cadasto.io/.well-known/smart-configuration" \ -H "Accept: application/json" ``` This returns a JSON document advertising the available endpoints and capabilities: ```json { "authorization_endpoint": "https://.auth.prod.cadasto.io/oauth/authorize", "token_endpoint": "https://.auth.prod.cadasto.io/oauth/token", "grant_types_supported": ["client_credentials"], "scopes_supported": ["api.read", "api.write"] } ``` The actual response may include additional fields such as API service URLs (e.g., `org.openehr.rest`), supported client authentication methods, and SMART capabilities. For the Client Credentials flow described below, you only need the `token_endpoint`. Using the discovery endpoint makes your integration more resilient to future URL changes. ## How the Client Credentials flow works The Client Credentials flow lets a backend service authenticate directly with the authorization server, without a user being present. Your application sends its `client_id` and `client_secret` to the token endpoint and receives a time-limited access token in return. ![Authentication sequence — Client Credentials flow through discovery, token request, and authenticated API calls](/images/diagrams/04-authentication-flow.svg) For more detail on the underlying standard, see [RFC 6749 Section 4.4](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4). ## Obtain an access token Send a `POST` request to the token endpoint with your client credentials. The recommended approach is HTTP Basic authentication (credentials in the `Authorization` header). **Token endpoint** ```http POST https://.auth.prod.cadasto.io/oauth/token ``` **Parameters** | Parameter | Location | Required | Description | | --------- | -------- | -------- | ----------- | | `grant_type` | Body | Yes | Must be `client_credentials` | | `client_id` | Header (Basic Auth) | Yes | Your OAuth client ID | | `client_secret` | Header (Basic Auth) | Yes | Your OAuth client secret | | `audience` | Body | Yes | The API you want to access, e.g. `https://.api.prod.cadasto.io/openehr/v1` | | `scope` | Body | No | Space-separated list of scopes, e.g. `api.read api.write` | **Example request** ```bash curl -X POST "https://.auth.prod.cadasto.io/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -u ":" \ -d "grant_type=client_credentials" \ -d "audience=https://.api.prod.cadasto.io/openehr/v1" \ -d "scope=api.read+api.write" ``` If you cannot use HTTP Basic authentication, you may include `client_id` and `client_secret` as form body parameters instead (less preferred). **Successful response** ```json { "access_token": "eyJhbGciOiJSUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600, "scope": "api.read api.write" } ``` | Field | Description | | ----- | ----------- | | `access_token` | The [Bearer token](https://datatracker.ietf.org/doc/html/rfc6750) to include in API requests | | `token_type` | Always `Bearer` | | `expires_in` | Token lifetime in seconds (typically 3600 = 1 hour) | | `scope` | The scopes granted by the server | ## Use the access token Include the token in the `Authorization` header of every API request: ```bash curl "https://.api.prod.cadasto.io/openehr/v1/definition/template/adl1.4" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Example response** (list of uploaded templates): ```json [ { "template_id": "vital_signs.v1", "concept": "Vital Signs", "archetype_id": "openEHR-EHR-COMPOSITION.encounter.v1" } ] ``` ## Handle token expiry The Client Credentials flow does not return a `refresh_token`. When your access token expires, request a new one by repeating the token request above. **Recommended approach:** Check the `expires_in` value from the token response and schedule a new token request before the current token expires. This avoids failed API calls due to expired tokens. ``` Token received at 14:00:00 with expires_in=3600 → Token expires at 15:00:00 → Request new token at ~14:55:00 (with a safety margin) ``` ## Error responses If the token request fails, the auth server returns an error in the standard [OAuth 2.0 error format](https://datatracker.ietf.org/doc/html/rfc6749#section-5.2): ```json { "error": "invalid_client", "error_description": "Client authentication failed." } ``` Common errors: | Error | Cause | | ----- | ----- | | `invalid_client` | Wrong `client_id` or `client_secret` | | `invalid_grant` | Incorrect `grant_type` | | `invalid_request` | Invalid client credentials or refresh token | | `invalid_scope` | Requested scope not allowed for this client | ## Next steps - [Quick Start](/docs/quick-start) — Create your first EHR, upload a template, and commit a composition - [AQL Guide](/docs/guides/aql-archetype-query-language) — Query your openEHR data - [openEHR Concepts](/docs/guides/openehr) — Understand the data model behind the APIs # Quick Start This guide walks you through the typical Cadasto workflow: authenticate, create an EHR, upload a template, commit a composition, and query data. All examples use `curl` — replace `` and `` with your actual values. ![Developer journey — from credentials to querying clinical data in 6 steps](/images/diagrams/03-developer-journey.svg) ## Prerequisites - A Cadasto environment with credentials (`client_id` and `client_secret`). See [Requesting Credentials](/docs/guides/faq) if you don't have them yet. - An access token obtained via the [Authentication guide](/docs/guides/authentication). All API requests require a Bearer token in the `Authorization` header. See [Authentication](/docs/guides/authentication) for how to obtain one. **Base URL for all API calls:** ``` https://.api.prod.cadasto.io/openehr/v1 ``` ## 1. Create an EHR Create a new Electronic Health Record. The server generates a unique `ehr_id`. ```http POST /ehr Prefer: return=representation Authorization: Bearer ``` **Response** (`201 Created`): ```json { "system_id": { "value": "cadasto.io" }, "ehr_id": { "value": "7d44b88c-4199-4bad-97dc-d78268e01398" }, "ehr_status": { ... }, "time_created": { "value": "2025-01-15T10:30:00.000+00:00" } } ``` Retrieve an existing EHR: ```http GET /ehr/{ehr_id} Accept: application/json Authorization: Bearer ``` ### Understanding the response - `ehr_id` is a UUID that uniquely identifies this EHR. Use it in all subsequent API calls. - `ehr_status` contains subject information and queryable/modifiable flags. - You can optionally provide an `EHR_STATUS` body with `POST /ehr` to set subject references or other details at creation time. Already have a Cadasto PERSON for this patient? Add the `cadasto-person-uid: ` request header on `POST /ehr` and the EHR is linked to that PERSON in the same call. See the [Cadasto demographic link guide](/docs/guides/cadasto-demographic-link). ## 2. Upload a template Templates define the structure and constraints for clinical documents. You must upload at least one template before committing compositions. Upload an ADL 1.4 Operational Template (OPT) in XML format. ```http POST /definition/template/adl1.4 Content-Type: application/xml Prefer: return=minimal Authorization: Bearer ``` ```xml ``` List all uploaded templates: ```http GET /definition/template/adl1.4 Authorization: Bearer ``` Retrieve a specific template: ```http GET /definition/template/adl1.4/{template_id} Authorization: Bearer ``` ## 3. Commit a composition A [Composition](https://specifications.openehr.org/releases/RM/development/ehr.html#_composition_package) is a clinical document committed to an EHR. It is validated against the template specified by its `archetype_node_id`. Not sure what a valid composition looks like for your template? Use the example endpoint to generate one: `GET /definition/template/adl1.4/{template_id}/example` This returns a fully populated example composition based on your template, which you can use as a starting point. ```http POST /ehr/{ehr_id}/composition Content-Type: application/json Prefer: return=representation Authorization: Bearer ``` ```json { "archetype_node_id": "openEHR-EHR-COMPOSITION.encounter.v1", "name": { "value": "Vital Signs" }, "uid": { "_type": "OBJECT_VERSION_ID", "value": "8849182c-82ad-4088-a07f-48ead4180515::openEHRSys.example.com::1" }, "language": { ... }, "territory": { ... }, "category": { ... }, "composer": { ... }, "content": [ ... ] } ``` **Response** (`201 Created`): The committed composition with a `Location` header pointing to its versioned URI. ## 4. Retrieve or update a composition Compositions are versioned. There are two ways to address them: - **Versioned object UID** (e.g., `8849182c-82ad-4088-a07f-48ead4180515`) — always resolves to the latest version. - **Version UID** (e.g., `8849182c-82ad-4088-a07f-48ead4180515::cadasto.io::1`) — resolves to a specific version. The format is `{object_id}::{system_id}::{version_tree_id}`. **Latest version:** ```http GET /ehr/{ehr_id}/composition/{versioned_object_uid} Authorization: Bearer ``` **Specific version:** ```http GET /ehr/{ehr_id}/composition/{version_uid} Authorization: Bearer ``` **Update** (optimistic locking with `If-Match`): ```http PUT /ehr/{ehr_id}/composition/{versioned_object_uid} If-Match: "" Content-Type: application/json Authorization: Bearer ``` The `If-Match` header prevents lost updates: if another client has modified the composition since you last read it, the server returns `412 Precondition Failed`. Retrieve the latest version and retry. **Delete** (logical): ```http DELETE /ehr/{ehr_id}/composition/{version_uid} Authorization: Bearer ``` Deletions are logical — the data is marked as deleted but remains in the version history for audit purposes. ## 5. Organize with directories Use [Directories (Folders)](https://specifications.openehr.org/releases/RM/development/ehr.html#_directory) to organize compositions within an EHR. > **Note:** The directory is automatically created when the EHR is created — there is no need to call `POST /ehr/{ehr_id}/directory`. **Update** (with concurrency control): ```http PUT /ehr/{ehr_id}/directory If-Match: "" Authorization: Bearer ``` **Fetch a path** at a specific time: ```http GET /ehr/{ehr_id}/directory?path=episodes/a/b&version_at_time=2025-01-15T10:30:00.000+00:00 Authorization: Bearer ``` ## 6. Batch commit with contributions A [Contribution](https://specifications.openehr.org/releases/RM/development/common.html#_contribution_class) groups multiple versioned object changes into a single atomic commit. ```http POST /ehr/{ehr_id}/contribution Content-Type: application/json Authorization: Bearer ``` The request body contains a `versions` array with the operations and audit metadata. ## 7. Query data with AQL Use [AQL (Archetype Query Language)](/docs/guides/aql-archetype-query-language) to query clinical data across EHRs using archetype paths. AQL queries are portable — they work regardless of which template was used to capture the data. The recommended approach is `POST` with the query in the request body: ```http POST /query/aql Content-Type: application/json Authorization: Bearer ``` ```json { "q": "SELECT e/ehr_id/value, c/uid/value FROM EHR e CONTAINS COMPOSITION c WHERE e/ehr_id/value = $ehr_id", "query_parameters": { "ehr_id": "7d44b88c-4199-4bad-97dc-d78268e01398" } } ``` See the [AQL Guide](/docs/guides/aql-archetype-query-language) for query syntax, clauses, and clinical examples. ## Quick reference | Action | Endpoint | Method | Notes | | ------ | -------- | ------ | ----- | | Create EHR | `/ehr` | POST | Optional `EHR_STATUS` body | | Get EHR | `/ehr/{ehr_id}` | GET | `404` if not found | | Create composition | `/ehr/{ehr_id}/composition` | POST | `201` + `Location` header | | Get composition (latest) | `/ehr/{ehr_id}/composition/{versioned_object_uid}` | GET | Uses container ID | | Get composition (version) | `/ehr/{ehr_id}/composition/{version_uid}` | GET | Specific version | | Update composition | `/ehr/{ehr_id}/composition/{versioned_object_uid}` | PUT | `If-Match` required | | Delete composition | `/ehr/{ehr_id}/composition/{version_uid}` | DELETE | Logical delete | | Upload template (ADL 1.4) | `/definition/template/adl1.4` | POST | XML body | | List templates | `/definition/template/adl1.4` | GET | Returns metadata list | | Get template | `/definition/template/adl1.4/{id}` | GET | Returns template | | Store query | `/definition/query/{name}` | PUT | `text/plain` AQL body | | Execute ad-hoc AQL | `/query/aql` | POST | JSON body | | Execute stored query | `/query/{qualified_query_name}` | GET | Query params for variables | | Create person | `/demographic/person` | POST | Demographic entity | | Get person | `/demographic/person/{person_id}` | GET | Returns versioned party | | Create organisation | `/demographic/organisation` | POST | Demographic entity | | Create party relationship | `/demographic/party_relationship` | POST | Directed relationship | Cadasto also provides a [Demographic API](/demographic-api) for managing persons, organisations, roles, and party relationships. Demographic data is versioned and separated from clinical content by design. See the [openEHR Concepts](/docs/guides/openehr) guide for how demographics relate to EHRs. Beyond the standard openEHR REST APIs above, Cadasto offers an [Extra API](/extra-api) with convenience endpoints for datamaps, episodes of care, terminology helpers, and user information. These simplify common integration tasks but are Cadasto-specific — they are not part of the openEHR specification and may change between platform releases. ## Next steps - [Authentication](/docs/guides/authentication) — Token lifecycle and service discovery - [AQL Guide](/docs/guides/aql-archetype-query-language) — Full query syntax and clinical examples - [openEHR Concepts](/docs/guides/openehr) — Understand the data model - [Data Validation](/docs/guides/data-validation) — Validate templates and models before uploading - [EHR API Reference](/ehr-api) — Full OpenAPI specification - [Extra API Reference](/extra-api) — Cadasto-specific convenience endpoints # AQL — Archetype Query Language AQL (Archetype Query Language) is a SQL-like query language designed for openEHR data. Unlike SQL, which queries database tables and columns, AQL queries use **archetype paths** — references to the clinical model structure. This means you can retrieve specific data points (e.g., all blood pressure readings above 140 mmHg) regardless of which template was used to capture them or how the data is physically stored. AQL sits between the semantic clinical models ([archetypes and templates](/docs/guides/openehr)) and the storage layer. It references RM attributes and archetype node identifiers to locate values, making queries **portable across different openEHR implementations**. After reading this guide, you should be able to: - Write SELECT, FROM, WHERE, ORDER BY, and LIMIT clauses - Use parameters for dynamic queries - Execute queries against the Cadasto [Query API](/query-api) For the full language specification, see the [openEHR AQL specification](https://specifications.openehr.org/releases/QUERY/latest/AQL.html). AQL support is under development. Some features are experimental and may change in future releases. ## Executing queries in Cadasto Use the Query API to run AQL queries. The recommended approach is `POST` with the query in the request body: ```http POST /query/aql Content-Type: application/json Authorization: Bearer ``` ```json { "q": "SELECT e/ehr_id/value FROM EHR e", "query_parameters": {} } ``` See the [Query API reference](/query-api) for full request/response details. ## SELECT — choosing data The `SELECT` clause specifies which columns to return. You use archetype paths to navigate the openEHR data structure. **Syntax:** ```sql SELECT [DISTINCT] ``` **Examples:** ```sql SELECT * FROM EHR SELECT e/ehr_id FROM EHR e SELECT c/uid/value, c/name/value FROM COMPOSITION c SELECT MAX(c/time_created) FROM COMPOSITION c SELECT DISTINCT c/archetype_node_id FROM COMPOSITION c SELECT COUNT(*) FROM EHR ``` ## FROM — specifying data sources The `FROM` clause defines which openEHR types to query and how they are nested. Use `CONTAINS` to express the containment hierarchy (e.g., an EHR contains compositions, which contain observations). **Syntax:** ```sql FROM [CONTAINS [] [AND | OR] ...] ``` **Examples:** ```sql -- All EHRs SELECT * FROM EHR e -- Compositions within EHRs SELECT * FROM EHR e CONTAINS COMPOSITION c -- Observations within compositions SELECT * FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o -- Multiple content types (OR) SELECT * FROM EHR e CONTAINS (COMPOSITION c CONTAINS OBSERVATION o OR EVALUATION v) -- Multiple content types (AND) SELECT * FROM EHR e CONTAINS (COMPOSITION c CONTAINS OBSERVATION o AND EVALUATION v) -- Nested containment SELECT * FROM EHR e CONTAINS (COMPOSITION c CONTAINS (OBSERVATION o AND EVALUATION v CONTAINS CLUSTER cl)) ``` ### Understanding archetype paths Archetype paths like `o/data[at0001]/events[at0006]/data[at0003]/items[at0004]` navigate the data tree defined by an archetype: - `o` — alias for the OBSERVATION - `data[at0001]` — the `data` attribute, constrained by archetype node `at0001` - `events[at0006]` — a specific event type within the data - `items[at0004]` — a specific data item (e.g., systolic blood pressure) You can find these paths in the archetype definition or by retrieving the template from the [Definition API](/definition-api). ## WHERE — filtering results The `WHERE` clause filters results using predicates on archetype paths. **Syntax:** ```sql WHERE ``` **Examples:** ```sql -- Filter by EHR ID SELECT * FROM EHR e WHERE e/ehr_id/value = '7d44b88c-4199-4bad-97dc-d78268e01398' -- Combine conditions SELECT * FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v2] WHERE o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude >= 140 ``` ## ORDER BY — sorting results **Syntax:** ```sql ORDER BY [ASC | DESC] ``` **Examples:** ```sql SELECT * FROM EHR e ORDER BY e/ehr_id/value ASC SELECT c/uid/value, c/time_created FROM EHR e CONTAINS COMPOSITION c ORDER BY c/time_created DESC ``` ## LIMIT — paging results **Syntax:** ```sql LIMIT [OFFSET ] ``` **Examples:** ```sql SELECT * FROM EHR e LIMIT 10 SELECT * FROM EHR e LIMIT 10 OFFSET 20 ``` ## Parameters — dynamic queries Use parameters to make queries reusable. Parameter names are prefixed with `$` and supplied in the `query_parameters` object. **In the query:** ```sql SELECT c/uid/value FROM EHR e CONTAINS COMPOSITION c WHERE e/ehr_id/value = $ehr_id AND c/name/value = $composition_name ``` **In the API request:** ```json { "q": "SELECT c/uid/value FROM EHR e CONTAINS COMPOSITION c WHERE e/ehr_id/value = $ehr_id AND c/name/value = $composition_name", "query_parameters": { "ehr_id": "7d44b88c-4199-4bad-97dc-d78268e01398", "composition_name": "Vital Signs" } } ``` ## Querying demographic data Cadasto supports an **experimental AQL language extension** for querying the [Demographic Information Model](https://specifications.openehr.org/releases/RM/development/demographic.html). This allows you to query demographic entities (PERSON, ORGANISATION, ROLE, PARTY_RELATIONSHIP) and join them with clinical data in a single query. Demographic queries use the same syntax but start from demographic types instead of `EHR`: ```sql -- All persons SELECT p/uid/value FROM PERSON p -- Persons with a specific role SELECT p/uid/value FROM PERSON p CONTAINS ROLE r[openEHR-DEMOGRAPHIC-ROLE.patient.v1] ``` You can combine clinical and demographic data in a single query — for example, finding patients with high blood pressure filtered by demographic attributes. See the examples below. Demographic AQL querying is an experimental extension. Syntax and behaviour may change in future releases. ## Clinical examples ### Blood glucose values above threshold Retrieve all blood glucose results where the value exceeds 11 mmol/L or 200 mg/dL, with the corresponding subject IDs: ```sql SELECT e/ehr_status/subject/external_ref/id/value AS subjectId, a/items[at0001]/value AS analyteName, a/items[at0024]/value AS analyteResult FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.laboratory_test_result.v1] CONTAINS CLUSTER a[openEHR-EHR-CLUSTER.laboratory_test_analyte.v1] WHERE (a/items[at0001]/value/defining_code/code_string MATCHES {'14743-9', '2345-7'} AND a/items[at0001]/value/defining_code/terminology_id = 'LOINC') AND ((a/items[at0024]/value/magnitude > 11 AND a/items[at0024]/value/units MATCHES {'mmol/L'}) OR (a/items[at0024]/value/magnitude >= 200 AND a/items[at0024]/value/units MATCHES {'mg/dL'})) ``` ### Patients with high blood pressure Find patients with systolic blood pressure >= 140 mmHg, including demographic details: ```sql SELECT o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude AS systolic, pd/items[at0021]/value AS education_level, pd/items[at0018]/value AS marital_status FROM PERSON p CONTAINS ITEM_TREE pd[openEHR-DEMOGRAPHIC-ITEM_TREE.person_details.v1] AND ROLE [openEHR-DEMOGRAPHIC-ROLE.patient.v1] AND EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v2] WHERE o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude >= 140 AND pd/items[at0021]/value MATCHES {'primary_education', 'no_formal'} ``` ### Administered medication by practitioner Find medications administered to a patient by a specific caregiver: ```sql SELECT patient/uid/value AS uid, caregiver/details[at0001]/items[at0002] AS caregiver_name, comm/details[at0001]/items[at0004] AS caregiver_contact, instruct AS medications FROM PERSON patient CONTAINS (PARTY_RELATIONSHIP rel[openEHR-DEMOGRAPHIC-PARTY_RELATIONSHIP.informal_carer.v1] CONTAINS PERSON caregiver CONTAINS ADDRESS comm[openEHR-DEMOGRAPHIC-ADDRESS.electronic_communication.v2]) AND ROLE role[openEHR-DEMOGRAPHIC-ROLE.patient.v1] AND EHR CONTAINS INSTRUCTION instruct[openEHR-EHR-INSTRUCTION.medication_order.v1] WHERE instruct/activities/description/items[at0001]/value LIKE '*insulin*' AND rel/details[at0005]/value = 'primary_medication_manager' ``` ## Stored queries For queries you run repeatedly, you can store them on the server via the [Definition API](/definition-api) and execute them by name. Stored queries support versioning, better governance, and potential performance optimizations. ```http PUT /definition/query/my_org::blood_pressure_readings Content-Type: text/plain Authorization: Bearer SELECT o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude AS systolic FROM EHR e CONTAINS COMPOSITION c CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v2] WHERE e/ehr_id/value = $ehr_id ``` Execute by name. Stored queries use `GET` with parameters passed as query string values (unlike ad-hoc queries which use `POST` with a JSON body): ```http GET /query/my_org::blood_pressure_readings?ehr_id=7d44b88c-4199-4bad-97dc-d78268e01398 Authorization: Bearer ``` ## Next steps - [Query API Reference](/query-api) — Full API specification for query execution - [openEHR AQL Specification](https://specifications.openehr.org/releases/QUERY/latest/AQL.html) — Complete language reference - [openEHR Concepts](/docs/guides/openehr) — Understand the data model behind the queries - [Quick Start](/docs/quick-start) — End-to-end workflow including querying # 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](/docs/guides/openehr#demographics) 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](https://specifications.openehr.org/releases/ITS-REST/latest/ehr.html) 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. ## Two ways to establish the link 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](#cadasto-specific-behaviour) below. ## The `cadasto-person-uid` request header ### Request format A single bare PERSON UID, in the [HIER_OBJECT_ID](https://specifications.openehr.org/releases/BASE/latest/base_types.html#_hier_object_id_class) form — the same form returned as `uid.value` by the Demographic API on create: ```http cadasto-person-uid: 7d44b88c-4199-4bad-97dc-d78268e01398 ``` The versioned form (`::::`) 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 ```http POST /ehr Authorization: Bearer Accept: application/json Prefer: return=representation cadasto-person-uid: 7d44b88c-4199-4bad-97dc-d78268e01398 ``` ```http 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](#reading-the-link-back) for how to retrieve it on subsequent calls. ### Example: backfill a link on an existing EHR ```http PUT /ehr/3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718/ehr_status Authorization: Bearer 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 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. ## Reading the link back ### 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: ```http GET /ehr/3f9b8aa1-7c63-4d12-9e05-2a4d8b0a2718 Authorization: Bearer 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](/docs/guides/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 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](#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](/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 `409`s — this prevents the response from leaking link state for an unknown EHR. ### Sample `409 Conflict` body ```json { "title": "Conflict", "errors": [ { "detail": "The supplied PERSON is already linked to another EHR." } ] } ``` ```json { "title": "Conflict", "errors": [ { "detail": "The EHR is already linked to a PERSON. The cadasto-person-uid header is append-only." } ] } ``` ## API references - [`POST /ehr`](/ehr-api#tag/EHR/operation/ehr_create) - [`PUT /ehr/{ehr_id}`](/ehr-api#tag/EHR/operation/ehr_create_with_id) - [`PUT /ehr/{ehr_id}/ehr_status`](/ehr-api#tag/EHR_STATUS/operation/ehr_status_update) - [`GET /ehr/{ehr_id}`](/ehr-api#tag/EHR/operation/ehr_get_by_id) ## Next steps - [Quick Start](/docs/quick-start) — Walk through EHR creation, with and without the link - [Demographic API](/demographic-api) — Manage PERSONs to link against - [openEHR concepts — Demographics](/docs/guides/openehr#demographics) — Background on the PARTY hierarchy and why demographic data is separated from clinical content # openEHR Concepts [openEHR](https://openehr.org) is an open specification for vendor-neutral, interoperable electronic health records. This page covers the key concepts you need to understand when working with the Cadasto API. After reading this page, you should be able to: - Explain the **two-level modelling** split (Reference Model vs clinical models) and why it matters for integrations - Navigate **EHR**, **template**, **composition**, and **contribution** in the Cadasto APIs - Choose the right mental model before you open the [Quick Start](/docs/quick-start) or [AQL Guide](/docs/guides/aql-archetype-query-language) For normative detail, see the [openEHR specifications](https://specifications.openehr.org); the sections below focus on what matters for day-to-day API use. ## The two-level modelling approach openEHR separates **technical infrastructure** from **clinical content**: 1. **Reference Model (RM)** — A stable set of data types and structures that define *how* data is stored and exchanged. The RM rarely changes. See the [RM specification](https://specifications.openehr.org/releases/RM/development). 2. **Clinical models (archetypes and templates)** — Define *what* clinical data looks like. These can evolve independently of the software, allowing clinicians and informaticians to update clinical definitions without changing code. This separation means you can build your integration against the RM once and support new clinical content by uploading new templates — **often without changing your integration code** (only configuration and validation rules change). ## Key concepts ### EHR An [EHR (Electronic Health Record)](https://specifications.openehr.org/releases/RM/development/ehr.html#_ehr_package) is the root container for a single patient or subject. Each EHR has a unique `ehr_id` (UUID, stable for the lifetime of the record) and contains: | Part | Description | |------|-------------| | **EHR_STATUS** | Subject link, queryable/modifiable flags. Controls whether this EHR participates in population queries. | | **Compositions** | Clinical and administrative documents (versioned). The main content of the EHR. | | **Directory** | Optional folder hierarchy for organizing compositions (e.g., by episode or problem). | | **Contributions** | Change-set records grouping versions committed together in one operation. | By design, the EHR contains **no identifying demographic information** about the patient. The subject is referenced via `EHR_STATUS.subject` (a `PARTY_SELF`), which optionally points to a separate demographic service. This separation supports privacy — accessing an EHR does not automatically reveal patient identity. In Cadasto, you create an EHR per subject using `POST /ehr` and reference it by `ehr_id` in all subsequent operations. ### Archetypes An [archetype](https://specifications.openehr.org/releases/AM/development/Overview.html) is a reusable clinical content model. For example, there are archetypes for blood pressure measurement, medication orders, and laboratory results. Archetypes are developed collaboratively and published in the [Clinical Knowledge Manager (CKM)](https://ckm.openehr.org/ckm/). As an integrator, you typically use existing archetypes rather than creating new ones. ### Templates A template combines and constrains one or more archetypes for a specific use case — for example, a "Vital Signs" template that includes blood pressure, heart rate, and body temperature archetypes with specific constraints. Templates are distributed as **Operational Templates (OPT)** — compiled, self-contained XML files ready for use by a CDR. In Cadasto, you upload ADL 1.4 OPT templates via the [Definition API](/definition-api) (`POST /definition/template/adl1.4`). Templates drive validation: every composition committed to the EHR is validated against its template. ### Compositions A [Composition](https://specifications.openehr.org/releases/RM/development/ehr.html#_composition_package) is a clinical document committed to an EHR. Every composition is validated against the **template** it was created with (the template identifier is carried in the composition metadata). Compositions are **versioned**: each update creates a new version while preserving the full history. Every composition has a **category** that determines its lifecycle: | Category | Meaning | |----------|---------| | **event** | Records a healthcare event (encounter, test, procedure). Most common type; accumulates over time. | | **persistent** | Ongoing patient state (problem list, medication list, allergies). Maintained as a single updated instance. | | **episodic** | Relevant for a bounded period of care (e.g., a hospital admission). Similar to persistent but scoped to an episode; transitions to inactive when the episode ends. Cadasto's [Extra API](/extra-api) provides dedicated endpoints for creating, activating, and managing episodes of care within an EHR. | Compositions also carry mandatory metadata: `composer` (who is responsible for the content), `language`, `territory`, and optional `context` (the healthcare event time, setting, and participants). In Cadasto, commit compositions via `POST /ehr/{ehr_id}/composition`. ### Entry types Within a composition, all clinical content is expressed as **Entry** instances. openEHR defines distinct entry types based on the nature of the information, which makes querying unambiguous: | Entry type | What it records | Examples | |------------|----------------|---------| | **OBSERVATION** | Measured or observed phenomena (time-structured) | Blood pressure, lab result, patient-reported symptom | | **EVALUATION** | Clinical opinions and assessments | Diagnosis, risk assessment, care plan | | **INSTRUCTION** | Orders for future actions | Medication order, referral, investigation request | | **ACTION** | What was actually performed | Drug administered, procedure completed | | **ADMIN_ENTRY** | Administrative logistics | Admission, discharge, appointment | Using the correct entry type matters for querying: "all active medications" can be found by querying INSTRUCTIONs by state, while "all administered medications" comes from ACTIONs. ### AQL (Archetype Query Language) [AQL](https://specifications.openehr.org/releases/QUERY/latest/AQL.html) is a SQL-like query language designed for openEHR data. It queries using **archetype paths** rather than database columns, making it possible to retrieve specific clinical data points (e.g., all blood pressure readings above 140 mmHg) regardless of which template was used to capture them. AQL sits between clinical models and physical storage: archetypes define the expected structure, AQL references those paths to retrieve data, and the query engine translates this into storage-level operations. See the [AQL Guide](/docs/guides/aql-archetype-query-language) for syntax and examples. ### Versioning and change control All mutable content in the EHR (compositions, directories, EHR status) is **change-controlled**: - Every change creates a new **VERSION** in a **VERSIONED_OBJECT** container. - Each version has a unique identifier: `{object_id}::{creating_system_id}::{version_tree_id}` (e.g., `8849182c-82ad-4088-a07f-48ead4180515::cadasto.io::1`). - Deletions are **logical** — the data is marked as deleted but remains in the version history. - Every commit records an **audit trail**: who committed, when, and what type of change. This means previous states of the record can always be reconstructed — a key requirement for medico-legal traceability. ### Contributions A [Contribution](https://specifications.openehr.org/releases/RM/development/common.html#_contribution_class) groups one or more version commits into a single atomic change-set representing one user action (e.g., a GP encounter that creates an event composition and updates the medication list). All members are committed atomically with shared audit metadata. When using the Cadasto REST API, you can either commit individual compositions (the API creates the contribution automatically) or explicitly create a contribution with multiple versioned objects via `POST /ehr/{ehr_id}/contribution`. ### Demographics openEHR includes a separate [Demographic Information Model](https://specifications.openehr.org/releases/RM/development/demographic.html) for managing the identities and relationships of parties involved in healthcare. The model is built around a **PARTY** hierarchy: | Type | Description | |------|-------------| | **PERSON** | An individual (patient, clinician, administrator) | | **ORGANISATION** | A healthcare facility, insurer, or other entity | | **ROLE** | A function performed by an actor (e.g., "GP", "nurse", "patient") — roles have their own identity and time validity | | **PARTY_RELATIONSHIP** | A directed relationship between parties (e.g., "patient of", "employed by") | Demographic data is **versioned and separated from clinical content by design**. The EHR references the subject via `PARTY_SELF` but does not embed demographic details — this supports privacy and allows the demographic service to be managed independently. In Cadasto, manage demographic entities via the [Demographic API](/demographic-api). Demographic data follows the same versioning and contribution model as EHR data. When an EHR is created for a PERSON that already exists in the Demographic API, Cadasto offers a vendor-extension `cadasto-person-uid` request header that links the two in a single call — see the [Cadasto demographic link guide](/docs/guides/cadasto-demographic-link). Cadasto’s AQL engine includes an **experimental** extension for querying demographic data alongside clinical content (for example, filtering by attributes that live in the demographic service). Behaviour and syntax may change; confirm with Cadasto before relying on it in production. See the [AQL Guide](/docs/guides/aql-archetype-query-language) for examples and limitations. ## Content formats The Cadasto API supports the standard openEHR canonical representation formats for compositions: | Format | Content-Type | Use case | |--------|-------------|----------| | **Canonical JSON** | `application/json` | Full RM fidelity, standard openEHR format | | **Canonical XML** | `application/xml` | Full RM fidelity, XML variant | ## How these concepts connect ![openEHR data model — Reference Model, clinical models, AQL, and demographics](/images/diagrams/05-data-model.svg) A typical workflow: 1. Upload a **template** that defines the clinical form structure 2. Create an **EHR** for a patient 3. Commit **compositions** (clinical documents) to the EHR — Cadasto validates each against its template 4. **Query** data across patients using AQL ## Next steps - [Quick Start](/docs/quick-start) — Walk through this workflow with real API calls - [AQL Guide](/docs/guides/aql-archetype-query-language) — Learn to query clinical data - [Authentication](/docs/guides/authentication) — Set up API access - [Data Validation](/docs/guides/data-validation) — Validate templates and models before upload - [EHR API Reference](/ehr-api) — Full API specification for EHR operations - [Definition API Reference](/definition-api) — Template upload and management - [Demographic API Reference](/demographic-api) — Party and relationship management ![Cadasto logo](/images/landscape/Landscape_Logo_Cadasto_Pos_RGB_tm.png) # Cadasto Developer Documentation Welcome to the Cadasto developer docs. Cadasto is a healthcare data platform built on [openEHR](https://openehr.org), providing a standards-based API for storing, querying, and managing clinical data. These docs cover authentication, API usage, the openEHR data model, and query language — everything you need to start integrating with Cadasto. # [Quick Start](/docs/quick-start) [Authenticate and make your first API request in minutes.](/docs/quick-start) # [Authentication](/docs/guides/authentication) [Set up OAuth 2.0 Client Credentials and obtain an access token.](/docs/guides/authentication) # [openEHR Concepts](/docs/guides/openehr) [Understand the data model behind the APIs: archetypes, templates, and compositions.](/docs/guides/openehr) # [AQL Query Language](/docs/guides/aql-archetype-query-language) [Write queries to retrieve clinical data from EHR records.](/docs/guides/aql-archetype-query-language) ## API References EHR API Create and manage EHRs, versioned compositions, directories, and contributions with full audit trails. [View API](/ehr-api) Query API Execute ad-hoc and stored AQL queries across EHRs using archetype paths. [View API](/query-api) Definition API Upload and manage operational templates (OPT) and stored queries. [View API](/definition-api) Demographic API Manage persons, organisations, roles, and party relationships — separated from clinical data by design. [View API](/demographic-api) Admin API Privileged operations for system administration and EHR management. [View API](/admin-api) System API Discover server capabilities, supported services, and conformance details. [View API](/system-api) Extra API Cadasto-specific convenience endpoints: datamaps, episodes of care, terminology helpers, and user info. [View API](/extra-api) SMART on openEHR SMART App Launch for openEHR — service discovery, authorization, and third-party app integration. [View API](/smart-on-openehr) ## Getting Started Before calling any API, you need an access token. See the [Authentication guide](/docs/guides/authentication) to set up OAuth 2.0 Client Credentials. New to openEHR? Read the [openEHR Concepts](/docs/guides/openehr) guide first, then follow the [Quick Start](/docs/quick-start) to create an EHR, upload a template, and commit your first composition. # What is Cadasto? Cadasto is a healthcare data platform built on the [openEHR](https://openehr.org) open standard. It provides a set of REST APIs for storing, validating, querying, and managing clinical data in a vendor-neutral, interoperable format. The foundation for real data availability in healthcare. ## What Cadasto provides Cadasto exposes the [openEHR platform services](https://specifications.openehr.org/releases/SM/development/openehr_platform.html) as REST APIs. The platform is organized into distinct services, each handling a specific concern: - **[System Service](/system-api)** — Discover server capabilities, supported openEHR services, and [conformance](https://specifications.openehr.org/releases/SM/development/openehr_platform.html#_platform_conformance) details. A useful first call for any integration — your application can inspect what is available before making further requests. - **[EHR Service](/ehr-api)** — Create and manage Electronic Health Records. Store versioned clinical documents (compositions), organize them in folder structures, and track all changes through contributions with full audit trails. - **[Definition Service](/definition-api)** — Upload and manage openEHR templates and stored queries. Templates define the structure and constraints of your clinical documents — Cadasto validates data against them on every commit. - **[Query Service](/query-api)** — Execute [AQL (Archetype Query Language)](https://specifications.openehr.org/releases/QUERY/latest/AQL.html) queries to retrieve clinical data across EHRs. Supports both ad-hoc and stored queries with parameters. This is the API you use when you need insights rather than individual records — for example, finding all blood-pressure readings above 140 mmHg or generating a report of patients with their latest HbA1c values. - **[Demographic Service](/demographic-api)** — Manage the *who* behind the clinical data: persons, organisations, roles, and party relationships. Demographic data is versioned and separated from clinical content by design — the EHR records what happened, the Demographic Service records who was involved, and the two are linked only through privacy-safe references. - **[Admin Service](/admin-api)** — Privileged operations for system administration, such as deleting or merging EHRs. - **[Extra API](/extra-api)** — Cadasto-specific convenience endpoints for datamaps, episodes of care, terminology helpers, and user information. These endpoints simplify common tasks that would otherwise require multiple openEHR REST calls. Unlike the standard openEHR services above, the Extra API is **not part of the openEHR specification** and does not follow the same stability lifecycle — interfaces may change between platform releases. - **[Authentication](/docs/guides/authentication)** — OAuth 2.0 Client Credentials flow for secure machine-to-machine access. - **[SMART on openEHR](/smart-on-openehr)** — Implements the [SMART App Launch](https://specifications.openehr.org/releases/ITS-REST/development/smart_app_launch.html) standard adapted for openEHR, handling service discovery, patient-context selection, and authorization so that third-party applications can integrate with the platform securely. ![Cadasto platform API surface — services, processing, and data stores](/images/diagrams/02-api-surface.svg) All clinical data is **versioned and change-controlled** — every modification creates a new version with an audit trail, so previous states can always be reconstructed. ## Who this is for These docs are for **developers integrating with Cadasto** — whether you're building a clinical application, connecting an existing system, or querying health data for analytics. No prior openEHR experience is required. The [openEHR Concepts](/docs/guides/openehr) guide covers the key ideas you need to get started. ## Next steps - [Quick Start](/docs/quick-start) — Authenticate, create an EHR, upload a template, and commit your first composition. - [Authentication](/docs/guides/authentication) — Set up OAuth 2.0 and obtain an access token. - [openEHR Concepts](/docs/guides/openehr) — Understand archetypes, templates, compositions, and the two-level modelling approach. - [AQL Guide](/docs/guides/aql-archetype-query-language) — Write queries to retrieve clinical data. Contact us to set up a development or production environment with your own credentials. # Frequently Asked Questions ## Requesting Client Credentials Client credentials (`client_id` and `client_secret`) are issued by Cadasto after we provision an environment for you. This section explains what to provide when requesting credentials and includes a request template you can copy. ### Why you need credentials - Client credentials enable server-to-server integrations using the [OAuth 2.0 Client Credentials flow](/docs/guides/authentication). - Credentials are issued per environment (development, acceptation, production) and must be treated as secrets. ### What to provide | Information | Description | | ----------- | ----------- | | Application name | A friendly name used for identification | | Environment(s) | `development`, `acceptation`, `production` | | Redirect URIs | Required for interactive flows (PKCE). Leave empty for pure machine-to-machine (Client Credentials) | | Allowed origins (CORS) | Required if your application runs in a browser | | Scopes | Minimum set required, e.g. `api.read`, `api.write` | | Contact | Name, email, and team alias of the technical owner | | Description | What the integration does and expected usage patterns | ### Request template Copy and adapt for your email or support ticket: **Subject:** Request: Cadasto API client credentials for <application-name> ``` Application name: Environment: development | acceptation | production Redirect URIs: (optional; required for interactive flows) Allowed origins (CORS): (optional) Requested scopes: api.read api.write Contact: Jane Doe Description: ``` ### What you will receive - `client_id` — public identifier for your application - `client_secret` — confidential secret; store it securely (secret manager, environment variables, or vault) - Optional: additional metadata about allowed scopes or token lifetime ### Security and lifecycle - **Store secrets securely.** Never check `client_secret` into source control. Use a secret manager, environment variables, or vault solution. - **Rotate on compromise.** If a secret is exposed, rotate it immediately. Follow your organization's rotation schedule. - **Per-environment isolation.** Credentials for development, acceptation, and production are separate. Treat each set independently. ### After you receive credentials 1. Follow the [Authentication guide](/docs/guides/authentication) to obtain an access token using the Client Credentials flow. 2. Follow the [Quick Start](/docs/quick-start) to create your first EHR and commit data. Need help? Contact Cadasto support with the information above and we'll get you set up. --- ## Template Versioning Cadasto supports **multiple versions of the same template**. Each published version receives a unique identifier following the pattern: ``` {template_id}::{uid} ``` For example: ``` Example.v1::c7ec861c-c413-39ff-9965-a198ebf44747 ``` - `Example.v1` — the template identifier (including its semantic version) - `c7ec861c-c413-39ff-9965-a198ebf44747` — a unique UUID assigned to this specific published version ### Why this matters - You can upload a new version of a template without removing the previous one. - Existing compositions remain valid against the template version they were committed with. - When committing a composition, you can target a specific template version by using the full `{template_id}::{uid}` identifier. - When listing templates via `GET /definition/template/adl1.4`, the response includes all published versions with their unique identifiers. ### Working with template versions **Upload a new version** of an existing template: ```http POST /definition/template/adl1.4 Content-Type: application/xml Authorization: Bearer ``` The server assigns a new UUID to the uploaded version. If a template with the same `template_id` already exists, both versions remain available. **Retrieve a specific version:** ```http GET /definition/template/adl1.4/Example.v1::c7ec861c-c413-39ff-9965-a198ebf44747 Authorization: Bearer ``` ## Linking an EHR to a Cadasto Person ### How do I associate a new EHR with an existing Cadasto PERSON? Send the `cadasto-person-uid` request header (value = bare PERSON UID) on `POST /ehr`, `PUT /ehr/{ehr_id}`, or — for backfilling an unlinked EHR — `PUT /ehr/{ehr_id}/ehr_status`. The link is written in the same contribution as the EHR/EHR_STATUS write. ```http POST /ehr Authorization: Bearer cadasto-person-uid: 7d44b88c-4199-4bad-97dc-d78268e01398 ``` The link is **append-only** on EHR_STATUS update — an EHR that already has a link cannot be re-linked. See the [Cadasto demographic link guide](/docs/guides/cadasto-demographic-link) for the full conflict matrix and how to retrieve the link back on a `GET`. # ITEM_TAG — labelling versioned objects `ITEM_TAG` is the openEHR mechanism for attaching small key/value labels to a versioned object — a Composition, EHR_STATUS, or a Demographic resource (Person, Agent, Group, Organisation, Role). Use it to mark a resource with categories, workflow flags, or pointers to a specific path inside the resource (for example `/composition/start_time/value`), without changing the underlying clinical content. A tag has three fields: | Field | Required | Purpose | |---------------|----------|--------------------------------------------------------------------------------------| | `key` | yes | Identifier of the tag (case-insensitive). Unique per target + `target_path`. | | `value` | no | Optional value associated with the tag. | | `target_path` | no | Optional pointer into the target resource. Max 500 characters. | Tags are scoped to the **versioned object** they are attached to — when a new version is committed, the tags follow along automatically. After reading this guide, you should be able to: - Manage tags on Composition, EHR_STATUS, and every demographic resource through dedicated endpoints - Send tags inline on write endpoints with the `openehr-item-tag` header - Opt in to tag retrieval on `GET` calls via the `Prefer: include_item_tags` token - List all tags within an EHR or the demographic store with a single query - Understand which tag features Cadasto does and does not support For background, see the [openEHR ITS-REST ITEM_TAG specification](https://specifications.openehr.org/releases/ITS-REST/latest/ehr.html#tag/ITEM_TAG). ## Two ways to manage tags Cadasto exposes two complementary entry points for tags. Both write to the same underlying storage and both honour the same uniqueness and validation rules. | Entry point | Use when | |----------------------------------|-----------------------------------------------------------------------------------| | `/tags` subresource | You want to manage tags **independently** of the parent resource | | `openehr-item-tag` HTTP header | You want to set tags **in the same transaction** as a create/update of the resource | Both routes are available for `Composition`, `EHR_STATUS`, and every demographic resource (Person, Agent, Group, Organisation, Role). The `/tags` subresource on demographic targets lives under the same parent path as the resource itself, e.g. `PUT /demographic/person/{uid}/tags`. ## Listing tags across a scope Two read-only collection endpoints return every tag attached to any target within a given scope, with optional `tag_key`, `tag_value`, and `tag_target_path` filters: | Scope | Endpoint | What it returns | |--------------|---------------------------------------------------------|----------------------------------------------------------------------------------------------| | EHR | `GET /ehr/{ehr_uid}/tags` | Every tag on the EHR_STATUS and every Composition within the given EHR | | Demographic | `GET /demographic/tags` | Every tag on any Person, Agent, Group, Organisation, or Role across the demographic store | Each entry includes a `target` (the resource the tag is attached to) and an `owner_id` (the EHR for EHR-scoped tags, or the demographic system for party-scoped tags). Use these endpoints to drive worklists or to discover all resources matching a workflow flag without iterating over each target individually. ## The `openehr-item-tag` header On every write endpoint that accepts tags, you can send `openehr-item-tag` to attach (or replace) tags in the same DB transaction as the write itself. This avoids the round-trip of "create resource → call /tags". On every `GET` of a resource that supports tags, the response can include an `openehr-item-tag` header containing the current tag set, so a client can read the resource and its tags in a single call. This is opt-in — see [Reading tags on GET](#reading-tags-on-get-the-prefer-include-item-tags-opt-in) below. ### Request format The header value accepts two interchangeable formats: **JSON (recommended)** — matches the body shape of the `/tags` endpoint exactly: ```http openehr-item-tag: [{"key":"category","value":"final"}, {"key":"flag","value":"follow-up","target_path":"/composition/start_time/value"}] ``` A single tag may be sent as a bare object: ```http openehr-item-tag: {"key":"priority","value":"high"} ``` **Spec format** — the original ITS-REST grammar with quoted values separated by semicolons: ```http openehr-item-tag: key="category",value="final"; key="flag",value="follow-up",target_path="/composition/start_time/value" ``` ### Response format When the header is emitted, it always uses the JSON form: ```http HTTP/1.1 200 OK openehr-item-tag: [{"key":"category","value":"final"},{"key":"flag","value":"follow-up","target_path":"/composition/start_time/value"}] ``` The header is omitted when the resource has no tags **or** when the client did not opt in on a `GET` (see below). It is always emitted on a write that successfully attached tags via the `openehr-item-tag` request header. ### Reading tags on GET — the `Prefer: include_item_tags` opt-in The `openehr-item-tag` response header is opt-in on read endpoints. Set `Prefer: include_item_tags` (RFC 7240 token) to request inline tags on a `GET`: ```http GET /ehr/{ehr_uid}/composition/{uid} Prefer: include_item_tags Accept: application/json Authorization: Bearer ``` ```http HTTP/1.1 200 OK Preference-Applied: return=representation, include_item_tags openehr-item-tag: [{"key":"category","value":"final"}] ``` Without the token the response does not include the header and Cadasto skips the lookup entirely. When the resource is tagged but you did not opt in, the tags can still be read via the `/tags` subresource. The token composes with the other Prefer tokens that GET endpoints accept; send them as a comma-separated list or repeat the header: ```http Prefer: return=representation, include_item_tags, resolve_refs ``` The `Preference-Applied` response header echoes the tokens the server actually honoured — `return=`, `include_item_tags`, and `resolve_refs` when applicable — so clients can confirm which preferences took effect. ### Receiving tags back on writes — automatic On write endpoints (`POST` create, `PUT` update) the opt-in is automatic when the request itself carries an `openehr-item-tag` header: a tag-aware request gets a tag-aware response, with no need to also send the `Prefer: include_item_tags` token. ```http POST /ehr/{ehr_uid}/composition Content-Type: application/json Prefer: return=representation openehr-item-tag: [{"key":"category","value":"final"}] { "_type": "COMPOSITION", "...": "..." } ``` ```http HTTP/1.1 201 Created Preference-Applied: return=representation, include_item_tags openehr-item-tag: [{"key":"category","value":"final"}] ``` If a write does not carry tags in the request but you still want the current tag set returned (for example to read tags set previously via the `/tags` subresource), add `Prefer: include_item_tags` explicitly. ### Endpoints that emit and accept the header | Resource | Write endpoints that accept the header | Read endpoints that emit the header (with `Prefer: include_item_tags`) | |--------------|----------------------------------------|------------------------------------------------------------------------| | Composition | `POST` create, `PUT` update | `GET` by id, `GET` versioned, `GET` at_time | | EHR_STATUS | `PUT` update | `GET` by version id, `GET` at_time | | Person | `POST` create, `PUT` update | `GET` by id | | Agent | `POST` create, `PUT` update | `GET` by id | | Group | `POST` create, `PUT` update | `GET` by id | | Organisation | `POST` create, `PUT` update | `GET` by id | | Role | `POST` create, `PUT` update | `GET` by id | `DELETE` endpoints intentionally ignore the `openehr-item-tag` header — tags are removed when the underlying versioned object is removed. ### Example: create a Composition with tags in one call ```http POST /ehr/{ehr_uid}/composition Content-Type: application/json Authorization: Bearer openehr-item-tag: [{"key":"category","value":"final"},{"key":"reviewer","value":"alice"}] { "_type": "COMPOSITION", "...": "..." } ``` ```http HTTP/1.1 201 Created ETag: "...uid..." Location: /ehr/{ehr_uid}/composition/{uid} Preference-Applied: return=representation, include_item_tags openehr-item-tag: [{"key":"category","value":"final"},{"key":"reviewer","value":"alice"}] ``` The same composition can later be re-tagged with a `PUT`, or its tags managed via the [Composition API](/ehr-api) `/tags` subresource. ## Cadasto-specific behaviour and limitations > [!warning] > The following limitations are intentional. Sending an unsupported header or identifier shape returns `400 Bad Request`. ### `object_version_id` is not supported as a target The openEHR specification allows tags to target either a `versioned_object_uid` (the resource as a whole) or an `object_version_id` (one specific version). **Cadasto only supports the `versioned_object_uid` form.** Tags are conceptually attached to the resource, not to a single version, and they automatically apply to whichever version is current. If you provide an `object_version_id` (e.g. `::node::1`) where a `versioned_object_uid` is expected on the `/tags` endpoints, the request is rejected with `400 Bad Request`. ### `openehr-version-item-tag` is not supported The optional `openehr-version-item-tag` request header (which the spec defines for sending tags scoped to a specific version) **is rejected by Cadasto with `400 Bad Request`**. Use `openehr-item-tag` instead. ```http HTTP/1.1 400 Bad Request Content-Type: application/json { "title": "Bad Request", "errors": [ {"detail": "Header 'openehr-version-item-tag' is not supported by this implementation."} ] } ``` ### Tag keys are case-insensitive Tag `key` is stored with case-insensitive collation. `priority` and `Priority` are the same tag — sending one will overwrite the other. ### Uniqueness rule Tags on a target are unique by `(key, target_path)`. Sending a list with two entries that share both fields keeps the first occurrence; the duplicate is silently dropped (deterministic dedup). ### Replace, not merge Both `PUT /tags` and the `openehr-item-tag` header use **replace semantics**: the supplied list becomes the complete tag set for the target. Send an empty list to clear all tags. Tags that were present before but are not in the new list are removed. ## Validation and error responses When the header is malformed, Cadasto returns `400 Bad Request` with **all** detected violations in a single response — you do not need to retry repeatedly to see the next error. ### Tag-level violations Each malformed tag produces one violation per problematic field, with a `pointer` indicating the array index and field: **Request:** ```http openehr-item-tag: [{"key12":"priority","value":123,"target_path":42}] ``` **Response:** ```json { "title": "Bad Request", "errors": [ {"detail": "Each tag must have a non-empty string \"key\".", "pointer": "[0].key"}, {"detail": "Field \"value\" must be a string.", "pointer": "[0].value"}, {"detail": "Field \"target_path\" must be a string.", "pointer": "[0].target_path"} ] } ``` ### Header-level violations Errors that affect the whole header (invalid JSON, unsupported header) are returned as a single error without a pointer: ```json { "title": "Bad Request", "errors": [ {"detail": "Invalid JSON in 'openehr-item-tag' header."} ] } ``` ## API references For the full request/response schemas of the dedicated `/tags` endpoints on `Composition` and `EHR_STATUS`, plus the EHR-wide `GET /ehr/{ehr_uid}/tags` filter, see the [EHR API reference](/ehr-api). The demographic equivalents — `/tags` subresources on every party type and the cross-resource `GET /demographic/tags` filter — live in the [Demographic API reference](/demographic-api). All of these accept and emit the `openehr-item-tag` header on the operations listed in the table above.