NAV
C# Go Java JavaScript PHP Python Ruby Shell

Introduction

Welcome to the Open HealthHub Open API Hub! You can use our APIs to access the Open HealthHub Open API endpoints based on HL7 FHIR, which can interact with the Open HealthHub and SMART on FHIR applications for questionnaires, information and remote patient monitoring.

FHIR logo

Open API overview

Welcome to the Open HealthHub! You can use our APIs to access the Open HealthHub Open API endpoints based on HL7 FHIR, which can interact with the Open HealthHub and SMART on FHIR applications for questionnaires, information and remote patient monitoring. This way you can integrate our solution for digital forms and remote patient monitoring in your own platform, portal or application. This saves both healthcare professionals and patients a lot of time. You can also become an Open HealthHub partner and get access to our technical expertise, training program and more to support your integration.

Open HealthHub Open API hub

Client libraries

We have language bindings in Shell, .NET, Java, JavaScript, PHP, Python and Ruby! You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right.

For complete implementation please have a look at our FHIR Client Example Repository

Clients
Shell FHIR client Ruby FHIR client Python FHIR client PHP FHIR client JAVASCRIPT FHIR client JAVA FHIR client C# FHIR client GO FHIR client
Shell Ruby Python PHP JS Java C# Go

Client types

We support 2 different type of clients to access our FHIR APIs:

Data centered clients will need to upload a public key, so the patient data can be encrypted and retrieved by this client.

In the following sections we'll show the following icons for the different types of client that could use that API:

Data Centered Client

Data centered clients are mainly used to invite patients for a program created in the Improve Designer and to retrieve the (encrypted) responses to the questionnaires.

After setting-up accounts and clients the standard flow for a data centered clients is:

The data centered client starts with inviting a patient by creating a CarePlan. The response of this request will contain the code the patient needs to register for this program (for Enterprise customers this can be automated, see CarePlan creation for more information). Once the patient is registered for this program he can fill out the questionnaires in the Improve App or in another app that acts as Person centered client.

The next step is retrieving the QuestionnaireResponse for a patient. This can be done by polling for new responses, but the preferred way is using Subscriptions to get notified about new responses.

Once the responses are retrieved they need to be decrypted to read the actual answers given by the patient.

Person Centered Client

Person centered clients are used to present the programs and questionnaires to the patient and capture the responses of the patient.

After setting-up accounts and clients the standard flow for a person centered clients is:

The person centered client might start with retrieving all CarePlans for a patient. The CarePlan contains all tasks the patient should complete for this specific program. For every task there is a Questionnaire that the patient should fill out.

After the patient has filled out the Questionnaire the person centered client should have a complete QuestionnaireResponse with all the given answers. To provide this response to the Open HealthHub APIs the response should be encrypted. For encrypting the client will need the public keys of the participants in the program. These can be retrieved as part of the CarePlan, as part of a CareTeam, or as a list of Practitioners.

When the QuestionnaireResponse is encrypted the client needs to send a Bundle of QuestionnaireResponses (one response per public key/participant), to the Open HealthHub FHIR API. This will fulfill the task of the CarePlan, optionally triggering new tasks.

Get started

Welcome to the Open HealthHub Open API Hub! You can use our API's to access the Open HealthHub Open API endpoints based on FHIR, which can interact with the Open HealthHub hub for questionnaires, information and remote patient monitoring.

Step 1. Play and learn

Get familiar with our FHIR REST APIs, FHIR Client Example Repository and our example code here on this website. You can play with our sandbox with the listed credentials.

Learn more about FHIR via the following links:

Our Postman Collection offers a great start at discovering our FHIR REST APIs (sandbox environment). It is pre-configured with the sandbox credentials (api-key, openid credentials) so you can start right away:

Now you can start making requests and view the results in the console.

Step 2. Get Credentials

To generate FHIR API credentials for the live environment:

  1. Request for an Open HealthHub API developer account.
  2. We will send your credentials.
  3. You can start using your credentials.
  4. Replace the sandbox credentials with your credentials.
  5. Replace the sandbox URL with the live URL.

For more information about authentication see our documentation

Step 3. Upload your public key

If you want to create a data centered client, you'll need to upload your public key.

  1. Download and install the latest version of GPG command line tools for your operating system.
  2. Generate a GPG key pair.
    • in your terminal/command prompt enter gpg --expert --full-generate-key
    • choose ECC and ECC for the kind of key, Curve 25519 for the kind of curve, choose default 0 (does not expire) for keys validity, choose a user ID to match your usecase, when choosing a passphrase remember that it will need to be provided every time the private key is used.
    • use gpg --list-secret-keys --keyid-format=long to view all keys
    • for the key you want to use, find the line that looks like sec ed25519/9EB63F8FEEC4DEB8 2021-09-23 [SC] take the key ID, in this case: 9EB63F8FEEC4DEB8
    • run gpg --armor --export <KEYID> to export your key. You want to upload your public key that looks like: -----BEGIN PGP PUBLIC KEY BLOCK----- ..... -----END PGP PUBLIC KEY BLOCK-----
    • run gpg --armor --export-secret-key <KEYID> to export your private key that looks like -----BEGIN PGP PRIVATE KEY BLOCK----- ..... -----END PGP PRIVATE KEY BLOCK-----. Store this in a password manager/secret available only to your software for decrypting.
  3. Using our Binary resource upload your public key

Step 4. Get an Improve Designer account

You can design your own questionnaires and patient instructions which you can use for your app project using the Improve Designer.

  1. Register your own Improve Designer account or deliver your API key to one of your colleagues who has an Improve Designer account.
  2. Use the program credentials for your app project.

Step 5. Start your integration

Start building your own App project using our documentation and our example FHIR Client Example Repository.

Authentication

Retrieve the access token:

// Create Interceptor class that implements IClientInterceptor
@Override
public void interceptRequest(IHttpRequest theRequest) {
    String token = getToken();
    theRequest.addHeader(Constants.HEADER_AUTHORIZATION, (Constants.HEADER_AUTHORIZATION_VALPREFIX_BEARER + token));
}

private String getToken() {
    HttpRequest request = HttpRequest.newBuilder()
            .uri(new URI("https://auth.openhealthhub.com/auth/realms/OpenHealthHub/protocol/openid-connect/token"))
            .POST(ofString("client_id=api-sandbox&client_secret=95810e52-4307-41f5-99a4-d873ab63b536&grant_type=client_credentials"))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .build();

    String response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()).body();
    JsonObject respAsJson = JsonParser.parseString(response).getAsJsonObject();

    String token = respAsJson.getAsJsonPrimitive("access_token").getAsString();
}
const headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
headers.append('Accept', 'application/json');

const urlencoded = new URLSearchParams();
urlencoded.append('client_id', 'api-sandbox');
urlencoded.append('client_secret', '95810e52-4307-41f5-99a4-d873ab63b536');
urlencoded.append('grant_type', 'client_credentials');

const requestOptions = {
  method: 'POST',
  headers: headers,
  body: urlencoded,
  redirect: 'follow'
};

const token = await fetch('https://auth.openhealthhub.com/auth/realms/OpenHealthHub/protocol/openid-connect/token', requestOptions)
  .then(response => response.json())
  .then(result => result.access_token);
$ch = curl_init('https://auth.openhealthhub.com/auth/realms/OpenHealthHub/protocol/openid-connect/token');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => 'client_id=api-sandbox&client_secret=95810e52-4307-41f5-99a4-d873ab63b536&grant_type=client_credentials',
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/x-www-form-urlencoded'
    ],
]);
$res = curl_exec($ch);
curl_close($ch);
$decoded_json = json_decode($res, true);
$token = $decoded_json['access_token'];
type TokenResponse struct {
    Access_token string
}

func authenticate() string {
    url := "https://auth.openhealthhub.com/auth/realms/OpenHealthHub/protocol/openid-connect/token"
    method := "POST"

    payload := strings.NewReader("client_id=api-sandbox&client_secret=95810e52-4307-41f5-99a4-d873ab63b536&grant_type=client_credentials")

    client := &http.Client{}
    req, err := http.NewRequest(method, url, payload)

    if err != nil {
        fmt.Println(err)
        return ""
    }
    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

    res, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
        return ""
    }
    defer res.Body.Close()

    data := new(TokenResponse)
    err = json.NewDecoder(res.Body).Decode(&data)
    if err != nil {
        fmt.Println(err)
        return ""
    }

    return data.Access_token
}
# The ruby fhir client takes care of retrieving the token, if configured correctly as shown below
token=$(curl --location --request POST 'https://auth.openhealthhub.com/auth/realms/OpenHealthHub/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=api-sandbox' \
--data-urlencode 'client_secret=95810e52-4307-41f5-99a4-d873ab63b536' \
--data-urlencode 'grant_type=client_credentials'  | jq -r '.access_token')
def get_token():
    url = 'https://auth.openhealthhub.com/auth/realms/OpenHealthHub/protocol/openid-connect/token'
    response = requests.post(url,
                  f'client_id={api-sandbox}&client_secret={95810e52-4307-41f5-99a4-d873ab63b536}&grant_type=client_credentials',
                  headers={'Content-Type': 'application/x-www-form-urlencoded'}).json()
    return response['access_token']
const string authUrl =
        "https://auth.openhealthhub.com/auth/realms/OpenHealthHub/protocol/openid-connect/token";
IEnumerable<KeyValuePair<string, string>> auth = new[]
{
        new KeyValuePair<string, string>("client_id", "api-sandbox"),
        new KeyValuePair<string, string>("client_secret", "95810e52-4307-41f5-99a4-d873ab63b536"),
        new KeyValuePair<string, string>("grant_type", "client_credentials")
};
HttpClient http = new HttpClient();
HttpContent content = new FormUrlEncodedContent(auth);
HttpResponseMessage response = http.PostAsync(authUrl, content).Result;
return JsonConvert.DeserializeObject<Dictionary<string, string>>(
        response.Content.ReadAsStringAsync().Result);

The client_id and client_secret need to be replaced with the id and secret for your client

Adding the API key and token to the request:

client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
client.additional_headers = {'X-API-KEY': 'ad880601-b7e6-4d86-901d-b6fca96fc725'}
client.set_oauth2_auth('client_id', 'client_secret', 'https://auth.openhealthhub.com/auth/realms/OpenHealthHub/protocol/openid-connect/auth', 'https://auth.openhealthhub.com/auth/realms/OpenHealthHub/protocol/openid-connect/token')
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4',
    authorization=f'Bearer {get_token()}',
    extra_headers={'x-api-key': 'ad880601-b7e6-4d86-901d-b6fca96fc725'}
)
# With shell, you can just pass the correct header with each request
curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4" \
  -H "X-API-KEY: ad880601-b7e6-4d86-901d-b6fca96fc725" \
  -H "Authorization: Bearer $token"
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const requestObj = typeof request === 'string' ? {url: request} : request;
const parameterSeparator = requestObj.url.indexOf('?') === -1 ? '?' : '&';
requestObj.url = `${requestObj.url}${parameterSeparator}apikey=ad880601-b7e6-4d86-901d-b6fca96fc725`;
requestObj.headers = {
  Authorization: `Bearer ${this.token}`
};
client.request(requestObj);
$ch = curl_init('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => array(
        'X-API-Key: ' . self::API_KEY
    ),
    CURLOPT_XOAUTH2_BEARER => $token,
    CURLOPT_HTTPAUTH => CURLAUTH_BEARER
]);
req := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4", nil)
req.Header.Add("X-API-KEY", "ad880601-b7e6-4d86-901d-b6fca96fc725")
token := authenticate()
req.Header.Add("Authorization", "Bearer " + token)
@Interceptor
public class ApiKeyInterceptor {
    @Hook(Pointcut.CLIENT_REQUEST)
    public void addApiKey(IHttpRequest request) {
        request.addHeader("X-API-KEY", "ad880601-b7e6-4d86-901d-b6fca96fc725");
    }
}

FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
client.registerInterceptor(new ApiKeyInterceptor());
client.registerInterceptor(new AuthorizationInterceptor());
public class ApiKeyMessageHandler : HttpClientHandler
{
                protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
                {
                        request.Headers.Add("x-api-key", "ad880601-b7e6-4d86-901d-b6fca96fc725");
                        request.Headers.Add("Authorization", $"Bearer {_token}");
                        return await base.SendAsync(request, cancellationToken);
                }
}

var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4", settings, new ApiKeyMessageHandler());

Make sure to replace api-key with your personal API key.

Improve uses API keys and OpenID Connect to allow access to the API. You can register a new Improve API key at our developer portal.

