NAV
graphql

Introduction

About

Healthie offers a web and mobile platform for healthcare tech organizations to launch and scale digital services. Developer teams can use our API to build client-facing, and provider-facing, apps, leveraging Healthie’s back-end, data architecture, and sample front-ends.

Healthie makes available the same API that our own front-end developers use to build Healthie (Healthie Help Guide Linked Here), so the functionality covers our entire platform, including client engagement, coaching, provider management, telehealth, insurance billing, EHR, and more. As a result, product and engineering teams are able to focus on the implementation of specific care models to deliver ideal client-facing experiences.

Many organizations also leverage our API to pull detailed data for reporting & analytics that spans business operations, financial performance, client engagement, and client outcomes.

Our developer support team is here to help answer questions you may have about the API. We encourage you to contact us: organizations@gethealthie.com and also share your feedback & requests as you explore and utilize our API.

GraphQL Overview

Healthie offers a GraphQL API. If you have not worked with GraphQL before, It is recommended to read Introduction to GraphQL and Queries and Mutations over at Learn about GraphQL before you begin development.

Using our GraphQL endpoint, you can do three types of operations - Queries, Mutations, and Subscriptions.

Operation Usage
Queries Get data
Mutations Perform actions and alter objects
Subscriptions Automatically listen for data changes (e.g a new chat message comes in)

Making GraphQL Queries

Requests to the Healthie API are all HTTP requests, so any HTTP client (cURL, Axios, Net:HTTP, etc) can be used to make API requests. However, using tools with better GraphQL support can help a lot when using Healthie's API. Here are our reccomendations.

The easiest way to learn about and run your first GraphQL query is by using our API Explorer. It is a fully-fledged GraphQL IDE based on GraphiQL, providing you documentation and an easy to use interface to run queries.

As you progress beyond that, you can make queries via API tools like Insomnia or Postman.

Finally, when it comes time to write the actual code, we highly recommend using a GraphQL client, versus trying to construct the direct HTTP requests. Two examples are Apollo Client (for JS) and GraphQL-Client (for Ruby).

Example Projects

We have an open-source widget that utilizes the API on Github. That repo is a great place is to get started if you are looking for an example project that uses our API.

Developer Support

Healthie prides ourselves on our hands-on developer support, available to all API customers.

API Concepts

Environments

Healthie offers two API environments - Sandbox and Production. Sandbox and Production are fully seperate environments, and it is not possible for Healthie to copy data between them. Please note, before hard-coding IDs into your integration, that they will be different on Sandbox and Production.

The examples in this documentation use the Sandbox environment's API URL. The URLs are as follows

Name URL
Sandbox https://staging-api.gethealthie.com/graphql
Production https://api.gethealthie.com/graphql

Authentication

Healthie uses API keys to allow access to the API. Each Healthie account can have its own API key.

Healthie expects the API key to be included in all authenticated API requests to the server, via headers that look like the following:

Authorization: Basic YOUR_API_KEY_HERE

AuthorizationSource: API

Versioning

As per the GraphQL Best Practices, Healthie's API is not versioned. We avoid breaking changes, and old API clients will continue to work. This is possible since GraphQL only returns the data that's explicitly requested. New capabilities can be added to Healthie's API via new types and new fields on those types without creating a breaking change.

Error Handling

Errors in GraphQL work differently than in a traditional REST API. Notably, you may receive errors even though the HTTP status code is 200. Broadly, they are seperated into two categories - Server Errors and GraphQL Errors.

Error Error Code Description
Server Error 5xx Server internal errors that prevent a successful response.
GraphQL Error 200 Some fields / attributes have errors and (may) appear alongside successful data.

Pagination

Many queries in Healthie's API are paginated. We use offset-based pagination. When a query takes in an offset argument, that specifies where the data returned should start. For example, our entries query has a page size of 10. if you have a list of 20 entries, and want to load all of them in, you would first do an entries query with offset of 0 and then an entries query with an offset of 10. Many queries have a corresponding "count" query (e.g entries has an entriesCount), That "count" query lets you see the total amount of data that you will need to page through.

If you send in an offset value that is too large (e.g 150 for a data set that only contains 100 objects), our API will just return an empty array.

Client Mutation ID

In the schema, you will come across the field clientMutationId on all of our mutations. This is a GraphQL auto-generated field that is not used in our API. You should not send in a clientMutationId and can ignore the field.

Webhooks

Healthie's API offers webhooks for common events. When the event happens, Healthie will POST the following body to a URL you provide us. We support different URLs for each event, as well as multiple URLs for the same event.

You can see an example POST body on the right-hand column.

{

"resource_id": resource_id, # The ID of the resource that was affected 

"resource_id_type": resource_id_type, # The type of resource (can be 'Appointment', 'FormAnswerGroup', 'Entry', or 'Note')  

"event_type": event_type # The event that occurred

}

Please note no data about the event is included besides the resource ID and event category. It is expected that you make another API call after, using the resource ID, to get the data you need.

The available event types are currently

Type
message.created
message.deleted
payment.created
payment.updated
payment.deleted
appointment.created
appointment.updated
appointment.deleted
form_answer_group.created
metric_entry.created
metric_entry.updated
metric_entry.deleted
patient.created

Don't see a webhook for an event you want to listen to? Please reach out to us.

Examples

Say you would like to add a special external video link when patients under a particular provider book an appointment. You could: * listen to the "appointment.created" webhook event * fetch the appointment when the event is received * if the appointment's provider is a match, use the updateAppointment mutation to update the appointment's external_videochat_url

Terminology

Due to the API's evolution over time, and the wide range of use-cases we support, there are a few terms that are used interchangeably throughout the API docs. There are also some terms that may have API objects with non-obvious names. Here is a quick glossary to help you better understand the documentation (especially the Schema mentioned below).

