Integration Documentation
API Client
packages/api-client/src/endpoints/createChallengeSubscription.ts
import { request } from './util'
import { Challenge, ChallengeSubscription } from '../models'
export interface CreateChallengeSubscriptionBody {
challengeSubscription: {
challengeId: Challenge['id']
startTomorrow: boolean
}
}
export type CreateChallengeSubscriptionResponse =
| { success: false; message: string }
| { success: true; challengeSubscription: ChallengeSubscription }
/**
* Starts a user on a new challenge, creating a challenge subscription.
*
* @param userId The identifier assigned by Diet ID for the user.
* @param challengeId The identifier assigned by Diet ID for the challenge.
* @param startTomorrow (optional) Whether the user should start the challenge tomorrow instead of today (default: false).
*
* @returns The challenge subscription (join record between user and challenge).
*/
export const createChallengeSubscription = async (
challengeId: CreateChallengeSubscriptionBody['challengeSubscription']['challengeId'],
startTomorrow: CreateChallengeSubscriptionBody['challengeSubscription']['startTomorrow'] = false
) =>
request<CreateChallengeSubscriptionResponse>('POST', '/api/v1/challenge_subscriptions', {
challengeSubscription: { challengeId, startTomorrow }
})
packages/api-client/src/endpoints/createCheckin.ts
import { request } from './util'
import { ChallengeSubscription, Checkin } from '../models'
export interface CreateCheckinBody {
checkin: {
challengeSubscriptionId: ChallengeSubscription['id']
successful: boolean
forYesterday: boolean
metadata?: object
}
}
export type CreateCheckinResponse = { success: false; message: string } | { success: true; checkin: Checkin }
/**
* Check in a user for a particular challenge.
*
* @param challengeSubscriptionId The id of the challenge subscription the user is checking in for.
* @param successful Whether the user indicated success or failure in the checkin.
* @param forYesterday (optional) Whether the checkin is for yesterday instead of today (default: false).
* @param metadata (optional) Arbitrary additional data you'd like to attach to the checkin.
*
* @returns The checkin record.
*/
export const createCheckin = (
challengeSubscriptionId: CreateCheckinBody['checkin']['challengeSubscriptionId'],
successful: CreateCheckinBody['checkin']['successful'],
forYesterday: CreateCheckinBody['checkin']['forYesterday'] = false,
metadata?: CreateCheckinBody['checkin']['metadata']
) =>
request<CreateCheckinResponse>('POST', '/api/v1/checkins', {
checkin: {
challengeSubscriptionId,
successful,
forYesterday,
metadata
}
})
packages/api-client/src/endpoints/createSSO.ts (/api/v3)
import { request } from './util'
import { CreateUserBody } from './createUser'
export interface CreateSSOBody {
user: {
/**
* The user's unique identifier within a partner system (assigned by you).
*/
partnerUserId: User['partnerUserId']
/**
* The user's email address.
*/
email?: string
/**
* The user's first name.
*/
firstName?: string
/**
* The user's last name.
*/
lastName?: string
/**
* The user's desired username (optional).
*/
desiredUsername?: string
/**
* The user's gender (optional).
- female
- male
*/
gender?: enum
/**
* The user's postal code (optional).
*/
postalCode?: string
/**
* The user's date of birth formatted as YYYY-MM-DD (optional).
- 1999-11-12
*/
DOB?: string
/**
* The user's age in years (optional).
*/
age?: integer
/**
* The user's height in inches (optional).
*/
height?: integer
/**
* The user's weight in lbs (optional).
*/
weight?: integer
/**
* The user's activity level (optional).
*/
activityLevel?: enum:ActivityLevel
/**
* The user's pre-defined health goals (optional).
*/
healthGoals?: array:HealthGoal
/**
* The user's initial patient alert (optional).
*/
patientAlert?: string
/**
* The user's initial patient alert (optional).
*/
parentalConsentGiven?: bool
/**
* Any addditional user params (optional).
*/
additionalParams?: string - JSON blob
}
export type CreateSSOResponse = { success: false; message: string } | { success: true; url: string }
/**
* Returns a url the frontend can use to embed a Diet ID assessment in an iframe, webview, etc.
* The user is automatically signed in and prompted to complete the assessment.
*
* @param user The fields for finding or creating a Diet ID user (id required; email, firstName, and lastName optional)
* @returns A url that signs in a user and prompts them to complete an assessment.
*
*/
export const createSSO = (user: CreateSSOBody['user']) => request<CreateSSOResponse>('POST', '/api/v3/sso', { user })
packages/api-client/src/endpoints/createUser.ts
import { request } from './util'
import { User } from '../models'
export interface CreateUserBody {
user: {
/**
* The user's unique identifier within a partner system (assigned by you).
*/
partnerUserId: User['partnerUserId']
/**
* The user's email address.
*/
email?: string
/**
* The user's first name.
*/
firstName?: string
/**
* The user's last name.
*/
lastName?: string
}
}
export type CreateUserResponse = { success: false; message: string } | { success: true; user: User }
/**
* Creates a corresponding Diet ID user for a local user.
*
* @param user The required fields for creating a Diet ID user (id, email, first name, last name)
*
* @returns The newly created Diet ID user.
*/
export const createUser = async (user: CreateUserBody['user']) =>
request<CreateUserResponse>('POST', '/api/v1/user', { user })
packages/api-client/src/endpoints/getCurrentUser.ts
import { request } from './util'
import { User } from '../models'
export type GetCurrentUserResponse = { success: false; message: string } | { success: true; user: User | null }
/**
* Returns the current Diet ID user.
*
* @returns The Diet ID user record.
*/
export const getCurrentUser = async () => request<GetCurrentUserResponse>('GET', '/api/v1/user')
packages/api-client/src/endpoints/getWorkflow.ts
import { request } from './util'
import { Workflow } from '../models'
export interface GetWorkflowParams {
workflowId: Workflow['id']
}
export type GetWorkflowResponse =
| {
success: false
message: string
}
| { success: true; workflow: Workflow }
/**
* Returns the data collected and results from an assessment (workflow).
*
* @param workflowId The identifier assigned by Diet ID for the workflow.
*
* @returns The workflow with the id.
*/
export const getWorkflow = (workflowId: GetWorkflowParams['workflowId']) =>
request<GetWorkflowResponse>('GET', `/api/v1/workflows/${workflowId}`)
packages/api-client/src/endpoints/index.ts
export * from './createChallengeSubscription'
export * from './createCheckin'
export * from './createSSO'
export * from './createUser'
export * from './getCurrentUser'
export * from './getWorkflow'
export * from './util'
packages/api-client/src/endpoints/util.ts
let gateway = 'http://localhost:3500'
/**
* Sets the url to the API Gateway all requests should route through.
*
* @param url The url to your API Gateway. e.g., https://yourapiserver.com/pathtodietidgateway
*/
export const setGateway = (url: string) => {
gateway = url
}
/**
* Possible HTTP verbs within Diet ID's API.
*/
type RequestMethod = 'GET' | 'POST'
/**
* Utility method for sending API requests to Diet ID.
*
* @param method The HTTP verb for the request ("GET", "POST", etc.)
* @param endpoint The url slug for a specific portion of the API
* @param body Optional data to be sent with POST and PUT requests
*
* @returns The JSON response from Diet ID for the API request.
*/
export const request = async <T>(method: RequestMethod, endpoint: string, body?: object): Promise<T> => {
if (!gateway) {
throw new Error("Please configure your API Gateway before making an API request (with setGateway('...'))")
}
const url = `${gateway}${endpoint}`
const headers = { Accepts: 'application/json', 'Content-Type': 'application/json' }
const data = JSON.stringify(body)
return (await fetch(url, { headers, method, body: data })).json()
}
packages/api-client/src/models/Challenge.ts
import { Opaque } from '../types'
export interface Challenge {
id: Opaque<number, 'Challenge'>
/**
* The name of the challenge.
*/
name: string
}
packages/api-client/src/models/ChallengeSubscription.ts
import { ChallengeSubscriptionState, Opaque } from '../types'
import { Challenge } from './Challenge'
import { Checkin } from './Checkin'
import { Tip } from './Tip'
import { User } from './User'
export interface ChallengeSubscription {
id: Opaque<number, 'ChallengeSubscription'>
/**
* The user the challenge subscription belongs to.
*/
userId: User['id']
/**
* The challenge the challenge subscription belongs to.
*/
challengeId: Challenge['id']
/**
* @todo Add description
*/
day: any
/**
* A helpful tip for the user to complete the challenge.
*/
tip: Tip | null
/**
* The number of times the user should check in throughout the day.
* For example, a "drink 8 glasses of water" challenge may be 8.
*/
checkinsPerDay: number
/**
* The challenge subscription's state. Valid transitions are:
*
* StartingTomorrow -> Active
*
* Active -> MissedYesterday | Failed | Succeeded
*
* Failed -> StartingTomorrow | Active
*/
state: ChallengeSubscriptionState
/**
* Past checkins the user has made for the challenge subscription.
*/
checkins: Checkin[]
}
packages/api-client/src/models/Checkin.ts
import { Opaque } from '../types'
import { ChallengeSubscription } from './ChallengeSubscription'
export interface Checkin {
id: Opaque<number, 'Checkin'>
/**
* The challenge subscription this checkin is for.
*/
challengeSubscriptionId: ChallengeSubscription['id']
/**
* Arbitrary additional data passed to Diet ID when the user checked in.
*/
data: object | null
}
packages/api-client/src/models/Diet.ts
import { Opaque } from '../types'
export interface Diet {
id: Opaque<number, 'Diet'>
/**
* Human-readable label for the diet type (e.g., "American").
*/
name: string
/**
* Unique code for the diet type (e.g., "AME")
*/
code: string
/**
* The quality of the diet on a scale of 1 to 10 (e.g., 5).
*/
quality: number
/**
* Human-readable label for the diet type and quality (e.g., "American quality 7 (7 out of 10)")
*/
longName: string
/**
* A short sentence describing the diet (e.g., "American Quality 5 (Q5) is characterized by fresh and processed meat, ...")
*/
description: string
/**
* The Healthy Eating Index (HEI) of the diet.
*/
hei: number
/**
* A photo of the diet's typical food and drink.
*/
fingerprintPhotoUrl: string
/**
* @todo Add description
*/
foodGroupValues: { [foodGroupId: string]: number }
/**
* Url to a PDF of a mealplan
*/
mealPlanUrl: string
}
packages/api-client/src/models/NutritionInfo.ts
import { Opaque } from '../types'
import { NutrientUnit } from '../types'
export interface NutritionInfo {
/**
* Unique identifier (e.g., "added-sugars-by-total-sugars")
*/
key: Opaque<string, 'NutritionInfo'>
/**
* Quantitative value (e.g., 60.06351427805127)
*/
value: number
/**
* Value's unit of measurement (e.g., "g")
*/
unit: NutrientUnit
/**
* Human-readable label for display of the value and unit (e.g., "Added Sugars")
*/
label: string
/**
* @todo Add description
*/
isDefault: boolean
}
packages/api-client/src/models/Tip.ts
import { Opaque } from '../types'
export interface Tip {
id: Opaque<number, 'Tip'>
/**
* The content of the tip.
*/
content: string
}
packages/api-client/src/models/User.ts
import { Opaque } from '../types'
import { Challenge } from './Challenge'
import { ChallengeSubscription } from './ChallengeSubscription'
import { Workflow } from './Workflow'
/**
* A user identifier provided by you, the partner
*/
export type PartnerUserID = Opaque<string, 'PartnerUserID'>
export interface User {
/**
* The user's unique identifier within Diet ID's system (assigned by Diet ID).
*/
id: Opaque<number, 'User'>
/**
* The user's unique identifier within a partner system (assigned by you).
*/
partnerUserId: PartnerUserID | null
/**
* The record of the assessment the user took to determine their ID diet.
*/
dietIdWorkflowId: Workflow['id'] | null
/**
* The record of the assessment the user took to determine their Ideal diet.
*/
dietIdealWorkflowId: Workflow['id'] | null
/**
* The challenge the user is currently participating in.
*/
challenge: Challenge | null
/**
* The join record between the user and their current challenge, which contains additional info specific to that user.
*/
challengeSubscription: ChallengeSubscription | null
}
packages/api-client/src/models/Workflow.ts
import {
ActivityLevel,
DietRestriction,
Gender,
HealthGoal,
Opaque,
ScreenerResponse,
StyleScreenerResponse,
WeightGoal,
WeightTrend
} from '../types'
import { Challenge } from './Challenge'
import { Diet } from './Diet'
import { NutritionInfo } from './NutritionInfo'
import { User } from './User'
/**
* A workflow identifier provided by you, the partner
*/
export type PartnerWorkflowID = Opaque<string, 'PartnerWorkflowID'>
export interface Workflow {
id: Opaque<number, 'Workflow'>
/**
* The workflow's unique identifier within a partner system (assigned by you).
*/
partnerWorkflowId: PartnerWorkflowID | null
/**
* The user the workflow belongs to.
*/
userId: User['id']
/**
* A private url to view the workflow results.
*/
shareUrl: string
/**
* Basic demographic and lifestyle info
*/
userInfo?: {
gender: Gender
ageInYears: number
weightInPounds: number
heightInInches: number
weightTrend: WeightTrend
activityLevel: ActivityLevel
}
/**
* Diet screeners describe what a user currently does and does not eat.
* Note: Many of these will be null since we ask the user one screener question
* at a time and often skip a number of them based on branching logic.
*/
dietScreener: {
meat: ScreenerResponse
poultry: ScreenerResponse
fish: ScreenerResponse
grains: ScreenerResponse
dairy: ScreenerResponse
style: StyleScreenerResponse
}
/**
* Diet restrictions are what a user cannot eat due to allergies, religious observance, etc.
*/
dietRestrictions: DietRestriction[]
/**
* Ideal goals are selected by the user to help determine what ideal diets are optimal for their needs.
*/
idealGoals: {
/**
* Health goals are specific things the user wants to accomplish with their change in diet.
*/
forHealth: HealthGoal[]
/**
* A weight goal qualifies the "ManageWeight" health goal (whether to lose, maintain, or gain weight)
*/
weightGoal: WeightGoal | null
/**
* The goal diet is a diet the user specifically wants to move towards apart from any health goals.
*/
goalDiet: Diet['code'] | null
}
/**
* The user's ID diet as determined by the assessment.
*/
dietId: Diet | null
/**
* The user's Ideal diet as determined by the assessment.
*/
dietIdeal: Diet | null
/**
* Nutrition info for the user's ID diet.
* This is workflow-specific data, with baseline diet nutrients adjusted for height, weight, etc.
*/
dietIdNutritionInfo: NutritionInfo[] | null
/**
* Nutrition info for the user's Ideal diet.
* This is workflow-specific data, with baseline diet nutrients adjusted for height, weight, etc.
*/
dietIdealNutritionInfo: NutritionInfo[] | null
/**
* The sequence of challenges a user is pre-determined to complete based on their ID and Ideal diets.
*/
challengeIds?: Challenge['id'][]
/**
* Whether the workflow has been completed or is a work in progress.
*/
completed: boolean
/**
* The UTC datetime when the workflow was started.
*/
createdAt: Date
}
packages/api-client/src/models/index.ts
export * from './Challenge'
export * from './ChallengeSubscription'
export * from './Checkin'
export * from './Diet'
export * from './NutritionInfo'
export * from './Tip'
export * from './User'
export * from './Workflow'
packages/api-client/src/webhooks/Workflow.ts
import { User, Workflow } from '../models'
export interface WorkflowWebhook {
/**
* The user who completed the workflow
*/
user: User
/**
* The workflow that was completed
*/
workflow: Workflow
}
packages/api-client/src/webhooks/index.ts
export * from './Workflow'
packages/api-client/src/fixtures.ts
import * as Models from './models'
// necessary to efficiently avoid "Block-scoped variable used before its declaraton" ts(2448) errors on foreign keys
const id = {
challenge: 1 as Models.Challenge['id'],
challengeSubscription: 2 as Models.ChallengeSubscription['id'],
checkin: 1 as Models.Checkin['id'],
dietId: 76 as Models.Diet['id'],
dietIdeal: 29 as Models.Diet['id'],
tip: 1 as Models.Tip['id'],
user: 575 as Models.User['id'],
workflowWithoutPHI: 1928 as Models.Workflow['id'],
workflowWithPHI: 2048 as Models.Workflow['id']
}
export const challenge: Models.Challenge = {
id: id.challenge,
name: 'Avoid Soda'
}
export const challengeSubscription: Models.ChallengeSubscription = {
id: id.challengeSubscription,
userId: id.user,
challengeId: id.challenge,
day: null,
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-use-before-define
tip: tip,
checkinsPerDay: 1,
state: 'Active',
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-use-before-define
checkins: [checkin]
}
export const checkin = {
id: id.checkin,
challengeSubscriptionId: id.challengeSubscription,
data: { someData: 'youPassedIn' }
}
export const dietId: Models.Diet = {
id: id.dietId,
name: 'Mediterranean',
code: 'MED',
quality: 6,
longName: 'Mediterranean quality 6 (6 out of 10)',
description:
'This diet pattern includes lean meats, poultry, eggs and dairy, baked/grilled seafood, vegetables and fruits, some beans and nuts, mainly refined and some whole grains, some processed foods, olives, olive oil, and red wine in moderation (optional).',
hei: 59,
fingerprintPhotoUrl:
'https://dqpn.imgix.net/assets/diet-images/76_MED/6/fingerprint_photo_CldNusqDTzpZeHFhShWrOHXzdEjnBi.png?w=960&auto=format,compression&txtalign=bottom%2Cright&txtclr=c1cdbd&txt=%C2%A9%202020%20Diet%20ID%20Inc&txtsize=36&txtfont=Arial%2CBoldMT&txtpad=15&txtalign=center',
mealPlanUrl: 'http://dietid.com',
foodGroupValues: {
FRU0100: 0.03133333333333333,
FRU0200: 0.0,
FRU0300: 0.0,
FRU0400: 0.12
}
}
export const dietIdeal: Models.Diet = {
id: id.dietIdeal,
name: 'Flexitarian',
code: 'FLX',
quality: 9,
longName: 'Flexitarian quality 9 (9 out of 10)',
description:
'This diet pattern includes a variety of fresh vegetables and fruits, predominantly whole grains, beans, nuts and seeds, eggs, limited amounts of high-quality poultry and/or fish, and non-fat dairy products and plant-based dairy alternatives.',
hei: 92,
fingerprintPhotoUrl:
'https://dqpn.imgix.net/assets/diet-images/29_FLX/9/fingerprint_photo_AbZPPZtzJVVIpfSPrLUBiYHPdoteBM.png?w=960&auto=format,compression&txtalign=bottom%2Cright&txtclr=c1cdbd&txt=%C2%A9%202020%20Diet%20ID%20Inc&txtsize=36&txtfont=Arial%2CBoldMT&txtpad=15&txtalign=center',
mealPlanUrl: 'http://dietid.com',
foodGroupValues: {
FRU0100: 0.142,
FRU0200: 0.0,
FRU0300: 0.7240000000000001,
FRU0400: 3.2693333333333334
}
}
export const nutritionInfo: Models.NutritionInfo = {
key: 'added-sugars-by-total-sugars' as Models.NutritionInfo['key'],
value: 60.06351427805127,
unit: 'g',
label: 'Added Sugars',
isDefault: false
}
export const tip = {
id: id.tip,
content: 'Some tip'
}
export const user = {
id: id.user,
partnerUserId: '71' as Models.PartnerUserID,
dietIdWorkflowId: id.workflowWithPHI,
dietIdealWorkflowId: id.workflowWithPHI,
challenge: challenge,
challengeSubscription: challengeSubscription
}
export const workflowWithoutPHI: Models.Workflow = {
id: id.workflowWithoutPHI,
partnerWorkflowId: '86' as Models.PartnerWorkflowID,
userId: id.user,
dietScreener: {
meat: 'Yes',
poultry: null,
fish: null,
grains: 'Yes',
dairy: null,
style: null
},
dietRestrictions: ['DairyFree', 'Organic', 'PeanutFree'],
idealGoals: {
forHealth: ['ManageWeight'],
weightGoal: 'Lose',
goalDiet: 'FLX' as Models.Diet['code']
},
dietId: dietId,
dietIdeal: dietIdeal,
dietIdNutritionInfo: [nutritionInfo],
dietIdealNutritionInfo: [nutritionInfo],
challengeIds: [id.challenge],
completed: true,
createdAt: new Date()
}
export const workflowWithPHI: Models.Workflow = {
...workflowWithoutPHI,
id: id.workflowWithPHI,
partnerWorkflowId: '85' as Models.PartnerWorkflowID,
userInfo: {
gender: 'Female',
ageInYears: 20,
weightInPounds: 185,
heightInInches: 71,
weightTrend: 'Rising',
activityLevel: 'HighlyActive'
}
}
packages/api-client/src/index.ts
import * as fixtures from './fixtures'
export * from './endpoints'
export * from './models'
export * from './webhooks'
export { fixtures }
packages/api-client/src/types.ts
import { Opaque as _Opaque } from 'type-fest'
export type Opaque<Type, Token = unknown> = _Opaque<Type, Token>
/**
* Screener questions that are not shown to the user are sent as null.
*/
export type ScreenerResponse = 'Yes' | 'No' | 'Sometimes' | null
/**
* If during the screener the user indicates that they consume meat and grains
* then they are also asked if they follow a particular style of diet. Otherwise this value is null.
* The string type is only necessary for when the user specifies a style other than what's listed.
* Free text responses to "Other ethnic style" and "Other pattern" are sent as "OtherEthnic:foo" and
* "OtherPattern:foo" respectively where foo is what the user typed in the textbox.
*/
export type StyleScreenerResponse = 'None' | 'LOC' | 'LOF' | 'MED' | 'MEX' | 'SOU' | string | null
export type Gender = 'Male' | 'Female'
export type WeightTrend = 'Rising' | 'Constant' | 'Falling'
export type ActivityLevel = 'Sedentary' | 'BelowGuidelines' | 'MeetsGuidelines' | 'AboveGuidelines' | 'HighlyActive'
export type DietRestriction =
| 'DairyFree'
| 'NutFree'
| 'GlutenFree'
| 'WheatFree'
| 'ShellfishFree'
| 'SoyFree'
| 'PeanutFree'
| 'AlcoholFree'
| 'EggFree'
| 'Halal'
| 'Kosher'
| 'Organic'
/**
* The string type is only necessary for when the user specifies a goal other than what's listed.
* "Other" goals are sent as "other:foo" where foo is what the user typed in the textbox.
*/
export type HealthGoal =
| 'ReduceCardioRisk'
| 'ControlBloodPressure'
| 'PreventOrControlDiabetes'
| 'ManageHeartFailure'
| 'DecreaseInflammation'
| 'ImproveOverall'
| 'ManageWeight'
| 'ManageHighCholesterol'
| 'ManageFoodSensitivities'
| 'ImproveGutHealth'
| string
export type WeightGoal = 'Lose' | 'Maintain' | 'Gain'
export type ChallengeSubscriptionState = 'StartingTomorrow' | 'Active' | 'MissedYesterday' | 'Succeeded' | 'Failed'
export type NutrientUnit = 'g' | 'mg' | 'mcg' | '%' | 'kcal'