Improve expects the API key and OpenID Connect access token to be included in all API requests to the server in a header, or as request parameter on the url.

For the sandbox environment this looks as follows:

You can retrieve the access token header from the token endpoint using the fields below. In some cases the language's FHIR library will take care of this. You'll need to set these values in your code then (see the examples to the right).

FHIR 4 API Reference

Overview

FHIR Resource Open HealthHub entity
PlanDefinition Program
Questionnaire Module
CarePlan Invited patient
CareTeam Program participants for specific invited patient (including public key)
Practitioner Program participant (including public key)
Questionnaire Module
QuestionnaireResponse Module Response

Entity Mapping FHIR

Times

Before going into detail with the exposed FHIR API's it's important to note that all dates and timestamps mentioned in these API's are in UTC. This means that any localisation of these dates and timestamps will need to happen on the client side. For exact details on how to fill these, we recommend looking into the FHIR documentation

Binary (Upload Key)

Upload public key

String publicKey = Base64.getEncoder().encodeToString(IOUtils.resourceToByteArray("sandbox.pub"));

HttpRequest request = HttpRequest.newBuilder()
        .uri(new URI("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Binary"))
        .POST(ofString(publicKey))
        .header("Content-Type", "text/plain")
        .header("Authorization", "Bearer " + getToken())
        .header("x-api-key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
        .build();

HttpResponse<Void> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.discarding());
HttpClient http = new HttpClient();

var publicKey = File.ReadAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "sandbox.pub"));
var encodedKey = System.Convert.ToBase64String(publicKey);
var content = new ByteArrayContent(Encoding.ASCII.GetBytes(encodedKey));
content.Headers.ContentType = MediaTypeHeaderValue.Parse("text/plain");

http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AuthenticationUtil.authenticate());
http.DefaultRequestHeaders.Add("x-api-key", "ad880601-b7e6-4d86-901d-b6fca96fc725");

HttpResponseMessage response = http.PostAsync("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Binary", content).Result;
Console.Out.WriteLine(response.StatusCode);
publicKey, err := ioutil.ReadFile("sandbox.pub")
if err != nil {
  return 0, err
}

pubKeysBytes := []byte(publicKey)
encodedBytes := []byte(b64.StdEncoding.EncodeToString(pubKeysBytes))

req, err := http.NewRequest("POST", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Binary", bytes.NewBuffer(encodedBytes))
if err != nil {
  return 0, err
}

token := client.Authenticate()
req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("Content-Type", "text/plain")
req.Header.Add("X-API-KEY", "ad880601-b7e6-4d86-901d-b6fca96fc725")

resp, err := http.DefaultClient.Do(req)
const client = new Client();
const token = await client.getToken();
const request = {
  method: 'POST',
  headers: {
    'Content-Type': 'text/plain',
    'Authorization': `Bearer ${token}`,
    'X-API-KEY': 'ad880601-b7e6-4d86-901d-b6fca96fc725'
  },
  body: btoa(publicKey)
};
const promise = fetch('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Binary', request);
return promise.then(resp => {
  return {responseStatus: resp.status, request};
});
$client = new FhirClient();

$keyASCII = file_get_contents(dirname(__FILE__) . 'sandbox.pub');

$encodedPubKey = base64_encode($keyASCII);
return $client->createRaw("Binary", $encodedPubKey, 'text/plain');
with open('sandbox.pub') as file:
    key = file.read()
key_bytes = key.encode('ascii')
base64_bytes = base64.b64encode(key_bytes)
encoded_key = base64_bytes.decode('ascii')
token = get_token()
resp = requests.post(url='https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Binary',
                     data=encoded_key,
                     headers={'Content-Type': 'text/plain', 'Authorization': f'Bearer {token}', 'X-API-KEY': 'ad880601-b7e6-4d86-901d-b6fca96fc725'})
return resp
uri = URI('https://auth.openhealthhub.com/auth/realms/OpenHealthHub/protocol/openid-connect/token')
params = { 'client_id' => 'api-sandbox',
           'client_secret' => '95810e52-4307-41f5-99a4-d873ab63b536',
           'grant_type' => 'client_credentials' }
res = Net::HTTP.post_form(uri, params)
token = JSON.parse(res.body)['access_token']

file = open('sandbox.pub')
key = file.read
encoded_key = Base64.strict_encode64(key)

uri = URI('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Binary')
Net::HTTP.post(uri, encoded_key, { 'Content-Type' => 'text/plain',
                                   'X-API-KEY' => 'ad880601-b7e6-4d86-901d-b6fca96fc725',
                                   'Authorization' => 'Bearer ' + token })
base64Key=$(base64 -w 0 sandbox.pub)
curl -X POST "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Binary" -H "Content-Type: text/plain" -H "X-API-KEY: ad880601-b7e6-4d86-901d-b6fca96fc725" -H "Authorization: Bearer $token" --data-raw "$base64Key"

This endpoint can be used to upload your public key to Open HealthHub. This key will be used to encrypt patient data so you/your application can decrypt the patient data using your private key.

HTTP Request

POST https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Binary

The body of the post should be the base64 encoded version of the public key.

Request headers

Header Value Description
Content-Type text/plain the Content-Type should be text/plain

CarePlan

Create CarePlan

curl -X POST "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan" -H "Content-Type: application/json" --data-binary "@careplan.json"
CarePlan carePlan = new CarePlan();
carePlan.setPeriod(new Period().setStart(Date.from(Instant.now())));

Patient patient = new Patient();
patient.setId("patient");
HumanName humanName = new HumanName();
humanName.setText("Test Patient");
patient.setName(Collections.singletonList(humanName));

ContactPoint email = new ContactPoint();
email.setSystem(ContactPoint.ContactPointSystem.EMAIL);
email.setValue("test@patient.ohh");
patient.addTelecom(email);

Identifier programPatientId = new Identifier();
programPatientId.setSystem("urn:oid:2.16.840.1.113883.2.4.99");
programPatientId.setValue("1234");
patient.addIdentifier(programPatientId);

carePlan.addContained(patient);
carePlan.setSubject(new Reference("#patient"));
carePlan.setInstantiatesCanonical(List.of(new CanonicalType("PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6")));

return client.create().resource(carePlan).execute();
var patient = new Patient
{
    Id = "patient",
    Identifier = new List<Identifier>
    {
        new Identifier
        {
            System = "urn:oid:2.16.840.1.113883.2.4.99",
            Value = "1234"
        }
    },
    Name = new List<HumanName> {new HumanName {Text = "Test Patient"}},
    Telecom = new List<ContactPoint>
    {
        new ContactPoint
        {
            System = ContactPoint.ContactPointSystem.Email,
            Value = "test@patient.ohh"
        }
    }
};
var carePlan = new CarePlan
{
    Period = new Period(FhirDateTime.Now(), null),
    Contained = new List<Resource>{patient},
    Subject = new ResourceReference("#patient"),
    InstantiatesCanonical = new List<string> { "PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6" }
};

var plan = client.Create(carePlan);

Console.Out.WriteLine(plan.InstantiatesCanonical.First());
}
const client = new Client();
return client.create({
    resourceType: "CarePlan",
    contained: [
      {
        resourceType: "Patient",
        id: "patient",
        identifier: [
          {
            system: "urn:oid:2.16.840.1.113883.2.4.99",
            value: "1234"
          }
        ],
        name: [
          {
            text: "Test Patient"
          }
        ],
        telecom: [
          {
            system: "email",
            value: "test@patient.ohh"
          }
        ]
      },
      {
          resourceType: "Task",
          id: "2",
          status: "requested",
          intent: "original-order",
          input: [
              {
                  type: {
                      coding: [
                          {
                              system: "http://openhealthhub.com/fhir/StructureDefinition/care-plan-task",
                              code: "create-user"
                          }
                      ]
                  },
                  valueBoolean: true
              },
              {
                  type: {
                      coding: [
                          {
                              system: "http://openhealthhub.com/fhir/StructureDefinition/care-plan-task",
                              code: "register-program"
                          }
                      ]
                  },
                  valueBoolean: true
              },
              {
                  type: {
                      coding: [
                          {
                              system: "http://openhealthhub.com/fhir/StructureDefinition/care-plan-task",
                              code: "send-invitation-mail"
                          }
                      ]
                  },
                  valueBoolean: true
              }
          ]
      }
    ],
    instantiatesCanonical: [
      "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6"
    ],
    status: "active",
    intent: "proposal",
    subject: {
      reference: "#patient"
    },
    period: {
      start: "2021-03-16"
    },
    activity: [
        {
            reference: {
                reference: "#2"
            }
        }
    ]
  }
);
FhirClient.new

careplan = FHIR::CarePlan.new
careplan.period = FHIR::Period.new
careplan.period.start = Date.new(2021, 7, 9)

patient = FHIR::Patient.new
patient.id = 'patient'
patient_identifier = FHIR::Identifier.new
patient_identifier.system = 'urn:oid:2.16.840.1.113883.2.4.99'
patient_identifier.value = '1234'
patient.identifier = [patient_identifier]
patient.name = 'Test Patient'
patient_email = FHIR::ContactPoint.new
patient_email.system = 'email'
patient_email.value = 'test@patient.ohh'
patient.telecom = [patient_email]
careplan.contained = [patient]

careplan.instantiatesCanonical = 'PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6'

patient_reference = FHIR::Reference.new
patient_reference.reference = '#patient'
careplan.subject = patient_reference

FHIR::CarePlan.create(careplan)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

with open('careplan.json', 'r') as file:
        careplan_json = json.load(file)

create_response = await client.execute ('CarePlan', method='post', data=careplan_json)
print(create_response)
file, err := os.Open("careplan/careplan.json")
if err != nil {
    return nil, err
}

req, err := http.NewRequest("POST", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan", file)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)
$client = new FhirClient();
$careplan = new FHIRCarePlan();

$patient = new FHIRPatient();
$patientName = new FHIRHumanName();
$patientName->setText('Test Patient');
$patientEmail = new FHIRContactPoint();
$telecomType = new FHIRContactPointSystem();
$telecomType->setValue('email');
$patientEmail->setSystem($telecomType)->setValue('test@patient.ohh');
$patientIdentifier = new FHIRIdentifier();
$patientIdentifier->setSystem('urn:oid:2.16.840.1.113883.2.4.99')->setValue('1234');
$patient->setId('patient')->addName($patientName)->addTelecom($patientEmail)->addIdentifier($patientIdentifier);
$careplan->addContained($patient);
$careplan->setSubject(new FHIRReference(['#patient']));

$careplan->setInstantiatesCanonical([new FHIRCanonical('PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6')]);
$period = new FHIRPeriod();
$period->setStart([new FHIRInstant('2021-03-16')]);
$careplan->setPeriod($period);

$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($careplan)
]);
$res = curl_exec($ch);
curl_close($ch);
$createdCarePlan = new FHIRCarePlan(json_decode($res, true));

Update CarePlan

curl -X PUT "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/1" -H "Content-Type: application/json" --data-binary "@careplan.json"
CarePlan carePlan = new CarePlan();
carePlan.setId("1");
carePlan.setPeriod(new Period().setStart(Date.from(Instant.now())));

Patient patient = new Patient();
patient.setId("patient");
HumanName humanName = new HumanName();
humanName.setText("Test Patient");
patient.setName(Collections.singletonList(humanName));

ContactPoint email = new ContactPoint();
email.setSystem(ContactPoint.ContactPointSystem.EMAIL);
email.setValue("test@patient.ohh");
patient.addTelecom(email);

Identifier programPatientId = new Identifier();
programPatientId.setSystem("urn:oid:2.16.840.1.113883.2.4.99");
programPatientId.setValue("1234");
patient.addIdentifier(programPatientId);

carePlan.addContained(patient);
carePlan.setSubject(new Reference("#patient"));
carePlan.setInstantiatesCanonical(List.of(new CanonicalType("PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6")));

return client.update().resource(carePlan).execute();
var patient = new Patient
{
    Id = "patient",
    Identifier = new List<Identifier>
    {
        new Identifier
        {
            System = "urn:oid:2.16.840.1.113883.2.4.99",
            Value = "1234"
        }
    },
    Name = new List<HumanName> {new HumanName {Text = "Test Patient"}},
    Telecom = new List<ContactPoint>
    {
        new ContactPoint
        {
            System = ContactPoint.ContactPointSystem.Email,
            Value = "test@patient.ohh"
        }
    }
};
var carePlan = new CarePlan
{
    Id = "1",
    Period = new Period(FhirDateTime.Now(), null),
    Contained = new List<Resource>{patient},
    Subject = new ResourceReference("#patient"),
    InstantiatesCanonical = new List<string> { "PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6" }
};