API Explorer

All possible queries, mutations, and subscriptions are covered in our API Explorer. GraphQL is incredibly flexible. Using the API Explorer is a great way to see the different types of queries and mutations you can do, and the data you can get back from them.

Here is the API Explorer in action:

You will need a Sandbox API key to use the API explorer.

Launch the Explorer

If you don't have a Sandbox API key, You can also view the schema as a website here

Patient (Client) Management

The Patient Object

 # Some Example Fields

 "user": {
      "id": "9900",
      "first_name": "Jonas",
      "last_name": "Salk",
      "dob": "2010-10-10",
      "gender": "Male",
      "email": "jonas.salk@example.com",
      "phone_number": "555-444-2244",
      "next_appt_date": null
    }

Patients are User objects. This is the same object type that is used for providers/staff members. Due to this, The User object has an immense amount of fields, many of which are not relevant for patients. With GraphQL, you select the exact fields you want returned, so you don't need to deal with any non-relevant data.

You can view the full list of available fields here.

If you have questions about how legal_name and gender/gender_identity/sex interact with each other, We recommend reading our general help articles here and here.

Creating a Patient

Patient accounts are created in three main ways.

Method Corresponding Mutation
Added by a provider/staff member createClient
Self-Sign Up signUp
Booking and/or buying completeCheckout

createClient Mutation

mutation createClient($first_name: String, 
                      $last_name: String, 
                      $email: String, 
                      $skipped_email: Boolean, 
                      $phone_number: String, 
                      $dietitian_id: String, 
                      $user_group_id: String, 
                      $dont_send_welcome: Boolean ) {
  createClient(input: { first_name: $first_name, 
                        last_name: $last_name, email: $email,
                        skipped_email: $skipped_email, 
                        phone_number: $phone_number, 
                        dietitian_id: $dietitian_id, 
                        user_group_id: $user_group_id, 
                        dont_send_welcome: $dont_send_welcome  }) {
    user {
      id
    }
    messages {
      field
      message
    }
  }
}

The createClient mutation is called from an authenticated provider/staff account.

You can view a full list of potential inputs here. We will go over some of them below.

Input Info
first_name Required
last_name Required
email Required if skipped_email is false
skipped_email Optional
phone_number Optional
dietitian_id ID of the provider. Defaults to the authenticated user's ID
user_group_id Optional
dont_send_welcome When true, an invite email will not be sent to the new client.

Patient signUp Mutation

mutation signUp($timezone: String, $first_name: String, $last_name: String, $email: String, $password: String, $phone_number: String, $invite_code: String, $provider_type: String, $role: String, $dietitian_id: String) {
  signUp(
    input: {
      first_name: $first_name,
      last_name: $last_name,
      email: $email,
      password: $password,
      phone_number: $phone_number,
      invite_code: $invite_code,
      role: $role,
      dietitian_id: $dietitian_id,
      timezone: $timezone,
       }
      ) {
      user {
          id
        }
      messages {
        field
        message
      }
    }
}

The signUp mutation is called unauthenticated.

You can view a full list of potential inputs here. We will go over the inputs relevant for new patients below.

Input Info
first_name Required
last_name Required
email Required
password Required. Needs to be a minimum of 8 chars, and meet a calculated password strength requirement.
role Required. Needs to be "patient"
phone_number Optional
dietitian_id ID of the provider. Either this or invite_code need to be provided. This can be provided along with a user group's invite code
invite_code Invite code for a specific provider or user group.
timezone Optional. Sets the timezone of the new patient's account

completeCheckout Mutation

The completeCheckout mutation is used for a patient to book an appointment, or make a payment. It creates a new patient if the following conditions are present

We will go over this mutation in more depth in the Scheduling and Payments sections.

Retrieving a Patient

query getUser($id: ID) {
  user(id: $id) {
  id
  first_name
  last_name
  dob
  gender
  email
  phone_number
  next_appt_date
  }
}

Retrieving a specific patient's information is done via the user query.

Input Info
id Either this needs to be provided, or or_current_user needs to be true.
clear_notifs When true, existing notifications from this patient will be marked as seen.
or_current_user When true, the authenticated user will be returned if id is invalid or not present.

Updating a Patient

Patient account information is updated in two main ways.

Method Corresponding Mutation
Updated by a provider/staff member updateClient
Updated in bulk by a provider/staff member bulkUpdateClients
Updated by the patient themselves updateUser

updateClient Mutation

mutation updateClient(
    $id: ID,
    $first_name: String,
    $last_name: String,
    $legal_name: String,
    $email: String,
  ) {
  updateClient(input:
    {
      id: $id,
      first_name: $first_name,
      last_name: $last_name,
      legal_name: $legal_name,
      email: $email,
    }) {
    user {
      id
      first_name
      last_name
      legal_name
      email
      }
    messages {
      field
      message
    }
  }
}

The updateClient mutation is called from an authenticated provider/staff account.

You can view a full list of potential inputs here.

We will go over some of them of the required and less clear ones below.

Input Info
id Required
resend_welcome When true, a new invite email will be sent to the patient.
send_form_request_reminder If this is ture, is_intake_item or requested_form_completion_id must be sent in as well
is_intake_item When sent with send_form_request_reminder, will send the patient an email reminding them to complete their intake forms.
requested_form_completion_id When sent with send_form_request_reminder, will send the patient an email reminding them to complete a specific form request.
for_changing_groups When true, the patient will be removed from their group. It is normally better to just send in a user_group_id of ""
password When passed in, this will change the password the patient uses to sign in. This is helpful if you want to set passwords for your patients.

The inputs that trigger emails/reminders (resend_welcome through requested_form_completion_id) take precendece over other demographic updates you are trying to make to the patient record. For example, if you send in a new first_name and also resend_welcome, the invite email will be sent, but the patient's name will not be updated.

