openEHR REST APIs

Apiary proposal herehttp://docs.mytest49.apiary.io/

The main discussion has now (June 2016) moved to: https://github.com/openEHR/specifications-ITS/issues

 

New call proposals are in blue.

API levels in green.

Initial minimal REST API

Resource EHR

MethodURLParametersDescriptionAPI levelNotes
POST/ehrs
  • subjectId (optional): id of the subject in the external system
  • subjectNamespace (optional): namespace of the subject id in the external system
  • committer
  • audit change type
  • commit audit description

Create a new EHRW1

HF: How do subjectId and subjectNamespace manifest themselves in the EHR model/resource?

PP: IMO the EHR resource should contain the EHR_STATUS and include the PARTY_PROXY with the id and namespace.
BTW, I would remove the "... in the external system", the subject can be in the same system, we should not imply specific architecture decisions.

HF: We should allow EHR_STATUS and EHR_ACCESS objects so it is not necessary to make additional requests resulting in multiple contributions

BL: Good idea.

PP: ok for EHR_STATUS, not sure about EHR_ACCESS since we don't know what should be in ACCESS_CONTROL_SETTINGS.
 

HF: We need audit details for the contribution

PP: should be related to the EHR_STATUS and EHR_ACCESS since are the versioned classes, not sure how a contribution to create the EHR itself will look like, since contribution is for versioned objects and EHR is not a v.o.
 

HF: What is returned in the response?

BL: At the moment only:

{
  "meta": {
    "href": "http://localhost:8082/rest/v1/ehr/f77f9b4a-cfda-414d-aa6c-4f78bcac7601"
  },
  "action": "CREATE",
  "ehrId": "f77f9b4a-cfda-414d-aa6c-4f78bcac7601"
}

HF: What is the purpose of meta and action? Shouldn't we use HTTP headers for these?

PP: what is the "action" for? Shouldn't be better to return the resource created? I agree it would be better to reuse the HTTP status codes e.g. 201 Created.
 

HF: Why have we change the URL to /ehrs? The resource is an EHR.

BL: Plurals are favoured in REST. To be consistent with /compositions, etc,

PP: POST to /ehrs will create an EHR under the collection of all EHRs. We choose plurals to refer at the collection of resources so we could do /ehrs/1 to refer to one resource in the collection.

PUT/ehrs/{ehrId}As aboveCreate a new EHR with specified ehr_idW1

HF: Required when creating an EHR with same ehr_id as in another system

PP: if the resource exists, since this is PUT, should this also update it?

GET/ehrs/{ehrId} Get EHRR1 

HF: Useful to get profile of an EHR. E.g. time created, how many compositions, is there a directory, what was the last contribution etc.

PP: should return EHR_STATUS and the root folder of EHR.directory (if it exists). Not sure about "how many compositions" or "the last contribution", that depends on specific use cases. I prefer to let the user decide giving generic info and doing other requests. e.g. GET /ehrs/{ehrid}/compositions/count

DELETE/ehrs/{ehrId} Delete an EHR?

TB: this isn't possible (well not by an external REST access - it would be an internal admin operation); all you can do is mark an EHR as inactive, which would be a POST (I think) to /ehr/{ehr_id}/ehr_status/other_details/is_active or similar. But even this operation, if it succeeds, is serious - it makes it look like an EHR is deleted, so it's probably not appropriate via any externally visible REST interface.

SI: In some countries the patient/client is owner of the data and it has the right (by the law) to ask for a complete (total) removal of all his data, which is more than just an inactive flag.

TB: that's true, but doing the delete would require some documentation and/or special permission. I still doubt very much that this could ever be done through a visible REST interface, unless all the proof parameters were provided, and I think these would be too variable to standardise.

DB: I agree with SI, this can be a requirement. The authentication/permission is a completely different issue. You could argue that the same kind documentation or special permission would be needed for creating a new composition.

BL: I would leave this out of the REST API for the moment. If this is a requirement somewhere it can be performed by other means (special admin interface to the system or similar).

DB: I won't leave this out, delete has a clear use case (i.e. opt-out from clinical research)

ES: Seems like delete EHR is needed in some cases, so it should be system configurable, we just need to agree on what HTTP status codes/messages to return for different cases.


PP: I might call this "delete request", so we can mark the ehr as "deletion requested", and maybe send notifications to admins or other systems in order to start the complete deletion process. I agree this should be a process, it should be validated and double checked, and requires very special permissions.