var plan = client.Update(carePlan);

Console.Out.WriteLine(plan.InstantiatesCanonical.First());
}
const client = new Client();
return client.update({
    resourceType: "CarePlan",
    id: "1",
    contained: [
      {
        resourceType: "Patient",
        id: "patient",
        identifier: [
          {
            system: "urn:oid:2.16.840.1.113883.2.4.99",
            value: "1234"
          }
        ],
        name: [
          {
            text: "Test Patient"
          }
        ],
        telecom: [
          {
            system: "email",
            value: "test@patient.ohh"
          }
        ]
      }
    ],
    instantiatesCanonical: [
      "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6"
    ],
    status: "active",
    intent: "proposal",
    subject: {
      reference: "#patient"
    },
    period: {
      start: "2021-03-16"
    }
  }
);
FhirClient.new

careplan = FHIR::CarePlan.new
careplan.period = FHIR::Period.new
careplan.period.start = Date.new(2021, 7, 9)

patient = FHIR::Patient.new
patient.id = 'patient'
patient_identifier = FHIR::Identifier.new
patient_identifier.system = 'urn:oid:2.16.840.1.113883.2.4.99'
patient_identifier.value = '1234'
patient.identifier = [patient_identifier]
patient.name = 'Test Patient'
patient_email = FHIR::ContactPoint.new
patient_email.system = 'email'
patient_email.value = 'test@patient.ohh'
patient.telecom = [patient_email]
careplan.contained = [patient]

careplan.instantiatesCanonical = 'PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6'

patient_reference = FHIR::Reference.new
patient_reference.reference = '#patient'
careplan.subject = patient_reference

FHIR::CarePlan.partial_update(1, careplan)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

with open('careplan.json', 'r') as file:
        careplan_json = json.load(file)

update_response = await client.execute ('CarePlan', method='put', data=careplan_json)
print(update_response)
file, err := os.Open("careplan/careplan.json")
if err != nil {
    return nil, err
}

req, err := http.NewRequest("PUT", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/1", file)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)
$client = new FhirClient();
$careplan = new FHIRCarePlan();
$careplan->setId("1");

$patient = new FHIRPatient();
$patientName = new FHIRHumanName();
$patientName->setText('Test Patient');
$patientEmail = new FHIRContactPoint();
$telecomType = new FHIRContactPointSystem();
$telecomType->setValue('email');
$patientEmail->setSystem($telecomType)->setValue('test@patient.ohh');
$patientIdentifier = new FHIRIdentifier();
$patientIdentifier->setSystem('urn:oid:2.16.840.1.113883.2.4.99')->setValue('1234');
$patient->setId('patient')->addName($patientName)->addTelecom($patientEmail)->addIdentifier($patientIdentifier);
$careplan->addContained($patient);
$careplan->setSubject(new FHIRReference(['#patient']));

$careplan->setInstantiatesCanonical([new FHIRCanonical('PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6')]);
$period = new FHIRPeriod();
$period->setStart([new FHIRInstant('2021-03-16')]);
$careplan->setPeriod($period);

$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/1';
$ch = curl_init($url);
$body = json_encode($careplan);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS => $body
]);
$res = curl_exec($ch);
curl_close($ch);
$updatedCarePlan = new FHIRCarePlan(json_decode($res, true));

Get CarePlan

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/3"
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
CarePlan carePlan = client.read()
    .resource(CarePlan.class)
    .withId(3L)
    .execute();
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
var app = client.Read<CarePlan>("CarePlan/1");

Console.Out.WriteLine(app.Description);
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const carePlan = await client.request('CarePlan/1');
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

FHIR::CarePlan.read(3)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

carePlan = await client.resource('CarePlan').execute('1', 'GET')
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/1", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)

unmarshaller, err := jsonformat.NewUnmarshaller(config.TimeZone, config.FhirVersion)
if err != nil {
    return nil, err
}

bytes, err := ioutil.ReadAll(response.Body)
if err != nil {
    return nil, err
}

unmarshal, err := unmarshaller.Unmarshal(bytes)
if err != nil {
    return nil, err
}

unmarshal.(*r4pb.ContainedResource).GetCarePlan()
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/4?_format=json';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$carePlan = new FHIRCarePlan(json_decode($res, true));

Search CarePlan

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/?instantiates-canonical=PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806%7C1&patient.identifier=1234"
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
CarePlan carePlan = client.search()
      .forResource(CarePlan.class)
      .where(CarePlan.INSTANTIATES_CANONICAL.hasId("PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806|1"))
      .and(CarePlan.PATIENT.hasChainedProperty(Patient.IDENTIFIER.exactly().identifier("1234")))
      .returnBundle(Bundle.class)
      .execute();
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
var searchPlan = client.Search<CarePlan>(new[]
      {"instantiates-canonical=PlanDefinition/4944e73f-e447-49ba-a64c-a246b9ef4bdd|1", "patient.identifier=1234"});

searchPlan.Entry.ForEach(cp => Console.WriteLine(cp.FullUrl));
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const carePlan = await client.request('CarePlan/?patient.identifier=1234');
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

params = {
  'instantiates-canonical': 'PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806|1',
  'patient.identifier': '1234'
}
FHIR::CarePlan.search(params)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

response = await client.resources('CarePlan').search(instantiates_canonical='PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806|1').fetch()
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan?instantiates-canonical=PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806|1&patient.identifier=1234", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")

response, err := http.DefaultClient.Do(req)
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/?_format=json&instantiates-canonical=PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806|1&patient.identifier=1234';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$bundle = new FHIRBundle(json_decode($res, true));

Search CarePlan by ID with Practitioners

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/?_id=3&_include=CarePlan:care-team&_include=CareTeam:participant"
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/?_format=json&_id=4&_include=CarePlan:care-team&_include=CareTeam:participant';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$bundle = new FHIRBundle(json_decode($res, true));
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
CarePlan carePlan = client.search()
                .forResource(CarePlan.class)
                .where(IAnyResource.RES_ID.exactly().identifier("4"))
                .include(new Include("CarePlan:" + CarePlan.SP_CARE_TEAM))
                .include(new Include("CareTeam:" + CareTeam.SP_PARTICIPANT))
                .returnBundle(Bundle.class)
                .execute();
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

params = {
  '_id': id,
  '_include': %w[CarePlan:care-team CareTeam:participant]
}
FHIR::CarePlan.search(params)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

response = await client.resources('CarePlan').search(_id='4').include('CarePlan', 'care-team').include('CareTeam', 'participant').fetch()
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const carePlan = await client.request('CarePlan/?_id=1&_include=CarePlan:care-team&_include=CareTeam:participant');
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan?_id=1&_include=CarePlan:care-team&_include=CareTeam:participant", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")

response, err := http.DefaultClient.Do(req)
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
var searchPlan = client.Search<CarePlan>(new[]
      {"_id=1", "_include=CarePlan:care-team", "_include=CareTeam:participant"});

searchPlan.Entry.ForEach(cp => Console.WriteLine(cp.FullUrl));

Delete CarePlan

curl -X DELETE "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/3"
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
CarePlan carePlan = client.delete().resourceById("CarePlan", "1").execute();
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
client.Delete("CarePlan/1");
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
await client.delete('CarePlan', 1);
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

FHIR::CarePlan.destroy(3)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

await client.resource('CarePlan').execute('1', 'DELETE')
req, err := http.NewRequest("DELETE", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/1", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/4';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_CUSTOMREQUEST => 'DELETE'
]);
$res = curl_exec($ch);
curl_close($ch);

When a care plan is scheduled by a doctor via Open HealthHub the patient will be notified by email. They will receive a link and pin code to access the program. When a care plan is scheduled through a connected EHR system the link (with pin code) will be given in the CarePlan response and can then be integrated where needed in the requesting application. The link will be specified in an extension with url https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/program-link. The pin code is part of the link but is also specified separately in an extension with the url https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/program-pin.

The APIs can be used to connect Open HealthHub to, for example, your EHR or scheduling software.

Retrieving a CarePlan

A CarePlan can be retrieved by its ID using a GET request. The CarePlan will contain the following information:

Using with the Embedded Form Filler

Besides using the embedded form filler links in the extensions, you can also build the links to the embedded form filler yourself:

The URL has the following format: https://formfiller.openhealthhub.com/user/<user-uuid>/program/<program-uuid>-<invite-id>/module/<task-uuid>?signature=<signature>&key=<key>

The values to use in the link can be retrieved from the following fields in the CarePlan:

URL placeholder CarePlan location Description
user-uuid 'contained subject'.identifier with system urn:ietf:rfc:3986, without the urn:uuid: prefix
program-uuid instantiatesCanonical only the UUID part after PlanDefinition, this is the program UUID used when creating the CarePlan
invite-id id the id of the careplan
task-uuid activity.detail.extension optional when you want to open this questionnaire directly. As described above the value from the task_uuid extension
signature 'contained subject'.extension extension with url https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/patient-signature identifying the user-uuid, used to prevent changing the url
key see note below

Searching CarePlans

CarePlans can be retrieved by searching on (a combination) of the following fields, using a GET request. The content of the CarePlan will be the same as for Retrieving a CarePlan.

When no parameters are given all CarePlans for the client will be returned.

Including referenced resources

When searching CarePlans you can include referenced resources by using the _include parameter. The following include parameters are supported for CarePlan:

The included participants will be part of the returned Bundle. For included resources the search.mode of the Bundle entry will be set to include.

If you want to include these resource for a specific CarePlan (by id) you can use the request below. Ofcourse you can also combine the include parameters with other search parameters.

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan?_id=<ID>&_include=CarePlan:care-team&_include=CareTeam:participant

Creating a CarePlan

For creating new CarePlan the following fields are required (on top of the FHIR requirements):

Field Description Example Value
subject.actor There should be exactly one patient subject
subject.actor.name.text The patient subject should have a name specified in the text field, other properties will be ignored. If no name present the email address of the patient will be used Test Patient
subject.actor.telecom The patient subject should have a telecom element of type 'email', if multiple email addresses are found the first one will be used test@patient.ohh
subject.actor.identifier.value The patient should have a patient number, this should be identifiable by the system creating the care plan. 564372819
subject.actor.identifier.system The patient identifier, this should be the system of the healthcare institution. urn:oid:2.16.840.1.113883.2.4.99
subject.actor.identifier.use The patient identifier use. Only required when supplying more than 1 identifier. If you list more than 1 identifier there should be one and only one with use official. official, usual, secondary
instantiatesCanonical The instantiatesCanonical should hold a canonical URI according to FHIR specifications, Open Health Hub is a bit more lenient and accepts a reference to the PlanDefinition (i.e. Improve Program) for which the patient is invited PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6
period.start * A start date (for details regarding the format refer to Times) should be entered when the patient can start the program. When retrieving a care plan we will always fill the end date. If no end date is known, the start date will be used. 2021-03-16
activity For our enterprise customers we support adding an activity to specify whether:
  • a user should be created for the patient
  • the patient should be registered to the program
  • an invitation email should be send to the patient
For more information see bolow

Activities

As described above we also offer the possibility to automatically create a user for the patient (if it doesn't exist yet), register the patient to the program and send an invitation email to the patient. To achieve this you should add an activity element to the CarePlan. The activity should contain a reference to a contained Task resource. This Task can have input items that look as follows:

    {
        "type": {
            "coding": [
                {
                    "system": "http://openhealthhub.com/fhir/StructureDefinition/care-plan-task",
                    "code": "<action>"
                }
            ]
        },
        "valueBoolean": true
    }
   

Here action should be replaced with the actual action:

Note that you can't use register-patient without also enabling create-user.

Please see the create CarePlan JavaScript snippet on the left for an example what this looks like in a complete CarePlan request.

For more information about the returned CarePlan see Retrieving a CarePlan.

Updating a CarePlan

A CarePlan can be updated by sending a PUT request with the updated CarePlan. The CarePlan to update can be specified in 2 ways. The first is by setting the ID on the URL. The second is by adding a patient.identifier query parameter with one of the identifiers of the patient on the url.

There are some restrictions when updating a CarePlan:

Deleting a CarePlan

A CarePlan can be deleted by sending a DELETE request. The CarePlan to delete can be specified in 2 ways. The first is by setting the ID on the URL. The second is by adding patient.identifier and instantiates-canonical query parameters with on the url. The patient.identifier should be one of the identifiers of the patient; the instantiates-canonical should be a reference to the PlanDefinition as a complete URL ( e.g. https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition/<ID>) or a resource reference (e.g. PlanDefinition/<ID>). If the query returns multiple results an error response with status 412 will be returned.

HTTP Request

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/<ID>

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan?_id=<ID>&_include=CarePlan:care-team&_include=CareTeam:participant

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/?patient.identifier=<PatientIdentifierToken>&patient.email=<PatientEmail>&instantiates-canonical=<PlanDefinitionReference>

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan?patient.identifier=<PatientIdentifierToken>&patient.email=<PatientEmail>&instantiates-canonical=<PlanDefinitionReference>&_include=CarePlan:care-team&_include=CareTeam:participant

POST https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan

PUT https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/<ID>

PUT https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/?patient.identifier=<PatientIdentifierToken>

DELETE https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/<ID>

DELETE https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CarePlan/?patient.identifier=<PatientIdentifierToken>&instantiates-canonical=<PlanDefinitionReference>

URL Parameters

Parameter Description
ID The ID of the CarePlan to retrieve
PatientIdentifierToken A token representing a patient identifier
PlanDefinitionReference A reference to the PlanDefinition (specified as: https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition/<PlanDefinitionId> or PlanDefinition/<PlanDefinitionId>
PatientEmail The patient email address

CareTeam

Retrieve CareTeam including Practitioners

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CareTeam/?_id=3&_include=CareTeam:participant"
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CareTeam/?_format=json&_id=4&_include=CareTeam:participant';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$bundle = new FHIRBundle(json_decode($res, true));
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
CarePlan carePlan = client.search()
                .forResource(CareTeam.class)
                .where(IAnyResource.RES_ID.exactly().identifier("4"))
                .include(new Include("CareTeam:" + CareTeam.SP_PARTICIPANT))
                .returnBundle(Bundle.class)
                .execute();
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

params = {
  '_id': id,
  '_include': 'CareTeam:participant'
}
FHIR::CareTeam.search(params)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

response = await client.resources('CareTeam').search(_id='4').include('CareTeam', 'participant').fetch()
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const careTeam = await client.request('CareTeam/?_id=1&_include=CareTeam:participant');
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CareTeam?_id=1&_include=CareTeam:participant", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")

response, err := http.DefaultClient.Do(req)
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
var searchTeam = client.Search<CareTeam>(new[] {"_id=1", "_include=CareTeam:participant"});

searchTeam.Entry.ForEach(cp => Console.WriteLine(cp.FullUrl));

The CareTeam resembles the participants in the program as defined in Improve Designer's Add colleagues step. Since the actual participants allowed to see the patient data differs per patient, the CareTeam is linked to a specific CarePlan for a patient. The ID of the CareTeam will match with the ID of the CarePlan.

When retrieving a CareTeam by ID it will contain references to the Practitioners in this CareTeam. To retrieve a Bundle with both the CareTeam and all participating Practitioners you can use the following request:

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CareTeam?_id=<ID>&_include=CareTeam:participant

The included Practitioner resource entries will have the search.mode set to include.

HTTP Request

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CareTeam/<ID>

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/CareTeam/?_id=<ID>&_include=CareTeam:participant

URL Parameters

Parameter Description
ID The ID of the CareTeam to retrieve

Practitioner

Retrieve Practitioners from CareTeam

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Practitioner/?_has:CareTeam:_id=3"
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Practitioner/?_format=json&_has:CareTeam:_id=4';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$bundle = new FHIRBundle(json_decode($res, true));
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
CarePlan carePlan = client.search()
                .byUrl(String.format("Practitioner?_has:CareTeam:_id=%s", 4))
                .returnBundle(Bundle.class)
                .execute();
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

params = {
  '_has:CareTeam:_id': id
}
FHIR::Practitioner.search(params)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

response = await client.resources('Practitioner').search(**{'_has:CareTeam:_id':1}).fetch()
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const practitioner = await client.request('Practitioner/?_has:CareTeam:_id=1');
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Practitioner/?_has:CareTeam:_id=1", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")

response, err := http.DefaultClient.Do(req)
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
var practitioners = client.Search<Practitioner>(new[] {"_has:CareTeam:_id=1"});

practitioners.Entry.ForEach(cp => Console.WriteLine(cp.FullUrl));

The Practitioner resembles a user in the Improve Platform and can be used to retrieve a public key used for encrypting a QuestionnaireResponse.

For the Practitioner content we use a custom profile (https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/PractitionerWithKey). The profile contains an extension with the public key of the participant. The url for this extension is https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/participant-program-public-key. Besides the public key the Practitioners with this profile also have 1 identifier which contains the UUID of the public key. The use of this identifier is set to secondary. This information is needed for creating an encrypted QuestionnaireResponse.

Retrieving a Practitioner by ID is currently only possible for the client itself. To search for other Practitioners you'll need to search them by CareTeam id. To accomplish this you can use the following request:

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Practitioner?_has=CareTeam:_id=<CareTeam ID>

This will return a Bundle with all Practitioners that are part of that CareTeam.

HTTP Request

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Practitioner/<ID>

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Practitioner/?_has=CareTeam:_id=<CareTeam ID>

URL Parameters

Parameter Description
ID The ID of the Practitioner to retrieve
CareTeam ID The ID of the CareTeam for which you want to retrieve the practitioners

VitalSigns

Get Observation

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Observation/1"
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");

Observation observation = client.read()
      .resource(Observation.class)
      .withId(1)
      .execute()
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
var obs = client.Read<Observation>("Observation/1");
Console.Out.WriteLine(obs.Category[0].Text);
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const observation = await client.request("Observation/1");
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

FHIR::Observation.read(1)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

obs = await client.resource('Observation').execute('1', 'GET')

print(obs.resource_type, obs.id)
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Observation/4?_format=json';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$observation = new FHIRObservation(json_decode($res, true));

Search Observation

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Observation?identifier=identifier&device-name=devicename"
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");

Bundle observationBundle = client.search()
      .forResource(Observation.class)
      .where(Patient.IDENTIFIER.exactly().identifier("1234"))
      .where(Device.DEVICE_NAME.matches().value("deviceName"))
      .returnBundle(Bundle.class)
      .execute();
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");

var obs = client.Read<Observation>("Observation/1");
Console.Out.WriteLine(obs.Category[0].Text);
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const bundle = await client.request("Observation?identifier=1234&device-name=testDevice");
const observations = bundle.entry.map(entry => entry.resource);
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

 params = {
  'identifier': '1234',
  'device-name': 'testDevice'
}
FHIR::Observation.search(params)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

observations = await client.resources('Observation').search(identifier='identifier', device_name='devicename').fetch()

for o in observations:
    print(o.as_json())
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Observation/1", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Observation?device-name=deviceName&identifier=1234?_format=json';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$bundle = new FHIRBundle(json_decode($res, true));

Having accurate patient’s vital signs available whenever and wherever needed is crucial for medical professionals to provide the best care possible. Vital signs acquired via Improve Connected Health can be monitored on Improve Dashboard, but also added to, for example, the patient’s EHR file.

The APIs can be used to (automatically) add vital signs retrieved via Open HealthHub in any of the supported formats to your EHR system(s), or to integrate or create a different dashboard.

HTTP Request

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Observation/<ID>

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Observation/search?identifier=<PATIENT-NUMBER>&device-name=<DEVICE-NAME>

URL Parameters

Parameter Description
ID The ID of the Observation to retrieve
PATIENT-NUMBER the patient-number to retrieve measurements Observations for
DEVICE-NAME name of the device that the measurements were made with

PlanDefinition

Get PlanDefinition

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition/57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca"
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");

PlanDefinition planDefinition = client.read()
    .resource(PlanDefinition.class)
    .withId("57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca")
    .execute();
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/", settings);
var qr = client.Read<PlanDefinition>("PlanDefinition/57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca");
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const resp = await client.request("PlanDefinition/57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca");
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

FHIR::PlanDefinition.read('57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca')
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

response = await client.resource('PlanDefinition').execute('57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca', 'GET')

print(response)
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition/57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)

unmarshaller, err := jsonformat.NewUnmarshaller(config.TimeZone, config.FhirVersion)
if err != nil {
    return nil, err
}

bytes, err := ioutil.ReadAll(response.Body)
if err != nil {
    return nil, err
}

unmarshal, err := unmarshaller.Unmarshal(bytes)
if err != nil {
    return nil, err
}

unmarshal.(*r4pb.ContainedResource).GetPlanDefinition()
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition/57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca?_format=json';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$planDefinition = new FHIRPlanDefinition(json_decode($res, true));

Search PlanDefinition

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition?definition=Questionnaire/97f680b9-e397-4298-8c53-de62a284c806"
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");

Bundle bundle = client.search()
    .forResource(PlanDefinition.class)
    .where(PlanDefinition.DEFINITION.hasId("Questionnaire/97f680b9-e397-4298-8c53-de62a284c806"))
    .returnBundle(Bundle.class)
    .execute();
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
var qr = client.Search<PlanDefinition>(new []{"definition=Questionnaire/97f680b9-e397-4298-8c53-de62a284c806"});

qr.Entry.ForEach(component => Console.WriteLine(component.FullUrl));
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const bundle = await client.request("PlanDefinition?definition=Questionnaire/97f680b9-e397-4298-8c53-de62a284c806");
const responses = bundle.entry.map(entry => entry.resource);
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

params = {
  'definition': 'Questionnaire/97f680b9-e397-4298-8c53-de62a284c806'
}
FHIR::PlanDefinition.search(params)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

response = await client.resources('PlanDefinition').search(definition='Questionnaire/97f680b9-e397-4298-8c53-de62a284c806').fetch()

print(response)
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition?definition=Questionnaire/97f680b9-e397-4298-8c53-de62a284c806", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition?definition=Questionnaire/97f680b9-e397-4298-8c53-de62a284c806?_format=json';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$bundle = new FHIRBundle(json_decode($res, true));

The program as created in Improve Designer.

Programs and therefore plan definitions are versioned. If you get the plan definition by id you get the latest version. You can also request a specific version. It is possible that some patients used an old version where new patients are already on the latest version of the plan definition. When referencing the plan definition in the care plan we therefore reference a specific version of the plan definition.

This endpoint can be used to retrieve a PlanDefinition by id, by id and version or search using a small set of parameters.

Getting by ID returns the latest version of the plan if you append | after the Id followed by the version you get the specific version of the plan. Make sure to use the url encoding %7C for |

HTTP Request

Get by ID

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition/<ID>

Get by ID and Version

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition/<ID>|<VERSION>

Search

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/PlanDefinition?definition=Questionnaire/<MODULE_UUID>|<MODULE_VERSION>&publisher=<PUBLISHER_NAME>

URL Parameters

Note that in case of search all parameters are optional, a search without parameters will return all resources that are accessible for your client.

Parameter Description
ID UUID of the PlanDefinition to retrieve
VERSION (optional) The version of the PlanDefinition to retrieve
MODULE_UUID (optional) UUID of the Questionnaire that is part of this PlanDefinition
MODULE_VERSION (optional) Version of the questionnaire, can only be used in combination with MODULE_UUID
PUBLISHER_NAME (optional) name of the user that published the PlanDefinition

Questionnaire

Get Questionnaire

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Questionnaire/781dc338-a932-4c0e-8c02-8d419fad4264"
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");

Questionnaire questionnaire = client.read()
      .resource(Questionnaire.class)
      .withId("781dc338-a932-4c0e-8c02-8d419fad4264")
      .execute();
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
var questionnaire = client.Read<Questionnaire>("Questionnaire/781dc338-a932-4c0e-8c02-8d419fad4264");

Console.Out.WriteLine(questionnaire.Description);
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const questionnaire = client.request("Questionnaire/781dc338-a932-4c0e-8c02-8d419fad4264");
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

FHIR::Questionnaire.read("781dc338-a932-4c0e-8c02-8d419fad4264")
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

questionnaire = await client.resource('Questionnaire').execute('781dc338-a932-4c0e-8c02-8d419fad4264', 'GET')


print(questionnaire.description)
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Questionnaire/781dc338-a932-4c0e-8c02-8d419fad4264", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)

unmarshaller, err := jsonformat.NewUnmarshaller(config.TimeZone, config.FhirVersion)
if err != nil {
    return nil, err
}

bytes, err := ioutil.ReadAll(response.Body)
if err != nil {
    return nil, err
}

unmarshal, err := unmarshaller.Unmarshal(bytes)
if err != nil {
    return nil, err
}

unmarshal.(*r4pb.ContainedResource).GetQuestionnaire()
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Questionnaire/781dc338-a932-4c0e-8c02-8d419fad4264?_format=json';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$questionnaire = new FHIRQuestionnaire(json_decode($res, true));

Patients interact with healthcare professionals through questionnaires on Improve Mobile App. A Questionnaire can either be selected from Improve Library or custom-made in Improve Designer. Improve Designer allows the user to specify custom codings for questions. These codings can be used to map data in this resource to existing data. The unfilled-out questionnaire is made available in FHIR format through this end-point.

Questionnaires are versioned. If you get the questionnaire by id you get the latest version which is used in one of your plan definitions. You can also request a specific version. It is possible that patients answered an old version of the questionnaire in the past, therefore references to the questionnaire, for example in the questionnaire response are always to a specific version.

Generally the Questionnaire item components retrieved through this endpoint correspond to questions created in the Improve Designer. An exception to this is the readonly text items that are generated for the formulas defined in the Improve Designer. These are made so that even if a result slide is not defined (where the formula is mentioned as a sub item), the formula will still be in the questionnaire.

Questionnaires contain a list of item elements which represents the specific questions in the questionnaire.

Question types

Users can make and design their own questionnaire in the designer, native FHIR does not contain any elements which are focused on design so we added some extensions to make it clear how the question should be displayed.

There are some general principles to keep in mind

Below is an example of every question type with how they are displayed in the designer and how it is represented in FHIR.

Date Question

A date question is of type date

date question
{
  "id": "1",
  "linkId": "date_1",
  "prefix": "Date question",
  "text": "<p> This is an example of a date question<\p>",
  "type": "date",
  "repeats": false,
  "item": [
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "footer",
                "display": "Footer"
              }
            ]
          }
        }
      ],
      "linkId": "date_1_footer",
      "type": "display"
    }
  ]
}

Multiple Select Question

A multi select question is of type choice and the value of repeats is set to true indicating multiple answers are allowed. In this example every answer option has a label, a value and a code attached to it.

multi select question
{
  "id": "2",
  "linkId": "mselect_2",
  "prefix": "Multiple select question",
  "text": "<p>This is an example of a multiple select question in which every answers is linked to a SNOMED code. The question is about different symptoms and you can select multiple</p>",
  "type": "choice",
  "repeats": true,
  "answerOption": [
    {
      "id": "mselect_2_answer-option_0",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "Sneezing"
        },
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "162367006",
            "display": "Sneezing"
          }
        }
      ],
      "valueString": "0"
    },
    {
      "id": "mselect_2_answer-option_1",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "Coughing"
        },
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "162367006",
            "display": "Coughing"
          }
        }
      ],
      "valueString": "1"
    },
    {
      "id": "mselect_2_answer-option_2",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "Fever"
        },
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "386661006",
            "display": "Fever"
          }
        }
      ],
      "valueString": "2"
    },
    {
      "id": "mselect_2_answer-option_3",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "Diarrhea"
        },
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "62315008",
            "display": "Diarrhea"
          }
        }
      ],
      "valueString": "3"
    }
  ],
  "item": [
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "footer",
                "display": "Footer"
              }
            ]
          }
        }
      ],
      "linkId": "mselect_2_footer",
      "type": "display"
    }
  ]
}

Horizontal Slider

Horizontal slider questions can be recognised because they have an extension https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/display-type with the value horizontal-slider. We also use the FHIR extension http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl with value slider but the custom extension is needed to indicate the direction of the slider. Sliders have answer options in which every answer option represents a tick on the slider, with labels. Sliders also use the extension https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/slider-step-decimal-value to indicates the step size on the slider. It is possible that there is no label and therefore no answer option for every allowed step value. We use a custom extension because step values can be decimal. The order of the answers options matter and should be represented in the slider. Horizontal sliders also have optional labels which should be presented left, right or in the middle of the slider

In the following example some ticks are linked to codes but not all

horizontal slide question
{
  "id": "3",
  "extension": [
    {
      "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/display-type",
      "valueString": "horizontal-slider"
    }
    {
      "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/slider-step-decimal-value",
      "valueDecimal": 10
    },
    {
      "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
      "valueCodeableConcept": {
        "coding": [
          {
            "system": "http://hl7.org/fhir/questionnaire-item-control",
            "code": "slider",
            "display": "Slider"
          }
        ]
      }
    }
  ],
  "linkId": "hslide_3",
  "prefix": "Horizontal slide",
  "text": "<p>This is an example of a horizontal slider question. Asking about blood pressure, the low and high blood pressure values are mapped to the corresponding SNOMED codes</p>",
  "type": "decimal",
  "repeats": false,
  "answerOption": [
    {
      "id": "hslide_3_answer-option_70",
      "extension": [
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "45007003",
            "display": "Low Blood pressure"
          }
        },
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "low"
        }
      ],
      "valueInteger": 70
    },
    {
      "id": "hslide_3_answer-option_80",
      "extension": [
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "45007003",
            "display": "Low Blood pressure"
          }
        },
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "80"
        }
      ],
      "valueInteger": 80
    },
    {
      "id": "hslide_3_answer-option_90",
      "extension": [
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "45007003",
            "display": "Low Blood pressure"
          }
        },
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "90"
        }
      ],
      "valueInteger": 90
    },
    {
      "id": "hslide_3_answer-option_100",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "100"
        }
      ],
      "valueInteger": 100
    },
    {
      "id": "hslide_3_answer-option_110",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "110"
        }
      ],
      "valueInteger": 110
    },
    {
      "id": "hslide_3_answer-option_120",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "120"
        }
      ],
      "valueInteger": 120
    },
    {
      "id": "hslide_3_answer-option_130",
      "extension": [
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "194757006",
            "display": "High Blood pressure"
          }
        },
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "130"
        }
      ],
      "valueInteger": 130
    },
    {
      "id": "hslide_3_answer-option_140",
      "extension": [
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "194757006",
            "display": "High Blood pressure"
          }
        },
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "140"
        }
      ],
      "valueInteger": 140
    },
    {
      "id": "hslide_3_answer-option_150",
      "extension": [
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "194757006",
            "display": "High Blood pressure"
          }
        },
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "high"
        }
      ],
      "valueInteger": 150
    }
  ],
  "item": [
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "lower",
                "display": "Lower-bound"
              }
            ]
          }
        }
      ],
      "linkId": "hslide_3_before",
      "text": "left",
      "type": "display"
    },
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "upper",
                "display": "Upper-bound"
              }
            ]
          }
        }
      ],
      "linkId": "hslide_3_after",
      "text": "right",
      "type": "display"
    },
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "prompt",
                "display": "Prompt"
              }
            ]
          }
        }
      ],
      "linkId": "hslide_3_center",
      "text": "center",
      "type": "display"
    },
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "footer",
                "display": "Footer"
              }
            ]
          }
        }
      ],
      "linkId": "hslide_3_footer",
      "type": "display"
    }
  ]
}
  

Vertical Slider

Vertical slider questions can be recognised because they have an extension https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/display-type with the value vertical-slider. We also use the FHIR extension http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl with value slider but the custom extension is needed to indicate the direction of the slider. Sliders have answer options in which every answer option represents a tick on the slider. Sliders also use the extension https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/slider-step-decimal-value to indicates the step size on the slider. It is possible that there is no label and therefore no answer option for every allowed step value. We use a custom extension because step values can be decimal. The order of the answers options matter and should be represented in the slider. Vertical sliders also have optional labels which can be represented above, or below the slider

In the example below the slider has answer options for the values 0, 10, 20, 30 and 40, but all answers between 0 and 40 rounded to 0.5 are valid, as indicated by the slider-step-decimal-value

vertical slide question
{
  "id": "4",
  "extension": [
    {
      "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/display-type",
      "valueString": "vertical-slider"
    },
    {
      "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/slider-step-decimal-value",
      "valueDecimal": 0.5
    },
    {
      "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
      "valueCodeableConcept": {
        "coding": [
          {
            "system": "http://hl7.org/fhir/questionnaire-item-control",
            "code": "slider",
            "display": "Slider"
          }
        ]
      }
    }
  ],
  "linkId": "vslide_4",
  "type": "decimal",
  "repeats": false,
  "answerOption": [
    {
      "id": "vslide_4_answer-option_40",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "40"
        }
      ],
      "valueInteger": 40
    },
    {
      "id": "vslide_4_answer-option_30",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "30"
        }
      ],
      "valueInteger": 30
    },
    {
      "id": "vslide_4_answer-option_20",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "20"
        }
      ],
      "valueInteger": 20
    },
    {
      "id": "vslide_4_answer-option_10",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "10"
        }
      ],
      "valueInteger": 10
    },
    {
      "id": "vslide_4_answer-option_0",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "0"
        }
      ],
      "valueInteger": 0
    }
  ],
  "item": [
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "lower",
                "display": "Lower-bound"
              }
            ]
          }
        }
      ],
      "linkId": "vslide_4_below",
      "text": "Text below slider",
      "type": "display"
    },
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "upper",
                "display": "Upper-bound"
              }
            ]
          }
        }
      ],
      "linkId": "vslide_4_above",
      "text": "Text above slider",
      "type": "display"
    },
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "footer",
                "display": "Footer"
              }
            ]
          }
        }
      ],
      "linkId": "vslide_4_footer",
      "type": "display"
    }
  ]
}

Number question

Number questions are of type decimal they can be restricted in range, we use the FHIR extensions for minValue and maxValue. We also added our own extension with the url https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/slider-step-decimal-value. This indicates the steps numbers can be increased with. If it is zero any number in the range is allowed, if its 2 and the range starts with 0 only even numbers are allowed.

In the example below the question is linked to a SNOMED code

number question
{
  "id": "5",
  "extension": [
    {
      "url": "http://hl7.org/fhir/StructureDefinition/minValue",
      "valueDecimal": 0
    },
    {
      "url": "http://hl7.org/fhir/StructureDefinition/maxValue",
      "valueDecimal": 300
    },
    {
      "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/slider-step-decimal-value",
      "valueDecimal": 0
    }
  ],
  "linkId": "410668003",
  "code": [
    {
      "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
      "version": "2022-09",
      "code": "410668003",
      "display": "Length"
    }
  ],
  "prefix": "Numbers",
  "text": "<p>An example of a numerical question, asking for the length of the patient, linked to a SNOMED  code </>",
  "type": "decimal",
  "repeats": false,
  "item": [
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "prompt",
                "display": "Prompt"
              }
            ]
          }
        }
      ],
      "linkId": "410668003_before",
      "text": "How tall are you",
      "type": "display"
    },
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "prompt",
                "display": "Prompt"
              }
            ]
          }
        }
      ],
      "linkId": "410668003_after",
      "text": "centimeters",
      "type": "display"
    },
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "footer",
                "display": "Footer"
              }
            ]
          }
        }
      ],
      "linkId": "410668003_footer",
      "type": "display"
    }
  ]
}

Result Question

A result question is used to display the result of a formula to the patient. It can be based on questions earlier in the questionnaire. The result question is of type group. Note that it has the ability to hide footers, and add custom text for the button to move to the next question. Result questions are only part of the questionnaire if the result of a formula is displayed to a patient. It is also possible to use a formula without displaying it, in that case only a formula is added to the questionnaire.

An explanation of how to deal with the formula itself can be found in the explanation of formula types at the bottom of this section.

The example below is linked to a SNOMED code and hides the footer, this is not the case for every result question. The formula in the example refers to input questions which are not part of these examples, see the sandbox to see a complete questionnaire.

number question
{
  "id": "7",
  "extension": [
    {
      "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/hide-footer",
      "valueBoolean": true
    }
  ],
  "linkId": "result_7",
  "code": [
    {
      "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
      "version": "2022-09",
      "code": "60621009",
      "display": "Body mass index"
    }
  ],
  "prefix": "Result",
  "text": "<p>This is an example of a result question, it displays the result of applying the formula to calculate BMI, the answers to the questions about length and mass are used as input for the calculation. The result is linked to the SNOMED code for BMI</p>",
  "type": "group",
  "repeats": false,
  "item": [
    {
      "linkId": "result_7_formula_1",
      "code": [
        {
          "system": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/CodeSystem/question-formula-result",
          "code": "formula_result",
          "display": "Formula Result"
        }
      ],
      "text": "($410668003$/(($4147007$)/100)^(2))",
      "type": "decimal",
      "item": [
        {
          "linkId": "result_7_formula_1_display_name",
          "text": "BMI",
          "type": "display"
        }
      ]
    },
    {
      "linkId": "result_7_next-button-label",
      "text": "Continue",
      "type": "display"
    }
  ]
}
 

Yes No Question

A yes no question is essentially a boolean question with just two answer options, but there is a possibility that other options then true or false are used, for example yes or no. So we map it to a question of type choice and add an extension with url https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/display-type" and the value toggle to indicate that this should be displayed different from regular multiple choice questions. Like other questions with answer options we have the possibility to add custom labels or codes to an answer option.

yes no question
{
  "id": "8",
  "extension": [
    {
      "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/display-type",
      "valueString": "toggle"
    }
  ],
  "linkId": "yesno_8",
  "prefix": "Yes/No question",
  "text": "<p> Example of a boolean question with custom labels and the yes answers is linked to the SNOMED code for pregnancy. Question are you pregnant </p>",
  "type": "choice",
  "repeats": false,
  "answerOption": [
    {
      "id": "yesno_8_answer-option_0",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "No, or not certain"
        }
      ],
      "valueString": "0"
    },
    {
      "id": "yesno_8_answer-option_1",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "Yes definitely"
        },
        {
          "url": "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/question-answer-option-coding",
          "valueCoding": {
            "system": "urn:uuid:13f4adda-718c-11ed-a1eb-0242ac120002",
            "version": "2022-09",
            "code": "77386006",
            "display": "Pregnant"
          }
        }
      ],
      "valueString": "1"
    }
  ],
  "item": [
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "footer",
                "display": "Footer"
              }
            ]
          }
        }
      ],
      "linkId": "yesno_8_footer",
      "type": "display"
    }
  ]
}

Open Question

An open question is a question of type text. We use the FHIR extension http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl with the value text-box to indicate that it is a free field.

open question
{
  "id": "9",
  "extension": [
    {
      "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
      "valueCodeableConcept": {
        "coding": [
          {
            "system": "http://hl7.org/fhir/questionnaire-item-control",
            "code": "text-box",
            "display": "Text Box"
          }
        ]
      }
    }
  ],
  "linkId": "open_9",
  "prefix": "Example of an open question",
  "text": "<p>Example of an open questionlt/p>",
  "type": "text",
  "repeats": false,
  "item": [
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "footer",
                "display": "Footer"
              }
            ]
          }
        }
      ],
      "linkId": "open_9_footer",
      "type": "display"
    }
  ]
}

Multiple choice Question

A multiple choice question is a question in which only one answer option can be chosen. The type of the question is choice. You can distinguish it from the multiple select question because the value of repeats is set to false.

In the example below there are custom labels, but no codes attached to the different answer options.

multiple choice question
 {
  "id": "10",
  "linkId": "mchoice_10",
  "prefix": "Multiple choice question",
  "text": "<p>Example of a multiple choice question, the patient can only select one option for the following question: On what type of device are you responding to this questionnaire</p>",
  "type": "choice",
  "repeats": false,
  "answerOption": [
    {
      "id": "mchoice_10_answer-option_0",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "Laptop"
        }
      ],
      "valueString": "0"
    },
    {
      "id": "mchoice_10_answer-option_1",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "Mobile phone"
        }
      ],
      "valueString": "1"
    },
    {
      "id": "mchoice_10_answer-option_2",
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-optionPrefix",
          "valueString": "Tablet"
        }
      ],
      "valueString": "2"
    }
  ],
  "item": [
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "footer",
                "display": "Footer"
              }
            ]
          }
        }
      ],
      "linkId": "mchoice_10_footer",
      "type": "display"
    }
  ]
}

Picture Question

A picture question is a question of type attachement. If repeats is true the patient is allowed to upload multiple pictures. In that case we use the maxOccurs extension from FHIR to indicate how many pictures can be uploaded.

picture question
{
  "id": "11",
  "extension": [
    {
      "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-maxOccurs",
      "valueInteger": 3
    }
  ],
  "linkId": "image_11",
  "prefix": "Picture question",
  "text": "<p> Example of a picture question, asking patient to make a picture of an injury for example</p>",
  "type": "attachment",
  "repeats": true,
  "item": [
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "inline",
                "display": "In-line"
              }
            ]
          }
        }
      ],
      "linkId": "image_11_below",
      "text": "<p>Text below picture</p>",
      "type": "display"
    },
    {
      "extension": [
        {
          "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
          "valueCodeableConcept": {
            "coding": [
              {
                "system": "http://hl7.org/fhir/questionnaire-item-control",
                "code": "footer",
                "display": "Footer"
              }
            ]
          }
        }
      ],
      "linkId": "image_11_footer",
      "type": "display"
    }
  ]
}

Info Question

An info question is either of type display or of type group. Info questions are meant to display information to the patient. The text element will contain html and can include a picture, table, list or other html content. There are also questionnaires which only contain information questions. The starting and final questions of a questionnaire are always of type information.

info question
{
    "id": "12",
    "linkId": "info_12",
    "prefix": "Info question",
    "text": "<p>An example of an info question, containing only information.</p>
             <p>The final and starting question of a questionnaire are always of type info</p>
             <h5>&nbsp;</h5>",
    "type": "group",
    "repeats": false,
    "item": [
        {
            "linkId": "info_12_next-button-label",
            "text": "Send answers to server, and don't forget to encrypt them",
            "type": "display"
        }
    ]
}

Formula

We use formulas to calculate results based on previous inputs. We have the result type question to show the result of a formula to the user. But it is also possible to have a formula question without the result being shown to the user. In that scenario you'll still need to use the formula to calculate a result and send it to the server as part of the questionnaire response. For this reason we also add the formula as a questionnaire item, but it should not be displayed and only used for calculation. Formula questions can be recognised because they will contain the word 'formula' in the linkId and are added to end of the questionnaire.

We use the FHIR extension http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces to indicate the amount of decimal places to expres the result of the formula in.

Formulas mostly follow mathjs syntax. There is exactly one exception to this, which is the variables. To clarify which QuestionnaireItem result is used in a formula, the variables used in the formulas will be replaced by the linkId, surrounded by $, of the QuestionnaireItem result that should replace the variable. This linkId may be specified by using the Export label field in Improve Designer and does not necessarily follow mathjs's syntax.

For example, given answers {"some-long-link-id": 2, "something-else": 3} and formula $some-long-link-id$ * $something-else$, the result should be 2 * 3 or when evaluated 6.

An example of the fhir code can be found below

{
  "extension": [
    {
      "url": "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces",
      "valueInteger": 2
    }
  ],
  "linkId": "formula_2",
  "prefix": "BMI",
  "text": "($410668003$/(($4147007$)/100)^(2))",
  "type": "decimal",
  "readOnly": true
}

Enable when behavior

All questions can contain a list of enableWhen elements. If this list is not empty it means that the specific question should be hidden by default and only switched on if the conditions in the list are met. We follow the FHIR specifications, but we added two custom extensions to allow for some behavior which is allowed in our platform but is not currently supported by default by FHIR. Both extensions have to do with the fact that some questions allow for multiple answers.

Be aware that this logic adds on top of the default FHIR definitions of dealing with enableWhen conditions, and FHIR allows for multiple enableWhen conditions on a single question. The final example below shows an example of a question which should be enabled when the answer to "symptoms_question" contained either " Sneezing" or "Coughing". It does not use any of our custom extensions. This implementation is less strict, meaning that the question is shown in any scenario, except if only "Headache" was answered.

"enableWhen": [
    {
        "question": "symptoms_question",
        "operator": "=",
        "answerString": "Sneezing"
    },
    {
        "question": "symptoms_question",
        "operator": "=",
        "answerString": "Coughing"
    }
],
"enableBehavior": "any",

Available endpoints

The endpoint can be used to retrieve a Questionnaire by id, and by id and version.

Getting by ID returns the latest version used in any planDefinition if you append | after the Id followed by the version you get the specific version of the plan. Make sure to use the url encoding %7C for |

HTTP Request

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Questionnaire/<ID>

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Questionnaire/<ID>|<VERSION>

URL Parameters

Parameter Description
ID The ID of the Questionnaire to retrieve
VERSION The version of the Questionnaire to retrieve

QuestionnaireResponse

Get QuestionnaireResponse

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/QuestionnaireResponse/57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca"
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");

QuestionnaireResponse questionnaireResponse = client.read()
    .resource(QuestionnaireResponse.class)
    .withId("57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca")
    .execute();
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/", settings);
var qr = client.Read<QuestionnaireResponse>("QuestionnaireResponse/57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca");
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const resp = await client.request("QuestionnaireResponse/57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca");
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

FHIR::QuestionnaireResponse.read('57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca')
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

response = await client.resource('QuestionnaireResponse').execute('57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca', 'GET')

print(response)
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/QuestionnaireResponse/57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)

unmarshaller, err := jsonformat.NewUnmarshaller(config.TimeZone, config.FhirVersion)
if err != nil {
    return nil, err
}

bytes, err := ioutil.ReadAll(response.Body)
if err != nil {
    return nil, err
}

unmarshal, err := unmarshaller.Unmarshal(bytes)
if err != nil {
    return nil, err
}

unmarshal.(*r4pb.ContainedResource).GetQuestionnaireResponse()
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/QuestionnaireResponse/57a1f708-d9cf-4d8c-9f25-b5a450e7f0ca?_format=json';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$questionnaireResponse = new FHIRQuestionnaireResponse(json_decode($res, true));

Search QuestionnaireResponse

curl "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/QuestionnaireResponse?patient.identifier=6226217e&based-on.instantiates-canonical=PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806"
FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");

Bundle bundle = client.search()
    .forResource(QuestionnaireResponse.class)
    .where(QuestionnaireResponse.BASED_ON.hasChainedProperty(
        CarePlan.INSTANTIATES_CANONICAL.hasId("PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806")))
    .and(QuestionnaireResponse.PATIENT.hasChainedProperty(Patient.IDENTIFIER.exactly().identifier("6226217e")))
    .returnBundle(Bundle.class)
    .execute();
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");
var qr = client.Search<QuestionnaireResponse>(new []{"patient.identifier=6226217e", "based-on.instantiates-canonical=97f680b9-e397-4298-8c53-de62a284c806"});

qr.Entry.ForEach(component => Console.WriteLine(component.FullUrl));
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const bundle = await client.request("QuestionnaireResponse?based-on.instantiates-canonical=PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806&patient.identifier=6226217e");
const responses = bundle.entry.map(entry => entry.resource);
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

params = {
  'based-on.instantiates-canonical': 'PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806',
  'patient.identifier': '6226217e'
}
FHIR::QuestionnaireResponse.search(params)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

response = await client.resources('QuestionnaireResponse').search(based_on.instantiates_canonical='PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806', patient__identifier='6226217e').fetch()

print(response)
req, err := http.NewRequest("GET", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/QuestionnaireResponse?based-on.instantiates-canonical=PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806&patient.identifier=6226217e", nil)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)
$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/QuestionnaireResponse?based-on.instantiates-canonical=PlanDefinition/97f680b9-e397-4298-8c53-de62a284c806&patient.identifier=6226217e&_format=json';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
]);
$res = curl_exec($ch);
curl_close($ch);
$bundle = new FHIRBundle(json_decode($res, true));

The patient’s filled out questionnaire is immediately visible for the medical professional on Improve applications.

The APIs can be used to add responses to, for example, the patient’s EHR file, or can be used to integrate or create a different dashboard.

Retrieving & Searching QuestionnaireResponse

This endpoint can be used to retrieve a QuestionnaireResponse by id, or search using a small set of parameters. Note when using the search endpoint a Bundle containing the QuestionnaireResponses that match the search criteria will be returned.

The answers of the questionnaire will be encrypted using OpenPGP. Details on decrypting the response can be found in the Decrypting QuestionnaireResponse section.

Creating QuestionnaireResponse(s)

Create QuestionnaireResponses as Bundle

curl -X POST "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Bundle" -H "Content-Type: application/json" --data-binary "@response-bundle.json"
MethodOutcome post(QuestionnaireResponse response) {
    var bundle = new Bundle();
    bundle.addEntry().setResource(response);

    return client.create().resource(bundle).execute();
}
Bundle CreateQuestionnaireResponse(QuestionnaireResponse response)
{
    var bundle = new Bundle
    {
        Entry = new List<Bundle.EntryComponent>
        {
            new Bundle.EntryComponent
            {
                Resource = response
            }
        }
    };
    return client.Create(bundle);
}
const bundle = await encryptQuestionnaireResponse(questionnaireResponse, practitioners)
const client = new Client();
return client.create(bundle);
FhirClient.new

def createQuestionnaireResponse(response)
    bundle = FHIR::Bundle.new
    bundle_entry = FHIR::Bundle::Entry.new
    bundle_entry.resource = response
    bundle.entry = [bundle_entry]

    FHIR::Bundle.create(bundle)
end
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

with open('bundle.json', 'r') as file:
        bundle_json = json.load(file)