bulkUpdateClients Mutation

mutation bulkUpdateClients($ids: [ID!], $active_status: Boolean) {
  bulkUpdateClients(input: { ids: $ids, active_status: $active_status } ) {
    users {
      id
    }
    messages {
      field
      message
    }
  }
}

The bulkUpdateClients mutation is called from an authenticated provider/staff account. It allows you to update multiple patients at the same time.

You can view a full list of potential inputs here.

We will go over some of them of the common ones below.

Input Info
ids Required
user_group_id When present, all specified patients will be moved to the specified user group.
active_status If user_group_id is not present, this field is required. All specified patients will have their status update.

This mutation can only bulk update one field at a time. If user_group_id and active_status are both passed in, active_status will be ignored.

Patient updateUser Mutation

mutation updateUser(
  $first_name: String
  $legal_name: String
  $last_name: String
  $email: String
  $id: ID
) {
  updateUser(
    input: {
      first_name: $first_name
      legal_name: $legal_name
      last_name: $last_name
      email: $email
      id: $id
    }
  ) {
    user {
      id
    }
    messages {
      field
      message
    }
  }
}

The updateUser mutation is called from an authenticated patient account or (more rarely) from an authenticated provider/staff account with access to the patient.

You can view a full list of potential inputs here.

We will go over some of them of the required and less clear ones below.

Input Info
current_email Email of the patient to update. Normally better to pass id in instead
id ID of the patient to update. If this is blank, the mutation will default to updating the authenticated user.

Deleting a Patient

Patients (and User objects in general) cannot be deleted via the Healthie API. Instead, you should archive the patient using the updateClient mutation mentioned above.

List All Patients

query users(
  $offset: Int,
  $keywords: String,
  $sort_by: String,
  $active_status: String,
  $group_id: String,
  $show_all_by_default: Boolean,
  $should_paginate: Boolean,
  $provider_id: String,
  $conversation_id: ID,
  $limited_to_provider: Boolean,
) {
  usersCount(
    keywords: $keywords,
    active_status:$active_status,
    group_id: $group_id,
    conversation_id: $conversation_id,
    provider_id: $provider_id,
    limited_to_provider: $limited_to_provider
  )
  users(
    offset: $offset,
    keywords: $keywords,
    sort_by: $sort_by,
    active_status: $active_status,
    group_id: $group_id,
    conversation_id: $conversation_id,
    show_all_by_default: $show_all_by_default,
    should_paginate: $should_paginate,
    provider_id: $provider_id,
    limited_to_provider: $limited_to_provider
  ) {
      id
  }
}

A list of patients can be retrieved via the users query. This query is called from an authenticated provider/staff account.

You can view a full list of potential arguments here.

We will go over some of them below.

Input Info
should_paginate Defaults to true. When false, the returned patient list is not paginated.
keywords Optional. A term to search patients by. Patients can be searched by first_name, last_name, email, legal_name, dob, and record_identifier.
sort_by Defaults to "last_name_desc". View the query here to see all potential options.
active_status Optional. Options are ["active", "archived"]. When passed in, only patients of the specified active status will be returned.
group_id Optional. When passed in, only patients in the specified User Group will be returned
show_all_by_default Defaults to true. When false, no patients will be returned unless keywords are also provided to the query.
provider_id Defaults to the ID of the authenticated user. When passed in, only patients the specified user has access to will be returned.
limited_to_provider Defaults to false. When true, only patients with the specified user as a provider will be returned.
conversation_id Optional. When passed in, returns patients in the specified conversation.

Create Location

mutation createLocation($user_id: String, $name: String, $line1: String, $zip: String, $city: String, $state: String, $npi: String) {
  createLocation(input: {user_id: $user_id, name: $name, line1: $line1, zip: $zip, city: $city, state: $state, npi: $npi }) {
    location {
      id
      name
      line1
      zip
    }
    messages {
      field
      message
    }
  }
}

You can use the creatLocation or updateLocation mutations to create update address information for patients.

The User Group Object

 # Some Example Fields

 "userGroup": {
      "id": "9900",
      "name": "Discovery Patients",
      "users_count": "7",
      "invite_code": "fbeb7f",
      "onboarding_flow_id": 11
    }

Patients can be organized into groups. Groups are a powerful feature in Healthie's API. You can trigger automations, automatically share content, restrict features, and more based on groups. You can read a full overview of the feature here.

Groups are UserGroup objects.

You can view the full list of available fields here.

Creating a User Group

mutation createGroup($name: String ) {
  createGroup(input: {name: $name }) {
    user_group {
      id
      name
    }
    messages {
      field
      message
    }
  }
}

User Groups are created via the createGroup mutation. The createGroup mutation is called from an authenticated provider/staff account.

You can view a full list of potential inputs here.

We will go over them below.

Input Info
name Required. The name of the group.

Retrieving a User Group

query getUserGroup($id: ID) {
  userGroup(id: $id) {
    id
    name
    users_count
    invite_code
  }
}

Retrieving information on a a specific group is done via the userGroup query.

Input Info
id Required.

Updating a User Group

mutation updateUserGroup($id: ID, 
                         $name: String, 
                         $onboarding_flow_id: ID) {
  updateUserGroup(input: { id: $id, 
                           name: $name, 
                           onboarding_flow_id: $onboarding_flow_id}) {
    user_group {
      id
      name
    }
    messages {
      field
      message
    }
  }
}

User Groups are updated via the updateUserGroup mutation. The updateUserGroup mutation is called from an authenticated provider/staff account.

You can view a full list of potential inputs here.

We will go over them below.

Input Info
id Required. The ID of the group that will be updated
name Optional.
onboarding_flow_id Optional. The ID of the Onboarding Flow that patients in the group will be asked to fill out.

Deleting a User Group

mutation deleteUserGroup($id: ID!, 
                         $group_to_receive_clients: ID) {
  deleteUserGroup(input: { id: $id, 
                           group_to_receive_clients: $group_to_receive_clients }) {
    user_group {
      id
    }
    messages {
      field
      message
    }
  }
}

User Groups are deleted via the deleteUserGroup mutation. The deleteUserGroup mutation is called from an authenticated provider/staff account.

You can view a full list of potential inputs here.

We will go over them below.

Input Info
id Required. The ID of the group to delete.
group_to_receive_clients Optional. The ID of a group to transfer all patients to that are currently assigned to the deleted group. When left blank, patients are not placed into any group.

List All User Groups

query userGroups($offset: Int, $keywords: String, $sort_by: String) {
  userGroupsCount(keywords: $keywords)
  userGroups(offset: $offset, keywords: $keywords, sort_by: $sort_by, should_paginate: true) {
    id
    name
    users_count
  }
}

A list of groups can be retrieved via the userGroups query. This query is called from an authenticated provider/staff account.

You can view a full list of potential arguments here.

We will go over some of them below.

Input Info
should_paginate Defaults to false. When true, the returned list of groups is paginated.
keywords Optional. A term to search groups by. Groups can be searched by name.
sort_by Defaults to "name". Options are ["name", "invite_code", "created_at_asc", "created_at_desc", "group_name_asc", "group_name_desc", "users_count_asc", "users_count_desc"]

Patient Self-Scheduling

 query appointmentTypes(
                          $clients_can_book: Boolean, 
                          $provider_id: String 
  ) {
      appointmentTypes(provider_id: $provider_id,
                       clients_can_book: $clients_can_book ) {
        id
        name
        length
        available_contact_types
        is_group
      }
  }

Client self-scheduling is a common use case for our API. With the API, you can build a self-scheduling experience with a much more customized UI than Healthie's built-in embeds allow for.

Our example repo is an example (built with React) on how to implement self-scheduling with the API. Let's break down the API calls we make in that widget.

1) Get Potential Appointment and Contact Types

All self-scheduled appointments in Healthie require an appointment type and a contact type. In this appointmentTypes query, we grab all appointment types that are bookable by clients, and the contact type options for each appointment type. We will use these in subsequent queries to receive accurate availability, and ultimately book the appointment. If you already know what appointment type and contact type you want the user to book, you can skip this step, and proceed to step 2.

2) Get Available Days

query daysAvailableForRange(
      $provider_id: String
      $date_from_month: String
      $org_level: Boolean
      $timezone: String
      $provider_ids: [String]
      $appt_type_id: String
    ) {
      daysAvailableForRange(
        provider_id: $provider_id
        date_from_month: $date_from_month
        org_level: $org_level
        timezone: $timezone
        provider_ids: $provider_ids
        appt_type_id: $appt_type_id
      )
    }

A very common self-scheduling UI component is to present the client with days that have open time. The client then selects a day to see the specific available time slots. This interface is used in Healthie's hosted widgets, in the open source example, and in other popular tools like Calendly. To power this, we can call the availableDaysForRange query.

This query example shows six arguments, but only three are required, provider_id, date_from_month, and appt_type_id.

appt_type_id is the ID of the appointment type you grabbed in step 1. provider_id is the ID of the provider (or just a provider in the organization in the case of searching availability across multiple providers), and date_from_month is a date (e.g "2021-10-10") in the month that you want to search available days in.

To briefly go over some of the other potential arguments, org_level determines if you want availability just for the given provider_id, or for multiple providers in the given provider's organization. It defaults to false.

If you send in org_level as true, you can also send in an array of provider_ids. This argument lets you search against a given subset of providers in the organization. For example, my organization has 10 providers, I can send in an array with five ids, and the query will only return availabilities for those five providers.

Finally, the timezone argument determines what timezone to calculate the availability in. It takes in a TZ database name. If you don't send this in, the query will use the provider's timezone.

This query will return an array of days with availability from the given month. e.g ["2021-10-10", "2021-10-15", "2021-10-20"]. We then have the user select a day, and move to step 3. If we already knew the appointment type, contact type, and date the user wanted, we could have skipped the first two steps.

 query availableSlotsForRange(
  $provider_id: String
  $start_date: String
  $end_date: String
  $org_level: Boolean
  $timezone: String
  $provider_ids: [String]
  $appt_type_id: String
) {
  availableSlotsForRange(
    provider_id: $provider_id
    start_date: $start_date
    end_date: $end_date
    timezone: $timezone
    org_level: $org_level
    provider_ids: $provider_ids
    appt_type_id: $appt_type_id
  ) {
    user_id
    date
  }
}

3) Get Available Slots

Now that we have the day, the next step is to get specific available time slots on that day. To do so, we use the availableSlotsForRange query. Six of the above arguments match daysAvailableForRange. In all cases, those arguments do the same thing, so let's focus on one, start_date and end_date.

start_date and end_date are both required and are used to set up the range of time that we check availability for. For example, if I send in a start_date of "2021-10-10", an end_date of "2021-10-12", and a timezone of America/New_York, the query will check availability starting at "2021-10-10 00:00:00 EDT -04:00" to "2021-10-12 23:59:59 EDT -04:00".

In our case, we are just checking availability on one day, so we would send in an identical start_date and end_date.

The query returns an array of PotentialAppointmentSlots. We have the user select one of those slots, and use the user_id and date from the slot in the next (and final) step. Please note the date here is a full datetime string (e.g "2021-10-10 10:00:00 EDT -04:00") and user_id is the ID of the available provider.

4) Book the Appointment


mutation completeCheckout(
    $appointment_type_id: String,
    $contact_type: String,
    $date: String,
    $first_name: String,
    $last_name: String,
    $email: String,
    $phone_number: String,
    $provider_id: String,
    $timezone: String,
  ) {
    completeCheckout(
      input: {
        appointment_type_id: $appointment_type_id,
        contact_type: $contact_type,
        date: $date,
        timezone: $timezone,
        first_name: $first_name,
        last_name: $last_name,
        email: $email,
        phone_number: $phone_number,
        provider_id: $provider_id,
      }
    ) {
      appointment {
        provider {
          id
          full_name
        }
        id
        date
        contact_type
        appointment_type {
          id
          name
          length
        }
      }
      messages {
        field
        message
      }
    }
  }

Now, we have the desired appointment type, contact type, appointment date/time, and provider. That means we have everything we need to schedule the appointment. To do so, we use the completeCheckout query.

The contact_type, date, timezone, appointment_type_id, and provider_id should match what we collected in the prior steps. If the client is authenticated, then those are the only fields you need. However, in our example, the client is unauthenticated, so we need to send in some info about them as well. That extra info (email, first_name, last_name, and phone_number) are used to either match the appointment to an existing client in the provider's account, or create a client record on the fly.

Patient Rescheduling

 query appointments(
  $filter: String, # "upcoming"
  $should_paginate: Boolean, # true
  $is_active: Boolean, # true
) {
  appointmentsCount(filter: $filter, is_active: $is_active)
  appointments(
    is_active: $is_active,
    filter: $filter,
    should_paginate: $should_paginate,
  ) {
      id
      date
      other_party_id
      appointment_type_id
  }
}

In addition to scheduling new appointments, The Healthie API can also be used to allow patients to reschedule already booked ones. A patient's ability to reschedule is controlled by your appointment settings. You can find more info on adjusting those here.

The rescheduling workflow is similar to self-scheduling, but has some key differences. Here are the API steps we use for patient rescheduling. All of the below steps are meant to be taken from an authenticated patient account.

1) Get The Appointment ID

To check availability and reschedule an appointment, you will need the ID for the appointment, the provider (other_party_id), and appointment type. Here's a query to get paginated upcoming active appointments for a patient. These IDs will be used in the subsequent steps.

2) Get Available Days

query daysAvailableForRange(
      $provider_id: String # Should be the other_party_id from Step 1
      $date_from_month: String 
      $timezone: String
      $appt_type_id: String # should be the appointment_type_id from Step 1
      $appointment_to_reschedule_id: ID # should be the ID of the appointment from Step 1
    ) {
      daysAvailableForRange(
        provider_id: $provider_id
        date_from_month: $date_from_month
        timezone: $timezone
        appt_type_id: $appt_type_id
        appointment_to_reschedule_id: $appointment_to_reschedule_id
      )
    }

In our rescheduling UI, we present the patient with days that have open time. The patient then selects a day to see the specific available time slots. To power this, we can call the availableDaysForRange query.

Input Info
provider_id Required. The provider whose availability should be checked. Needs to match the other_party_id of the appointment that is being rescheduled.
appt_type_id Required. The ID of the appointment's appointment type. Needs to match the appointment_type_id of the appointment that is being rescheduled.
date_from_month Required. A date (e.g "2021-11-20") from the month that you want to check available days in.
appointment_to_reschedule_id Required. Needs to match the id of the appointment that is being rescheduled.
timezone Optional. Determines what timezone to calculate the availability in. Defaults to the patient account's timezone.

This query will return an array of days with availability from the given month. e.g ["2021-11-20", "2021-11-28", "2021-11-29"]. We then have the patient select a day, and move to step 3.

 query availableSlotsForRange(
  $provider_id: String
  $start_date: String
  $end_date: String
  $timezone: String
  $appt_type_id: String
  $appointment_to_reschedule_id: ID
) {
  availableSlotsForRange(
    provider_id: $provider_id
    start_date: $start_date
    end_date: $end_date
    timezone: $timezone
    appt_type_id: $appt_type_id
    appointment_to_reschedule_id: $appointment_to_reschedule_id
  ) {
    user_id
    date
  }
}

3) Get Available Slots

Now that we have the day, the next step is to get specific available time slots on that day. To do so, we use the availableSlotsForRange query. The arguments are very similar to the daysAvailableForRange. In all cases, those arguments do the same thing, so let's focus on the one difference, start_date and end_date.

start_date and end_date are both required and are used to set up the range of time that we check availability for. For example, if I send in a start_date of "2021-10-10", an end_date of "2021-10-12", and a timezone of America/New_York, the query will check availability starting at "2021-10-10 00:00:00 EDT -04:00" to "2021-10-12 23:59:59 EDT -04:00".

In our case, we are just checking availability on one day, so we would send in an identical start_date and end_date.

The query returns an array of PotentialAppointmentSlots. We have the patient select one of those slots, and use the date from the slot in the next (and final) step. Please note the date here is a full datetime string (e.g "2021-10-10 10:00:00 EDT -04:00").

4) Update the Appointment


mutation updateAppointment(
    $client_updating: Boolean, # true
    $datetime: String,
    $id: ID,
  ) {
    updateAppointment(
      input: {
        $client_updating: Boolean,
        $datetime: String,
        $id: ID,
      }
    ) {
      appointment {
        id
        date
      }
      messages {
        field
        message
      }
    }
  }

Finally, we can use the updateAppointment mutation to move the appointment to the new available slot.

Input Info
id Required. The ID of the appointment to update.
datetime Required. This should be the date from the slot that was selected in the previous step.
client_updating Required. Needs to be true.

In the case that the slot is no longer available (e.g another patient simultaneously booked an appointment for the same slot), an error will be returned in the messages of the response.

Creating Appointments


mutation createAppointment(
  $user_id: String, # ID of patient in Healthie
  $appointment_type_id: String, # ID of appointment type in Healthie
  $contact_type: String, # e.g "Phone Call"
  $other_party_id: String, # ID of provider in Healthie. Defaults to authenticated user if left blank,
  $datetime: String, # Timestamp in YYYY-MM-DD HH:MM:SS or ISO8601 format, supercedes date, time params.

) {
  createAppointment(
    input: {
      user_id: $user_id, 
      appointment_type_id: $appointment_type_id,
      contact_type: $contact_type,
      other_party_id: $other_party_id,
      datetime: $datetime,
    }
  ) {
    appointment {
      id
    }
    messages {
      field
      message
    }
  }


There are two ways (via mutations) to book an appointment, createAppointment and completeCheckout.

completeCheckout books the appointment from the patient's perspective (and can be done unathenticated).

createAppointment books the appointment from a staff member's perspective, requires authentication, and allows for scheduling appointments outside of available slots. The code sample to the right is an example of a simple createAppointment mutation.

Creating a Filled Out Form (e.g a chart note, or intake form)

mutation createFormAnswerGroup(
  $finished: Boolean, # Should almost always true
  $custom_module_form_id: String, # ID of the custom_module_form (e.g "100")
  $user_id: String, # ID of the patient (e.g "61")
  $form_answers: [FormAnswerInput!]!, # e.g [{custom_module_id: "1", answer: "foo", user_id: "61"}, 
                      #     {custom_module_id: "2", answer: "bar", user_id: "61"}]
) {
  createFormAnswerGroup(
    input: {
      finished: $finished,
      custom_module_form_id: $custom_module_form_id,
      user_id: $user_id,
      form_answers: $form_answers,
    }
  ) {
    form_answer_group {
      id
    }
    messages {
      field
      message
    }
  }
}

In the Healthie API, a filled-out form is known as a FormAnswerGroup. The most important fields when creating a FormAnswerGroup are user_id, custom_module_form_id, finished, form_answers. Let's go over each one.

user_id is the ID of the patient who the form is on. When you create a FormAnswerGroup for a patient, that will be visible on the patient's chart.

custom_module_form_id is the ID of the template (known as a CustomModuleForm) that the FormAnswerGroup completes. All filled-out forms need to be backed by a CustomModuleForm. These are the templates that you can create, view, and edit in the Form Builder in Healthie.

finished is a Boolean value. This needs to be passed in as true for the form to be visible in the patient's chart and when you query for formAnswerGroups. If you do not pass in finished (or pass it in as false), you will need to query the formAnswerGroup by its ID.

form_answers is an array containing all the individual answers. Each submitted form answer needs to have a custom_module_id, user_id, and an answer. The user_id should match the user_id of the FormAnswerGroup. The custom_module_id should be the ID of the question (known as a CustomModule) that is being answered. The answer (as the name suggests) is the answer to the question.

Those fields are used in a createFormAnswerGroup mutation to create the filled-out form. Please see the right sidebar for an example.

Querying Filled Out Forms

query formAnswerGroups(
  $date: String, # e.g "2021-10-29"
  $custom_module_form_id: ID, # e.g "11"
  ) {
  formAnswerGroups(
    date: $date,
    custom_module_form_id: $custom_module_form_id,
    ) {
    id
    name
    created_at
    form_answers {
      label
      displayed_answer
      id
      custom_module {
        required
        id
        mod_type
        label
      }
    }
  }
}

To retrieve filled out forms, you can use the formAnswerGroups query. You can view all the argument options here but lets go over some of the most important ones below.

filler_id is the ID of the user who filled out the form. For example, you can use this argument to see all forms filled out by a given provider.

user_id is the ID of the patient whose chart the filled out form is in.

custom_module_form_id is the ID of the form template that was filled out. This can be helpful to use to see all filled out answers for a specific template (e.g you want to see all filled out "Health History" forms)

date refers to the date the form was filled out (or the date of service in the case of chart notes).

On the right, you can find an example of a simple query that returns filled out form data for a given form template and day.

Storing Metric Data

mutation createEntry (
  $metric_stat: String, # e.g "182"
  $category: String, # e.g "Weight"
  $type: String, # "MetricEntry"
  $user_id: String # e.g "61"
  $created_at: String, # e.g "2021-09-23 15:27:01 -0400"

 ) {
  createEntry (input:{
    category: $category,
    type: $type,
    metric_stat: $metric_stat,
    user_id: $user_id,
      created_at: $created_at,
  })
  {
    entry {
      id
    }
    messages
    {
      field
      message
    }
  }
}

Oftentimes, you may want to store and retrieve quantitative data (e.g weight, stress level, or A1C) on a patient. You would do this in Healthie using Entries, and more specifically, via a MetricEntry. The most important fields when creating an Entry are type, category, metric_stat, category, user_id, and created_at. Let's go over each one.

type specificies what type of Entry you are creating. We offer a (growing) range of entry types (e.g FoodEntry or WorkoutEntry). Since we storing a datapoint on the patient, the type would be MetricEntry.

category specifies what kind of metric we are storing. Healthie has built in metric categories like Weight or A1C that can be turned on or off. You can also add any metric category you'd like via our custom metrics feature. Managing these can be done via the API (by updating FeatureToggle objects), but you may just want to set this up in our UI. Here is information about managing these in our UI

metric_stat is the actual data value for the metric. e.g if a patient weights 182 lbs, you would pass in 182 with a category of Weight.

user_id is the ID of the patient that this entry should be attached to.

created_at is a timestamp that specifies when the data is from. You can backdate data, and forward-date data (up to 3 months in the future). If you don't pass this in, the entry will be created with a timestamp of the current time.

Those fields are used in a createEntry mutation to create the metric entry. Please see the right sidebar for an example.

Using the Entries Query for Metric Data

query entries(
  $category: String # e.g "Weight"
  $type: String # "MetricEntry"
) {
  entries(
    category: $category
    type: $type
  ) {
    id
  }
}

To retrieve metric data, you can use the entries query. The query is paginated, with a page size of 10. To see the total amount of metric data, you can use the entriesCount query.
You can view all the argument options here but lets go over some of the most important ones below.

First, since we only want metric data (and not other type of entry data like food), we will pass in MetricEntry to the type argument.

The category argument lets us specifiy what category of metric we want data for (e.g just get back "Weight" or "Blood Pressure"). If left blank, the query will return all categories.

If you are authenticating as a patient, then client_id, is_org, and group_id will have no effect on the query. If you are authenticating as a provider/staff member, you use client_id, is_org, and group_id to specifiy which client's entries you want returned. If none of these fields are passed in, the query will return entries posted by clients who have the authenticated user as their primary provider or care team member. If is_org is passed in as true, the query will instead return entries for all clients in the organization that the authenticated user can access. client_id will return entries for the specified client. group_id will return entries for clients in the specified user group.

On the right, you can find an example of a simple query that returns metric data for the authenticated user's patients.

Storing a Credit Card

mutation createStripeCustomerDetail(
  $token: String # e.g tok_1JjpBZ2eZvKYlo2CdI1Qwgf7
  $card_type_label: String # e.g 'personal'
  $user_id: ID # e.g "61"
  $is_default: Boolean # e.g true
) {
  createStripeCustomerDetail(
    input: {
      token: $token
      card_type_label: $card_type_label
      user_id: $user_id
      is_default: $is_default
    }
  ) {
    stripe_customer_detail {
      id
    }
    messages {
      field
      message
    }
  }
}

Healthie (via our usage of Stripe) allows you to store a patient's credit/debit cards on file. You can store multiple cards on file for the same patient. There are two main ways to store a card. The first way happens automatically when a patient makes a payment (see "Charging a Patient" below). The second is via the createStripeCustomerDetail mutation. That mutation takes in 4 input fields - card_type_label, is_default, token, and user_id. Let's go over each one.

user_id is the ID of the patient that the card should be attached to. This is required.

token is the tokenized card information. You generate this via Stripe's front-end libraries and can find info on how to do so here. This is required.

card_type_label lets you store the type of card. Options are personal, hsa, and fsa. Defaults to personal if nothing is passed in. The card_type_label field is just for your own informational purposes, and does not affect how payments are processed.

is_default sets the default payment source for the patient. If the patient only has one stored payment source, that payment source is automatically the default, so this field only matters for patients with multiple payment sources.

Creating an Invoice

mutation createRequestedPayment(
  $recipient_id: ID, # e.g "61"
  $offering_id: ID, # e.g "11"
  $price: String,  # can be left blank since it will default to the price of the package
  $invoice_type: String, # "offering"
  ) {
  createRequestedPayment(input: {
    recipient_id: $recipient_id,
    offering_id: $offering_id,
    price: $price,
    invoice_type: $invoice_type,
    })
    {
    requestedPayment {
      id
    }
    messages {
      field
      message
    }
  }
}

Invoices (called RequestedPayment in our API) allow you to request and track owed payments from patients. Patients can be invoiced for a package (offering in our API), a CMS1500, or for other custom products/services. Invoices can be sent to third-party payers even if they don't have a Healthie account (helpful in cases like corporate wellness), but are most commonly sent directly to the patient. In this example, we'll cover how to send an invoice to a patient.

Invoices are created via the createRequestedPayment mutation. Let's break down the inputs that mutation accepts.

recipient_id is the ID of the patient. This is required unless are you invoicing a third-party (in which case you'd send in requested_payer).

sender_id is the ID of the staff member that the invoice is associated with. This defaults to the current user if not sent in.

offering_id is the ID of the package that the invoice is associated with. This is only used if you are invoicing for a package.

cms1500_id is the ID of the CMS1500 that the invoice is associated with. This is only used if you are invoicing for a CMS1500.

price is the amount you are invoicing the patient for. If you are invoicing for a package, this field is optional (and the invoice will default to the package price if this is left out)

notes is optional and lets you add extra details to the invoice. These notes are visible to the patient.

invoice_type is required. The options are cms1500, offering, and other

service_date is optional, and can only be used the invoice_type is other.

services_provided is required if invoice_type is other. Otherwise, it should not be sent in. This lets you describe the products/services that you are invoicing the patient for.

status is optional (and is normally not sent in). status lets you explicitly set the status of the invoice. The options are Paid, Not Yet Paid, and Partial. Normally it is best to let Healthie automatically determine the status based on payments applied to the invoice.

is_preview defaults to false. When passed in as true, the invoice is kept in a preview state and hidden from the Healthie UI.

user_package_selection_id is only used if you are invoicing a patient for an existing UserPackageSelection.

So in sum, you'd normally send in a recipient_id, invoice_type, price (unless it is for a package and you want to default to the package price), and what you are invoicing for (either a offering_id, cms1500_id, or services_provided). The example on the sidebar shows creating an invoice for a package.

Charging a Patient

mutation createBillingItem(
  $amount_paid: String, # e.g "567.53"
  $sender_id: ID, # e.g "61"
  $requested_payment_id: ID, # e.g 11
  $stripe_idempotency_key: String, # Stripe reccommends using V4 UUIDs 
  $stripe_customer_detail_id: ID # e.g 21
  $should_charge: Boolean, # true
  ) {
  createBillingItem(input: {
     amount_paid: $amount_paid,
     sender_id: $sender_id,
     should_charge: $should_charge,
     stripe_customer_detail_id: $stripe_customer_detail_id,
     requested_payment_id: $requested_payment_id,
     stripe_idempotency_key: $stripe_idempotency_key,
     }) {
     billingItem {
      id
    }
    messages {
      field
      message
    }
  }
}

Patients make payments in two ways. You can charge a patient from a provider/staff account (via the createBillingItem mutation) or patients can make payments themselves (via completeCheckout mutation). In this example, we will cover how to charge a patient using createBillingItem. Only patients with a stored payment source can be charged using createBillingItem. Take a look at "Storing a Credit Card" above if you are not yet storing payment sources.

Let's break down the inputs createBillingItem accepts.

created_at is used when tracking outside payments, and should be left out when actually charging a card.

amount_paid is the amount to charging the patient. If left blank, the patient will be charged the full amount of the package or invoice.

note is used to provide info about outside payments and can be left out.

sender_id is the ID of the patient to charge. This is required.

recipient_id is the ID of the provider/staff member who should be listed as the recipient of the payment. This is primarily important in sitatuons where each provider has their own bank account. If left out, it defaults to the sender's primary provider.

payment_medium is just used for outside payments and can be left out.

offering_coupon_id is the ID of the promo code you want to apply to the payment. The promo code discount is applied to the initial payment amount. (e.g if you want to apply a 30% promo code, and have the client end up paying $700, you would pass in amount_paid of 1000). Optional

offering_id is the ID of the package that the patient is paying for. If you are charging the patient for a package via an invoice connected to the package, you should send in the requested_payment_id instead.

should_charge determine whether the charge actually runs through our payment processor. This should be passed in as true if you want to charge the card.

stripe_customer_detail_id is the ID of the StripeCustomerDetail (e.g the payment source) that the patient is using to pay. If left out, the patient is charged on their default payment source.

requested_payment_id is the ID of the invoice that the patient is paying for. You can have the patient make multiple payments that apply to the same invoice. To do so, just include the same requested_payment_id in each charge.

user_package_selection_id is only used if you are charging a patient for an existing UserPackageSelection.

stripe_idempotency_key is a key generated on the front-end that is used by Stripe to prevent duplicate requests. Find more info about them here. This is optional, but recommended.

Sometimes the charge will be declined by the card issuer. The decline reason is included in the messages you can get back from the mutation. For more information on specific decline reasons, please view Stripe's list

Chat

Chat is a very commonly-feature in both our own app and customers' API integrations. Here is info on the feature in general. Within the API, a "Chat" is known as a Conversation. A conversation has multiple ConversationMemberships which determine the users in the conversation. Conversations are created in two main ways. The first is automatically when a patient is assigned to a provider (either as their primary provider or part of their care team). When this happens, our system automatically creates a two-person conversation between the patient and the provider (if one does not exist already). The second is manually. Users have no limits on the number of conversations they can be in, and the same patient and provider can have multiple conversations together.

mutation createConversation(
  $simple_added_users: String # e.g "user-1,group-2,user-3"
  $owner_id: ID # e.g "4"
  $name: String # e.g "Questions for Next Appointment"
) {
  createConversation(
    input: {
      simple_added_users: $simple_added_users
      owner_id: $owner_id
      name: $name
    }
  ) {
    conversation {
      id
    }
    messages {
      field
      message
    }
  }
}

In the API, Conversations are created via the createConversation mutation, which needs to be called from an authenticated staff/provider account. The most important fields when creating a Conversation are owner_id, simple_added_users, and name. Let's go over each one.

owner_id specifies the staff member who is the "Owner" of the conversation. The owner is automatically added to the conversation, and has full ability to add and remove patients and staff from the conversation. If left blank, this defaults to the current user.

simple_added_users is a comma-seperated string of which patients, staff, and user groups should be added to the conversation. This comma seperated string uses the doc_share_id (which is a field on UserType and UserGroupType). For example, it could look like user-1,group-2,user-3.

name is optional, and lets you set a subject title for the conversation. If left out, the conversation name default to the users and groups in the conversation.

Syncing with Google/Outlook

Healthie supports a 2-way calendar sync with Google Calendar and Outlook, as well as a 1-way ICS calendar feed (to be used with iCal).

You can read information about the capabilities here

To enable Google/Outlook sync, the provider will need to authorize the sync via OAuth. By default, Healthie has its own verified OAuth apps with both Google and Outlook. Due to the nature of OAuth, utilizing those will ask the provider if they want to allow "Healthie" access to their calendar data, and redirect them back to a Healthie-controlled URL.

Here are the scopes that our OAuth apps request

Service Scopes
Google Calendar "email", "calendar", "userinfo.profile"
Outlook 'openid', 'email', 'offline_access', 'https://graph.microsoft.com/Calendars.ReadWrite'

If you want to brand the OAuth connection (or have more direct control), you have a couple of options. Both involve your company creating and maintaing OAuth apps with Google Calendar and/or Outlook. You create the application, and provide Healthie with a Client ID and Secret for your apps.

For either option, our support team needs to make configuration changes to your account. Please reach out to us if you'd like to do either of the options.

1) Healthie-controlled redirect flow (Full Web Whitelabel Required)

If you have a full web whitelabel, Healthie can replace the links to our authorized OAuth apps with yours. Your users will be able to follow the same steps mentioned in the help article above to connect. However, they will be taken to authorize your OAuth app, and then be redirected back to your whitelabeled URL after. Healthie maintains the sync from there, and no development work is needed on your end.

2) Via the API (You handle the redirect/authorization flow)

If you do not have a full web whitelabel, or want to handle the connection without sending users to the (branded or otherwise) Healthie platform, you can use the API.

You handle the OAuth authorization flow and redirection yourself, fully outside of Healthie. Once you have the user's authorize and refresh tokens, you can then make an API call to Healthie to store their calendar sync information. We then maintain the sync from there.