GET/ehrs/{ehrId}/ehr_status Retrieve EHR_STATUS R1PP: remove this call and return the EHR_STATUS resource embedded in the EHR resource, is a very small resource and is a weak entity related to the EHR.
POST/ehrs/{ehrId}/ehr_status/{versionUid}
  • committer
  • audit change type
  • commit audit description
Update EHR_STATUS 

HF: Can we create using this also?

BL: Do we need to - would it not be better to create a default status and access when creating EHR (or use the provided ones in the EHR create call body).

HF: If that is our suggested approach then fine, but this needs to be specified.

HF: We need audit details for the contribution

HF: We should provide precedingVersionUid or something to support optimistic locking

BL: Added versionUid.

  HF: Is version uid the new version UID or preceding version uid? The proposed URL would suggest we are posting to this version, not replacing it.

HF: I think preceding version uid should be represented as a HTTP If-Match header rather than in the URL. This keeps urls simple and clean.

HF: What is returned in the response?

BL: What would you have in response?

HF: I would suggest we just need HTTP status and headers Content-Location (version specific URL, although we don't have one) and ETag (new version uid)

HF: FHIR uses the HTTP Prefer header to allow the client to specify return minimal or representation. The latter would return the new version of the resource. The server can decide how it responds if it is not specified.

PP: to update we should use PUT. IMO it is better to update the status via the EHR, not directly, e.g. by PUT to the EHR with the JSON object for the EHR containing the status.

PUT/ehrs/{ehrId}/ehr_status/... Update an attribute in ehr_status 

TB

HF: Do we really want to provide this level of updates, it may result in many contributions when multiple attributes need to be updated

PP: update through the EHR, not directly.

GET/ehrs/{ehrId}/ehr_access Retrieve EHR_ACCESS PP: access is a big unknown, how do we specify the resource structure?
POST/ehrs/{ehrId}/ehr_access/{versionUid}
  • committer
  • audit change type
  • commit audit description
Update EHR_ACCESS 

HF: Can we create using this also?

HF: We need audit details for the contribution

HF: We should provide precedingVersionUid or something to support optimistic locking

HF: What is returned in the response?

PUT/ehrs/{ehrId}/ehr_acess/... Update an attribute in ehr_access 

TB

HF: Do we really want to provide this level of updates, it may result in many contributions when multiple attributes need to be updated

Resource FOLDER

MethodURLParametersDescriptionAPI levelNotes
GET/ehrs/{ehrId}/directory Get folder objectR1

HF: Do we want to indicate that this is the root directory folder?

PP: we might need to add a path attribute (question) (path should depend on ids not names see page 36 of http://openehr.org/releases/1.0.2/architecture/rm/common_im.pdf)

PP: we should return the root folder when we return an EHR resource. So we give a reference in ehr.directory the user can use to get the whole folder structure.

PP: can we return the whole directory structure from this call? so the user have a reference to each folder and can ask for a specific folder contents using another call.

GET/ehrs/{ehrId}/directory/{folderId}/... Get subfolder objectR1

HF: Do we want to indicate that this is subfolder?

PP: we might need to add a path attribute (question)

POST/ehrs/{ehrId}/directory/{versionUid} Create or update a folderW1

HF: We need audit details for contribution

PP: for the returned resource?
 

HF: We should provide precedingVersionUid or something to support optimistic locking

PP: shouldn't that be the versionUid? IMO we should tell which versionUid we are updating, and let the server side lock the resource and assign the new version id, and return it in the results.
 

HF: What is returned in the response?

PP: the new folder with the new version id assigned by the server side, it might include references to the children folders and to the contained compositions (just the ids, or the uris as hrefs like https://stormpath.com/blog/linking-and-resource-expansion-rest-api-tips/).

DELETE/ehrs/{ehrId}/directory/{versionUid} Deletes a folder 

SI

Resource COMPOSITION

MethodURLParametersDescriptionAPI levelNotes
POST

/compositions

/ehrs/{ehrId}/compositions

  • ehrId
  • committer
  • audit change type
  • version lifecycle state
  • commit audit description

Create a new composition

W1

HF: If we use the second URL we don't need to specify ehrId as parameter

PP: +1
 

HF: should use the RM term of commit audit description rather than commit comment

HF: audit parameters should be group under commit audit parameter

HF: What is returned in the response?

BL: response is very simple:

{
  "meta": {
    "href": "http://example.domain.com:8082/rest/v1/composition/bddcedc8-46cc-4df6-8b1a-b05534235f17::example.domain.com::1"
  },
  "action": "CREATE",
  "compositionUid": "bddcedc8-46cc-4df6-8b1a-b05534235f17::example.domain.com::1"
}

PP: use status code 201 Created instead of "action".

PUT/ehrs/{ehrId}/compositions/{objectId} Create a new composition with specified objectIdW1

HF: Useful when you want to control the object ID

PP: what happens if the id exists? does this updates? what happens with versions here?

GET

/compositions/{compositionId}
/ehrs/{ehrId}/compositions/{compositionId}

 Retrieve a compositionR1

Do we return a VERSION or a COMPOSITION here?

HF: I would prefer VERSION

BL: I would prefer a different resource returning versions

HF: Perhaps that is the difference between /composition/{compositionId} and /ehr{ehrId}/compositions/{compositionId}, or are you suggesting something like /version/{uid}?

HF: is compositionId the version UID or object ID?

BL: we could have both - one returns exact version specified, the other latest one?

HF: Should be able to request version by uid, point in time or at specified contribution?

BL: Good idea - can you suggest parameters?

HF: We use the idea of versionTime, which may be symbolic such as LATEST_TRUNK_VERSION, contribution uid or timestamp

GET

/compositions/{compositionId}/version
/ehrs/{ehrId}/compositions/{compositionId}/version 

 

 Retrieve a VERSIONW1

BL

HF: I would prefer the reverse approach to align with RM
E.g. /compositions/{compositionId}/data

HF: is compositionId the version UID or object ID?

POST

/compositions/{compositionId}

/ehrs/{ehrId}/compositions/{compositionId}

  • committer
  • audit change type
  • version lifecycle state
  • commit comment
Update a compositionW1

HF: is compositionId the version UID or object ID? If version UID then it should be PUT. Having said that, FHIR uses PUT for update using an object ID.

BL: I think it should be version uid as it then also gives us optimistic locking

HF: We should provide precedingVersionUid or something to support optimistic locking, this could be done using the HTTP If-Match header

HF: What is returned in the response?

PP: if we use version_uid, the resource might be /versioned_compositions instead of /compositions

DELETE

/compositions/{compositionId}

/ehrs/{ehrId}/compositions/{compositionId}

  • committer
  • audit change type
  • commit comment
Delete a compositionW1

HF: is compositionId the version UID or object ID?

BL: I think it should be version uid as it then also gives us optimistic locking


HF: We should provide precedingVersionUid or something to support optimistic locking

 

Composition format is the canonical openEHR JSON or XML format. Sample JSON and XML are attached. JSON is using the attribute names based on the RM (snake-case, i.e.: composition.archetype_details.archetype_id) and it also adds the type information - this is the extra attribute "@class" which allows such JSON to be deserialised into proper objects of the target language.

Authentication, sessions should be up to the implementation.

Resource QUERY

MethodURLParametersDescriptionAPI levelNotes

GET

POST

/query

 

aqlExecute an AQLQL1

GET has a max limit on the query length that will be too short for some queries. (Also if using GET make sure that the server sends proper caching headers.) Should we allow both POST and GET?

Added post for querying to allow loooong queries

HF: I think we should support POST only

PP: we should support path-based queries also, not just AQL. E.g. GET /queries/execute

{ ehrId: ..., q: [ { archid:.., path:.. }, {archid:.., path:.. }, {archid:.., path:.. } ] }

This would get rm objects from certain archetype ids and paths, from the ehrId.

We use a similar structure in the EHRServer: https://cabolabs-ehrserver.rhcloud.com/ehr/query/show/2 (screen capture http://postimg.org/image/gso1jsr0x/)

PP: we should add support for stored queries also, so queries can be stored on the server side, and executed by id + context parameters like date ranges, ehr uids, etc.

POST/q/{query-language} Generates a query-id and redirects to resulting resource (se rows below)QL2

(see http://www.biomedcentral.com/1472-6947/13/57) By default the query-id could be a SHA hash of the query itself (that way repeated identical queries do not need to be re-parsed/stored)

If the http-server-log is used as the main source for audit-logging, then the query needs to be stored somewhere since the content of POST is usually not logged in http-logs (The stored query could be inspected using GET q/{query-language}/{query-id}/info/ ). Storing a query (could be an ad-hoc query) for logging purposes does not mean that it needs to be converted to a stored procedure in the database. Manual or automatic routines can be used for deciding when recurring queries (same hash) should be optimized/stored.

If we want to support different query languages (or query language versions) we might want urls on the form q/{QUERY_LANGUAGE}/ (examples: /q/AQL/, /q/AQL2/, /q/AQLinXQuery/)

HF: Is this an specification alternative to the above or will both be included in the specification?

HF: Although I like the idea of the redirect approach, I have some reservation about how javascript clients will react to these redirections

HF: Should AQL be assumed in a shortform of this?

GET

q/{query-language}/{query-id}

the dynamic parameters defined when query was storedExecutes named query (using dynamic parameters)QL2
GET/ehr/{ehrId}/q/{query-language}/{query-id}the dynamic parameters defined when query was stored Executes stored query (using named parameters)QL2

Discussion:  Do we want to allow the possibility to (optionally?) split URIs for single- and multi-patient queries like this (including EHR ID in the URI)? Single-patient queries may have simpler requirements regarding security and may be more efficiently implemented in certain distributed environments. See http://www.biomedcentral.com/1472-6947/13/57

HF: I think this is useful to indicate single-ehr queries from multi-ehr queries. This should also be specified in the original query POST.

Resource CONTRIBUTION

MethodURLParametersDescriptionAPI levelModelNotes
POST

/contributions

 Atomically commits a set of changes (composition creates, updates or deletes).W2
[
    {
        "action": "CREATE",
        "ehrId": "054ea32b-9ffa-4b00-ba58-a7e92be68087",
        "composition": {
			...
        }
    },
    {
        "action": "UPDATE",
        "compositionUid": "5ab5d99c-ee84-4571-9470-e5fb5002016b::default::1",
        "composition": {
			...
        }
    },
    {
        "action": "DELETE",
        "compositionUid": "1cb4e6bc-860e-4075-9fab-f3c9c113d30d::default::1"
    }
]

This call might also be under /composition resource.

HF: Although the RM doesn't explicitly state this, I think contributions should be related to an EHR. E.g. /ehr/{ehrId}/contributions

HF: Where multi-ehr contributions are required, we may consider a transaction mechansim

HF: The contribution resource should align more with the RM such as using a cut down representation of the ORIGINAL_VERSION including attributes such as lifecycle_state, preceding_version_id, change_type, data.

BL: Even in the current call - where multiple EHRs are concerned contributions should be created for each EHR separately - but the whole operation should still be transactional - so all or nothing. I do agree it would also be nice to have a call with /ehr/{ehrId}/ prefix. Perhaps the name contribution is not right although I hardly imagine somebody would want to POST contributions.

HF: /contributions makes sense in the context of /ehr/{ehrId}/contributions. If there remains a stand alone CONTRIBUTION resource then /contribution would still make sense.

       

Implementation levels (suggestion)

It can be hard for some implementers to support all kinds of calls, we could provide a well specified conformance ladder with basic levels that are easy to add but still are very useful for integrations. Defining R1+W1+Q1 should probably be first priority timewise.

Level 
R1Basic read-only. COMPOSITION+FOLDER listing and retrieval.
R2Level 2 read-only. R1 as above plus: Support for listing and retrieval of CONTRIBUTIONS, EHR_STATUS and EHR_ACCESS...
W1Basic write. Writing/updating COMPOSITIONS one by one. Creating new EHRs...
W2Level 2 write. W1 as above plus Writing several changes at once using a CONTRIBUTION...
W3Level 3 write. W2 as above plus multi-EHR-spanning-commit mechanism (allowing Atomic correction moves etc between different EHRs)
QL1Basic Query...
QL2Level 2 Query. Named/identified queries with dynamic parameters (allows stored procedures and other optimizations).
CDS1...?Clinical Decision Support?

A system could for example support R1+QL1 another might support R2+W2 

TODO-list

  • define & describe return model format for all calls (JSON & XML) 
    • ...including MIME-types suggested by DIPS: application/vnd.openehr+xml or application/vnd.openehr+json
  • define all required parameters (esp commit calls are missing changetype, lifecycle status, ...)
  • define additional resources - FHIR like archetype-based resources, conformance?, ...
  • Generate machine-readable representation of the REST interface (WADL etc)

Background info/resources

 

Comments: