FHIR v2 Migration Guide
Canvas’s v2 FHIR API is a from-the-ground-up rewrite of our v1 FHIR API. We have matched and exceeded the v1 API’s capabilities, while providing improvements in performance, reliability, consistency, expandability, and general usability.
Gradual Migration #
You can control which API you use on a request-by-request basis by simply changing the subdomain. Your authentication tokens work on both versions.
Expected General Differences in the v2 API #
The goal in developing v2 FHIR API was not to match the behavior of the v1 API in all respects. The goal was to support all the same FHIR resource interactions and simultaneously make additional improvements in validation and FHIR standard compliance. Given that, it is expected that there will be differences between the old and new APIs.
Observability #
The v2 API adds a correlation ID to the headers of every response it sends. When reporting problems, please provide Canvas with the correlation ID for the failed request, as it expedites the problem resolution process. The correlation ID streamlines our investigation process by allowing Canvas to find all linked log data for that request.
Validation #
The v2 API performs validation of request bodies for create/update interactions that is much more extensive than the old API. Strict conformance to FHIR where possible is the goal. As a result, some fields that were not required before will be required going forward.
According to the FHIR specification, string values must be at least length 1. Empty strings may have been accepted some create or update endpoints in the previous version of Canvas’s FHIR API, but the validation that the v2 API does will reject FHIR request bodies that do not conform to this.
Pagination #
Pagination will be turned on by default and required. It can no longer be assumed that a search-type interaction providing no search parameters will return all results in the database within a single response. Clients that want all results will need to make use of the provided links in the search bundle response to page through the data. Pagination links will not be provided for an empty result set.
There is a maximum page size that is enforced by the server. This value is at 100 at the time of writing, but can change without warning. Clients must use the links in a search bundle to paginate after an initial search request is sent.
OperationOutcome Responses #
There is a new validation and error handling framework in place that will provide more detailed error responses. As such, there will be significant departures from how the v1 API reports errors.
Note: The improvement of error handling is an ongoing effort that will continue after the v2 API is complete and the v1 API has been fully deprecated. Some error handling pathways from the v1 API will live on in v2 until they are updated.
Resource-specific Differences in the v2 API #
We created a diff-tool that allowed for requests sent to the v1 API to be mock-replayed in the v2 API, such that the responses from old and new can then be compared. The results of this were captured and analyzed for any differences. From there we eliminated unwanted differences and identified and explained all desired differences. The information below is the result of that analysis.
Appointment #
Create/Update
- Canvas’s v1 FHIR API supports an incorrect request body format for create and update interactions. The Appointment resource could be embedded in the request body under a key named
resource. The v1 API supports both this incorrect format and the correct format, but v2 only supports the valid FHIR format. - Per FHIR, the following applies to
contained:- There must be exactly one resource, and it must be of type
Endpoint. - Per FHIR, the following applies to the Endpoint resource:
- The
statusfield is required, but is not used by Canvas. Canvas recommends sending active. - The
connectionTypefield is required, but is not used by Canvas. Canvas recommends sending an empty JSON object:{} - The
payloadTypefield is required, but is not used by Canvas. Canvas recommends sending:[{"coding": []}]
- The
- There must be exactly one resource, and it must be of type
- The
appointmentTypefield must contain one coding, and it must be a SNOMED or INTERNAL coding. - Per FHIR, the
statusfield onparticipantelements is required, but is not used by Canvas. Canvas recommends sending active.
Search
- New parameter
statusGET /Appointment?status=proposed
- New parameter
appointment-typeGET /Appointment?appointment-type=http://snomed.info/sct|308335008
- New parameter
locationGET /Appointment?location=Location/6bf7d3a8-06c1-424f-a153-15e53b775769
CareTeam #
Update
- Per US Core,
statusis required (e.g.active). - Per US Core,
subjectis required. In Canvas each patient only has one care team, so CareTeam identifiers match Patient identifiers. The identifier in the subject reference must match the identifier URL. If provided, the value in id must also match the identifier in the URL.
CarePlan #
Read/Search
- Per FHIR,
category[*].codeis a list rather than an object. Instead of returning a single object, this field will now return a list of size 1 with the same object.
Claim #
Create
- Per FHIR,
supportingInfosequence must be a positive integer - Per FHIR,
quantitymust be aSimpleQuantityrather than an integer. - Per FHIR,
priorityis required. Canvas only acceptsnormalforpriority.coding[0].codeandhttp://hl7.org/fhir/ValueSet/process-priorityforpriority.coding[0].system - Canvas previously supported identifying the
providerby their NPI. This will no longer be supported andprovider.referenceshould refer to a Practitioner reference from our Practitioner Read/Search endpoints.provider.typecan be omitted but if valued, it should bePractitioner.
Communication #
Create
- Per FHIR,
statusis a required value, and per Canvas must be set tounknown. - In the
payload, thecontentattribute must be namedcontentString.
Consent #
Create
- The
scopefield is required by FHIR, but is not used by Canvas. Canvas recommends sending an empty JSON object as a value:{}
Condition #
Read
- Read must be performed using the resource
id(a UUID) of the Condition. Read by database primary key was previously supported, but that support has been removed. - The coding system for
codeis now set tohttp://hl7.org/fhir/sid/icd-10-cmwhen this code system applies; previously, the stringICD-10was returned.
Search
- New parameter
clinical-statusGET /Condition?clinical-status=active
- New parameter
verification-statusGET /Condition?verification-status=entered-in-error
Coverage #
Create/Update
- The
resourceTypefield needs to be the stringCoverageexactly, extra quotes will not be supported (e.g."Coverage"). - The optional fields
payor.displayandclass.valuedo not accept empty strings anymore. If provided, they must contain a string at least of length 1.- These were previously returned by the Read/Search endpoints though can now be expected to be empty when a value is not present.
CoverageEligibilityRequest #
Create
- The
createdfield is required by FHIR, but is not used by Canvas. Canvas recommends sending the current timestamp. - The
insurerfield is required by FHIR, but is not used by Canvas. Canvas recommends sending an empty JSON object as a value:{}
DiagnosticReport #
Read/Search
- Previously, Canvas included a non-FHIR implementation of
idin theencounter,identifier,performer,result, andsubjectfields. These fields have been removed.
DocumentReference #
Read/Search
- Removed erroneous usage of
identifier.id - For performance reasons, search results no longer include
url. Read will continue to includeurl.- Requesting authenticated, expiring URLs from AWS S3 requires making one API call per document. Only providing this on read requests ensures you only wait on this when you need to.
- Previously, content format system was set to
http://hl7.org/fhir/R4/valueset-formatcodes.html, but now it is set tohttp://ihe.net/fhir/ValueSet/IHE.FormatCode.codesystem
Encounter #
Read/Search
- The
appointment.referencefield now has the stringAppointment/as a prefix (so it is in the formatAppointment/{id}instead of just{id}).
Goal #
Read/Search
- The
startDatefield in the Goal resource will now be pulled from thestart_datefield on the Canvas Goal model. Previously,startDatewas populated withlocal_date_of_serviceof the associated note.
Location #
Read/Search
- Per FHIR, the
activefield is now namedstatus.
Medication #
Read
- Previously,
code.codingrepeated the RxNorm codes with display values for an internal field. This has been corrected to remove the repetitions and include the display value for the Medication.
MedicationRequest #
Read/Search
- Per FHIR, the field
reasonCode.codingis now a list rather than an object. This means instead of returning an object, the field will now return a list of size 1 with the same object. - Dispense quantity was previously sent for both
doseAndRate.doseQuantityanddispenseRequest.quantity. ThedoseAndRate.doseQuantitynow represents the amount of medication per dose rather than the quantity of medication units. - The coding system for
reasonCodeis now set tohttp://hl7.org/fhir/sid/icd-10-cmwhen this code system applies; previously, the stringICD-10was returned.
Observation #
Create
- Per FHIR, the
statusis required, but is not used by Canvas. Canvas recommends sendingunknown. - The
statusfield will always be returned with the valueentered-in-errorif the Observation data instance has a value in theentered_in_errorfield in Canvas. valueQuantity.valuemust be provided as a number, not a string.- Vital sign observations for composite measurements such as blood pressure must not use the root level
valueQuantityfield since these are instead provided as entries under thecomponent[*].valueQuantityfield. Blood pressure is the only observation supported by Canvas that must be sent as components. - For non-composite measurements such as weight (and all other Canvas Vitals command measurements except blood pressure) the root level
valueQuantityfield should be used and no duplicate component valueQuantities should be included.
Read/Search
- The
statusfield will always be returned with the valueentered-in-errorif the Observation data instance has a value in theentered_in_errorfield in Canvas. - A
dataAbsentReasonentry will never be present if a valid value is provided forvalueQuantity - Canvas does not make use of the
interpretationfield, so this has been removed in the interest of clarity instead of returning an explicitunknownentry. - Previously, for Observations where the
hasMemberfield contained references to other Observations, thecodefield contained the codings for all of the members. Now,codewill only contain the codings associated with the Observation itself, and will not include codings of the members.
Patient #
Create/Update
- Per FHIR,
photomust be a list of typeAttachmentrather than a singleAttachment. Canvas will still only utilize the first entry. - Per FHIR,
genderis a required field and must be one of the fields accepted by FHIR: (male | female | other | unknown) - Per FHIR,
telecomfield must be a list. - Per FHIR,
telecom[*].rankmust be an integer of value 1 or greater. 0 will no longer be accepted as a value fortelecom[*].rank. - The
birthDatefield must meet FHIR’s date format requirements. identifier[*].usemust now be one of (usual | official | temp | secondary | old) if it is providedcontact[*].namepreviously accepted a list. Now it conforms to the FHIR spec and no longer accepts a list.- Previously, the
genderfield value was the same as the storedbirthsexvalue. Now the gender field value in read and search responses is extracted from the gender identity extension value if one was passed in to create or update, or the value of the gender field if the gender identity extension was not used. - The
birthsexextension will now follow standard FHIR and can only be one of(M | F | OTH | UNK | ASKU) http://schemas.canvasmedical.com/fhir/extensions/emergency-contact,http://schemas.canvasmedical.com/fhir/extensions/authorized-for-release-of-information,http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity, andhttp://schemas.canvasmedical.com/fhir/extensions/sexual-orientationextensions were added.- Previously, omitting the
telecom,address, orcontactarrays entirely would have no effect on the database values for those fields. In the new API, omitting these fields entirely will have the same effect as sending an empty array would (i.e. removing any data currently in the database for those fields), to properly adhere to how REST APIs should handle updates. - The race and ethnicity extensions are now specified with this format:
{
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race",
"extension": [
{
"url": "ombCategory",
"valueCoding": {
"system": "urn:oid:2.16.840.1.113883.6.238",
"code": "2028-9"
}
},
{
"url": "detailed",
"valueCoding": {
"system": "urn:oid:2.16.840.1.113883.6.238",
"code": "1006-6"
}
}
]
}
- The structure of the race and ethnicity extensions is the same. Codings can be obtained from the FHIR documentation for each extension:
- The
urlvalue for each inner extension can be:ombCategoryordetailed - The concept of unknown can be specified as follows:
- The
urlfor the ValueSet should be set toombCategory. - The
systemfor thevalueCodingshould be set tohttp://terminology.hl7.org/CodeSystem/v3-NullFlavor - The
codefor thevalueCodingshould be set toUNK
- The
- Previously, language coding systems were set to
http://hl7.org/fhir/ValueSet/all-languages, but now they are set tourn:ietf:bcp:47
Read/Search
telecom[*].periodpreviously contained hardcoded values not linked to any underlying data. This has been removed.- There are some minor spacing differences in the values of the
text.divfield. - Extensions with no
valueStringvalues (namelyhttp://schemas.canvasmedical.com/fhir/extensions/clinical-noteandhttp://schemas.canvasmedical.com/fhir/extensions/administrative-note extensions) are no longer returned. - Previously, the
genderfield value was the same as the storedsexAtBirthvalue. Now thegenderfield value in read and search responses is extracted from the gender identity extension value if one was passed in to create or update, or the value of the gender field if the gender identity extension was not used. - The
birthsexextension will now follow standard FHIR and be one of(M | F | OTH | UNK | ASKU) http://schemas.canvasmedical.com/fhir/extensions/emergency-contact,http://schemas.canvasmedical.com/fhir/extensions/authorized-for-release-of-information,http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity, andhttp://schemas.canvasmedical.com/fhir/extensions/sexual-orientationextensions were added.
Special note regarding telecom[*].rank
- Per FHIR,
telecom[*].rankmust be a positive integer. Please note that if you haverankvalues stored in your own systems from read/search responses prior to this migration, you may see a difference between some of therankvalues received after the migration and your storedrankvalues. This is because Canvas previously allowed and returnedrankvalues of 0.
PaymentNotice #
Create
- The
createdfield is required by FHIR, but is not used by Canvas. Canvas recommends sending the current timestamp. The value returned by a read or search interaction will be the creation timestamp of the actual database record. - The
paymentandrecipientfields are required by FHIR, but are not used by Canvas. Canvas recommend sending empty JSON objects as values:{}
Questionnaire #
Search
- Search behavior has generally been simplified and clarified. Previously, when using search filters like
identifier,code, orname, other search filters likestatuswould be automatically and implicitly applied. Now, search results will only be filtered by what is specified by the supplied search parameters. - New parameter
statusGET /Questionnaire?status=activeGET /Questionnaire?status=retired
QuestionnaireResponse #
Create
- Per FHIR,
statusis required, and Canvas only acknowledgescompletedstatus. All QuestionnaireResponse messages should contain astatusofcompleted. - Per FHIR,
item -> answer -> valueString, if present, may not be an empty string. Empty text answers must be omitted. - Per FHIR,
item -> answer -> valueCoding -> descriptionmust not be an empty string. Coded answers must have a non-empty display value.- Note: in the rare case a database record already contains an empty string display value, it will be returned as the string
<no display available>as a notification.
- Note: in the rare case a database record already contains an empty string display value, it will be returned as the string
Search
- New parameter
questionnaire.codeGET /QuestionnaireResponse?questionnaire.code=http://loinc.org|69725-0
- New parameter
questionnaire.item.code- GET
/QuestionnaireResponse?questionnaire.item.code=http://loinc.org|69725-0
- GET
Task #
Create/Update
- Per FHIR,
intentis required, and must be set tounknown. - The following incorrectly-named attributes are accepted by the old API and will be renamed in Fumage.
assigneemust be referred to asownercreatormust be referred to asrequestertitlemust be referred to asdescriptionduemust be referred to as the nested valuerestriction -> period -> end- within a
note, author must be referred to asauthorReference -> reference
Search
- The parameter
creatormust be referred to asrequester. - The parameter
identifieris no longer supported but should be replaced with_id, which is the functionality thatidentifierpreviously implemented - New parameter
labelto filter by task labelGET /Task?label=example_label
- New ability to sort by due date
GET /Task?_sort=due-dateGET /Task?_sort=-due-date
- New parameter
descriptionto filter on title/descriptionGET /Task?description=Netcare
Patient Scoped Tokens #
Canvas has added support for patient scoped tokens. These are access tokens requested on behalf of a specific patient and have read/write access to that patient’s records only.
To acquire a patient scoped token, you will need to include some parameters in the body of your token request:
- A
client_idandclient_secretthat your application will use to authenticate with Canvas. - The Canvas resource id for the
patientthe token will be scoped to - A
scopeparameter with space separated patient level scopes requested for the token.- Additional requested scopes should follow the pattern
patient/<ResourceName>.<read/write/*>patient/Appointment.*patient/Appointment.read patient/Appointment.writepatient/Patient.readpatient/Practitioner.read
- Canvas provides support for the wildcard character, but highly recommends only requesting the minimal scopes and access needed and using it as a convenience when both read and write are truly required for the application.
- Additional requested scopes should follow the pattern
Example curl request #
curl --location --request POST \
'https://<your_subdomain_here>.canvasmedical.com/auth/token/' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=<client_id_from_Canvas>' \
--data-urlencode 'client_secret=<client_secret_from_Canvas>' \
--data-urlencode 'patient=abc123' \
--data-urlencode 'scope=patient/Patient.read patient/Appointment.* patient/Practitioner.read'
Expected Response Body #
{
"access_token": "<the_access_token_to_use_in_your_request>",
"expires_in": 36000,
"token_type": "Bearer",
"scope": "patient/Patient.read patient/Appointment.* patient/Practitioner.read",
"smart_style_url": "https://canvas-storages.s3.us-west-2.amazonaws.com/fhir-static-resources/smart-style.json",
"patient": "abc123",
"need_patient_banner": true
}
Using this access token in subsequent requests ensures that records referencing other patients cannot be retrieved through accidental or malicious misuse of the token. Canvas will also deny requests for any resource types which were not included in scope of the token request and requests for resource types which do not support patient-scoped tokens.
Note: A patient scoped token must include which patient scopes are being requested. Token requests that include the patient parameter but are missing scope or request invalid scopes will be rejected.