await client.resource('Bundle').save(bundle_json)
file, err := os.Open("bundle.json")
if err != nil {
    return nil, err
}

req, err := http.NewRequest("POST", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Bundle", file)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)
$bundle = new FHIRBundle();
$entry = new FHIRBundleEntry();
$entry->setResource($response);
$bundle->setEntry([$entry]);

$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Bundle';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($bundle)
]);
$res = curl_exec($ch);
curl_close($ch);
$responseBundle = new FHIRBundle(json_decode($res, true));

Person centered clients need to provide encrypted QuestionnaireResponses to the Open HealthHub platform. There are a couple of requirements for these QuestionnaireResponses when posting them:

HTTP Request

Get by ID

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/QuestionnaireResponse/<ID>

Search

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/QuestionnaireResponse?based-on.instantiates-canonical=PlanDefinition/<PROGRAM_UUID>|<PROGRAM_VERSION>&questionnaire=Questionnaire/<MODULE_UUID>|<MODULE_VERSION>&patient.identifier=<PATIENT_ID>&_since=<SINCE_DATE>

Creating single response

POST https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/QuestionnaireResponse

Creating multiple responses

POST https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4 also see transaction requests

URL Parameters

Note that in case of search all parameters are optional, a search without parameters will return all resources that are accessible for your client.

Parameter Description
ID UUID of the QuestionnaireResponse to retrieve
PROGRAM_UUID (optional) UUID of the program this Questionnaire response belongs to
PROGRAM_VERSION (optional) Version of the program this Questionnaire response belongs to, can only be used in combination with PROGRAM_UUID
MODULE_UUID (optional) UUID of the Questionnaire that was answered
MODULE_VERSION (optional) Version of the Questionnaire that was answered, can only be used in combination with MODULE_UUID
PATIENT_ID (optional) the id of the patient that answered the questionnaire in FHIR Token format
SINCE_DATE (optional) QuestionnaireResponses that were entered after this date, in ISO 8601 format. For details refer to Times

Decrypting QuestionnaireResponse

The decrypted JSON with all answers

{
    "date_1": [
        {
            "value": "1981-07-29",
            "codes": []
        }
    ],
    "photo_1": [
        {
            "value": "base64EncodedImage",
            "codes": []
        }
    ],
    "hslider_1": [
        {
            "value": "5.5",
            "codes": []
        }
    ],
    "vslider_1": [
        {
            "value": "-9",
            "codes": []
        }
    ],
    "formula_1": [
        {
            "value": "-3.5",
            "codes": []
        }
    ],
    "number_1": [
        {
            "value": "42",
            "codes": []
        }
    ],
    "mchoice_1": [
        {
            "value": "Raspberry",
            "codes": [{
                "code": "7658-8",
                "system": "https://fhir.loinc.org/CodeSystem/loinc",
                "display": "Raspberry BasoBnd Ab Qn",
                "version": "2.71"
            }]
        }
    ],
    "mselect_1": [
        {
            "value": "Laptop",
            "codes": []
        },
        {
            "value": "Smartphone",
            "codes": []
        }
    ],
    "boolean_1": [
        {
            "value": "1",
            "codes": []
        }
    ],
    "open_2": [
        {
            "value": "It's the best questionnaire I've ever seen!",
            "codes": []
        }
    ]
}

The private key used to decrypt the examples from the sandbox

-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP.js v4.10.10
Comment: https://openpgpjs.org

xYYEYQeklhYJKwYBBAHaRw8BAQdA7zkG2GrTERRlKqZRhbHEYARkzWjXt4Bj
RBx9PFEfwwb+CQMIwF4UP4mQC0bgQkxklm3EDi6SFHw1kfMzQ+gqpfaepGau
EPjpSqLN48gZJMznHK4UBe0xzu67OP6Y9UyT8SL2dj/gSVcku1MZvPZCEP2Y
Is0uU2FuZGJveCBBUEkyIDxlZGR5K3NhbmRib3gyQG9wZW5oZWFsdGhodWIu
Y29tPsKPBBAWCgAgBQJhB6SWBgsJBwgDAgQVCAoCBBYCAQACGQECGwMCHgEA
IQkQAyVBKg8H7ZgWIQSmxMhcFAXUQBd0uOYDJUEqDwftmLCiAQDVYGQ9eI+J
WL0PYwgrY7StV590qZar9S1dJvmGfkcGAgD/e/EBG7C5+Kd5KO2uCaS2q42B
HKzBUGVRYBjFQ8/SUQDHiwRhB6SWEgorBgEEAZdVAQUBAQdAum8OjYljCvYq
GCKW84Fub4/e/fp3l8dE/oZsrwArMwUDAQgH/gkDCJRAihNl5syQ4C/UKaHt
gM94KCfG48hvgIiOFrBp7ZBkNOl30H0r/mOLD6+BrvMFLHUG3H+OTIwsjDbc
Q5sHAE9tPVDXA98luvBQC/KNu2HCeAQYFggACQUCYQeklgIbDAAhCRADJUEq
DwftmBYhBKbEyFwUBdRAF3S45gMlQSoPB+2Y9IYBAMCPkUQO9r7Ot3nff45i
vThPkehhDk+MDb4zWW3xXVe7AP0ZXv6yBNGlsm2xFyGtVQH30nckhyBeKGcT
CEykt3SCBQ==
=X1eg
-----END PGP PRIVATE KEY BLOCK-----

Load private key:

gpg --import "../../sandbox.key"
privateKey = PGPainless.readKeyRing().secretKeyRing(ClientPerAnswerApplication.class.getResourceAsStream(PRIVATE_KEY_FILE));
keyRingProtector = SecretKeyRingProtector.unlockAllKeysWith(Passphrase.fromPassword(PRIVATE_KEY_PASSPHRASE), privateKey);
var privateKey = File.ReadAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "sandbox.key"));
var pgp = new Openpgp();
pgp.Keys.Add(new Key(privateKey));
pgp.Keys[0].Passphrase = PgpPassphrase;
const keys = await openpgp.key.readArmored(PRIVATE_KEY_FILE);
const key = keys.keys[0];
await key.decrypt(PRIVATE_KEY_PASSPHRASE);
this.decryptedPrivateKey = key;
GPGME::Key.import(File.open('../../sandbox.key'))
gpg = gnupg.GPG()
with open('../../sandbox.key') as file:
    gpg.import_keys(file.read(), passphrase='api-sandbox')
privateKey, err := ioutil.ReadFile("../sandbox.key")
$keyASCII = file_get_contents(dirname(__FILE__) . '/../../../../sandbox.key');
$gnupg = new gnupg();
$importKeyResult = $gnupg->import($keyASCII);
$fingerPrint = $importKeyResult['fingerprint'];
$gnupg -> adddecryptkey($fingerPrint, 'api-sandbox');
return $gnupg;

Determine if QuestionnaireResponse is encrypted

jq -r '.extension[] | select(.url="https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/encryptedAnswers") | .valueString'
boolean isEncryptedResponse(QuestionnaireResponse questionnaireResponse) {
    return questionnaireResponse.getMeta()
        .getProfile()
        .stream()
        .anyMatch(profile -> "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/EncryptedQuestionnaireResponse".equals(profile.getValue()));
}
questionnaireResponse.Meta.Profile.Any(profile =>
                "https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/EncryptedQuestionnaireResponse".Equals(profile));
isEncryptedResponse(resp) {
  return resp.meta.profile.some(profile => profile === 'https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/EncryptedQuestionnaireResponse');
}
response.meta.profile.any? { |profile| profile == 'https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/EncryptedQuestionnaireResponse' }
encryptedProfile = 'https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/EncryptedQuestionnaireResponse'


def is_encrypted(resource: Resource):
    encProfile = list(filter(lambda p: encryptedProfile == p, resource.meta.profile))
    return len(encProfile) > 0

func isEncrypted(qr *questionnaire_response_go_proto.QuestionnaireResponse) bool {
    for _, p := range qr.Meta.Profile {
        if p.GetValue() == encryptedProfileUrl {
            return true
        }
    }
    return false
}
foreach ($resp->getMeta()->getProfile() as $profile) {
    if ($profile->getValue() == 'https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/EncryptedQuestionnaireResponse') {
        return true;
    }
}

return false;

Decrypt a String value

gpg -d
String decryptValue(String value, String extensionUrl) {
    DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
            .onInputStream(new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8)))
            .decryptWith(keyRingProtector, privateKey)
            .doNotVerify()
            .build();
    byte[] bytes = decryptionStream.readAllBytes();
    decryptionStream.close();

    return new String(bytes, StandardCharsets.UTF_8);
}
pgp.Decrypt();
var decrypted = pgp.OutputMessage;
async decryptValue(value) {
  const messageObj = await openpgp.message.readArmored(value);

  const result = await openpgp.decrypt({message: messageObj, privateKeys: [this.decryptedPrivateKey]});
  return result.data;
}
crypto = GPGME::Crypto.new
crypto.decrypt(value, password: private_key_passphrase).to_s
dec = gpg.decrypt(encryptedAnswers[0].valueString)
print(dec)
message, err := helper.DecryptMessageArmored(string(privateKey), []byte("api-sandbox"), armored)
$gnupg->decrypt($value);

All answers in our QuestionnaireResponse are encrypted, so they can't be read unless you have the correct private key to decrypt the responses. The answers are encrypted in 2 separate ways.

Every answer is encrypted in the item -> answer field, using an extension with the prefix https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/encrypted- followed by the type of the answer (e.g. decimalType, stringType , attachment).

Because decrypting all answers one-by-one can have a big impact on performance, we also emit all answers in an encrypted JSON object. This is emitted in an extension at the top level of the QuestionnaireResponse with url https://api.openhealthhub.com/OpenHealthhub/fhir/4/StructureDefinition/encryptedAnswers.

All answers are emitted as a JSON object with a value property that contains the actual value of the answer. If the answer has codes linked to it (as entered in Improve Designer), they will be put in a codes array (see the mchoice_1 answer on the right). Most questionnaire items will have a one-to-one match between their linkids and the ids for the answer value objects. For result questionnaire items result_<result_index>_formula_<formula_index> will be created to indicate first which item (result_<result_index>) and second (formula_<formula_index>) which result sub-item the answer is for. Finally, all formula results will also be emitted as formula_<formula_id>.

For decrypting, the private key shown on the right can be used (passphrase: api-sandbox).

Some helper functions are displayed here to help you load the private key, determine if a QuestionnaireResponse is encrypted, and decrypt a value.

For a complete example please have a look at the FHIR QuestionnaireResponse Client Examples:

Language
C#
Go
Java
JavaScript
PHP
Python
Ruby
shell

Encrypting QuestionnaireResponse

All answers in our QuestionnaireResponse should be encrypted, so they can't be read unless you have the correct private key to decrypt the responses. The answers should be encrypted in 2 separate ways.

Using our javascript SDK

import {encryptQuestionnaireResponse} from '@openhealthhub/fhir-encrypter';

const questionnaireResponse = {}; // your QuestionnaireResponse
const practitioners = []; // your Practitioners
encryptQuestionnaireResponse(questionnaireResponse, practitioners)
      .then(bundle => {
        // post('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Bundle', bundle)
      })
      .catch(error => {
        // handle error
      });

To help you with creating an encrypted QuestionnaireResponse we offer a npm package with a single function with inputs the QuestionnaireResponse and a list of Practitioners which will output a Bundle that can be posted to our Bundle endpoint.

The following fields should already be set before passing the response through the encrypt function:

The practitioners can be retrieved as part of the CarePlan, as part of a CareTeam, or as a list of Practitioners in a CareTeam.

Encrypting answers yourself

If you want to handle the encryption yourself

Every answer should be encrypted in the item -> answer field, using an extension with the prefix http://openhealthhub.com/fhir/StructureDefinition/encrypted- followed by the type of the answer (e.g. decimalType, stringType , attachment).

Because decrypting all answers one-by-one can have a big impact on performance, we also want all answers in an encrypted JSON object. This should be added as an extension at the top level of the QuestionnaireResponse with url http://openhealthhub.com/StructureDefinition/encryptedAnswers.

All answers should be encrypted as a JSON object. The format of this object is as follows:

{
    "value": "Raspberry",
    "codes": [{
        "code": "7658-8",
        "system": "https://fhir.loinc.org/CodeSystem/loinc",
        "display": "Raspberry BasoBnd Ab Qn",
        "version": "2.71"
    }],
    "text": "Raspberry label"
}

Field explanation:

