The Indivo API provides an interface for Personal Health Applications to extend the functionality of the Indivo PCHR. The Indivo API abides by the REST design pattern: it is stateless, re-uses existing HTTP constructs such as caching, compression and content negotiation, and generally uses URLs to represent hierarchical resources, e.g. documents within a medical record.
This document provides an introduction to the API, with in-depth explanations of related core Indivo concepts where applicable. For a full listing of all available Indivo API calls, see API Reference.
Personal Health Applications (PHAs) make HTTP calls to the Indivo API endpoint using the REST convention. oAuth is used to authenticate all calls, either in 2-legged mode for simple authentication, or in 3-legged mode when the PHA is making a call with delegated authentication, i.e. on behalf of the user.
We consider three types of applications:
Per Indivo installation, there is a small handful of UI and administrative applications, and quite a number of user applications.
A record is the single set of medical information that pertains to an individual. It is composed of documents, including a demographics document which details the individual’s contact information and name. A record can be accessed by one or more accounts.
oAuth is the authentication protocol used by Indivo. In 2-legged oAuth, the PHA (the oAuth consumer) makes calls to Indivo (the oAuth service provider) using a consumer key to identify itself, and a consumer secret to sign the request. In 3-legged oAuth, the PHA makes calls to Indivo to access medical information as delegated by the user, using an additional token and token secret that pertain to the specific Indivo record being accessed.
All calls to Indivo are authenticated using oAuth.
We detail the authentication process at Indivo Authentication.
Some common design patterns are repeated throughout the API. Not all of these patterns are necessarily 100% supported by all Indivo API implementations, though when they are they are consistent.
Core accounts in Indivo X are identified by email addresses, because email addresses provide mechanisms for distributed identification and messaging. When an email address is included in a URL, it must be URL encoded, where the @ sign turns into %40.
When a list of results are returned, the URL ends in a / and the HTTP method is a GET, as is typical of REST design. In that case, Indivo X supports a generic query string that determines paging and ordering of the results:
?offset={offset}&limit={limit}&order_by={order_by}&status={document_status}&modified_since={modified_since}
As of the Beta3 release, calls that implement the basic paging operations above may also implement a more powerful query interface, also represented in the query string. In these cases (currently all of the minimal medical reports and the auditing calls), the following values may occur in the query string:
?offset={offset}&limit={limit}&order_by={order_by}&status={document_status}
These values function as before.
?group_by={group_field}&aggregate_by={aggregation_operator}*{aggregation_field}
group_by groups results by the specified field. It must be used in conjunction with aggregate_by, which aggregates the results by group, using the specified operation. If aggregate_by is passed without a group_by parameter, the aggregation is performed over the entire result set. Results that have been aggregated are returned in an aggregated format, not the typical reporting format.
?date_range={date_field}*{start_date}*{end_date}
date_range filters results and leaves only those with the specified field falling between start_date and end_date.
?date_group={date_field}*{time_increment}&?aggregate_by={aggregation_operator}*{aggregation_field}
date_group functions equivalently to group_by, except the groups are formed based on the values of the specified date field. For example, if the date field was ‘date_measured’, and the time increment was ‘month’, results would be returned grouped by the month of the date_measured field for each item. As with group_by, date_group must be used with an aggregator, and results are returned in an aggregated format.
?{FIELD}={VALUE}
This syntax adds additional filters to the query, returning only results having whose value for the property specified by ‘field’ matches ‘value’.
For each of these parameters, acceptable values for {field} are specified individually by the calls. A full listing of the minimal reporting fields, along with valid aggregation operators and date increments, may be found here.
When a resource is created, the Indivo API offers the ability to create this resource using a PUT with an external_id in the URL, so that the call is idempotent: if a failure occurs, the call can be repeated safely and only the resource will not be created on the second call if it was already created successfully during the first call.
An external_id is only valid within a particular PHA scope. Other PHAs cannot see the external_id of a given document if they didn’t create the document, and certainly cannot access the document by external_id.
Some API calls which involve both creating documents and retrieving them, such as:
PUT /records/{RECORD_ID}/documents/{DOCUMENT_ID}/rels/{REL_TYPE}/external/{APP_ID}/{EXTERNAL_ID}
For these calls, it can be confusing as to which document is referenced by an external id. In such cases, the following rule resolves confusion:
Data stored in Indivo cannot by permanently deleted by default: the API enforces only appending data, not fully replacing it or removing it.
List documents within a record. Supports order by document metadata fields (see Indivo Document Metadata Schema).
The calls to GET /records/{RECORD_ID}/documents/ and GET /carenets/{CARENET_ID}/documents/ take a type querystring parameter, which filters the list of returned documents by their types.
A document’s type is (by default) the suffix of a URL that corresponds to the XML schema datatype, where the prefix is http://indivo.org/vocab/xml/documents#. Thus, type can be Medication, Lab, etc.
Indivo X supports storing XML documents whose datatype is not among the default Indivo X recommended types. In those cases, if the XML schema namespace doesn’t end in a / or #, then as is typical in the XML/RDF community, a # is used as delimiter in the URI. Examples of document types include:
Create a new document, and possibly assign it an external id.
Medical data cannot be replaced wholesale, only versioned. Thus, this call will fail (with a 400 Bad Request error code) if a document already exists in the given record with the given external ID.
Replace one document with a new document content. The existing document remains, but is marked suppressed and replaced by the new document.
Medical data cannot be replaced wholesale, only versioned. Thus, this call will fail (with a 400 Bad Request error code) if a document already exists in the given record with the given external ID.
Generally, documents in Indivo cannot be removed, they can only be versioned. However, mistakes happen, and Indivo must deal with these somehow. Also, information eventually is out of date or no longer relevant.
All such changes are encoded in the Indivo API as changes to document status.
Change the status of a document. The passed status defines what happens to the specified document:
void: If a document is entered in error, it can be marked as voided to indicate that the data is invalid.
Only active documents can be voided. Voided documents are still reachable, but their metadata indicates their status, and by default they are not listed in typical document listings.
archived: If a document is no longer relevant, it can be archived so that it doesn’t show up by default. Archival is different from voiding in that an archived document is still considered medically correct, just not particularly relevant anymore.
Archived documents are still reachable, but their metadata indicates their archival status, and by default they are not listed in typical document listings.
active: An active document is readily usable and will appear in search lisings by default. Setting a document to active status will unvoid a voided document, or unarchive an archived document.
It is often useful to relate documents, e.g. annotating a document, re-filling a prescription, connecting diagnoses to an encounter, etc. In Indivo X, these relations can be declared no matter the data type of the underlying document. An image of an X-ray might be related to an XML document that interprets it, but of course there is no room in the image file for a pointer. So all references are stored externally to the documents.
Relationship types are taken from a fixed list, including:
Eventually, full URLs will be supported for relationship types. The fixed list of types will then correspond to http://indivo.org/vocab/documentrels#{rel_type}.
In the following calls, {DOCUMENT_ID} is the document being interpreted, and {OTHER_DOCUMENT_ID} or the POST content is the interpretation.
Create a new document and immediately relate it to an existing document, possibly assigning an external id to the newly created document.
Medical data cannot be replaced wholesale, only versioned. Thus, this call will fail (with a 400 Bad Request error code) if a document already exists in the given record with the given external ID.
List all documents related to the passed document by the relationship REL_TYPE.
DOCUMENT_ID is the interpreted document, and the calls return all interpretations (that are of type REL_TYPE) of that document.
The Demographics and Contact documents are special in that there should only be one of each per record, and they should be easy to find.
See also
Indivo supports a lightweight notification framework as well as a heavier message inbox. For more information, see Messaging and Notifications.
Send a message to a record. Messages to records can have attached documents (specified by the num_attachements parameter) which then need to be uploaded separately. The message isn’t delivered until all of its attachments are uploaded.
Since Accounts, not Records, are the users who log into the system to view messages, there is no way to view messages in a record’s inbox. Rather, when a message is sent to a record, every account authorized to view the message is sent a copy of the message, which they can retrieve via their account inbox.
Notifications are intended to be a lightweight system for applications to alert users of activity in the application. This is especially relevant for apps that use sharing functionality: an app might want to notify other users of the app about a given user’s activity in it. UI apps should display these notifications in a twitter-feed like interface (our reference UI call it the ‘healthfeed’).
Application-specific storage is meant for bookkeeping by individual applications that is not specific to any given record. These documents can be deleted, since they are not part of any permanent medical record. All application-specific storage API calls behave like the prior document API calls, though the documents are accessible only to the application in question. Most implementations of the Indivo API will likely impose a quota on Applications to ensure they do not store large amounts of data in the application-specific storage. This quota may be application-specific, depending on what the application is approved to do.
Application-specific storage calls, since they don’t correspond to any given record, are all 2-legged oAuth calls.
Create an application-specific document, possibly assigning it an external id.
As this is application-level storage, making this call with an external id will overwrite any existing document with the same external id.
Record-application-specific storage is meant for bookkeeping by individual applications. These documents can be deleted, since they are not part of the permanent medical record. All record-application-specific storage API calls behave like the prior document API calls, though the documents are accessible only to the application in question. Most implementations of the Indivo API will likely impose a quota on Applications to ensure they do not store large amounts of data in the record-application-specific storage. This quota may be application-specific, depending on what the application is approved to do.
Record-Application-specific storage calls are all 3-legged oAuth calls.
Create a record-application-specific document, possibly assigning it an external id.
As this is record-application-level storage, making this call with an external id will overwrite any existing document with the same external id.
Indivo processes documents into medical reports. Each report can be altered by the basic paging mechanism or the more complex query interface described above. Over time, new reports may be introduced. For now, we define these as the minimal set of reports. Fields supported by individual reports for the querying interface may be found here. Response formats correspond to the Indivo Reporting Schema, and individual reports fit their individual datatype’s schema (see Medical Documents). If a report is accessed via a carenet, only documents that are shared into the carenet will appear in the results.
A number of Indivo documents contain coded values. These can be based on UMLS, SNOMED, etc. Indivo provides a generic API for looking up coded values. This API is particularly built to support live autocomplete in JavaScript.
Autonomous user applications are unlike standard user apps in that they may not have a user interface, and require access to records without an active user session. In order to authenticate against Indivo on behalf of records at any time, autonomous apps may make the following calls:
Retrieve a valid access token providing the app with access to a record. This call will only succeed if the app is autonomous, and if the record has enabled the app.
Using this call, an autonomous app can retrive a valid access token for any record on which it is enabled, without an active user session.
Admin applications have access to Indivo’s administrative API, which enables control and setup of records and accounts.
Create an account.
The primary and secondary secret arguments are optional and are used for helping the user initialize their account securely. A primary secret is sent directly by Indivo X server to the user at their ACCOUNT_ID email address in the form of a URL with an embedded secret. A secondary secret is generated by Indivo X and made available to the admin application using the GET /accounts/{ACCOUNT_ID}/secret call for the account. If it is asked for in this call, it is required at account activation time right after the user clicks on the activation URL (aka the primary secret). A secondary secret makes sense only if a primary secret is also requested. That’s why it’s called “secondary.”
Add an authentication system to an account.
Accounts initially have no “authentication systems” attached to them. Over time, Indivo accounts will be usable with OpenID and other authentication systems. An account needs to enabled for each authentication system that we want to use for that account. The default system is “password”. Thus, this call, when used with the “password” system, will set up the password and username for a new user.
Reset an account when its password is forgotten.
If a password is forgotten, the solution is to reset the account and email the user as with their initialization email. This will prevent logins until the new initialization URL is clicked, and the new password is entered.
This could be accomplished with separate calls to POST /accounts/{ACCOUNT_ID}/reset, which sets the account state to uninitialized and resets the account secrets, and POST /accounts/{ACCOUNT_ID}/secret-resend, but this call combines both actions.
Note that this call resets both the primary and secondary secrets. The user will need to be given this secondary secret in a channel other than email. If a User Interface Application performed this reset, then the secondary secret should display on screen while the primary secret is automatically sent by email. The user interface could obtain the secondary secret (which is short) by calling GET /accounts/{ACCOUNT_ID}/secret, but the call to POST /accounts/{ACCOUNT_ID}/forgot-password returns the secondary secret to avoid the extra call.
Initialize a new account.
Initializing an account that has been reset requires both the primary and secondary secrets. The primary secret is sent in the URL, and the secondary secret should be collected by the user interface. Specifically, the recommended process is:
Indivo Backend server sends the reinitialization URL to the user as:
INDIVO_UI_APP_LOCATION/account/initialize/account_id/primary_secret
An Indivo UI App checks that the requested account is indeed in uninitialized state and prompts the user for his secondary secret (which the user knows simply as the “secret”) and his desired username and password.
The Indivo UI App initializes the account using this call.
The Indivo UI app sets up the account with the built-in password authsystem using the username/password provided by the user and the API call POST /accounts/{ACCOUNT_ID}/authsystems/.
Set an account’s state. Possible account states are:
These API calls are reserved for the UI server, which is deeply trusted to authorized other applications, proxy the user’s credentials, etc. It’s only a separate server for modularity, otherwise it has the same level of trust as the backend Indivo server.
Claim a request token on behalf of a user. Before a request token can be viewed at all, it has to be claimed by a user. This ensures that a request token can’t be partially used by one user and completed by another.
The session-based chrome authentication will indicate to the backend which Account to associate with this request token. Once this call has been made for a request token, a second call with different session authentication will fail. (A second call with the same user authentication will be just fine, we don’t want a reload to cause a problem.)
If the request token is bound to an Indivo record (because the PHA knew it was authorizing for a given record), and the claimant does not have administrative rights over the record, this call will fail and the request token will be invalidated.
Retrieve information about an oAuth request token.
When authorizing a request token, the Indivo UI needs to know what that token represents. Once the token is claimed, the request token yields information via this call.
This call can only be called with session authentication matching the Account which claimed the request token earlier.
If a record_id is present in the response data, then the kind element is also present and indicates:
In the same case, the Chrome UI is allowed to immediately approve the request token. In other cases, the Chrome UI must explain to the user that new permissions or rights are being granted and prompt the user for approval.
Approve a request token on behalf of a user.
If a user approves an app addition, then the Chrome UI server needs to let the backend know.
This call, if it succeeds with a 200 OK, will return the location to which the user’s browser should be redirected:
location={url_to_redirect_to}
This call’s session authentication must match that which claimed the request token. The record_id is the record to which the user is attaching the application (i.e. my child’s record, not my own.) If the request token was pre-bound to a record, this record_id parameter must match, or this will throw an error.
Verify a signed URL.
In some cases, an Indivo app will sign a URL that directs the user to the Indivo UI. A prime example is the use of Indivo Chrome widgets, i.e. the Document Sharing widget, that apps can embed within their user interface to reuse functionality from Indivo Chrome. A signed URL looks like this:
/widgets/WidgetName?param1=foo¶m2=bar&surl_timestamp={TIMESTAMP}&surl_token={TOKEN}&surl_sig={SIGNATURE}
The signature contained in surl_sig is effectively a signature on the rest of the URL. The signature algorithm is as follows:
An app, with oAuth access token TOKEN and oAuth access token secret SECRET, wishes to sign a URL.
The app generates the SURL secret that corresponds to this access token as follows:
<SURL_SECRET> = HMAC(<TOKEN_SECRET>, "SURL-SECRET")
using base64 encoding, where the idea is to actually sign the string “SURL-SECRET” to obtain the SURL secret itself.
this SURL secret is then used to sign the URL, first by appending a timestamp, the SURL token, and then computing the signature:
<SURL_SIG> = HMAC(<SURL_SECRET>, "/widgets/WidgeName?...&surl_timestamp=<TIMESTAMP>&surl_token=<TOKEN>")
in base 64, then appending it as a query parameter surl_sig.
We want to simplify sharing. Indivo has two main mechanisms for sharing patient records with other accounts: Full Shares and Carenets.
A user may choose to share the entirety of their record with another account. The recipient account will then have access to all data (past, present, and future) contained in the record, and will be able to run any apps that have been bound to the record. The recipient of a full share will also be able to add new applications to the record and run them against data in the record.
Similarly, a user may choose to add an application to their full record. This effectively creates a ‘full share’ of the record with that application: the app has access to all data in the record.
As an example, a teen user of Indivo might choose to set up a full share of his / her record with a parent of guardian.
Full shares are not very flexible: they are an all or nothing proposition. In cases where sharing data appropriately requires more granularity or complexity, Indivo provides carenets, which allow a record to specify groups of accounts and apps that all have transparent access to whatever data the record shares into the carenet.
By default, each record will have 3 simple carenets: physicians, family, and work/school.
As an example, a patient might create an ‘exercise’ carenet, into which they place:
Now anyone on the accounts list can log into Indivo and run any app on the apps list against any data on the data list.
Data can be placed into carenets individually, or autoshared by Document type. Users can override the type-auto-sharing on a document-by-document basis.
When an app is added, it is normally given, along with its oAuth token, an xoauth_indivo_record_id that the token corresponds to. If the app is added to a carenet instead of a record, the app will receive instead an xoauth_indivo_carenet_id.
Many of the document and reporting calls that can be made on /records/{RECORD_ID} can be made on /carenets/{CARENET_ID}. Where applicable, such calls have been listed throughout this document.
Importantly, carenets are (at present) READ-ONLY. Accounts placed in carenets may view any data in the carenets, but we have not implemented any calls for them to modify or add to that data. In the future, carenets will be write-capable.
Carenets are useless until data has been shared into them. Data can be shared explicitly at the granularity of individual documents, or implicitly at the granularity of document type.
Place a document in a carenet.
When a document is explicitly shared with a carenet, it is no longer tied to the auto-sharing rules for that carenet. However, auto-sharing rules with other carenets still apply.
Remove a document from a carenet
When a document is explicitly UNshared from a carenet, it is no longer tied to the auto-sharing rules for that carenet. However, auto-sharing rules with other carenets still apply.
List carenets where a document is present
The mode attribute indicates how this document is shared. explicit means that the sharing preferences for this document are explicitly set. bytype indicates that it was auto-shared by document type. Other modes may be enabled in the future.
The value attribute indicates a negative share with a carenet, meaning that the user explicitly wants this document not shared with this carenet, even if auto-share rules would otherwise share it. Obviously this only makes sense for explicit carenet-shares.
Revert a document to auto-share rules. This means that, for this carenet, this document reverts to automatic sharing rules. This might mean a removal of the share with this carenet, or an addition, or no effect. However, from this point on, the record-wide rules apply.
Warning
This call has not yet been implemented.
Users needs to be able to place apps inside carenets in addition to documents, so that other accounts can run the applications. There are no issues with read/write premissions here, as permissions are associated with the accounts in a carenet, not the apps.
Check an app’s permissions within a carenet. Since permissions are currently handled on accounts, not apps, this call will always indicate that the app has full permissions on the carenet.
Warning
This call has not yet been implemented.
Users needs to be able to place other accounts inside carenets so that they can share data and apps. When accounts are added to a carenet, they are assigned read/write permissions, which define what actions they can take on data in the carenet.
Check an acount’s permissions within a given carenet. This call will return a list of document types, and whether the account may write to each one within the given carenet.
or now, the document type is always “*”, since read/write permissioning is ot currently more granular than at the carenet level. We may eventually ermit permissioning by document types within a carenet, in which case this all will be more informative.
Indivo apps are given a record_id and an access token that matches that record to read and write documents, read reports, annotate, etc. In a sharing scenario, apps must become carenet-aware.
Alice owns her Indivo record and has shared it with Bob, her HR representative at work, placing Bob in the “Work/School” carenet. Alice is pregnant but does not wish to reveal this information to her co-workers just yet. She has added the “Pregnancy Tracker” app to her record, making it visible to her Family and Physician carenets, but not to to her Work/School carenet. Alice has a history of depression, information which she has shared with her Physicians, but not with her Family.
Visible Apps
The “Pregnancy Tracker” app has been added to the Family and Physicians carenets, but not the Work/School carenet, so Bob cannot even see the application when he visits Alice’s record. This is enforced by the Indivo platform itself.
Activating and using an App
Charlie, Alice’s father, is eager to check up on his future grandchild’s progress. He logs into Indivo, selects Alice’s record. He sees “Pregnancy Tracker” because that app is visible to the Family carenet. He launches the app, and uses its functionality to track Alice’s progress, her fetus’s growth, her blood tests, etc. The process when launching the app is:
Clicking on the app directs the IFRAME to the start_url for the pregnancy tracker. The app must receive an indication of which record is being accessed at this point. This cannot be the record_id alone, and we may not even want to include the record_id at all, otherwise the app might confuse this data with that accessible to Physicians later on. Thus, instead of passing record_id to the IFRAME, Indivo passes only carenet_id.
The oAuth process begins with the carenet_id only as part of the request for a request token.
Indivo checks that the logged-in-user has the right to access this carenet, and if so authorizes the token.
The token is bound to that carenet only, and cannot be used on any other carenet.
The app can make requests to
/carenets/{carenet_id}/documents/...
without using the record_id at all. It doesn’t need to know the record_id.
When the app is later activated by a Physician, who does have access to Alice’s history of depression, the app gets a different carenet_id, and from that carenet has access to the documents including mental health.
This is not fool-proof: we still probably need to give the app access to some record information that will yield a unique identifier using the name, DoB, etc... but at least the default behavior for the app will not allow error-prone tracking across carenets.
We start with:
The oAuth process is then:
As Indivo will be installed at HIPAA-compliant hospital sites, it is important that it be able to track system usage. The auditing system logs all incoming requests to Indivo that use the Indivo API. To learn more about auditing in Indivo, see the audit system’s documentation.
All subsequent calls are deprecated, but maintained (for now) for backwards compatibility.
List all audit log entries where a given record was accessed.
Deprecated since version 1.0.0.
List all audit log entries where a given document was accessed.
Deprecated since version 1.0.0.
List all audit log entries where a given document was accessed via a given internal Django view function.
Deprecated since version 1.0.0.