If the answer has multiple values (e.g. in case of a multiple select question) there should be multiple answer objects. Most questionnaire items should have a one-to-one match between their linkIds and the ids for the answer value objects. For result questionnaire items result_<result_index>_formula_<formula_index> should be created to indicate first which item (result_<result_index>) and second (formula_<formula_index>) which result sub-item the answer is for. Finally, all formula results should also be emitted as formula_<formula_id>.

The JSON object with all the combined answers should be an object, with a property for every question with key the linkId of the question. The value should be an array of answer objects as described above (also in case of single answer).

For a complete example please have a look at the JavaScript FHIR QuestionnaireResponse Client Example:

Subscription

Create Subscription

curl  -X POST 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription' \
      -H 'Content-Type: application/json' \
      -D '{
        "resourceType" : "Subscription",
        "status": "requested",
        "criteria": "Appointment?name=test",
        "channel" : {
          "type" : "rest-hook",
          "endpoint" : "https://your-webhook/endpoint",
          "header": ["Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"]
      }
  }'
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent()
        .setType(Subscription.SubscriptionChannelType.RESTHOOK)
        .setHeader(List.of(new StringType("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")))
        .setEndpoint("https://your-webhook/endpoint");
Subscription subscription = new Subscription()
        .setCriteria("Appointment?name=test")
        .setStatus(Subscription.SubscriptionStatus.REQUESTED)
        .setChannel(channel);

FhirContext ctx = FhirContext.forR4();
IGenericClient client = ctx.newRestfulGenericClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");

Subscription subscription = client.create()
      .resource(subscription)
      .execute();
// we use nuget package Hl7.Fhir.R4
var client = new FhirClient("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4");

var resource = new Subscription
{
    Status = Subscription.SubscriptionStatus.Requested,
    Criteria = "QuestionnaireResponse",
    Channel = new Subscription.ChannelComponent()
    {
        Type = Subscription.SubscriptionChannelType.RestHook,
        Header = new List<string>{"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}
    }
};
var subscription = client.Create(resource);

Console.Out.WriteLine(subscription.Id);
const client = FHIR.client('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4');
const subscription = client.create({
  resourceType: 'Subscription',
  criteria: 'Appointment?name=test',
  status: 'requested',
  channel: {
    type: 'rest-hook',
    endpoint: 'https://your-webhook/endpoint',
    header: ['Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8']
  }
});
client = FHIR::Client.new('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4')
FHIR::Model.client = client

subscription = FHIR::Subscription.new

subscription.criteria = 'Appointment?name=test'
subscription.status = 'requested'
subscription.channel = FHIR::Subscription::Channel.new
subscription.channel.type = 'rest-hook'
subscription.channel.endpoint = 'https://your-webhook/endpoint'
subscription.channel.header = ['Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8']

FHIR::Subscription.create(subscription)
client = AsyncFHIRClient(
    'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4'
)

with open('subscription.json', 'r') as file:
        subJson  = json.load(file)

createResponse = await client.execute('Subscription', method='post', data=subJson)

print(createResponse)
file, err := os.Open("subscription/subscription.json")
if err != nil {
    return nil, err
}

req, err := http.NewRequest("POST", "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription", file)
if err != nil {
    return nil, err
}
req.Header.Add("X-API-Key", "ad880601-b7e6-4d86-901d-b6fca96fc725")
if method == "POST" {
    req.Header.Add("Content-Type", "application/json")
}

response, err := http.DefaultClient.Do(req)
$subscription = new FHIRSubscription();
$status = new FHIRSubscriptionStatus(['requested']);
$subscription->setStatus($status);
$channel = new FHIRSubscriptionChannel();
$channelType = new FHIRSubscriptionChannelType();
$channelType->setValue('rest-hook')
$channel->setEndpoint('https://your-webhook/endpoint')->setType($channelType->setValue('rest-hook'))->addHeader(['Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8']);
$subscription->setCriteria('Appointment?name=test')->setChannel($channel);

$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($subscription)
]);
$res = curl_exec($ch);
curl_close($ch);
$createdSubscription = new FHIRSubscription(json_decode($res, true));

The endpoint can be used to create, get, search, and delete Subscriptions that trigger a webhook request when a resource that matches the criteria has been created or updated.

Creating a Subscription

To create a Subscription to get notified about changes you'll need to have an endpoint/URL available that can handle the notification requests.

If you want subscriptions to only be valid until a specific time you can specify the end value of a subscription. This can be helpful if you specify an authorization token in the headers that has a limited lifetime. Note that you should keep track of expired subscriptions yourself, we won't create new subscriptions if they expire. No notifications will be sent for this subscription after the specified timestamp. The status of the subscription will then also change to off.

When creating a Subscription you can choose to also retrieve the content with the notification by setting the payload to one of the 2 allowed values [application/fhir+json, application/fhir+xml]. If you don't want to receive the content you'll need to execute the request defined in the criteria to retrieve the items.

With Payload

If you have set the payload to one of the allowed values, we'll send the notifications with the (related) content of the subscribed resource in a Bundle of type history.

Headers

If your server endpoint needs specific headers in the HTTP Post request you can add them in the channel.header field. This can for example be used when the endpoint is secured with authentication. You can for example add an Authorization: Bearer <jwttoken> value to the headers. When the notification will be posted these headers will be added to the request.

Supported criteria

We currently support subscriptions on PlanDefinition and QuestionnaireResponse:

PlanDefinition

You can register a Subscription on PlanDefinitions to receive notification when new programs for your client are published or if changes occur to a specific program by adding an _id query parameter to the criteria.

When payload has been set the posted Bundle will contain the PlanDefinition that was changed/created.

QuestionnaireResponse

You can register a Subscription on QuestionnaireResponse to receive notification when new answers for a questionnaire have become available. Currently, you can register for notifications on all responses or on responses for a specific program/PlanDefinition. To listen for responses for a specific program, you'll need to add the based-on.instantiates-canonical query parameter to your criteria and set the value to the UUID of the program you want to receive notifications for.

When payload has been set the posted Bundle will contain the QuestionnaireResponse (with encrypted values) and the Questionnaire that corresponds to this QuestionnaireResponse.

CarePlan

You can register a Subscription on CarePlan to receive notification when a CarePlan is created or updated, or when a questionnaire that is part of the CarePlan has been filled out. Currently, you can register for notifications on all CarePlans, CarePlans for a specific program/PlanDefinition, or for a specific CarePlan. To listen for CarePlans for a specific program, you'll need to add the instantiates-canonical query parameter to your criteria and set the value to the UUID of the program you want to receive notifications for. When you want to receive notifications for a specific CarePlan, you can use the _id query parameter with the ID of the CarePlan as value.

When payload has been set the posted Bundle will contain the CarePlan that was updated/created.

Searching a Subscription

Searching a subscription can be done with or without supplying a criteria parameter (see HTTP Requests for examples). A search can return all subscriptions; subscriptions for a specific resource (i.e. PlanDefinition and QuestionnaireResponse); or subscriptions for a specific resource and a specific id.

Deleting a subscription

A Subscription can be deleted by sending a DELETE request. The subscription to delete is specified by supplying the id which is returned when creating or searching a subscription.

HTTP Requests

Create

POST https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription?criteria=PlanDefinition GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription?criteria=QuestionnaireResponse?based-on.instantiates-canonical=f9af7954-af7f-4c16-abde-74ffeea33956 GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription?criteria=CarePlan?instantiates-canonical=f9af7954-af7f-4c16-abde-74ffeea33956 GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription?criteria=CarePlan?_id=98524685

Read

GET https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription/8d2acacb-0351-42ca-8da6-c4632e20b6f1

Delete

DELETE https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4/Subscription/8d2acacb-0351-42ca-8da6-c4632e20b6f1

Subscription fields

Field Example value Description
channel.type rest-hook the only supported channel type
channel.header ["header1:value1", "header2:value2"] custom headers to put on the webhook request
channel.endpoint http://yourserver:8080/yourPostEndpoint the endpoint that the webhook should post to
channel.payload application/fhir+json if you want to receive the actual content of the resource set this value, otherwise leave it empty
status requested when creating a subscription requested is the only allowed value, other values are 'active' or 'off'
end 2024-12-05T09:00:00Z specifies the time after which no notifications should be sent anymore, can be left empty if no end time required
criteria QuestionnaireResponse?based-on.instantiates-canonical=21bf742e-5220-4b17-8eb8-241d20028de1 for more info see subscription criteria and search

Batch & Transaction Requests

Execute Batch

curl -X POST "https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4" -H "Content-Type: application/json" --data-binary "@batch.json"
var getCarePlanEntry = new Bundle.BundleEntryComponent().setRequest(
                new Bundle.BundleEntryRequestComponent().setMethod(Bundle.HTTPVerb.GET)
                        .setUrl("/Careplan/123"));
var createCarePlanEntry = new Bundle.BundleEntryComponent().setRequest(
        new Bundle.BundleEntryRequestComponent().setMethod(Bundle.HTTPVerb.POST)
                .setUrl("/Careplan")).setResource(new CarePlan());

var batchBundle = new Bundle().setType(Bundle.BundleType.BATCH)
        .addEntry(getCarePlanEntry)
        .addEntry(createCarePlanEntry);
Bundle bundle = new Bundle
{
    Type = Bundle.BundleType.Batch,
    Entry = new List<Bundle.EntryComponent>()
};

bundle.Entry.Add(new Bundle.EntryComponent
{
    Resource = new CarePlan
    {
       Period = new Period(FhirDateTime.Now(), null),
       InstantiatesCanonical = new List<string> { "PlanDefinition/cca2eaf3-03a9-46c0-88c6-e0287917cea6" }
    },
    Request = new Bundle.RequestComponent
    {
        Method = Bundle.HTTPVerb.POST,
        Url = "CarePlan"
    }
});

bundle.Entry.Add(new Bundle.EntryComponent
{
    Request = new Bundle.RequestComponent
    {
        Method = Bundle.HTTPVerb.GET,
        Url = "CarePlan/1"
    }
});

var result = client.Transaction(bundle);

Console.Out.WriteLine(result);
}
const client = new Client();
return client.create({
    resourceType: "Bundle",
    type: 'batch',
    entry: [{
      request: {
        method: 'GET',
        url: 'CarePlan/123'
      }
    }, {
      request: {
        method: 'POST',
        url: 'CarePlan'
      },
      resource: {
        type: 'CarePlan',
        ...content
      }
    }]
  });
    careplan = FHIR::CarePlan.new

    batch_request = FHIR::Transaction.new

    create_request = FHIR::Bundle::Entry::Request.new
    create_request.url = 'CarePlan'
    create_request.request_method = 'POST'
    create_request.entry = careplan

    get_request = FHIR::Bundle::Entry::Request.new
    get_request.url = 'CarePlan/123'
    get_request.request_method = 'GET'

    batch_request.entry = [create_request, get_request]

    FHIR::Client.post(batch_request)
with open("batch.json", "r") as file:
    batch_json = json.load(file)

    async with ClientSession() as session:
        async with session.post('https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4', data=json.dumps(batch_json)) as resp:
            print(await resp.text())
bundle, err := os.Open("batch/batch.json")
if err != nil {
  return nil, err
}

marshaller, err := jsonformat.NewMarshaller(jsonformat.STU3, "", "", jsonformat.ParserConfig{})
  if err != nil {
    return nil, err
  }

  bundleBytes, err := marshaller.MarshalResource(bundle)
  if err != nil {
    return nil, err
  }

  resp, err := client.Post("https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4", bundleBytes)
  if err != nil {
    return nil, err
  }

  unmarshalledBundle := bundle_and_contained_resource_go_proto.Bundle{}
  if err := proto.Unmarshal(resp, unmarshalledBundle); err != nil {
    return nil, err
  }

  return unmarshalledBundle, nil
$transactionBundle = array(
    'resourceType' => 'Bundle',
    'type' => 'transaction',
    'entry' => array(
        array(
            'request' => array(
                'method' => 'POST',
                'url' => 'CarePlan',
            ),
            'resource' => $this->createCarePlanResource()->getValues(),
        ),
        array(
            'request' => array(
                'method' => 'GET',
                'url' => 'CarePlan/1',
            ),
        ),
    )
);

$url = 'https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($careplan)
]);
$res = curl_exec($ch);
curl_close($ch);
$createdCarePlan = new FHIRBundle(json_decode($res, true));

Using FHIR's batch/transaction interactions you can execute multiple actions in a single HTTP request.

We currently support batch requests for all our supported resources. Please note that for batch requests there should not be any dependencies between actions in the request. Every action will be handled as a single standalone action.

For transaction requests we currently only support posting a set of QuestionnaireResponses.

HTTP Request

POST https://api.openhealthhub.com/OpenHealthhub/fhir-sandbox/4