NAV Navbar
curl android ios

Overview

Internal objects (Users, Surveys, Content, etc.) are separate from custom, user-defined objects. All URLs are prefixed with /v1, which is omitted in examples below.

Roadmap

  1. Classes

    1. Automatically created on first use (with default/no schema)
  2. Generic JSON-based storage

  3. Built-in attributes

    1. Generated and managed by the platform

      1. id: String
      2. createdAt: ISO 8601 Date String (see below)
      3. updatedAt: ISO 8601 Date String (see below)
    2. Will be ignored if uploaded as part of an object

    3. For result lists, we will have top level metadata about the request/response (e.g count, etc.)

  4. Pagination, counting, limiting

  5. Schema definition (for serialization/deserialization)

  6. Schema validation (on input)

  7. References (with auto-expansion)

Concepts

  1. Each data type is it’s own class/collection

  2. Access control/ownership

  3. Public data is not associated with a user

  4. Use custom methods to facilitate publicly accessible data

Data Types

  1. All JSON types

  2. Dates - ISO 8601 representation as strings, in UTC, including optional partial seconds

    1. Ex: “2016-06-23T14:52:35.123Z”
  3. References

  4. IDs - opaque strings

  5. Geo fields (geo-indexed)

    1. Note: Mongo only allows one geo index per collection

Blob/File storage

  1. Blob storage on S3

  2. Metadata

    1. Content-type
    2. File path (optional)
  3. Access control/ownership

Limitations - pass through MongoDB limitations

Thoughts

Getting Started

How data is Stored

VARA stores data in two forms:

  1. application configurations - email templates, authentication token timeout, file storage provider, etc.

  2. application generated data - user accounts, care plans, profiles, etc.

Currently, application configurations can only be accessed and updated by VARA admins, in the future we plan to grant API access via per application master keys and client dashboards where configurations can be easily updated.

To access application generated data, an application id/client key combination is required, these can be passed in via HTTP headers or as part of a query string, see authentication parameters.

Making Authenticated Requests

Requests are authenticated by supplying a clientKey to validate the client and in cases where user specific data is requested, an additional accessToken. The clientKey and accessToken can be supplied via query string or request header, see authentication parameters. The accessToken can also be passed in the Authorization header. If an invalid clientKey is supplied then a 404 ApplicationNotFound error will be returned. If an invalid or expired accessToken is supplied then a 401 InvalidAccessTokenError is returned.

The clientKey is issued when an application is created on the platform. MPS platform admins will securely communicate the clientKey and masterKey to the application administrator. These keys should be kept secure and confidential and should not be stored in source control.

An accessToken is issued when a request is made to the /user/login endpoint, see: Login.

If no accessToken is supplied, then the user is assumed anonymous and the ownerId and userId is set to null. When this happens the request will allow access to anonymous data only (i.e data not associated with a valid user).

N.B. this behavior is subject to change in the future.

Time to Live

The expiry date of an accessToken is determined by the login date and the time to live (TTL) value of that accessToken. By default, accessTokens expire in fifteen (15) minutes. This can be changed by adding a ttl query param to the login request, provided that the allowCustomTimeToLive application configuration is enabled.

Sliding Session Timeout

By default, once an authenticated request is successful, the accessToken’s expiration date is updated to X minutes from now, where X is the TTL for the token. This feature may be disabled via the allowSlidingSessionTimeout configuration. When the sliding session timeout feature is disabled, accessTokens will expire X minutes after their respective login dates, regardless of activity.

Authentication Parameters

Property Supported Request Header(s) Supported Query String Parameter(s)
application id App-Id
X-App-Id
appId
client key Client-Key
X-Client-Key
clientKey
access token Authorization
Access-Token
X-Access-Token
accessToken

Application Configuration

Once an application is created on the VARA platform, its configurations can be managed through our configuration APIs.

The following endpoints allow developers to configure their custom logic code and templates via the Master Key. These APIs are temporary and should not be programmatically accessed. In the future, configuration APIs will be provided for individual features.

Retrieve

curl -X GET \
  '{{BASE_URL}}/v1/configs' \
  -H 'Client-Key: <Master Key>'


GET /v1/configs

Required Auth Parameters: Master Key

Update

curl -X PUT \
  '{{BASE_URL}}/v1/configs?baseurl=https://example.com' \
  -H 'Client-Key: <Master Key>' \
  -H 'content-type: application/json'


{
  "customLogic": {
    // Review custom logic documentation for relevant attributes
  },
  "templates": {
    // Review template configuration schema for relevant attributes
  },
  "resourcePermissions": [
   // Review documentation on access control rules for further details
  ]
}

PUT /v1/configs

Required Auth Parameters: Master Key

Body Properties

Parameter Type Description
customLogic Object Set of custom logic configurations for an application, for e.g., protocol version, pre-hooks, post-hooks, etc. see updating custom logic configurations for more details
templates Object Set of template (email and HTML) configurations for an application

Template Configuration Schema

The table below identifies the set of templates that can be configured on the platform. The following user account variables are supported for email templates:

Sample template configurations

{
  "templates": {
    "email": {
      "verification": {
        "subject": "Registration confirmation",
        "body": "<h1>...</h1>",
        "from": "admin@example.com"
      },
      "closeAccount": {
        "subject": "Closing User Account",
        "success": {
          "admin": "<h1>Hey admin</h1> <br/> <%-user.firstName%> has closed their account.",
          "customer": "<h1>Hey <%-user.firstName%></h1> <br/> Your account has been closed."
        },
        "start": {
          "body": "<h1>Hey <%-user.firstName%>...</h1>"
        },
        "error": {
          "admin": "<h1>Hey admin</h1> <br/> <%-user.firstName%>...",
          "customer": "<h1>Hey <%-user.firstName%></h1>"
        }
      },
      "closeAccountKeepData": {
        "subject": "Closing User Account",
        "success": {
          "admin": "<h1>Hey admin</h1> <br/> <%-user.firstName%> has closed their account.",
          "customer": "<h1>Hey <%-user.firstName%></h1> <br/> Your account has been closed."
        },
        "start": {
          "body": "<h1>Hi <%-user.firstName%>,</h1> <br/> ..."
        },
        "error": {
          "admin": "<h1>Hey admin</h1> <br/> <%-user.firstName%> ...",
          "customer": "<h1>Hey <%-user.firstName%></h1>..."
        }
      },
      "accountLockout": {
        "subject": "For security reasons your account is temporarily locked",
        "body": "<div>Hi <%-firstName%>,</div><br/><div> You've exceeded the the maximum unsuccessful login attempts. Your account will remain locked for the next <%= accountLockoutDuration.total.minutes %> minutes. Please contact <a href='mailto:support@example.com'>support@example.com</a> for assistance if you need access before this time expires.<p>",
        "from": "admin@example.com"
      },
      "reset": {
        "subject": "Password Reset",
        "body": "<h1>Hey, <%-user.firstName%></h1><br/><div>Use this <a href=\"<%-resetHref%>\">link</a> to reset your password</div>",
        "from": "admin@example.com"
      },
      "passwordChange": {
        "subject": "Password Change",
        "body": "<h1>Hey, <%-user.firstName%></h1><br/> We are confirming that your password has been changed.<p>...",
        "from": "admin@example.com"
      }
    },
    "passwordReset": {
      "webpage": "https://example.com/password-reset",
      "formHtml": "<html>..<%-resetRef%>..</html>"
    },
    "accountConfirmation": {
      "successHtml": "<html>...</html>",
      "errorHtml": "<html>...</html>"
    }
  }
}
Path Description Supported Template Variables
templates.email
verification.subject Subject of email N/A
verification.from From email address N/A
verification.body Content of account verification email. Email is sent whenever a user account is created.
  • <%- verifyHref %> - URL to verify user account.
  • user account variables
closeAccount.subject Subject of email N/A
closeAccount.success.admin Content of account closure email. Email is sent to the admin when an account is deleted.
  • user account variables
closeAccount.success.customer Content of account closure email. Email is sent to the user when their account is deleted.
  • user account variables
closeAccount.start.body Content of account closure email. Email is sent to the user indicating that their account will be deleted.
  • user account variables
closeAccount.error.admin Content of email when account could not be deleted. Email is sent to the admin alerting that an error occurred during the deletion of a user’s account.
  • user account variables
closeAccount.error.customer Content of email when account could not be deleted. Email is sent to the user indicating that an error occurred during deletion of their account.
  • user account variables
closeAccountKeepData.subject Subject of email N/A
closeAccountKeepData.success.admin Content of account closure email. Email is sent to the admin when a user account has been deleted, but they chose to preserve their data.
  • user account variables
closeAccountKeepData.success.customer Content of account closure email. Email is sent to the user when their account has been closed, however, their data was preserved.
  • user account variables
closeAccountKeepData.start.body Content of account closure email. Email is sent to the user indicating that their account will be closed and their data will be preserved.
  • user account variables
closeAccountKeepData.error.admin Content of account closure email. Email is sent to the admin alerting them that an error occurred during the deletion of a user’s account, where the user chose to preserve their data.
  • user account variables
closeAccountKeepData.error.customer Content of account closure email. Email is sent to the user indicating that an error occurred during deletion of their account, where they chose to preserve their data.
  • user account variables
accountLockout.subject Subject of email N/A
accountLockout.from From email address N/A
accountLockout.body Content of account lockout email. Email is sent when the account lockout feature is configured and a user has exceeded the maximum number of failed login attempts, see login configurations for more details.
  • <%- accountLockoutDuration.total.days %> - account lockout duration in days
  • <%- accountLockoutDuration.total.hours %> - account lockout duration in hours
  • <%- accountLockoutDuration.total.minutes %> - account lockout duration in minutes
  • <%- accountLockoutDuration.total.seconds %> - account lockout duration in seconds
  • user account variables
reset.subject Subject of email N/A
reset.from From email address N/A
reset.body Content of password reset email. Email is sent when a password reset request is submitted.
  • <%- resetHref %> - URL to password reset form.
  • <%- resetToken %> - short-lived token for resetting an account password.
  • user account variables
passwordChange.subject Subject of email N/A
passwordChange.from From email address N/A
passwordChange.body Content of password change email. Email is send when a user’s password has been changed
  • user account variables
templates.passwordReset
webpage Custom URL where password reset form is hosted, takes precedence over formHtml. The following query parameters are added: appId, clientKey and resetToken. N/A
formHtml Custom HTML for password reset form rendered by the platform; ignored when a password reset webpage is configured.
  • <%- resetHref %> - URL to submit password change request.
templates.accountConfirmation
successHtml HTML rendered when the verification link is requested with the correct token.
NB. Any javascript code to detect mobile browser and redirect to a deep link into the mobile application should be added here.
N/A
errorHtml HTML rendered when an error occurs when the verification link is requested. This applies for user errors (e.g. bad token) or internal server errors.
NB. Any javascript code to detect mobile browser and redirect to a deep link into the mobile application should be added here specifically to handle error cases.
N/A

Notes:

See Also

Access Control Rules

VARA allows platform developers to configure custom access control rules for ALL resources/endpoints (including custom objects and custom functions) on the platform. These rules are applied in addition to the default access control rules provided by VARA. Therefore, access to resources may only be further restricted by custom access control rules.

Sample use cases

Preventing direct user registration

An application may use a custom registration process where complex validation and/or verification checks are performed before creating a user account. In these scenarios, a custom function could be an ideal solution. In this case, the custom function would execute the necessary business logic before registering the user using VARA’s account registration API.

To ensure that all registration requests are performed with the custom function, an access control rule can be used to only permit masterKey access to VARA’s account registration API. The custom function would therefore use the masterKey to register user accounts. The following request can be used to configure the previously mentioned access control rule:

PUT /v1/configs

{ "resourcePermissions": [ { "resource": { "type": "url", "url": "/v1/users", "actions": [ "POST" ] }, "principalType": " API_KEY", "principalId": "{{yourClientKey}}", "permission": "DENY" } ] }

The above configuration prevents the specified clientKey from being able to create user accounts. This restriction means that only the masterKey will be allowed to create user accounts.

Note: Access will be denied to the requester once the specified clientKey is used. This will occur whether or not the requester is authenticated with an access token.

In the future, the access control implementation will be extended to support both ALLOW and DENY permissions. This will allow the configuration of rules that deny access to all clientKeys while allowing access to a specific clientKey(s), for example:

{ "resourcePermissions": [ { "resource": { "type": "url", "url": "/v1/users", "actions": [ "POST" ] }, "principalType": "API_KEY", "principalId": "*", "permission": "DENY" }, { "resource": { "type": "url", "url": "/v1/users", "actions": [ "POST" ] }, "principalType": "API_KEY", "principalId": "{{yourClientKey}}", "permission": "ALLOW" } ] }

Restricting access to a custom object

One use case for custom objects is to store “public data”. “Public data” is managed by application administrators. End users are granted read-only access to this data. In order to accomplish this, a custom access control rule may be defined as follows:

PUT /v1/configs

{ "resourcePermissions": [ { "resource": { "type": "url", "url": "/v1/data/class/LearningResource*", "actions": [ "POST", "PUT", "DELETE" ] }, "principalType": "API_KEY", "principalId": "{{yourClientKey}}", "permission": "DENY" } ] }

The above configuration prevents the specified clientKey from being able to create, update or delete learning resources. The wildcard character (*) in the resource url is used to match all sub-paths under the “LearningResource” class for example, the url path /v1/data/class/LearningResources/123 will be matched. Alternatively, a separate configuration can be created to the match desired sub-path(s) i.e.:

{ "resourcePermissions": [ { "resource": { "type": "url", "url": "/v1/data/class/LearningResource", "actions": [ "POST", "DELETE" ] }, "principalType": "API_KEY", "principalId": "{{yourClientKey}}", "permission": "DENY" }, { "resource": { "type": "url", "url": "/v1/data/class/LearningResource/:id", "actions": [ "PUT", "DELETE" ] }, "principalType": "API_KEY", "principalId": "{{yourClientKey}}", "permission": "DENY" } ] }

Configuring Access Control Rules

Custom access control rules can be updated by performing the following request:

curl -X PUT \
  '{{BASE_URL}}/v1/configs' \
  -H 'Client-Key: <Master Key>' \
  -H 'content-type: application/json'
  -d '{
  "resourcePermissions": [
    {
      "resource": {
          "type": "url",
          "url": "/v1/data/class/LearningResource*",
          "actions": [
            "POST",
            "PUT",
            "DELETE"
          ]
      },
      "principalType": "API_KEY",
      "principalId": "{{yourClientKey}}",
      "permission": "DENY"
    }
  ]
}'
PUT /v1/configs

Note: The /v1/configs API is temporary and should not be programmatically accessed. In the future, a separate API will be provided for updating custom access control rules.

Body Properties

Parameter
Type
Description
resourcePermissions
Array
The set of access control rules for application resources. Rules are applied in addition to the default access control rules provided by VARA. Access control rules are defined as objects this array.
resource
Object
Configurations that identify an endpoint along with the actions that may or may not be executed on it.
type
String

Strategy for identifying a resource, for e.g. by url, model name, etc. Supported values are:

  • url - matches resources by url and actions.
url
String

Url path identifying a resource. The url path MUST contain the version prefix e.g. /v1 to match a resource. Any combination of path variables, wildcards and other regular expression operators may be used to match urls dynamically. Example url paths:

  • /v1/data/class/:classname - matches request to any custom object e.g. GET /v1/data/class/Activity

  • /v1/steps - matches requests to the Steps collection e.g. GET /v1/steps

  • /v1/data/class/Goal/:id - matches request performed on a specific Goal e.g. PUT /v1/data/class/Goal/123

  • /v1/data/class/*/:id - matches request to all custom object urls that contain an identifier, e.g. GET /v1/data/class/Activity/123

  • /v1/users/123 - matches requests performed on a specific user e.g. PUT /v1/users/123

Note: The url paths /v1/users and /v1/users/:id DO NOT match the same resource. The former matches collection level user operations while the latter matches operations performed on an individual user.
actions
Array

The set of actions on a resource that should be controlled. The permitted values for this configuration is based on the resource type. Supported values:

  • when the resource type is set to “url”, this property must contain one of the following HTTP methods: GET, POST, PUT, DELETE. A wildcard character (*) may be added to the array to match all HTTP methods.

    Note: When a wildcard character is used, all resource actions will be matched regardless of the other actions specified in the array.

principalType
String

Strategy for identifying the requester (principal). PrincipalType MUST be used in conjunction with the principalId to identify a requester. Supported values are:

  • API_KEY - matches requesters by clientKey.
principalId
String
Unique identifier for requester based on the principal type. For example, if the principalType is set to API_KEY, then the principalId must be set to a clientKey that should be granted or denied access to the specified resource.
permission
String

Determines whether or not access is granted to the principal. Supported values are:

  • DENY - denies access to the specified resource.

User Management

Create

curl -X POST \
  '{{BASE_URL}}/v1/users' \
  -H 'content-type: application/json' \
  -d '{
  "firstName": "John",
  "lastName": "Doe",
  "username": "john_doe",
  "email": "john.doe@example.com",
  "password": "Password123",
  "locale": "en"
}'
User defaultUser = new User("john_doe", "john.doe@example.com", "Password123");
defaultUser.setFirstName("John");
defaultUser.setLastName("Doe");

defaultUser.save(new VoidCallback() {
    @Override
    public void onSuccess() {
        // do something, created successfully
    }

    @Override
    public void onError(Throwable t) {
        // handle err
    }
});
let defaultUser: ETXUser = ETXUser(
    email: "john.doe@example.com",
    username: "john_doe",
    password: "Password123")

var userSvc: ETXUserService<ETXUser>!
userSvc = ETXUserService()
userSvc.createUser(defaultUser){
    (user, err) in
    if err != nil {
        // handle error
    }
    // use created user
}

The above command returns JSON structured like this:

{
  "id": "124r1242342133132edwcc332"
}
POST /users

After creating a user, the user’s account will need to be verified to facilitate a successful user login.

Body Properties

Parameter Type
firstName String
lastName String
password String
email String
username String

Verify

curl -X GET \
  '{{BASE_URL}}/v1/users/confirm?token=<verificationToken>'
// not implemented
// not implemented

User accounts may be verified through the verification link which is sent in the account registration email. The platform will attempt to verify the user account associated with the specified verification token. When account verification is successful, the token is invalidated and the successHtml from the templates.accountConfirmation configuration is rendered. The errorHtml from said configuration is rendered for requests with invalid tokens.

GET /users/confirm

Query Parameters

Parameter Required Description
token
String
true The verification token for user account
curl -X POST \
  '{{BASE_URL}}/v1/users/confirm' \
  -H 'Client-Key: <Master Key>'
  -d '{
  "email": "calvin.hollins@example.com"
}'
// not implemented
// not implemented

User accounts may also be verified through the master key. This is useful in scenarios where there is a need to register and use test accounts that are not tied to real email addresses.

POST /users/confirm

Required Auth Parameters: Master Key

Body Properties

Parameter Type Description
email String Email address of the user account to verify

Response

Status Meaning Description
204 No Content Returned when account verification is successful or if the account was previously verified
400 Bad Request Returned when the format of the email address is invalid
404 Not Found Returned when the user account could not be found

Login

curl -X POST \
  '{{BASE_URL}}/v1/users/login' \
  -H 'content-type: application/json' \
  -d '{
  "username": "john_doe",
  "password": "Password123"
}'
defaultUser.loginWithUsername(
    defaultUser.username,
    defaultUser.password,
    false,
    new ObjectCallback<User>() {
        @Override
        public void onSuccess(User user) {
            // success
        }

        @Override
        public void onError(Throwable t) {
           // handle err
        }
    });
userSvc.loginUserWithUsername(
    defaultUser.username,
    password: defaultUser.password,
    rememberMe: false) {
    (user, err) in
    if err != nil {
        // handle error
    }
    // use user
}

The above command returns an access token JSON structured like this:

{
  "id": "s9kDCglJEli8W8aVrQK66G5kxWYe7dK4BelbzPVB1pYt6w1U0WMKY17QhQMFWTXn",
  "ttl": 900,
  "created": "2017-05-02T00:32:37.576Z",
  "userId": "123456789"
}
POST /users/login

Query Parameters

Parameter Required Description
ttl false If allowCustomTimeToLive is set to true in Login Configurations, then this parameter will be accepted as a custom time to live on a successful login. This value represents the expiry time of the access token in seconds.

Login Configurations

The login configs are located in the modelConfig.auth section of the application config. These configurations can be modified by logging a support ticket.

Property Default / Type Description
maxFailedAttempts 5 (Number) The number of failed login attempts before the account is locked.
failedAttemptsEnabled true (Boolean) Enable or disable the account lockout feature.
ttl 900 (Number) Default time to live for accessTokens in seconds
loginLockTtl 1800 (Number) The duration of the lockout period in seconds
allowCustomTimeToLive true (Boolean) Allow the ttl of an accessToken to be set at login using the ttl querystring param.
allowSlidingSessionTimeout true (Boolean) Extends the expiration date of an accessToken when an authenticated request is successful.

Remember Me

The remember me feature overrides the base time to live (TTL) parameter. This is used to specify how long the user’s access token should be valid for. To use this feature, it needs to be enabled in the application config.

This can be done by configuring the allowCustomTimeToLive parameter of the modelConfig.auth config. This config can be either true or false:

Resend Verification Email

The platform provides the capability to resend account verification emails using either the default verification template or a custom template.

curl -X POST \
  '{{BASE_URL}}/v1/users/resend-verification-email' \
  -H 'content-type: application/json' \
  -d '{
  "template":"verification",
  "email": "sample@email.com"
}'
// IOS: not implemented
// ANDROID: not implemented
POST /users/resend-verification-email

Body Properties

Parameter Type Description
template String (optional) Name of the template that should be used to send the email. This should be equal to a property in the email template configuration schema i.e., the templates.email config. The templates.email.verification configuration is used when a custom template is not specified.
email String Email address of the account to verify.

Notes:

Response

Code Scenarios
204 - When the user account is verified and an attempt is made to resend the verification email
- When the user was not found
- When the verification email was sent
400 - When an invalid email address is specified
- When the custom template could not be found
500 - When the email template could not be compiled

User Update

curl -X PUT \
  '{{BASE_URL}}/v1/users/:id' \
  -H 'content-type: application/json' \
  -d '{
  "firstName": "Jon",
  "lastName": "Dow"
}'

A user’s account information can be updated by performing the following PUT request

PUT /users/:id

Required Auth Parameters: Access Token

Change Password

curl -X POST \
  '{{BASE_URL}}/v1/users/changePassword' \
  -H 'authorization: 2323eqwdqwd' \
  -H 'content-type: application/json' \
  -d '{
  "password": "NewPassword123",
  "oldPassword": "Password123"
}'
// not implemented
// not implemented

Authenticated users may change the password for their account by performing the following request:

POST /users/changePassword

Body Properties

Parameter Type
password String
oldPassword String

Response

Status Meaning Description
204 No Content The 204 (No Content) status code indicates that the server has successfully fulfilled the request and that there is no additional content to send

Reset Password

To reset the password for an account, a password reset request must be sent for the desired user. The user receives an email containing a link to a password reset form. The user clicks the link and submits the form with a new password to complete the process.

Password Reset Request

curl -X POST \
  '{{BASE_URL}}/v1/users/reset' \
  -H 'content-type: application/json' \
  -d '{
  "email": "test@user.com"
  // alternatively, the username can be used
  // "username": "test_user123"
}'
// not implemented
// not implemented

Password reset requests may be submitted with either an email address or a username, email takes precedence if both are specified. Once submitted, the server generates a reset token and sends an email with a link to the configured password reset form. The link to the password reset form contains a resetToken query parameter which is required to reset account passwords.

POST /users/reset

Body Properties

Parameter Type
email String
username String

Notes:

Response

Status Meaning Description
204 No Content The 204 (No Content) status code indicates that the server has successfully fulfilled the request and that there is no additional content to send

Password Change Request

curl -X POST \
  '{{BASE_URL}}/v1/users/changePassword?resetToken=TERdXPODXkGDsMTMMiDxMlky8JVnpU41FlEA9UApmmOYYfrkkLRdqEe57luZDyf1' \
  -H 'content-type: application/json' \
  -d '{
  "password": "NewPassword123"
}'
// not implemented
// not implemented

Response when an error occurs

{
    "error": {
    "name": "InvalidResetTokenError",
    "message": "The resetToken supplied is either expired or invalid.",
    "statusCode": 401,
    "code": "INVALID_RESET_TOKEN"
  }
}

Password change requests must be sent with a reset token and a new password. The new password has the same requirements as the password used for registration for e.g., minimum length, special characters, digits, etc.

POST /users/changePassword

Response

Status Meaning Description
204 No Content The 204 (No Content) status code indicates that the server has successfully fulfilled the request and that there is no additional content to send
401 Unauthorized A 401 is returned when an expired or invalid reset token is used

An email is sent to the user notifying them of a password change. This email is configured via the templates.email.passwordChange configuration, see template configuration for more details.

Close Account

The close account feature allows a developer to remove a user’s access through a hard or soft delete.

Config

Configurations are required to ensure the close account feature works appropriately. These configurations include email templates and rules for deletion failures (e.g number of retries).

Two sets of configurations are expected: modelConfig and emailConfig. These configurations are currently internal. The configs stated below will be used regardless of hard or soft deletion.

All configurations below are expected to be inside the emailConfig property:

Property Description Example
adminEmail The admin email that all emails relating to the closing of an account will be sent from. ‘loom@medullan.com’


All configurations below are expected to be inside the modelConfig property:

Property Description Example
retries.failedDeletion The number of times to retry a failed deletion request before stopping. 5


Hard Delete

This action deletes the user’s account and associated data, and cannot be undone. The process of deleting the user’s data may take up to few minutes to complete, and configurations can be set to send notification email messages to the user indicating when the process is complete.

curl -X DELETE \
  '{{BASE_URL}}/v1/users/:id' \
  -H 'content-type: application/json' \
  -H 'X-Tx-Delete-All-Data: true'
let user: ETXUser = ... // Get the current user
user.delete(hardDelete: true) { (err) in
  guard err == nil else {
    // Handle err
    return
  }
  print("User account and associated data has been deleted")
}
final PlatformSvc.VoidCallback userHardDeletionCb = new PlatformSvc.VoidCallback() {
  @Override
  public void onSuccess() {
   Log.d("AccountClosure", "User account and associated data has been deleted");
  }

  @Override
  public void onError(Throwable t) {
    // Handle Error
  }
};

User loggedInUser = ...;
boolean hardDelete = true;
loggedInUser.delete(hardDelete, userHardDeletionCb);

Result of the curl request

{
  "result": {
       "Success": true
  }
}

Required Auth Parameters: Access Token

For hard delete, you will need to set the header X-Tx-Delete-All-Data to true

DELETE /users/:id

Objects deleted on hard-delete

All instances of the the following models, owned by the user, will be cleared:

{
  "gdo":{
    "ignoreDelete": ["ModelName1", "ModelName2"],
  },
  "template":{
    "email":{
      "closeAccount":{
        "subject": "Closing User Account",
        "success": {
          "admin": "<h1>Hey admin</h1> <br/> <%=user.firstName%> has closed their account.",
          "customer": "<h1>Hey <%=user.firstName%></h1> <br/> Your account has been closed."
        },
        "start": {
          "body": "<h1>Hey <%=user.firstName%>,</h1> <br/> You have chosen to withdraw from the application but allow the application to keep your data."
        },
        "error": {
          "admin": "<h1>Hey admin</h1> <br/> <%=user.firstName%> has requested that their account be closed, but an error had occurred please remove all data associated to it.",
          "customer": "<h1>Hey <%=user.firstName%></h1> <br/> An error occurred in closing your account, we are working on resolving this and will contact you when it is finished."
        }
      }
    }
  }
}

Application configuration for user deletion

All configurations below are expected to be inside the templates property:

Configuration
gdo.ignoreDelete

A list of GDO models that should be ignored during hard deletion.
templates.email.closeAccount.subject

Subject email that will be used for all emails relating to closing a user account.
templates.email.closeAccount.success.admin

Email template to be sent to the admin when an account has been successfully closed.
templates.email.closeAccount.success.customer

Email template to be sent to the customer when an account has been successfully closed.
templates.email.closeAccount.start.body

Email template to be sent to the customer alerting them that their account is going to be closed.
templates.email.closeAccount.error.admin

Email template to be sent to the admin alerting them that an error occurred during deletion of user data.
templates.email.closeAccount.error.customer

Email template to be sent to the customer alerting them that an error occurred during deletion of user data.


Soft Delete

This action disables the user account and no data associated with the user is removed. The user will receive an email notifying that the account has been closed. Developers can also configure the content of the email the user receives.

At this time, the user can be restored by submitting a support ticket containing the email address of the soft deleted user.

curl -X DELETE \
  '{{BASE_URL}}/v1/users/:id' \
  -H 'content-type: application/json'
let user: ETXUser = ... // Get the current user
user.delete { (err) in
  guard err == nil else {
    // Handle err
    return
  }
  print("User account deleted")
}
final PlatformSvc.VoidCallback userSoftDeletionCb = new PlatformSvc.VoidCallback() {
  @Override
  public void onSuccess() {
   Log.d("AccountClosure", "User account closed");
  }

  @Override
  public void onError(Throwable t) {
    // Handle Error
  }
};

User loggedInUser = ...;
loggedInUser.delete(userSoftDeletionCb);

Required Auth Parameters: Access Token

DELETE /users/:id

The following models will be cleared:

Subsequent requests to access that User will result in the API returning a 401 error.

{
  "closeAccountKeepData":{
    "subject": "Closing User Account",
    "success": {
      "admin": "<h1>Hey admin</h1> <br/> <%=user.firstName%> has closed their account.",
      "customer": "<h1>Hey <%=user.firstName%></h1> <br/> Your account has been closed."
    },
    "start": {
      "body": "<h1>Hey <%=user.firstName%>,</h1> <br/> You have chosen to withdraw from the application but allow the application to keep your data."
    },
    "error": {
      "admin": "<h1>Hey admin</h1> <br/> <%=user.firstName%> has requested that their account be closed, but an error had occurred please remove all data associated to it.",
      "customer": "<h1>Hey <%=user.firstName%></h1> <br/> An error occurred in closing your account, we are working on resolving this and will contact you when it is finished."
    }
  }
}

All configurations of this payload are expected to be inside the templates property:

templates.email.closeAccountKeepData

Configuration
subject

Subject email that will be used for all emails relating to closing a user account.
success.admin

Email template to be sent to the admin when an account has been successfully closed.
success.customer

Email template to be sent to the customer when an account has been successfully closed.
start.body

Email template to be sent to the customer alerting them that their account is going to be closed.
error.admin

Email template to be sent to the admin alerting them that an error occurred during deletion of user data.
error.customer

Email template to be sent to the customer alerting them that an error occurred during deletion of user data.


The following are dynamic values that can be placed inside an email template:

Roles

Roles can be created and assigned to users.

curl -X POST \
  '{{BASE_URL}}/v1/roles' \ 
  -H 'Content-Type: application/json' \
  -d '{ 
    "name": "patient" 
}'
// not implemented
// not implemented

Response 200 OK

{
    "id": "...",
    "name": "patient",
    "created": "...",
    "modified": "..."
}

Creating a new Role

Required Auth Parameters: APP ID, Master Key.

Adds a new role.

POST /roles
Parameter Required Description
name true The name of the role being created.

Viewing existing Roles

Required Auth Parameters: APP ID, Master Key.

Views existing roles associated with application.

GET /roles
curl -X POST \
  '{{BASE_URL}}/v1/users/56/roles' \ 
  -H 'content-type: application/json' \
  -d '{
  "name": "admin"
}'
// not implemented
// not implemented

Response 200 OK

{
  "result": {
    "id": "...",
    "principalType": "USER",
    "principalId": "...",
    "roleId": "..."
  }
}

Add User to a Role

Required Auth Parameters: APP ID, Master Key

Add a user to an existing role.

POST /users/:id/roles

The :id segment can be replaced with me if the current logged in user is being assigned to a role.

View Roles assigned to user

curl -X GET \
  '{{BASE_URL}}/v1/users/56/roles' \ 
  -H 'content-type: application/json'  

// not implemented
// not implemented

Response 200 OK

{
    "result": [
        "patient"
    ]
}

Required Auth Parameters: APP ID, Client Key, Access Token

Returns a list of roles that are assigned to the specified user.

GET /users/:id/roles

A Master Key can be used to retrieve roles of other users. In this case the :id value must be that of the target user. This also requires impersonation.

The :id segment can be replaced with me if the roles of the current logged in user is to be retrieved.

Sharing

Create application wide sharing rule

TBC.

Create affiliation (Temporary)

curl -X POST \
  '{{BASE_URL}}/v1/users/:id/affiliations?appId=1234567890&clientKey=0987654321' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -d '{
    "owner": "586f47b43cfbacb48eb47555",
    "ownerRole": "patient",
    "target": "58950cbbb57e9878f6ac9cd5",
    "targetRole": "caregiver",
    "type": "role"
}'
// not implemented
// not implemented

Requires MasterKey.

POST /affiliations

200 OK

{
  "id": "...",
}

Retrieve Affiliations by User

{
  result: [
    {

     "id": "58795d1cdbf8ce38cb830ca1",     
     "owner": "586f47b43cfbacb48eb47555",     
     "ownerRole": "caregiver",
     "target": "58950cbbb57e9878f6ac9cd5",
     "targetRole": "patient",
     "type": "role"
    },
    {
     "id": "58795d1cdbf8ce38cb830ca1",
     "owner": "123950cbbb57ecb48eb47555",
     "ownerRole": "caregiver",
     "target": "586f47b43cfbacb48eb47555",
     "targetRole": "caregiver",
     "type": "role"
    }
  ]
}

Access is scoped to the logged in user.

Return affiliations where the user is either the actor or actee.

GET /users/:id/affiliations

eg.

GET /users/586f47b43cfbacb48eb47555/affiliations

200 OK

Get Affiliated Users

{
  "result": [
   {
     "id": "58950cbbb57e9878f6ac9cd5",
     "firstName": "John",
     "lastName": "Doe",
     "role": "patient",
     "myRole": "caregiver"
   }
 ]
}

GET /users/586f47b43cfbacb48eb47555/affiliatedUsers

200 OK

Search for Shared GDO

{
  "metadata": {
     "count": 1
  },
  result: [
    {  }
  ]
}

GET /v1/data/class/:name?shared=true&filter={“where”:{ “ownerId”: “123456”}}

200 OK

{
  "error": {
    "name": "ResourceNotFoundError",
    "message": "The resource being requested either does not exist or is forbidden/inaccessible",
    "code": "",
    "statusCode": 404,
    "requestId": "...",
    "className": "...",
    "id": "..."
  }
}

If there are no shared rules associated with the target user and current user:

404 NOT FOUND

Create Shared GDO

{
  "name": "pill"
}

{
  "id": "58dca0a9a6460a6333da8ac7",
  "name": "pill",
  "updatedAt": "2017-03-30T06:07:37.115Z",
  "updatedBy": "7890123",
  "createdAt": "2017-03-30T06:07:37.115Z",
  "ownerId": "123456",
  "createdBy": "7890123"
}

POST /v1/data/class/:name?shared=true&filter={“where”:{ “ownerId”: “123456”}}

200 OK

Update Shared GDO

{
  "name": "pill123"
}

{
  "id": "58dca0a9a6460a6333da8ac7",
  "name": "pill123",
  "updatedAt": "2017-03-30T06:07:37.115Z",
  "updatedBy": "7890123",
  "createdAt": "2017-03-30T06:07:37.115Z",
  "ownerId": "123456"
}

PUT /v1/data/class/:name/58dca0a9a6460a6333da8ac7?shared=true&filter={“where”:{ “ownerId”: “123456”}}

200 OK

Search for Shared First Class Model

{
    "metadata": {
       "count": 1
    },

    result: [
      {  }
    ]
}

To search First class models for shared data

GET /v1/Steps?shared=true&filter={“where”:{ “ownerId”: “123456”}}

200 OK

If there are no shared rules associated with the target user and current user:

{
  "error": {
    "name": "ResourceNotFoundError",

    "message": "The resource being requested either does not exist or is forbidden/inaccessible",

    "code": "",
    "statusCode": 404,
    "requestId": "...",
    "className": "...",
    "id": "..."
  }
}

404 NOT FOUND

Create Shared First Class Model

To create First class models for shared data

{
  "steps": 17,
  "date": "2017-03-22T21:36:16+00:00",
  "source": { "type": "manual-entry"}
}

POST /v1/Steps?shared=true&filter={“where”:{ “ownerId”: “123456”}}

{
  "steps": 17,
  "date": "2017-03-22T21:36:16.000Z",

  "source": {
    "type": "manual-entry"
  },

  "id": "58dc9fbaa6460a6333da8ac6",
  "updatedAt": "2017-03-30T06:03:38.456Z",
  "updatedBy": "7890123",
  "createdAt": "2017-03-30T06:03:38.456Z",
  "ownerId": "123456",
  "createdBy": "7890123"
}

200 OK

Update Shared First Class Model

To create First class models for shared data

{
  "steps": 999,
  "date": "2017-03-22T21:36:16+00:00",
  "source": { "type": "manual-entry"}
}

PUT /v1/Steps/58dc9fbaa6460a6333da8ac6?shared=true&filter={“where”:{ “ownerId”: “123456”}}

{
  "steps": 999,
  "date": "2017-03-22T21:36:16.000Z",

  "source": {
    "type": "manual-entry"
  },

  "id": "58dc9fbaa6460a6333da8ac6",
  "updatedAt": "2017-03-30T06:03:38.456Z",
  "updatedBy": "7890123",
  "createdAt": "2017-03-30T06:03:38.456Z",
  "ownerId": "123456"
}

200 OK

Data Schemas

Custom

All Generic Object requests should be authenticated with appId and clientKey.

Create

curl -X POST \
  '/data/class/:ClassName' \ 
  -H 'Content-Type: application/json' \ 
  -d '{
  "name": "John",
  "dob": "2000-01-01"
}'
POST /data/class/:ClassName
Sample Response
{
  id: "...",
  name: "John",
  dob: "2000-01-01",
  createdAt: "...",
  updatedAt: "..."
}

Update

Two methods of updating are supported - a full replace, POST with id param, or an update, PUT with the specified properties (patch).

NB: We chose to use HTTP PUTs for updates (patches), despite it going against the HTTP methods RFC (see explanation of spec here), as we’ve noted the convention was not being followed by REST SDKs (e.g loopback, apigee) and would break developer assumptions and/or expectations.

Returns a 404 with an appropriate error message if an object with that id does not exist, or the user doesn’t have access to it. If the user has read access but not write access, return a 403 Forbidden response.

Full Replace

curl -X POST \
  '/data/class/:ClassName/:id' \ 
  -H 'Content-Type: application/json' \ 
  -d '{
  "name": "John Doe" 
}'
POST /data/class/:ClassName/:id
Sample Response
{
  result: {
     name: "John Doe",
     createdAt: "...",
     updatedAt: "..."
  }
}

Patch Update

curl -X PUT \
  '/data/class/:ClassName/:id' \ 
  -H 'Content-Type: application/json' \ 
  -d '{
  "name": "David" 
}'
PUT /data/class/:ClassName/:id
Sample Response
{
  result: {
     id: "...",
     name: "David",
     dob: "2000-01-01",
     createdAt: "...",
     updatedAt: "..."
  }
}

Retrieve by Id

Returns a 404 Error with an appropriate message if the object is not found

curl -X GET \
  '/data/class/:ClassName/:id' \ 
  -H 'Content-Type: application/json' \  
GET /data/class/:ClassName/:id
Sample Response
{
  result: {
    id: "...",
    name: "John",
    createdAt: "...",
    updatedAt: "..."
    __owner: { ... },
    __access: { … }
  }
}
curl -X GET \
  '/data/class/:ClassName?filter=...' \ 
  -H 'Content-Type: application/json' \  
GET /data/class/:ClassName?filter=…
Sample Response
{
  result: [
    {
      id: "...",
      name: "John",
      __owner: { ... },
      __access: { ... }
    }
  ],
  meta: {
    count: 200,
    ...
  }
}

“filter” is the loopback-provided filter/search mechanism

“count” in the response metadata is only provided if paging is requested (it’s expensive to calculate how many objects in a Mongo collection match a query because it does not use an index)

If a filter is not specified, return all objects of the class.

Delete

Delete an individual object by a known id:

DELETE /data/class/:ClassName/:id

Delete all objects matching a search filter

DELETE /data/class/:ClassName?filter=…

Delete all objects of a specified class (does not delete the class itself). Note, this requires a special header so that this delete cannot be called accidentally (header name subject to change).

DELETE /data/class/:ClassName

X-Tx-Delete-All: true

Sample Response
{
  result: {
    deleted: 100
  }
}

The delete response should include the number of deleted objects.

200 OK

Standard

Spirometry

Model Configuration

Property Value Description
trends.dateField date The date field to use when generating trends
trends.trend avg Multiple values within a day should be averaged
trends.field fev1 Field to generate trends for

Fields

Property Validations Description
fev1 type: Number
required: true
Forced expiratory volume in one second (FEV1)

Forced expiratory volume (FEV) measures how much air a person can exhale during a forced breath. The amount of air exhaled may be measured during the first (FEV1), second (FEV2), and/or third seconds (FEV3) of the forced breath. Forced vital capacity (FVC) is the total amount of air exhaled during the FEV test.
fev1Unit type: String
default value: L (Liters)
Unit for FEV1
fvc type: Number
required: true
Forced expiratory vital capacity (FVC)

The volume change of the lung between a full inspiration to total lung capacity and a maximal expiration to residual volume. The measurement is performed during forceful exhalation; the preceding maximal inhalation need not be performed forcefully.
fvcUnit type: String
default value: L (Liters)
Unit for FVC
ffRatio type: Number FEV1/FVC

FEV1/FVC (FEV1%) is the ratio of FEV1 to FVC. In healthy adults this should be approximately 70–85% (declining with age).[11] In obstructive diseases (asthma, COPD, chronic bronchitis, emphysema) FEV1 is diminished because of increased airway resistance to expiratory flow; the FVC may be decreased as well, due to the premature closure of airway in expiration, just not in the same proportion as FEV1 (for instance, both FEV1 and FVC are reduced, but the former is more affected because of the increased airway resistance). This generates a reduced value (<80%, often ~45%). In restrictive diseases (such as pulmonary fibrosis) the FEV1 and FVC are both reduced proportionally and the value may be normal or even increased as a result of decreased lung compliance.
peakFlow type: Number Peak Flow

Peak flow is a simple measurement of how quickly you can blow air out of your lungs. It’s often used to help diagnose and monitor asthma. A peak flow test involves blowing as hard as you can into a small, hand-held device called a peak flow meter.
peakFlowUnit type: String
default value: L/S (Liters/Second)
Unit for peakFlow.
date type: ISO Date
required: true
The actual date the spirometry result was recorded.
source type: Object
required: true
Details about how values were logged.
source.type type: String
required: true
Method for logging spirometry result
source.device type: Object Information about device that recorded spirometry result
source.device.id type: String Device identifier where applicable

Create

curl -X POST \
  '/spirometry' \ 
  -H 'Content-Type: application/json' \ 
  -d '{
  "fev1": 0.5,
  "fvc": 0.8,
  "ffRatio": 32,
  "peakFlow": 70,
  "fef2575": 45,
  "date": "2018-03-18", 
  "source": {
    "type": "manual" 
  }
}'
POST /spirometry/
Sample Response
{
  id: "...",
  fev1: 0.5,
  fev1Unit: "L",
  fvc: 0.8,
  fvcUnit: "L",
  fef2575: 45,
  fef2575Unit: "L/S",
  ffRatio: 0.625,
  peakFlow: 70,
  peakFlowUnit: "L/S",

  source: {
    type: "manual"
  },

  date: "2017-03-01T03:25:16.042Z",
  createdAt: "...",
  updatedAt: "...",
  ownerId: "..."
} 

Update

PUT /spirometry/:id
curl -X PUT \
  '/spirometry/:id' \ 
  -H 'content-type: application/json' \
  -d '{
    "fef2575": 45
}' 
Sample Response
{
  result: {
    id: "...",
    fev1: 0.5,
    **fef2575: 45,
    ...**
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
} 

Retrieve by Id

curl -X GET \
  '/spirometry/:id' \ 
  -H 'content-type: application/json'  
GET /spirometry/:id
Sample Response
{
  result: {
    id: "...",
    fev1: 0.5,
    fef2575: 45,**
    ...**

    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
} 
curl -X GET \
  '/spirometry' \ 
  -H 'content-type: application/json'  
GET /spirometry/?filter=..
Sample Response
{
  result: [
    {
      id: "...",
      fev1: 0.5,
      fef2575: 45,**
      ...**

      createdAt: "...",
      updatedAt: "...",
      ownerId: "..."
    }
    ...
  ],

  meta: {
    count: 10,
    ...
  }
} 

Delete

curl -X DELETE \
  '/spirometry/:id' \ 
  -H 'content-type: application/json'  
DELETE /spirometry/:id
Sample Response
{
  result: {
    deleted: 1
  }
} 

Steps

Model Configuration

Property Value Description
trends.dateField date The date field to use when generating trends
trends.trend sum Multiple values within a day should be summed
trends.field steps Field to generate trends for

Fields

Property Validations Description
steps type: Number
required: true
The number of steps recorded
date type: ISO Date
required: true
The actual date the steps were taken.
calories type: Number The number of calories burned during the activity
duration type: Number The duration of the activity in minutes
source type: Object
required: true
Method for logging steps, for example device/manual
source.type type: String
required: true
Method for logging steps, for example device/manual
source.device type: Object Information about device that recorded steps
source.device.id type: String Device identifier where applicable

Create

curl -X POST \
  '/steps' \ 
  -H 'Content-Type: application/json' \ 
  -d '{
  "steps":500, 
  "date": "2018-03-18",
  "calories": 70,
  "duration": 20,
  "source": {
    "type": "normal", 
    "device": { "id" : "123"} 

  }
}'
POST /steps/
Sample Response
{
  id: "...",
  steps: 500,
  calories: 70,
  duration: 20,

  source": {
    type: "manual"
  },

  date: "2017-03-01T03:25:16.042Z",
  createdAt: "...",
  updatedAt: "...",
  ownerId: "..."
}

Update

curl -X PUT \
  '/steps/:id' \ 
  -H 'content-type: application/json' \
  -d '{
    "calories": "85"
}' 
PUT /steps/id
Sample Response
{
  result: {
    id: "...",
    steps: 500,
    calories: 85,
    ...

    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
} 

Retrieve by Id

curl -X GET \
  '/steps/:id' \ 
  -H 'content-type: application/json'  
GET /steps/id
Sample Response
{
  result: {
    id: "...",
    steps: 500,
    calories: 85,
    ...

    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
} 
curl -X GET \
  '/steps' \ 
  -H 'content-type: application/json'  
GET /steps?filter=…
Sample Response
{
  result: [
    {
      id: "...",
      steps: 500,
      calories: 85,
      ...

      createdAt: "...",
      updatedAt: "...",
      ownerId: "..."
    }
    ...
  ],

  meta: {
    count: 20,
    ...
  }
} 

Delete

curl -X DELETE \
  '/steps/:id' \ 
  -H 'content-type: application/json'  
DELETE /steps/id
Sample Response
{
  result: {
    deleted: 1
  }
} 

IndoorAirQuality

Indoor Air Quality (IAQ) refers to the air quality within and around buildings and structures, especially as it relates to the health and comfort of building occupants. Understanding and controlling common pollutants indoors can help reduce your risk of indoor health concerns.

Model Configuration

Property Value Description
trends.dateField date The date field to use when generating trends
trends.trend avg Multiple values within a day should be averaged
trends.field aqi Field to generate trends for

Fields

Property Validations Description
aqi type: Number required: true

Air Quality Index (AQI)

Measure of the overall air quality of a specified location at a specific time.

It tells you how clean or polluted your air is, and what associated health effects might be a concern for you.
voc type: Number required: true

Volatile Organic Compound (VOC)

Organic chemical compounds that have high vapor pressure (easy to evaporate; low boiling point) and can adversely affect the environment and human health.

VOCs are emitted as vapors from certain solids or liquids e.g paints, cleaning supplies, pesticides, office supplies such as copiers and printers.

Common VOCs include benzene, formaldehyde, methylene chloride, trichloroethylene, and tetrachloroethylene.

The WHO advises a maximum VOC level of 330 ppb (parts per billion).

vocUnit type: String default value: ppb (parts per billion) Unit for volatile organic compounds.
temp type: Number required: true

Measure of the warmth or coldness of the area.

Warmer temperatures may indicate higher VOC levels as warmer temperatures lead to evaporation of VOC sources.

tempUnit type: String default value: F Unit for temperature.
humidity type: Number required: true

Measure for the amount of water vapor in the air.

Areas with high humidity can accelerate the growth of biological contaminants such as bacteria, fungi and mold.

humidityUnit type: String default value: % Unit for humidity.
pm25 type: Number required: true

Particulate Matter (PM2.5)

Measure of the level of particle pollution in an area where particles (mixture of solid and/or liquid suspended in the air) are 2.5 micrometers in diameter or smaller.

Major sources of include motor vehicles, power plants, residential wood burning, forest fires, agricultural burning, some industrial processes, and other combustion processes.

Once inhaled, particles can affect the heart and lungs and in some cases cause serious health effects.

pm25Unit type: String default value: µg/m3 (micrometers/meter3) Unit for pm25.
pm10 type: Number

Particulate Matter (PM10)

Measure of the level of particle pollution in an area where particles (mixture of solid and/or liquid suspended in the air) are 2.5 to 10 micrometers in diameter.

Once inhaled, particles can affect the heart and lungs and in some cases cause serious health effects.

pm10Unit type: String default value: µg/m3 (micrometers/meter3) Unit for pm10.
co type: Number

Measure of the levels of Carbon monoxide (CO) in the air.

Prolonged exposure to high levels of CO can lead to brain damage or even death. According to the American Conference of Governmental Industrial Hygienists (ACGIH), the threshold limit value for CO is 25 ppm as an 8-hour time-weighted-average (TWA).

coUnit type: String default value: ppm (parts per million) Unit for Carbon monoxide.
co2 type: Number

Measure of the levels of Carbon dioxide in the air.

This is can be used as a rough indicator of the effectiveness of ventilation.
co2Unit type: String default value: ppm (parts per million) Unit for Carbon dioxide.
date type: ISO Date required: true Date when the indoor air quality measurements were taken.
location type: Object required: true Where the air quality measurements were taken
location.lat type: Number latitude for the area air quality measurements were taken
location.lng type: Number longitude for the area air quality measurements were taken
source type: Object How the values were logged or where they came from
source.type type: String Method for logging air quality measurements.
source.device type: Object Information about device that recorded the air quality measurements.
source.device.id type: String Device identifier where applicable

Create

curl -X POST \
  '/indoorAirQuality' \ 
  -H 'Content-Type: application/json' \ 
  -d '{
  "aqi": 70,
  "voc": 130,
  "temp": 70,
  "humidity": 80,
  "pm25": 9.58017,
  "location": {
    "lat": 28.5383,
    "lng": -81.3792
  },
  "source": {
    "type": "manual"
  },
  "date": "2018-03-18"
}'
POST /indoorAirQuality/
Sample Response
{
  id: "...",
  aqi: 70,
  voc: 130,
  vocUnit: "ppb",
  temp: 70,
  tempUnit: "F",
  humidity: 80,
  humidityUnit: "%",
  pm25: 9.58017,
  pm25Unit: "µg/m3",
  pm10: null,
  pm10Unit: "µg/m3",
  co: null,
  coUnit: "ppm",
  co2: null,
  co2Unit: "ppm",

  location: {
    lat: 28.5383,
    lng: -81.3792
  },

  source": {
    type: "manual"
  },

  date: "2017-03-01T03:25:16.042Z"
  createdAt: "...",
  updatedAt: "...",
  ownerId: "..."
} 

Update

curl -X PUT \
  '/indoorAirQuality/:id' \ 
  -H 'content-type: application/json' \
  -d '{
    "pm10": 30
}' 
PUT /indoorAirQuality/id
Sample Response
{
  result: {
    id: "...",
    aqi: 70,
    pm10: 30,
    ...

    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
} 

Retrieve by Id

curl -X GET \
  '/indoorAirQuality/:id' \ 
  -H 'content-type: application/json'  
GET /indoorAirQuality/id
Sample Response
{
  result: {
    id: "...",
    aqi: 70,
    pm10: 30,
    ...

    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
} 
curl -X GET \
  '/indoorAirQuality' \ 
  -H 'content-type: application/json'  
GET /indoorAirQuality?filter=…
Sample Response
{
  result: [
    {
      id: "...",
      aqi: 70,
      pm10: 30,
      ...

      createdAt: "...",
      updatedAt: "...",
      ownerId: "..."
    }
    ...
  ],

  meta: {
    count: 20,
    ...
  }
} 

Delete

curl -X DELETE \
  '/indoorAirQuality/:id' \ 
  -H 'content-type: application/json'  
DELETE /indoorAirQuality/id
Sample Response
{
  result: {
    deleted: 1
  }
} 

OutdoorAirQuality

Ambient (outdoor) air quality refers to the quality of outdoor air in our surrounding environment. It is typically measured near ground level, away from direct sources of pollution.

Model Configuration

Property Value Description
trends.dateField date The date field to use when generating trends
trends.trend avg Multiple values within a day should be averaged
trends.field aqi Field to generate trends for

Fields

Property Validations Description
aqi type: Number min: 0 max: 500 required: true

Air Quality Index (AQI)

Measure of the overall air quality of a specified location at a specific time.

It tells you how clean or polluted your air is, and what associated health effects might be a concern for you.

AQI values are calculated for five major air pollutants, which are ground-level ozone, Particulate Matter, Sulphur dioxide, Carbon monoxide and Nitrogen dioxide. The AQI value for a day is the highest AQI value for the set of pollutants being monitored.

pm25 type: Number

Particulate Matter (PM2.5)

Measure of the level of particle pollution in an area where particles (mixture of solid and/or liquid suspended in the air) are 2.5 micrometers in diameter or smaller.

Major sources of include motor vehicles, power plants, residential wood burning, forest fires, agricultural burning, some industrial processes, and other combustion processes.

Once inhaled, particles can affect the heart and lungs and in some cases cause serious health effects.

pm25Unit type: String default value: µg/m3 (micrometers per meter3) Unit for pm25.
co type: Number

Measure of the levels of Carbon monoxide (CO) in the air.

Prolonged exposure to high levels of CO can lead to brain damage or even death. According to the American Conference of Governmental Industrial Hygienists (ACGIH), the threshold limit value for CO is 25 ppm as an 8-hour time-weighted-average (TWA).

coUnit type: String default value: ppm (parts per million) Unit for Carbon monoxide.
so2 type: Number

Measure of the levels of Sulphur dioxide (SO2) in the air.

Moderate activity levels that trigger mouth breathing, such as a brisk walk, are needed for sulfur dioxide to cause health effects in most people.

At very high levels, sulfur dioxide may cause wheezing, chest tightness, and shortness of breath even in healthy people who do not have asthma.

so2Unit type: String default value: ppb (parts per billion) Unit for Sulphur dioxide.
no2 type: Number

Measure of the levels of Nitrogen dioxide (NO2) in the air.

NO2 primarily gets in the air from the burning of fuel.

Breathing air with a high concentration of NO2 can irritate airways in the human respiratory system. Such exposures over short periods can aggravate respiratory diseases, particularly asthma, leading to respiratory symptoms (such as coughing, wheezing or difficulty breathing), hospital admissions and visits to emergency rooms.

no2Unit type: String default value: ppb (parts per billion) Unit for Nitrogen dioxide.
ozone type: Number

Measure of the ground-level ozone.

Ground-level ozone forms near the ground when pollutants (emitted by sources such as cars, power plants, industrial boilers, refineries, and chemical plants) react chemically in sunlight.

ozoneUnit type: String default value: ppm (parts per million) Unit for ground-level ozone.
date type: ISO Date required: true Date when the outdoor air quality measurements were taken.
location type: Object required: true Where the air quality measurements were taken
location.lat type: Number latitude for the area air quality measurements were taken
location.lng type: Number longitude for the area air quality measurements were taken
source type: Object How the values were logged or where they came from
source.type type: String Method for logging air quality measurements.
source.device type: Object Information about device that recorded the air quality measurements.
source.device.id type: String Device identifier where applicable

Create

curl -X POST \
  '/outdoorAirQuality' \ 
  -H 'Content-Type: application/json' \ 
  -d '{
  "aqi": 70,
  "location": {
    lat: 28.5383,
    lng: -81.3792
  },
  "source": {
    type: "manual"
  },
  "date": "2018-03-18"
}'
POST /outdoorAirQuality/
Sample Response
{
  id: "...",
  aqi: 70,
  pm25: null,
  pm25Unit: "µg/m3",
  co: null,
  coUnit: "ppm",
  so2: null,
  so2Unit: "ppb",
  no2: null,
  no2Unit: "ppb",
  ozone: null,
  ozoneUnit: "ppm",
  location: {
    lat: 28.5383,
    lng: -81.3792
  },
  source": {
    type: "manual"
  },
  date: "2017-03-01T03:25:16.042Z"
  createdAt: "...",
  updatedAt: "...",
  ownerId: "..."
} 

Update

curl -X PUT \
  '/outdoorAirQuality/:id' \ 
  -H 'content-type: application/json' \
  -d '{
    "aqi": 80
}' 
PUT /outdoorAirQuality/id
Sample Response
{
  result: {
    id: "...",
    aqi: 80,
    ...
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
} 

Retrieve by Id

curl -X GET \
  '/outdoorAirQuality/:id' \ 
  -H 'content-type: application/json'  
GET /outdoorAirQuality/id
Sample Response
{
  result: {
    id: "...",
    aqi: 80,
    ...
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
} 
curl -X GET \
  '/outdoorAirQuality' \ 
  -H 'content-type: application/json'  
GET /outdoorAirQuality?filter=…
Sample Response
{
  result: [
    {
      id: "...",
      aqi: 80,
      ...
      createdAt: "...",
      updatedAt: "...",
      ownerId: "..."
    }
    ...
  ],

  meta: {
    count: 20,
    ...
  }
} 

Delete

curl -X DELETE \
  '/outdoorAirQuality/:id' \ 
  -H 'content-type: application/json'  
DELETE /outdoorAirQuality/id
Sample Response
{
  result: {
    deleted: 1
  }
} 

OxygenSaturation

Oximetry is a procedure for measuring the concentration of oxygen (oxygen saturation) in the blood. The test is used in the evaluation of various medical conditions that affect the function of the heart and lungs.

Model Configuration

Property Value Description
trends.dateField date The date field to use when generating trends
trends.trend avg Multiple values within a day should be averaged
trends.field spo2 Field to generate trends for

Fields

Property Validations Description
spO2 type: Number min: 0 max: 100

SpO2 stands for peripheral capillary oxygen saturation, an estimate of the amount of oxygen in the blood. More specifically, it is the percentage of oxygenated haemoglobin (haemoglobin containing oxygen) compared to the total amount of haemoglobin in the blood (oxygenated and non-oxygenated haemoglobin).

This value is represented by a percentage. If your Withings Pulse Ox™ says 98%, this means that each red blood cell is made up of 98% oxygenated and 2% non-oxygenated haemoglobin.

Normal SpO2 values vary between 95 and 100%.

spo2Unit type: String default value: % Unit for capillary oxygen saturation.
date type: String required: true Date the oxygen saturation measurement was taken
source type: Object How the values were logged or where they came from
source.type type: String Method for logging oxygen saturation.
source.device type: Object Information about device that recorded the air quality measurements.
source.device.id type: String Device identifier where applicable

Create

curl -X POST \
  '/oxygenSaturation' \ 
  -H 'Content-Type: application/json' \ 
  -d '{
  "spo2": 70,
  "source": {
    type: "manual"
  },
  "date": "2018-03-18"
}'
POST /oxygenSaturation/
Sample Response
{
  id: "...",
  spo2: 70,
  spo2Unit: "%",

  source": {
    type: "manual"
  },

  date: "2017-03-01T03:25:16.042Z"

  createdAt: "...",
  updatedAt: "...",
  ownerId: "..."
} 

Update

curl -X PUT \
  '/oxygenSaturation/:id' \ 
  -H 'content-type: application/json' \
  -d '{
    "spo2": 95
}' 
PUT /oxygenSaturation/id
Sample Response
{
  result: {
    id: "...",
    spo2: 95,
    ...
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
} 

Retrieve by Id

curl -X GET \
  '/oxygenSaturation/:id' \ 
  -H 'content-type: application/json'  
GET /oxygenSaturation/id
Sample Response
{
  result: {

    id: "...",
    spo2: 95,
    ...
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."

  }
} 
curl -X GET \
  '/oxygenSaturation' \ 
  -H 'content-type: application/json'  
GET /oxygenSaturation?filter=…
Sample Response
{
  result: [

    {
      id: "...",
      spo2: 95,
      ...

      createdAt: "...",
      updatedAt: "...",
      ownerId: "..."

    }
    ...

  ],

  meta: {
    count: 20,
    ...
  }
} 

Delete

curl -X DELETE \
  '/oxygenSaturation/:id' \ 
  -H 'content-type: application/json'  
DELETE /oxygenSaturation/id
Sample Response
{
  result: {
    deleted: 1
  }
} 

Medication

Fields

Property Description
id {String} Unique identifier for medication. An id may be specified for a medication to allow for convenient referencing. If an id is not specified it will be auto-generated.
tradeName {String}
required
Brand name of the drug
form {String}
required
The configuration or appearance of the drug (its form). This may be powder, tablet, inhaler, capsule, etc.
strength {Array}
required
Strength of active ingredients for the medication

Create

curl -X POST \
  '/med/data/medications' \ 
  -H 'content-type: application/json' \
  -d '{
  "id": "dulera_20",
  "tradeName": "dulera",
  "form": "inhaler",
  "strength": [
    {
      "value": 200,
      "unit": "ug"
    }
  ]
}' 

Required Auth Parameters: APP ID, MasterKey

POST /med/data/medications
Sample Response
{
  id: "dulera_20",
  tradeName: "dulera",
  form: "inhaler",
  strength: [
    {
      value: 200,
      unit: "ug"
    }
  ],
  createdAt: "...",
  updatedAt: "...",
  ownerId: "..."
}

Update

curl -X PUT \
  '/med/data/medications/:id' \ 
  -H 'content-type: application/json' \
  -d '{
    "tradeName": "Dulera",
}' 

Required Auth Parameters: APP ID, MasterKey

PUT /med/data/medications/:id
Sample Response
{
  result: {
    id: "...",
    tradeName: "Dulera",
    ...
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
}

Retrieve by Id

curl -X GET \
  '/med/data/medications/:id' \ 
  -H 'content-type: application/json'  

Required Auth Parameters: APP ID, MasterKey

GET /med/data/medications/:id
Sample Response
{
  result: {
    id: "...",
    tradeName: "Dulera",
    ...
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
}
curl -X GET \
  '/med/data/medications' \ 
  -H 'content-type: application/json'  

Required Auth Parameters: APP ID, MasterKey

GET /med/data/medications
Sample Response
{
  result: [
    {
      id: "...",
      tradeName: "Dulera",
      ...
      createdAt: "...",
      updatedAt: "...",
      ownerId: "..."
    }
    ...
  ],
  meta: {
    count: 20,
    ...
  }
}

Delete

curl -X DELETE \
  '/med/data/medications/:id' \ 
  -H 'content-type: application/json'  

Required Auth Parameters: APP ID, MasterKey

DELETE /med/data/medications/:id
Sample Response
{
  result: {
    deleted: 1
  }
}

MedicationPrescription

Fields

Property Description
description {String}
required
Directions for administering medication e.g 100 mcg/5 mcg, 2 inhalations twice daily
medicationId {String}
required
The medication linked to the prescription.
medication {Object}
read only
Details for the medication that corresponds to the medicationId. With the exception of delete requests, the medication property is returned for all successful prescription requests.
route {String}
required
The physiological pathway for drug to enter the body e.g topical, nasal, oral, etc.
effectivePeriod {Object}
required
Contains information about the duration of the prescription
effectivePeriod.start {ISODate} Start date for prescription
effectivePeriod.end {ISODate} End date for prescription
device {Object} Where applicable, information about the device that is used to dispense the medication
device.id {String} Identifier for device that dispenses medication
dosage {Array}
required
Detailed instructions for taking medication
dosage[n].asNeeded {Boolean}
required
Indicates if the medication should be taken as required (e.g a rescue inhaler would be taken in emergencies, thus asNeeded should be set to true) or should be taken all the times (e.g an insulin pump should be taken as prescribed, thus asNeeded should be set to false).
dosage[n].dose {Object}
required
Dosage information for the medication
dosage[n].dose.value {Number}
required
Number of units that make up a full dose of the medication
dosage[n].dose.unit {String}
required
Unit for the medication (e.g puff, tablet, capsule, etc.)
dosage[n].timing {Object}
required
Information on how doses should be taken (morning, afternoon, twice a day, etc.)
dosage[n].timing.repeat {Object}
required
Defines the interval for repeating medication doses.

Properties:

frequency {Number} - # of doses to be taken for period
period {Number} - length of period
periodUnits {String} - abbreviation for period (d - day; w - week; m - month)
dosage[n].timing.usage {Array} Recommended times for taking medication.

Properties:

label {String} - identifier/alias for time to take medication dose(s)
doses {Number} - # of doses to be taken
tod {String} - time dose(s) should be taken

Create

curl -X POST \
  '/med/data/prescriptions' \ 
  -H 'content-type: application/json' \
  -d '{
  description: "100 mcg/5 mcg, 2 inhalations twice daily",
  medicationId: "dulera_20", 
  "route": "oral",
  "device": { "id": "device_id" },
  "dosage": [{
    "asNeeded": false,
    "dose": { "value": 2, "unit": "puff" },
    "timing": {
      "repeat": {
        "frequency": 2,
        "period": 1,
        "periodUnits": "d"
      },
      "usage": [{
        "label": "morning",
        "doses": 1,
        "tod": "9:00am"
      },{
        "label": "evening",
        "doses": 1,
        "tod": "7:00pm"
      }]
    }
  }]
}' 

Required Auth Parameters: APP ID, CLIENT KEY

POST /med/data/prescriptions
Sample Response
{
  id: "...",
  description: "100 mcg/5 mcg, 2 inhalations twice daily",
  medicationId: "dulera_20",
  // medication that maps to the medicationId specified
  medication: { ... },
  "route": "oral",
  "device": {
    "id": "device_id"
  },
  dosage: [ ... ],
  createdAt: "...",
  updatedAt: "...",
  ownerId: "..."
}

Required Auth Parameters: APP ID, CLIENT KEY

POST /med/data/prescriptions
Sample Response
{
  id: "...",
  description: "100 mcg/5 mcg as needed",
  medicationId: "rescue_inhaler_100",
  // medication that maps to the medicationId specified
  medication: { ... },
  "route": "oral",
  "device": {
    "id": "device_id"
  },
  dosage: [ ... ],
  createdAt: "...",
  updatedAt: "...",
  ownerId: "..."
}

Update

curl -X PUT \
  '/med/data/prescriptions/:id' \ 
  -H 'content-type: application/json' \
  -d '{
  "description": "100 mcg/5 mcg, 2 inhalations once daily",
}' 

Required Auth Parameters: APP ID, CLIENT KEY

PUT /med/data/prescriptions/:id
Sample Response
{
  result: {
    id: "...",
    description: "100 mcg/5 mcg, 2 inhalations once daily",
    medicationId: "...", 
    // medication that maps to medicationId
    medication: { ... },  
    ...
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
}

Retrieve by Id

curl -X GET \
  '/med/data/prescriptions/:id' \ 
  -H 'content-type: application/json'  

Required Auth Parameters: APP ID, CLIENT KEY

GET /med/data/prescriptions/:id
Sample Response
{
  result: {
    id: "...",
    description: "100 mcg/5 mcg, 2 inhalations once daily",
    medicationId: "...", 
    // medication that maps to medicationId
    medication: { ... }, 
    ...
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
}
curl -X GET \
  '/med/data/prescriptions' \ 
  -H 'content-type: application/json'  

Required Auth Parameters: APP ID, CLIENT KEY

GET /med/data/prescriptions
Sample Response
{
  result: [
    {
      id: "...",
      description: "100 mcg/5 mcg, 2 inhalations once daily",
      medicationId: "...",
      // medication that maps to medicationId
      medication: { ... }, 
      ...
      createdAt: "...",
      updatedAt: "...",
      ownerId: "..."
    }
    ...
  ],
  meta: {
    count: 20,
    ...
  }
}

Delete

curl -X DELETE \
  '/med/data/prescriptions/:id' \ 
  -H 'content-type: application/json'  

Required Auth Parameters: APP ID, CLIENT KEY

DELETE /med/data/prescriptions/:id
Sample Response
{
  result: {
    deleted: 1
  }
}

MedicationAdministration

Fields

Property Description
prescriptionId {String}
required
The prescription that the taken medication corresponds to.
prescription {Object}
read only
Details for the prescription that corresponds to the prescriptionId. With the exception of deletes, the prescription property is returned for all successful administration requests.
effectiveDate {Array}
required
Date the medication was taken
note {String} Details about the administration of the medication
dosage {Object}
required
Details about medication taken
dosage.route {String} The physiological route for the administration of the drug e.g topical, nasal, oral, etc..
dosage.method {String} How the was drug administered
dosage.dose {String}
required
Information about the units of medication administered
dosage.dose.value {Number}
required
Number of units taken for the medication
dosage.dose.unit {String}
required
Unit for the medication (e.g puff, tablet, capsule, etc.)

Create

curl -X POST \
  '/med/data/administrations' \ 
  -H 'content-type: application/json' \
  -d '{
  "prescriptionId": "1234",
  "effectiveDate: "2017-04-22T15:00:00.000Z",
  "note: "this is a note",
  "dosage": {
    "route": "oral",
    "method": "How drug was administered",
    "dose": {
      "value": 2,
      "unit": "puff"
    }
  }
}' 

Required Auth Parameters: APP ID, CLIENT KEY

POST /med/data/administrations
Sample Response
{
  prescriptionId: '1234',
  // prescription matching prescriptionId
  prescription: { ... },
  effectiveDate: "2017-04-22T15:00:00.000Z",
  note: "this is a note",
  dosage: {
    route: "oral",
    method: "How drug was administered",
    dose: {
      value: 2,
      unit: "puff"
    }
  },
  createdAt: "...",
  updatedAt: "...",
  ownerId: "..."
}

Update

curl -X PUT \
  '/med/data/administrations/:id' \ 
  -H 'content-type: application/json' \
  -d '{
  "note": "this is a new note",
}' 

Required Auth Parameters: APP ID, CLIENT KEY

PUT /med/data/administrations/:id
Sample Response
{
  result: {
    id: "...",
    note: "this is a new note",
    ...
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
}

Retrieve by Id

curl -X GET \
  '/med/data/administrations/:id' \ 
  -H 'content-type: application/json'  

Required Auth Parameters: APP ID, CLIENT KEY

GET /med/data/administrations/:id
Sample Response
{
  result: {
    id: "...",
    note: "this is a new note",
    ...
    createdAt: "...",
    updatedAt: "...",
    ownerId: "..."
  }
}
curl -X GET \
  '/med/data/administrations' \ 
  -H 'content-type: application/json'  

Required Auth Parameters: APP ID, CLIENT KEY

GET /med/data/administrations
Sample Response
{
  result: [
    {
      id: "...",
      note: "this is a new note",
      ...
      createdAt: "...",
      updatedAt: "...",
      ownerId: "..."
    }
    ...
  ],
  meta: {
    count: 20,
    ...
  }
}

Delete

curl -X DELETE \
  '/med/data/administrations/:id' \ 
  -H 'content-type: application/json'  

Required Auth Parameters: APP ID, CLIENT KEY

DELETE /med/data/administrations/:id
Sample Response
{
  result: {
    deleted: 1
  }
}

File/Blob

All File/Blob requests should be authenticated with appId and clientKey.

Create

 curl -X POST \
   '{{BASE_URL}}/v1/blob?metadata=...' \
   -H 'cache-control: no-cache' \
   -H 'content-type: any/content-type' \
   -H 'content-length: 12345' \
   -H 'X-Tx-Blob-Metadata: {json object}' \

<binary data here>

// not implemented
// not implemented

Result of the above request: 201 Created


{
  id: "12345",
  contentType: "application/octet-stream",
  size: 12345,
  createdAt: "...",
  updatedAt: "..."
}
POST /blob

Parameter

metadata

Metadata can be provided with as a metadata query param with an encoded JSON object, or in a X-Tx-Blob-Metadata (name subject to change) header. Metadata is optional. If the metadata query param exists it will take precedence over the X-Tx-Blob-Metadata header.

The content type of the data can be specified, and will be sent back when the file is downloaded. It can be specified as a header (as per usual), or as the contentType field in the metadata object. Content type defaults to application/octet-stream if not specified.

The Content-Length header must be specified when uploading data. Alternatively, chunked encoding can be used when the upload size is not known (Transfer-Encoding: chunked).

On creation, object size is part of the metadata returned, in bytes.

Update

Full Replace

 curl -X POST \
   '{{BASE_URL}}/v1/blob/56?metadata=...' \
   -H 'cache-control: no-cache' \
   -H 'content-type: any/content-type' \
   -H 'content-length: 67890' \
   -H 'X-Tx-Blob-Metadata: {json object}' \

<binary data here>

// not implemented
// not implemented

Result of the above request: 200 OK


{
  id: "12345",
  contentType: "application/octet-stream",
  size: 67890,
  createdAt: "...",
  updatedAt: "...",
  
  [blob metadata]
  
}
POST /blob/:id?

Parameter

metadata

Patch Update

 curl -X PUT \
   '{{BASE_URL}}/v1/blob/56?metadata=...' \
   -H 'cache-control: no-cache' \
   -H 'content-type: any/content-type' \
   -H 'content-length: 67890' \
   -H 'X-Tx-Blob-Metadata: {"description": "updated file description"}' \
// not implemented
// not implemented

Result of the above request: 200 OK


  id: "12345",
  contentType: "application/octet-stream",
  size: 67890,
  createdAt: "...",
  updatedAt: "...",
  description: "updated file description"
PUT /blob/:id?

Parameter

metadata

Get/Download

Download a file by using its id. The metadata is returned in the header of the response. Content type header is also correctly set.

GET /blob/:id?access_token=...

200 Ok

X-Tx-Blob-Metadata: {id: “12345”, … }

ContentType: “application/octet-stream”, size: 12345 }

Content-Type: “application/octet-stream”

Content-Length: 12345

Image Resize

Resize a blob image by adding width (required) and height (optional) parameters to the request. These values represent the new value in pixels and should be sent as integers.

GET /blob/:id?width=500&height=400&access_token=...

200 Ok

X-Tx-Blob-Metadata: {id: “12345”, … }

ContentType: “application/octet-stream”, size: 12345 }

Content-Type: “application/octet-stream”

Content-Length: 12345

File metadata can be searched.

GET /blob?filter=...

200 OK “`javascript { result: [ { id: ”…“, size: 12345, name: ”…“, description: ”…“, contentType: "application/octet-stream”, __owner: { … }, __access: { … } } ],

meta: { count: 5 } } “`

Delete

Delete a single file by id

DELETE /blob/:id

Delete files by metadata search query

DELETE /blob?filter=...

Delete all files for the app. Must be accompanied by the X-Tx-Delete-All header to prevent accidental deletions

DELETE /blob

X-Tx-Delete-All: true

API Extension

The entire lifecycle of any API call can be extended with custom business logic as code that is developed and deployed for specific applications.

The platform utilizes “lifecyle hooks” and “standalone functions” to extend its functionality. Lifecycle hooks are executed at specific points in a request cycle and may modify the request input and/or the response. The platform has the following Lifecycle hooks:

  1. Pre-hooks

  2. Post-hooks

  3. Error hooks (future)

Lifecyle Hook execution
Request flow for API calls that support lifecycle hook execution. Lifecycle hooks can be used to extend the VARA API with custom business rules.

Standalone functions, on the other hand, are triggered by HTTP requests to custom API routes, see Standalone Code for more details.

Standalone Code execution
Request flow for executing standalone code. Standalone code can be used to add custom API routes to the VARA API.

Implementation

Custom code is implemented in Node.js (other platforms to be supported in the future), and have full use of the npm ecosystem.

Custom code can make requests to external APIs or to the platform to perform its function.

Loop detection is implemented to prevent deadlock (API call -> hook -> API call -> hook -> etc.)

Restrictions

No hard execution time or other runtime restrictions are imposed on custom code at this point. However, long-running requests, requests lasting two minutes or more, are timed out. Currently, this timeout is not configurable.

Execution Modes

Each invocation (a pre-hook, post-hook or standalone function) can run in either synchronous or asynchronous mode.

In asynchronous mode, custom code is executed concurrently with API processing and does not the affect the API response. In this mode, custom code utilizes the custom code protocol only for receiving http requests; responses (successful or failed) are logged by the platform.

Note: Asynchronous execution of custom code is not yet supported.

In synchronous mode, custom code is executed as part of the API request cycle where it may the change API request and/or response. In this mode, custom code must utilize the custom code protocol for both sending and receiving http requests; responses (successful or failed) are also logged by the platform.

Pre-hooks

Pre-hooks have the ability to change the input data (as seen by the core API), or prevent further processing. Pre-hooks are designed to operate on API calls before data access and validation, and before any platform business logic is executed. Pre-hooks, however, are executed after basic request processing (such as verifying user sessions) in order to provide contextual information, for e.g., the user’s preferred locale, to the hook.

Execution

A single API call may have zero or more configured pre-hooks. Each pre-hook is executed sequentially as it appears in the preHooks array. The configuration below contains four pre-hooks (i.e. validateSteps, countSteps, getDeviceId and getDeviceRating) which are to be executed whenever Steps are created:

{ ... "preHooks": [ { "resource": { "url": "/v1/steps", "httpMethods": [ "POST" ] }, "executionMode": "sync", "url": "http://localhost:5000/functions/validateSteps" }, { "resource": { "url": "/v1/steps", "httpMethods": [ "POST" ] }, "executionMode": "sync", "url": "http://localhost:5000/functions/countSteps", "failOnError": false }, { "resource": { "url": "/v1/steps", "httpMethods": [ "POST" ] }, "executionMode": "sync", "url": "http://localhost:5000/functions/getDeviceId" }, { "resource": { "url": "/v1/steps", "httpMethods": [ "POST" ] }, "executionMode": "sync", "url": "http://localhost:5000/functions/getDeviceRating" } ] }

The following describe the execution of these pre-hooks:

Supported Routes

Pre-hooks can be executed for all routes with the exception of the following:

Configuration

Pre-hooks are configured through the application configuration endpoint, see updating custom logic configurations for more details.

Post-hooks

Post-hooks have the ability to modify output before it’s returned to the original API caller. These hooks are only called after all other processing, including data access and storage, has been completed.

Execution

Only a single synchronous post-hook can be executed for an API call. When multiple synchronous post-hooks are configured for an API call, only the last synchronous post-hook is executed. The configuration below contains two synchronous post-hooks, sendWelcomeEmail and sendGettingStartedEmail which are to be executed for user registration API calls:

{ ... "postHooks": { "EtxUser": { "create": [ { "executionMode": "sync", "url": "http://localhost:5000/hooks/sendWelcomeEmail" }, { "executionMode": "sync", "url": "http://localhost:5000/hooks/sendGettingStartedEmail" } ] } } }

The platform only executes the sendGettingStartedEmail post-hook when a user registration request is made; the sendWelcomeEmail post-hook is ignored.

Platform developers should ensure that only a single post-hook is configured for an API call and this post-hook should execute all the desired functionality. In the future, VARA will support the execution of multiple synchronous post-hooks for an API call, so that platform developers can create modular post-hooks.

Supported Routes

The following table outlines the set of routes that post-hooks can be executed on:

Model Methods
EtxUser
  • confirm - GET /v1/users/confirm
  • create - POST /v1/users
GenericObject
  • create - POST /v1/data/class/:classname
  • find - GET /v1/data/class/:classname
  • findById - GET /v1/data/class/:classname/:id
  • updateById - PUT /v1/data/class/:classname/:id
  • deleteById - DELETE /v1/data/class/:classname/:id
Spirometry
  • create - POST /v1/spirometry
  • find - GET /v1/spirometry
  • findById - GET /v1/spirometry/:id
  • updateById - PUT /v1/spirometry/:id
  • deleteById - DELETE /v1/spirometry/:id
Steps
  • create - POST /v1/steps
  • find - GET /v1/steps
  • findById - GET /v1/steps/:id
  • updateById - PUT /v1/steps/:id
  • deleteById - DELETE /v1/steps/:id
IndoorAirQuality
  • create - POST /v1/indoorAirQuality
  • find - GET /v1/indoorAirQuality
  • findById - GET /v1/indoorAirQuality/:id
  • updateById - PUT /v1/indoorAirQuality/:id
  • deleteById - DELETE /v1/indoorAirQuality/:id
OutdoorAirQuality
  • create - POST /v1/outdoorAirQuality
  • find - GET /v1/outdoorAirQuality
  • findById - GET /v1/outdoorAirQuality/:id
  • updateById - PUT /v1/outdoorAirQuality/:id
  • deleteById - DELETE /v1/outdoorAirQuality/:id
OxygenSaturation
  • create - POST /v1/oxygenSaturation
  • find - GET /v1/oxygenSaturation
  • findById - GET /v1/oxygenSaturation/:id
  • updateById - PUT /v1/oxygenSaturation/:id
  • deleteById - DELETE /v1/oxygenSaturation/:id

Configuration

Post-hooks are configured through the application configuration endpoint, see updating custom logic configurations for more details.

Standalone Code

Standalone Code or Standalone Functions are custom functions that are executed in response to HTTP requests made to custom VARA API routes:

GET /v1/run/:name
POST /v1/run/:name
PUT /v1/run/:name
DELETE /v1/run/:name

The :name url path must resolve to a standalone function that has been configured for the application.

Configuration

Standalone Code is configured via the application configuration endpoint, see updating custom logic configurations for more details.

Custom Logic Protocol

The VARA Platform communicates with custom code (lifecycle hooks & standalone functions) via HTTP requests. The custom logic protocol defines the set of properties that are sent to custom code as well as those that can be returned from it.

Note: The platform only makes HTTP POST requests to custom code.

Version History

Date Version Effective Date Comments
08/19/2016 1.0 08/19/2016 Initial Version

Specifying A Version

The custom logic protocol version is specified by configuring the version property in the custom logic configuration, see updating configurations for more information.

Version 1.0

Custom Code Input

The following information is sent in the body of HTTP POST requests to custom code:

Note: All properties are sent, however their values are null when unavailable.

Property Pre-hook Support Post-hook Support Standalone Function Support
type {String} - indicates if custom code being executed is configured as a pre- hook, post-hook or standalone function. Y Y Y
executionMode {String} - whether custom code is being executed synchronously or asynchronously. Y Y Y
standaloneFn {String} - name of standalone function being executed N N Y
modelMethod {String} - name of the method (i.e method of the model) the lifecycle hook is being executed for. Y Y N
modelName {String} - name of the data model the lifecycle hook is being executed for. Y Y N
context {Object} - stores contextual information about a request (e.g appId, accessToken, user information, etc.) Y Y Y
context.appId {String} - application identifier Y Y Y
context.clientKey {String} - authentication key for application Y Y Y
context.accessToken {String} - access token query parameter/header passed in request Y Y Y
context.user {Object} - information about the user making the request. Contains details such as the user’s email address, email alias, first name, last name, etc. Y Y Y
context.userRoles {Array} - list of roles the requester (user) has been assigned. For example:

{ ..., "context": { ..., "userRoles": [ { id: '...', // unique identifier for role name: 'patient' } ] } }
Y Y Y
context.locale {Array} - list of ISO Standard locale codes for e.g. en-US and/or Language codes for e.g. en based on the end user’s language preference. Y Y Y

context.customState {Object} - values set by custom code for a particular API call.

For example, given an HTTP POST request to the /steps endpoint, pre-hooks for this endpoint may add values to customState that can be retrieved by successive pre and post-hooks, see custom code output for how to add values to custom state.

Note: Each API call creates a new customState object.

Y Y Y
request {Object} - stores information about the request sent to API (e.g url, query params, headers, etc.) Y Y Y
request.body {Object} - request body sent to the VARA API, for HTTP POSTs Y Y Y
request.headers {Object} - request headers sent to the VARA API Y Y Y
request.method {String} - request method executed on the VARA API Y Y Y
request.params {Object} - path parameters for the request sent to the VARA API Y Y Y
request.query {Object} - query parameters for the request sent to the VARA API Y Y Y
request.url {string} - API url requested; includes protocol, path and query parameters. Y Y Y
response {Object} - stores information about the request about to be sent from the API (e.g headers, status code, body, etc.) N Y N
response.body {Object} - response body to be sent to the client. N Y N
response.headers {Object} - response headers to be sent to the client. N Y N
response.statusCode {Integer} - response statusCode to be sent to the client. N Y N
Sample Requests to Custom Code
Lifecycle hook

The following is an example of a HTTP request VARA makes to lifecycle hooks:

POST http://example.com/postHooks/formatResponse

{ "type": "postHook", // OR prehook "executionMode": "sync", "standaloneFn": null, "modelMethod": "create", "modelName": "Steps", "context": { "appId": "...", "clientKey": "...", "accessToken": "...", "user": {...}, "userRoles": [...], "locale": [...], "customState": {...} }, "request": { "body": { "steps": 10, "duration": 20, "date": "2018-01-02", ... }, "headers": {...}, "method": "POST", "params": {...}, "query": {...}, "url": "https://api.us1.engaugetx.com/v1/steps" }, "response": { "body": {...}, "headers": {...}, "statusCode": "200" } }

Standalone Function

The following is an example of a HTTP request VARA makes to standalone functions:

POST http://example.com/functions/getCareTeam

{ "type": "standalone", "executionMode": "sync", "standaloneFn": "getCareTeam", "modelMethod": "create", "modelName": "Steps", "context": { "appId": "...", "clientKey": "...", "accessToken": "...", "user": {...}, "userRoles": [...], "locale": [...], "customState": {...} }, "request": { "body": { "steps": 10, "duration": 20, "date": "2018-01-02", ... }, "headers": {...}, "method": "POST", "params": {...}, "query": {...}, "url": "https://api.us1.engaugetx.com/v1/run/getCareTeam" }, "response": { "body": {...}, "headers": {...}, "statusCode": "200" } }

Custom Code Output

VARA uses a combination of the HTTP response status code and the response body sent from custom code to determine the appropriate response to send to a requester, see custom code response processing for more details.

The table below identifies all properties that can or should be provided in the HTTP response body by custom logic:

Property Pre-Hook Support Post-Hook Support Standalone Function Support

stopProcessing {Boolean} - indicates if an API response should be immediately returned to the requester without futher processing or execution of custom code. This value is false by default.

Notes:

  • This value must be set to true in order to send an immediate response from a pre-hook that executed successfully, see response processing for more details.
  • If a pre-hook fails i.e., returns a 4xx or 5xx HTTP status code, then this property is ignored and an error is returned to the requester, see response processing for more details.

Optional N/A N/A

statusCode {Integer} - status code API should send to client

Note: When 4xx or 5xx HTTP response codes are returned by the custom logic web service then the statusCode property becomes optional. In this case, adding a statusCode property in the body will override the default 500 response returned by VARA to the client.

Optional Required Required
headers {Object} - response/request headers to add/update Optional Optional Optional
query {Object} - query parameters to add/update Optional N/A N/A

responseMode {String} - indicates if a request/response body should be replaced/merged with the body property specified.

Supported values:

  • replace
  • merge

Notes:

  • When this property is missing the body property will be ignored.
  • This property only applies to lifecycle hooks (Pre-hooks and Post-hooks).
  • If the response cannot be merged, the default response is returned.

Optional Optional N/A

body {*} - used to replace, or can be merged with, the default API response body for lifecycle hooks (Pre-hooks and Post-hooks).

For standalone functions, this is what will be returned to the API client, if not specified or null, an empty JSON response will be returned.

For lifecycle hooks, depending on the response mode the value of this property will replace or be merged with the VARA API response.

A custom body can be returned to the client, i.e plain text, html, byte-stream, etc. provided that the appropriate headers are sent.

Optional Optional Optional

encoding {String} - encoding to use when sending non-JSON output e.g. text, html, pdf, etc. Supported values are:

  • utf8 (default)
  • base64

Note: Encoding defaults to utf-8 for non-JSON output.

Optional Optional Optional

customState {Object} - values that should be merged with the existing custom state for an API call. Properties specified here are made avaiable to other custom code executed for the API call.

Note: Values added to customState are merged to prevent the inadvertent removal of previous state.

Optional Optional Optional
Sample Response From Custom Code

200 OK ... [headers] ...
{ "statusCode": 200, // status code the API should send to requesters "headers": {...}, "responseMode": "replace", "body": { "username": "...", ... [custom properties] ... } }

Custom Code response processing

The table below outlines how VARA processes custom code responses:

Pre-hooks - Sync
Condition Pre-hook Response to VARA VARA Response to Client
Pre-hook failed to respond N/A

failOnError configuration is true (default):

  • Return 500 status code along with a CustomLogicRequestError.

failOnError is false:

  • Ignore failed hook and execute other hooks configured for the request.

Pre-hook responded with a 4xx or 5xx HTTP status code

Note: responseMode must be set to “replace” otherwise the body will be ignored.

500 Internal Server Error


{ "statusCode": 500, "headers": {...}, "responseMode": "replace", "body": {...} }

failOnError is true:

  • When statusCode is specified in the response body, return statusCode specified.
  • When statusCode is not specified, return 500 status code.
  • When responseMode is set to “replace” return the body specified. If no body is specified, return a CustomLogicRequestError.
  • When responseMode is not set or is set to a value other than “replace”, return a CustomLogicRequestError.

failOnError is false:

  • Ignore failed hook and execute remaining hooks.

Pre-hook responded with a 2xx or 3xx HTTP status code and stopProcessing is set to true

200 Ok

{ "stopProcessing": true, "statusCode": 200, "headers": {...}, "responseMode": "replace", "body": {...} }

Return statusCode specified or default to 400 status code. Update response headers, if headers are specified.


When responseMode is set to “replace”, return the body specified. Once the appropriate headers are passed, any custom body (html, JSON, etc.) can be returned to the client. If no body is specified, return request body.


When responseMode is set to “merge”, the body is merged with the request body. If no body is specified, return request body.


When responseMode is not set or set to an invalid value, return request body.

Pre-hook responded with a 2xx or 3xx HTTP status code and stopProcessing is set to false or is not set

200 Ok

{ "stopProcessing": false, "headers": {...}, "query": {...}, "body": {...} }

Update request headers, query parameters and/or body, if they are specified and process the request with the updated properites.

Note: The statusCode property is ignored when the stopProcessing property is false (or not set) as request processing will be continued instead of immediately returning an API response.

Post-hooks - Sync
Condition Post-hook Response to VARA VARA Response to Client
Post-hook failed to respond N/A

failOnError is true:

  • Return 500 status code along with a CustomLogicRequestError.

failOnError is false:

  • Return original API response.

Post-hook responded with a 4xx or 5xx HTTP status code

Note: responseMode must be set to “replace” otherwise the body will be ignored.

500 Internal Server Error

{ "statusCode": 500, "headers": {...}, "responseMode": "replace", "body": {...} }

failOnError is true:

  • When statusCode is specified in the response body, return statusCode specified.
  • When statusCode is not specified, return 500 status code.
  • When responseMode is set to “replace”, return the body specified. If no body is specified, return a CustomLogicRequestError.
  • When responseMode is not set or is set to a value other than “replace”, return a CustomLogicRequestError.

failOnError is false:

  • Return original API response.
Post-hook responded with a 2xx or 3xx HTTP status code and the response body does not contain a statusCode property 200 Ok

{ "headers": {...} "responseMode": "merge", "body": { ... [custom properties] ... } }
Return 500 response along with a InvalidCustomLogicResponseError as the post-hook did not indicate how VARA should respond.
Post-hook responded with a 2xx or 3xx HTTP status code and the response body contains a statusCode property 200 Ok

{ "statusCode": 200, "headers": {...}, "responseMode": "replace", "body: { "username": "...", ... [custom properties] ... } }

Return HTTP response based on properties specified in body of request (e.g response headers, body, status code, etc.)


When the responseMode property is set to “replace” return the body specified. Once the appropriate headers are passed, any custom body (html, JSON, etc.) can be returned to the client. If no body is specified, return the original response body.


When responseMode is set to “merge”, the body is merged with the original response body. If no body is specified, return the original response body.


Standalone Functions - Sync
Condition Standalone Function Response to VARA VARA Response to Client
Standalone function failed to respond N/A Return 500 response along with a CustomLogicRequestError.
Standalone function responded with a 4xx or 5xx HTTP status code 500 Internal Server Error

{ statusCode: 500, headers: {...}, body: {...} }
Update response status code and headers if statusCode and headers are specified, return body specified. If no body is specified, return a CustomLogicRequestError.
Standalone function responded with a 2xx or 3xx HTTP status code and the response body does not contain a status code 200 Ok

{ headers: {...}, body: {...} }
Return 500 response along with a InvalidCustomLogicResponseError as the standalone function did not indicate how VARA should respond.
Standalone function responded with a 2xx or 3xx HTTP status code and the response body contains a statusCode 200 Ok

{ statusCode: 200, headers: {...}, body: {...} }
Update response status code and headers if statusCode and headers are specified, return body specified. If no body is specified, return an empty object.
Standalone Functions, Pre-hooks & Post-hooks - Async (future)
Condition Custom Code Response to Etx Etx Response to Client
Custom code failed to respond or responded with failure N/A Platform continues normal processing. Failure is logged.
Custom code ran successfully 200 Ok Platform continues normal processing. Successful execution is logged.

Deploying Custom Code

Custom code is deployed to a VARA application via git push to the relevant git remote, see deploying custom code for more details. Custom code must be implemented using the Custom Logic SDK. Currently only a Node.js SDK is implemented, however, support for more languages will be coming soon.

Custom Logic code deployed to the VARA platform is only accessible to the VARA API, however, external requests can be made from the deployed code. The VARA API communicates with the Custom Logic Backend over HTTP using the Custom Logic Protocol.

Custom code deployment
Custom logic code deployment. Custom logic code is deployed to an isolated network which is only accessible to the VARA API, however, external HTTP requests can be made from custom code.

Updating Configurations (Temporary)

Custom logic configurations may be updated by performing the following request:

curl -X PUT \
  '{{BASE_URL}}/v1/configs?baseurl=https://example.com' \
  -H 'Client-Key: <Master Key>' \
  -H 'content-type: application/json'


{
  "customLogic": {
    "version": "1.0",
    "functions": {...},
    "preHooks": [...],
    "postHooks": {...},
    "actions": {...}
  }
}
PUT /v1/configs

Required Auth Parameters: Master Key

Note: The /v1/configs API is temporary and should not be programmatically accessed. In the future, APIs will be provided for each custom logic configuration.

Query Parameters

Parameter Type Description
baseurl String (optional) URL to use for custom logic configurations being updated. The baseurl replaces URLs in the custom logic configuration payload that match http://localhost:3000 or http://localhost:5000. Custom logic configurations that are not specified in the request payload will remain unchanged.

Body Properties

Parameter
Type
Description
customLogic
Object
Set of custom logic configurations for an application, for e.g., protocol version, pre-hooks, post-hooks etc.
version
String
Version of the custom logic protocol to use, see list of supported versions here.
Object

Set of standalone functions for an application

Array

Pre-hook configurations

Object
Post-hook configurations
Object
Custom logic action configurations
Standalone Functions

Sample Standalone function configuration

{
  ...
  "functions": {
    "generateReport": {
      "executionMode": "sync",
      "url": "http://localhost:5000/functions/generateReport",
      "httpMethods": [ "POST" ]
    },
    "propellerWebHook": {
      "executionMode": "sync",
      "url": "http://localhost:5000/functions/propellerWebHook",
      "httpMethods": [ "POST" ]
    },
    "customProxy": {
      "executionMode": "sync",
      "url": "http://localhost:5000/functions/customProxy",
      "supportAllMethods": true
    }
  }
}
Parameter
Type
Description
functions
Object

Each top-level key in this object represents the configuration of a standalone function. Given the following configuration:


{ ... functions: { generateReport: {...}, propellerWebHook: {...} } }

The generateReport and propellerWebHook functions can be executed via the /run/:generateReport, /run/:propellerWebHook endpoints respectively.

{nameOfFunction}
String
Contains configurations for a specific standalone function. The value of this key will be the name of the standalone function.
String

Indicates if the standalone function should be executed synchronously or asynchronously.

Supported values are:

  • sync

url
String
URL for standalone function. The platform only makes HTTP POST requests to this URL.
httpMethods
Array
List of HTTP methods (GET, PUT, POST, etc.) on the /run/:name endpoint that the standalone function supports.
supportAllMethods
Boolean

May be used instead of the httpMethods configuration to enable the standalone function to support all HTTP request methods executed on the /run/:name endpoint.

Pre-hooks

Sample Pre-hook configuration

{
  ...
  "preHooks": [
    {
      "resource": {
        "url": "/v1/steps",
        "httpMethods": [ "POST" ]
      },
      "executionMode": "sync",
      "url": "http://localhost:5000/functions/validateSteps"
    },
    {
      "resource": {
        "url": "/v1/steps",
        "httpMethods": [ "POST" ]
      },
      "executionMode": "sync",
      "url": "http://localhost:5000/functions/countSteps",
      "failOnError": false
    },
    {
      "resource": {
        "url": "/v1/data/class/Goals",
        "httpMethods": [ "*" ]
      },
      "executionMode": "sync",
      "url": "http://localhost:5000/functions/captureStatistics"
    }
  ]
}
Parameter
Type
Description
preHooks
Array

Pre-hooks are configured as objects in the preHooks array. Pre-hooks are executed based on the order of the configurations in the array, see pre-hook execution for more details.

resource
Object
Configurations for the API URL path(s) and request method(s) that the pre-hook(s) should be executed on.
url
String

API URL path that the pre-hook should be executed on, see supported routes for the set of URL paths that may be used. URL paths must be prefixed with the API version, for example: /v1/users.

Wildcard characters may be added to URL paths to execute pre-hooks on all matching subpaths. For example, the URL path /v1/data/class/* will match all requests made to custom objects whereas /v1/class/Acitivity/* will match all requests made to the custom Activity object.

Note: the /v1/* URL path can be used to match all version 1 API requests.

httpMethods
Array

List of HTTP methods (GET, PUT, POST, etc.) that the pre-hook supports. A wildcard character may be added to the array to support all HTTP methods.

Note: When a wildcard character is used, all HTTP methods for the resource will be matched regardless of the other HTTP methods specified in the array.

String

Indicates if the pre-hook should be executed synchronously or asynchronously.

Supported values are:

  • sync

url
String
URL for the pre-hook.
failOnError
Boolean
Indicates if the pre-hook should return an error to the requester, or if API processing should continue when it fails (returns a 4xx or 5xx HTTP status code) or does not respond. This configuration is true by default, which results in a CustomLogicRequestError being returned to the requester for both error scenarios, see response processing for more details.
Post-hooks

Sample Post-hook configuration

{
  ...
  "postHooks": {
    "GenericObject": {
      "findById": [
        {
          "executionMode": "sync",
          "url": "http://localhost:5000/functions/getMedicationSideEffects"
        }
      ]
    },
    "Steps": {
      "create": [
        {
          "executionMode": "sync",
          "url": "http://localhost:5000/functions/addMotivationalMessage"
        }
      ]
    }
  }
}
Parameter
Type
Description
postHooks
Object

Post-hooks are configured using model names and methods. For example, to configure a post-hook for the user registration request, the following configuration would be used:

{ ... "postHooks": { "EtxUser": { "create": [ { "executionMode": "sync", "url": "http://localhost:5000/hooks/sendWelcomeEmail" } ] } } }

where the model name is EtxUser and the method name is create, see supported routes for the set of model names and methods that can be used.

{ModelName}
Object
Contains post-hooks for methods on the data model. {ModelName} should be formatted in PascalCase and must be one of the model names documented here.
{methodName}
Array
Contains post-hooks for specific method of the data model, see supported post-hooks for a list of supported method names.
String

Indicates if the post-hook should be executed synchronously or asynchronously.

Supported values are:

  • sync

url
String
URL for the post-hook.
failOnError
Boolean
Indicates if the post-hook should return an error to the requester, or if API processing should continue when it fails (returns a 4xx or 5xx HTTP status code) or does not respond. This configuration is true by default, which results in a CustomLogicRequestError being returned to the requester for both error scenarios, see response processing for more details.
Custom Logic Actions

Custom logic actions are functions that that may executed whenever a set of conditions are met for an engagement engine rule, see engagement engine for more details.

Sample Custom logic action configuration

{
  ...
  "actions": {
    "sendEmailNotification": {
      "url": "http://localhost:5000/actions/sendEmailNotification"
    },
    "sendPushNotification": {
      "url": "http://localhost:5000/actions/sendPushNotification"
    }
  }
}
Parameter
Type
Description
actions
Object

Stores the set of custom logic actions for an application. Each top-level key in this object represents the configuration of a custom logic action. Given the following configuration:


{ ... "actions": { "sendEmailNotification": {...}, "sendPushNotification": {...} } }

sendEmailNotification and sendPushNotification are custom logic actions which may be executed by the engagement engine, see the engagement engine for more details.

{nameOfAction}
Object
Contains configurations for a specific custom logic action. The value of this key will be the name of the action.
url
String
URL to execute custom logic action. The platform only makes HTTP POST requests to this URL, see action input for more details.

Security (future)

In the future, requests to custom code will signed to ensure only authorized custom code can verify the request and decrypt its payload.

Engagement Engine

Introduction

The engagement engine allows developers of an application to define business rules for engaging users based on actions they’ve taken or the lack thereof. For example, creating a rule to send a notification to a user if they have not verified their account five (5) days after registering.

The engagement engine is configured using rules. Rules are composed of:

  1. conditions: criteria that evaluates to true or false. Example conditions include:

    • the occurrence of an event (e.g user registration)
    • the presence/absence of data over a specified time period (e.g user has not verified their account five (5) after registering their account)
    • a historical set of events that have occurred over a period of time (e.g user has logged data for ten unique (10) days)

  2. actions: processes that can be executed in response to a rule’s conditions being met. An action may be user defined (e.g Custom Logic Action) or provided by the platform out of the box (future).

Simple inactive rule
Engagement engine rule to illustrate rule evaluation and the execution of actions

{
  "name": "user-inactive",
  "conditions": {
    "any": [
      {
        "all": [
          {
            "state": "verificationDate",
            "operator": "equal",
            "value": null
          },
          {
            "state": "registrationDate",
            "operator": "daysAgoEqual",
            "value": 5
          }
        ]
      },
      {
        "state": "lastDataModificationDate",
        "operator": "daysAgoEqual",
        "value": 14
      }
    ]
  }
  "actions": [
    {
      "type": "clAction",
      "name": "sendEngagementEmail"
    }
  ]
}

When one or more conditions evaluate to true, the set of actions for the rule is executed. For example, given the Simple inactive rule to the right, the sendEngagementEmail action will be executed when:

  1. A user hasn’t verified their account within 5 days of registering

  2. A user hasn’t created or updated any data (e.g recorded or updated symptoms) for 14 days

Engagement engine components
Core components of the engagement engine. The engagement engine is driven by rules which are composed of conditions and actions. Actions are executed when one or more conditions are met.

A rule is composed of at least one condition and an action. All actions must have a name and a type. Only custom logic actions are supported, with more actions coming soon.

Note: Please see our glossary for more information on the concepts of the engagement engine.

A Simple rule.
The following rule will evaluate to true when the number of days transpired since the user verified their account is equal to 5

{
  "name": "user-verified-5-days-ago",
  "conditions": {
    "all": [
      {
        "state": "verificationDate",
        "operator": "daysAgoEqual",
        "value": 5
      }
    ]
  },
  "actions": [
    {
      "type": "clAction",
      "name": "sendAccountVerificationEmail"
    }
  ]
}

A more complex rule.
The following example has a number of conditions on how a user can become inactive

{
  "name": "user-inactive",
  "conditions": {
    "any": [
      {
        "all": [
          {
            "state": "verificationDate",
            "operator": "equal",
            "value": null
          },
          {
            "state": "registrationDate",
            "operator": "daysAgoEqual",
            "value": 5
          }
        ]
      },
      {
        "all": [
          {
            "state": "verificationDate",
            "operator": "equal",
            "value": null
          },
          {
            "state": "hasProfile",
            "operator": "equal",
            "value": true
          },
          {
            "state": "verificationDate",
            "operator": "daysAgoEqual",
            "value": 7
          }
        ]
      },
      {
        "all": [
          {
            "state": "verificationDate",
            "operator": "equal",
            "value": null
          },
          {
            "state": "lastDataModificationDate",
            "operator": "daysAgoEqual",
            "value": 14
          }
        ]
      }
    ]
  },
  "actions": [
    {
      "type": "clAction",
      "name": "sendInactiveUserNotification"
    }
  ]
}

Rules

A Rule is comprised of a set of conditions that can be compounded with an all (and) or an any (or) operator.

States

A state represents data about the user that is tracked and updated as through the lifetime of the application. Here is a list of states that are currently supported:

State Name Description
hasProfile {Boolean} Indicates whether or not the user has created a profile. By default (if there is no configuration) this will be set to true when the user verifies their account. To change this behaviour, you can provide a configuration for a “profile model” so that when an instance is created, the value of the hasProfile state will be set to true.

You can have your application’s configuration updated by submitting a ticket with the name of the model
verificationDate {Date} The date the user verified their account
lastDataModificationDate {Date} The last date the user modified (created or updated) some data. By default all models will update this date when they are modified.

Model instances that should not update this value when created or updated can be configured in the application. You can submit a ticket with the list of Models that should update this state
registrationDate {Date} The date the user registered
uniqueDataModifiedDays {Integer} The total number of unique days a user has modified data. This utilizes the same configuration that is set for the lastDataModificationDate state
event {String/Object} An event that happens on the platform. Possible values:

userRegistered - When a user creates an account

accountVerified - When a user verifies their account.

accountDeleted - When a user has closed their account. This will be triggered whether or not a user chooses to delete all their data. Read more on closing accounts.

You can also indicate whether or not it should execute for hard delete requests (when a user chooses to delete all their data):

{
"name": "accountDeleted",
"options": {
"hardDelete": true
}
}

Operators

To perform an evaluation

Operator Name Description
daysAgoEqual The number of days transpired since the value of the state (a date) must be equal to the specified value. The calculation of the number of days is always floored.
daysAgoIsLessThan The number of days transpired since the value of the state(a date) must be less than the specified value. The calculation of the number of days is always floored.
daysAgoIsMoreThan The number of days transpired since the value of the state(a date) must be greater than the specified value. The calculation of the number of days is always floored.
equal State must equal to the specified value. Uses: ===
notEqual State must not equal the specified value. Uses: !==
greaterThan State must be greater than the specified value
greaterThanInclusive State must be greater than or equal to the specified value
lessThan State must be less than the specified value
lessThanInclusive State must be less than or equal to the specified value
contains State (an array) must include the specified value
doesNotContain State (an array) must not include the specified value
in State must be included in the specified value (an array)
notIn State must not be included in the specified value (an array)

Custom logic actions

A custom logic action is a function that is defined by developers and configured to execute whenever a set of conditions are met for a rule.

When configuring custom logic actions for a rule, the action type must be set to clAction and the name of the action should be set to a configured custom logic action, see example rule configuration below:

{ "name": "user-verified-5-days-ago", "conditions": { "all": [ { "state": "verificationDate", "operator": "daysAgoEqual", "value": 5 } ] }, "actions": [ { "type": "clAction", "name": "sendAccountVerificationEmail" } ] }

Configuration of CL Actions

Similar to other types of Custom Business Logic, custom logic actions are configured through the application configuration endpoint, see updating custom logic configurations for more details.

Input for CL Actions

Given that custom logic actions are triggered as a result of engagement engine rules passing, authentication information such as a user’s access token or client keys are NOT available as input for CL actions.

As with other types of custom business logic, communication is done over HTTP where POST requests are sent to the custom logic action with the following data in the request body:

REST API Input

// Custom logic REST API input/request payload
{
  event: {
    name: "user.account.registered",
    date: "2017-04-12T13:00:00.000Z",
    state: {
      previous: {
        registrationDate: "...",
        verificationDate: "...",
        lastDataModificationDate: "...",
      },
      current: {
        registrationDate: "...",
        verificationDate: "...",
        lastDataModificationDate: "...",
      }
    },
    meta: {
      locale: ["..."]
    }
  },
  user: {
    id: "...",
    username: "...",
    email: "...",
    ...
  },
  userRoles: [
    {
      id: '...', // unique identifier for role
      name: 'patient'
    },
    ...
  ]
};
Field Description
event {Object} Details about an event that was triggered by the platform
event.name {String} Name of the event that occurred, for example “user.account.registered”
event.date {ISODate} Date the event occurred, this is represented as an ISO Date string with GMT time zone for eg. “2017-04-12T13:00:00.000Z”
event.state {Object} Contains information being tracked for a user, for example their registration date, account verification date, last date they modified some data, etc. For more information on states, see here
event.state.previous {Object} Values for the user state before the event occurred
event.state.current {Object} Values for the user state just after the event occurred (includes changes made as a result of the event)
event.meta {Object} Additional data about the event
event.meta.locale {[String]} The user’s preferred language based on the configured language detection order
user {Object} Information about the user the event was triggered for (for e.g. the user’s email address, first & last name, etc.). This information gives custom logic actions the ability to send follow-up emails to users who have removed their accounts and their data - in which case a lookup of the user account will not return any information.
userRoles {Array} List of roles the requester (user) has been assigned.The format of each role in this list is the same as the context.userRoles property for custom functions and lifecycle hooks.

Output for CL Actions

Each Custom logic actions sends a response the Tx Platform when its operation is complete i.e success or failure. The response returned to the Tx platform will be logged and developers will be alerted whenever an error response is returned.

REST API Output

Scenarios
Action executed successfully

Expected response from Custom logic action:

200 OK
Unable to communicate with external API

Expected response from Custom logic action:

503 Service Unavailable

    {
        "error": {
            ...
        }
    }

Messaging

Email

We need to support sending emails for user notifications, for example to indicate to a user that a long running job has finished. This would need to be a secure email to prevent the disclosure of PHI or PII information.

In the future, the app will allow secure email viewing as a fallback for email verification or login.

curl -X POST \
  /v1/email/send \
  -H 'Content-Type: application/json' \
  -H 'Client-Key: <Master Key>' \
  -d '{
  "to": "foo bar<foo@bar.com>",
  "from": "loom@medullan.com",
  "subject": "Please confirm your email account",
  "html": "<h1>Please confirm your email address to complete your study registration and download App. Click on the button below.</h1><a href=\"/v1/user/confirm\"><Confirm Now</a>"
}'

Send Email

Required Auth Parameters: APP ID, Master Key

The details of the email can be supplied in the request body or in the X-Tx-Email (name subject to change) header.

POST /v1/email/send
curl -X POST \
  /v1/email/send \
  -H 'Client-Key: <Master Key>' \
  -H 'X-Tx-Email: {\"to\":\"foo bar<foo@br.com>\",\"from\":\"loom@medullan.com\",\"subject\":\"Please confirm your email account\",\"html\":\"<h1>Please confirm your email address to complete your study registration and download App. It\'s easy - Just click on the button below.</h1><a href=\"/v1/user/confirm\\">Confirm Now</a>If you have questions, contact us at support@email.com\"}' \
  -H 'content-type: multipart/form-data;' \
  -F file=@my-file-attchmnt.txt

Adding attachments

To send an email with attachments, perform a POST request to the /v1/email/send endpoint and attach the necessary file(s).

Push Notifications

Send push notifications to Android and IOS devices.

curl -X POST \
  /v1/notifications/send \
  -H 'Client-Key: <Master Key>' \
  -d '{
  "users": ["57acf77e1a7e7f03d319f226"],
  "message": {
   "title": "We did it!",
   "body": "We sent a push notification to a device!",
   "sound": "default"
  },
  "payload": { "foo": "bar"}
}'

Send Notification

Required Auth Parameters: APP ID, Master Key

Sends a push notification to the specified user(s) via device tokens.

POST /notifications/send

Response

{
  "result": {
        "totalFailed": 1,
        "totalSuccess": 2
  }
}

The response result will indicate the total number of devices the notification was sent to and the total that failed. These failures indicate the number of device tokens, associated with the user, that were not found on the FCM server. See more details on the lifecycle of FCM’s device tokens

Store Token

In order to send push notifications to a device or user, the device must register it’s token with the server. This endpoint will implicitly get the user id from the calling user’s authentication token. As depicted below there are two properties that are sent in this request. See descriptions below.

Property Description
token (String) The token generated by the push notification provider
type (String) An optional value that describes which device the token belongs to. These values can be either ‘android’ or ‘ios’
{
  "token": "F23523",
  "type": "android"
}


POST /notifications/token

or

POST /notifications/token
{
  "token": "F23523",
  "type": "ios"
}

Internationalization (i18n)

The platform allows applications to be configured to support multiple languages. Serving localized content is done by referencing developer defined resources in email and form template configurations. It is enabled by default, and is applied when there are one or more languages defined for the desired resource(s).

Steps to Utilize Internationalization

Resources

Resources are values that can be referenced by its key, to be used in the configuration of an application. Resources of the same key can be created with several locales in order to be used by the i18n feature. Therefore, when referenced, the i18n feature will choose the appropriate value based on the locale set on the request.

Resources can be referenced in places like the application’s:

See:

ISO Standard locale code

All locale codes used in the configuration, or to later define resources, must be an ISO Standard locale code (eg. en-US) or Language code (eg. en).

Locale Codes are the hyphenated combination of an ISO Standard Language Code and an ISO Standard Country Code.

Example: en-US where en is the Language Code and US is the Country Code (Region Code).

Please Note: en-US and en are treated independently, therefore, resources can be created for both.

i18n Configuration

To add the supported languages and fallback/default language(s) you must configure them using the locale configuration endpoint.

curl -X POST \
  '{{BASE_URL}}/v1/config/resources' \
  -H 'content-type: application/json' \
  -d '{
        "language": {
          "default": ["en"],
          "detection": {
            "order": ["user", "querystring", "header"]
          },
          "fallback": {
            "pt": ["es", "en-US"],
            "en-GB": ["en-US"],
            "sv-FI": ["sv-SE"]
          }
        }
      }'
GET /v1/config/locale
PUT /v1/config/locale

Access: Master Key

Configurations

Property
Type
Description
language.default
String Array
required
The list of languages to use when resources are not found for the requested locales and their configured fallback locale codes. See locale codes
language.detection.order
String Array
default: ["user", "querystring", "header"]
The order (left to right) in which locale codes are detected in a request.

user - The locale preference stored for the user (user.locale property)

querystring - When the locale query string parameter is specified

header - Looks for the preferred languages in the Accept-Language header eg. en-GB,en-US;q=0.9,en;q=0.8
language.fallback
Object
Configuration for fallback languages. When translating and the requested locale:key pair is not available in the list of defined resources, the fallback configured for the desired locale will take priority over any locales defined in language.default.

For example, with "pt": ["es", "en-US"] as an entry in the fallback configuration, if a resource for Portuguese is not found during translation, it will fallback to Spanish to find an entry, or US English if it is not found in Spanish. Otherwise it will search the configured default language.
{
  "error": {
    "code": "MISSING_RESOURCES_ERROR"
  }
}


Please Note: When a resource is not found for a requested language or in any of its configured fallbacks, an internal server error (500) with code MISSING_RESOURCES_ERROR will be raised and returned in response to a request that requires resource translation

Define and Retrieve Resources

Defining resources is an essential part of the i18n feature. Below are the various ways it can be achieved.

Multiple Resource Sets

curl -X POST \
  '{{BASE_URL}}/v1/config/resources' \
  -H 'content-type: application/json' \
  -d '{
        "en": {
            "resend-verification-email.subject": "Please verify your email",
            "resend-verification-email.body": "RESEND: <%= verifyHref %>"
        },
        "es": {
            "resend-verification-email.subject": "Verifica tu correo electrónico",
            "resend-verification-email.body": "REENVIAR: <%= verifyHref %>"
        }
    }'

To add resources for the supported languages you must configure them using the resources configuration endpoint.

POST /v1/config/resources

Access: Master Key

Single Resource Set

curl -X POST \
  '{{BASE_URL}}/v1/config/resources/es' \
  -H 'content-type: application/json' \
  -d '{
        "resend-verification-email.subject": "Verifica tu correo electrónico",
        "resend-verification-email.body": "REENVIAR: <%= verifyHref %>"
    }'
GET /v1/config/resources/:locale
PUT /v1/config/resources/:locale
POST /v1/config/resources/:locale

Access: Master Key

URL Parameter Description
:locale The language or locale code for the resource

Retrieve Localized Value for Key

curl -X GET \
  '{{BASE_URL}}/v1/config/resources/es/resend-verification-email.subject' \
  -H 'content-type: application/json' 
{
 "value": "Verifica tu correo electrónico",
 "locale": "es"
}
GET /v1/config/resources/:locale/:key
URL Parameter Description
:locale The language or locale code for the resource
:key The key for the desired resource

Email Template Examples

{
 "emailVerified": {
   "subject": "Please verify your email",
   "body": { 
       "res": "emailVerified.body"
    }
 }
}

Form Template Examples

{
 "passwordReset": {
    "webpage": "http://localhost:9000/#/",
    "formHtml": {
      "res": "passwordReset.formHtml"
    }
  }
}

Configuring Email and Form Templates for i18n

When defining email and form templates, resources can be used to generate the content of the templates. The example shows how this is achieved when defining the body of the account verification email

As depicted, the template properties can either be set to a string literal (no resource look up will be performed) or an object with the res property set to the key of the resource value to use.

Look up for these values will use the following logic:

3rd Party Integrations

Oauth Integrations has two sets of configurations:

  1. Application Wide configs store AppKeys and AppSecrets and remain static. They a set in the appConfig which is the same type of configuration used to set up Custom Logic hooks.

  2. User Specific configs store apiKeyTokens, authTokens, DeviceID’s, providerUserId’s, userName etc and can be updated by the OAuth workflow if necesary (for eg. refresh token).

Foobot

AppConfig Configuration Example:

"config" : {
    "customLogic" : {

    },
    "oauth" : {
      "foobot" : {
        "appKey" : "fB7pvZA***********",
        "appSecret" : "apXRfB7pvZA***********"
      }
    }
  }

Create user integration for user

A user integration is required to use the proxy type for that user.

POST /v1/users/:id/integrations
{
    "type":"foobot",
  "userId": 13393,
  "userName": "julliette@medullan.com",
  "devices": [{
      "uuid": "2402466F449044E0",
      "userId": 13393,
      "mac": "ACCF23C57C6A",
      "name": "Julesbot"
  }],
  "apiKeyToken": "...",
  "refreshToken": "..."
}

UserIntegration Response

{
  "type": "foobot",
  "id": "58d3832c5e3108301506c683",
  "ownerId": "58d10816e8ecca65c397d047",
}
Status Meaning Description
200 OK The request was successful

Proxying to an Integration API

The /v1/proxy endpoint relays your request to the Foobot API and does a few extras. Pass the relative URI of the resource in the path query string parameter. The same method and body used to request /v1/proxy will be used to request Foobot resources for eg. to create post data simply POST the data to /v1/proxy with the data for the new resource in the body.

Finally either an accessToken or the impersonate parameter MUST be provided to scope the request to the appropriate user. This allows for placeholders in the path parameter for e.g. {uuid} in /v2/device/{uuid} to be replaced by values stored in the UserIntegration collection for that user.

GET /v1/proxy
POST /v1/proxy
PUT /v1/proxy
DELETE /v1/proxy
Query Parameters
type {String}

Required
true

Description
The oauth application type to proxy calls to. Available types are
foobot, withings
path {String}

Required
true

Description
The url for the integration’s API that VARA will proxy calls to. This must be uri encoded to to ensure the entire path is interpreted properly.

Example
If the path of the integration api we want to proxy to is
/v1/user/distance then we must set
path=%2Fv1%2Fuser%2Fdistance

Foobot Example

POST /v1/proxy?type=foobot&path=/v2/device/{uuid}/datapoint/2017-03-10T12:20:30.45+01:00/2017-03-10T13:20:30.45+01:00/300/
{
  "uuid": "2402466F449044E0",
  "start": 1489148430,
  "end": 1489151730,
  "sensors": [
    "time",
    "pm",
    "tmp",
    "hum",
    "co2",
    "voc",
    "allpollu"
  ],
  "units": [
    "s",
    "ugm3",
    "C",
    "pc",
    "ppm"
  ]
}

The /v1/Proxy endpoint will inject the following into the path:

Template Pattern Source
{{uuid}} UserIntegration.devices[0].uuid
{{userId}} UserIntegration.userId
{{userName}} UserIntegration.userName
{{timezonecity}} UserIntegration.timezonecity
{{timezonecontinent}} UserIntegration.timezonecontinent

Withings AppConfig Example

"config" : {
  "customLogic" : {

  },
  "oauth" : {
    "withings" : {
        "appKey" : "medullan_withings_app_key",
        "appSecret" : "apXRfB7pvZA*************"
    }
  }
}

Withings UserIntegration Example

{
  "type" : "withings",
  "oauthToken" : "akPkNqDGsyDCX****wxbI",
  "oauthSecret" : "eyJhbGciOiJIUzI1****zwJ"
}

Withings

User Integration Example

See Foobot Integration docs on how to create a user integration. The process is the same, however the body of the POST will be specific to the values required for Withings.

Sample Withings Proxy Call

POST call to /measure?action=getmeasures endpoint (note the Withings path must be URL encoded):

POST /proxy?type=withings&path=%2Fmeasure%3Faction%3Dgetmeasures

OpenId Connect (OIDC)

The platform allows applications to be configured to support multiple OIDC providers.

Steps to Utilize OIDC

Configure OIDC

Configuring an OIDC provider is done by sending a POST request. To demonstrate this, we will make references to a popular provider, Auth0, in the examples below.

Access: Master Key

curl -X POST \
  '{{BASE_URL}}/v1/config/oidc' \
  -H 'content-type: application/json' \
  -d '{
        "name": "auth0-client",
        "client": {
            "issuer": "https://my-oidc-provider.auth0.com/",
            "client_id": "vmghcvytfyt66t66fgukBFFEZN",
            "client_secret": "gdfFBDZGFnnfhdEZjiuy657t89",
            "redirect_uri": "https://my-site.com/callback/auth0-client"
        }
    }'
POST /v1/config/oidc
GET /v1/config/oidc
GET /v1/config/oidc/:name
PUT /v1/config/oidc/:name
DELETE /v1/config/oidc/:name

Configurations

Property
Type
Description
client_id
String
required
The Client Id provided by the OIDC host.
client_secret
String
The Client Secret provided by the OIDC host
redirect_uri
String
The Redirect URI provided to the OIDC host configuration. This is used as a verification to ensure that only the authorized callback url can process the token or code return from your OIDC host.
options.clockTolerance
Number
default: 0
set a clock tolerance (in seconds) which allows a time buffer given the servers communicating have a clock skew when validating tokens
options.userQuery.type
String
default: "or"
The query type to perform. This can be one of two values: "or", "and"
options.userQuery.filters
Array
default: [ Filter ]

Filters are used to map a user profile provided by the OIDC handshake to a user in VARA. There can be one or more filter objects used to properly match users. By default, an array with a single filter object is used that maps the email properties on the VARA user and the OIDC profile.

Filter Props:
varaProfileProp: The property name on a VARA user to be used to map to the provided profile.
default: "email"

providerProfileProp: The property name on the provided profile to be used to map to the VARA user.
default: "email"

replaceInProviderValue: value to replace in OIDC property before doing a comparison.
eg. Attempting to match {"id": "123"} with {"id": "auth0|123"}. We want to remove the string auth0| before comparing to get the proper result.
default: ""

Get Authorization URL

curl -X GET \
  '{{BASE_URL}}/v1/authenticate/auth0-client?response_type=code+id_token&state=1234&nonce=5678' \
  -H 'content-type: application/json' 
{
    "url": "https://my-oidc-provider.auth0.com/login?state=vytf67&client=vmghcvytfyt66t66fgukBFFEZN&response_type=code%20id_token&state=1234&nonce=5678"
}

The Authorization Url of the configured OIDC provider is the location where a user will go to login with their credentials. Once received, the user can be navigated to the url. Given a successful login, the user will be redirected to the Redirect URI provided with a single-use code to be given to VARA to perform further authorization.



Please note that any query params used on the endpoint will be transferred to the generated Authorization URL. As seen in the example, the query params response_type, state, and nonce in the result have the same values (encoded where necessary) as the query params for the request sent to VARA.

Access: Client Key

GET /v1/authenticate/:name

Authenticate with VARA

curl -X POST \
  '{{BASE_URL}}/v1/authenticate/auth0-client' \
  -H 'content-type: application/json' \
  -d '{ 
      "code": "dff8t7t978ggjyguy",
      "state": "1234",
      "code": "5678",
      "redirect_uri": "https://my-site.com/callback/auth0-client"  
  }'
{
  "id": "TCME8gdwCH23kl0W1uBBlo",
  "ttl": 86398292,
  "created": "2018-09-07T12:25:35.709Z",
  "userId": "58d150d2ef8ab50012f9363b",
  "auth0-client": {
    "access_token": "a2aTsoVcoEUvii4uxshqDZRgaQQBHzw5",
    "id_token": "eyJ0eXAiOiJKOdyJ9.eyJodHRwcCT0x0fQ.LtC3x8MAAiHl7lg",
    "expires_at": 1536409534,
    "token_type": "Bearer"
  },
  "source": "auth0-client"
}

After receiving a code from the OIDC provider, the code should be forwarded to VARA to verify the user and return an accessToken to the client application. The access token has two additional properties auth0-client and source which are used to identify the OIDC provider used to authorize the user and give additional information from the exchange to the client application. Properties accepted in the payload are code, redirect_uri, state, and nonce.

Access: Client Key

POST /v1/authenticate/:name

Reporting

GET /trends/?class=...&startDate=...&endDate=...&timezone=...

Query Parameters

Parameter Validations Description
class type: String required: true The class to generate the trend for. A comma delimited list of classes may also be specified. Classes can be specified in camelCase or PascalCase.

Supported classes are:
  • Steps
  • Spirometry
  • IndoorAirQuality
  • OutdoorAirQuality
  • OxygenSaturation
  • custom.{{GenericObjectClass}}


Note: Trends may be generated for custom objects by specifying the properties below (see rows below for more information):
* dateField * trendField * trend

These values will be used to generate trends for EVERY generic object (custom.GenericObjectClass) specified in the class parameter.
startDate type: ISO Date required: true The start date of the summary (Inclusive)
endDate type: ISO Date required: true The end date of the summary (Inclusive)
timezone type: String required: true Name of the client’s current timezone
bucketSize type: String default: ‘day’ Grouping for aggregated values. This determines the number of values produced in the values array, for example if month is specified then each item in the “values” array will represent a month’s worth of data.

Supported values are:
  • day
  • month
  • year
  • filter type: JSON Owner Id to filter shared objects e.g. { “where”: { “ownerId”: OWNDER_ID }}

    Note: Currently, only an owner filter is will work for this endpoint, other filters are ignored
    shared Type: Boolean Enable sharing context
    dateField (NEW) type: String The date property on the GDO class that should be used for generating trends for the class(es) specified.
    trendField (NEW) type: String The name of the property on the GDO class that should be analyzed and/or processed for gaining insights when generating trends for the custom class(es) specified.
    trend (NEW) type: String The type of analysis and/or processing to perform when generating trends for the GDO class(es) specified. Supported values are: sum avg

    Request

    GET /trends/?class=steps,OutdoorAirHumidity&startDate=2017-02-26T00:00:00.00-7:00&endDate=2017-02-28T23:59:59.999-07:00&timezone=America/Los_Angeles

    200 OK

    {
      "results":[
        {
          "class":"Steps",
    
          "values":[
            {
               "value": 0,
               "count": 0,
               "date": "2017-02-26",
               "_nodata": true
            },
            {
               "value": 11000,
               "count": 4,
               "date": "2017-02-27"
            },
            {
               "value": 4000,
               "count": 1,
               "date": "2017-02-28"
            }
          ],
    
           "timeframe":{
              "count": 5,
              "sum": 15000,
              "avg": 5000,
              "lastValue": 4000,
              "lastDate": "2017-03-28T16:59:47.143Z",
    
              "lastItem":{
                 "_id":"58c9731348b8b3a0b59ea383",
                 "steps":9834,
                 "owenrId":"NM",
                 "source":"manual",
                 "dateLogged":"2017-03-30T16:59:47.143Z"
              }
           },
    
           "meta":{
              "groupBy":"date",
              "field":"steps",
              "bucketSize":"day",
              "agg":"sum"
           }
        },
        ...
       ],
    
      "meta":{
        "count": "2"
      }
    }
    

    If timezone was not specified:

    400 BadRequest

    {
      "error": {
        "name": "BadRequestError",
    
        "message": "Invalid timezone; timezone param is required and must be a string",
    
        "statusCode": 400
      }
    }
    

    Response Properties

    Parameter
    results {Array}

    List of trend results for classes specified in the request
    results[n] {Object}

    Trend result for a specific class
    results[n].class {String}

    The name of the class the trend was generated for. This maps back to a class specified in the request, for example: GET /trends?class=Spirometry..
          {
            results: [
              {
                class: "Spirometry",
                ...
              }
              ...
            ]
          }
          
    results[n].values {Array}

    Represents the set of trends generated for the bucket size specified. For example, if the bucket size is set to day then each item in the values array represents a day’s worth of data, for example: GET /trends?class=Steps&startDate=2017-03-01&endDate=2017-03-04…
          results[0] =
          {
            class: "Steps"
            values: [
              {
                 value: 5000,
                 date: "2017-03-01"
               }
               ...
               {
                 value: 5000,
                 date: "2017-03-04"
               }
            ]
          }
          
    results[n].timeframe {Object}

    Provides a summary of the trend results over the period specified such as the total number of items, the last date an item was recorded, its last value, etc. see example below: GET /trends?class=Steps&startDate=2017-03-01&endDate=2017-03-04…
    results[0] =
    {
      class: "Steps",
      ...
      timeframe: {
        count: 5,
        sum: 386,
        avg: 64.3333333333,
        lastValue: 53,
        lastDate: "2017-03-03T01:25:16.042Z"
      }
    }
    
    results[n].meta {Object}

    Contains metadata about the particular class the trend result was generated for, such as the date property being used to group the results, the field name being used for trending, the type of trend being generated, etc. see example below: GET /trends?class=Steps&startDate=2017-03-01&endDate=2017-03-04…
    results[0] =
    {
      class: "Steps",
      ...
      meta: {
        groupBy: 'date', //date property used to group results/values
        // bucketSize determines how trend data is grouped i.e   
        // daily, monthly or yearly
        bucketSize: 'day',
        field: 'steps',  // property name used for trend report
        agg: 'sum,  // trend operation executed on the field
      }
    }
    
    meta {Object}

    Metadata about the set of trends generated for the class(es) specified
    meta.count {Number}

    Number of items in the result set; this maps to the number of items in the results array.

    Medication Adherence

    Generating Adherence Report

    curl -X GET \
      '/med/adherence?startDate=2017-04-22T14:00:00.000Z&endDate=2017-04-25T15:00:00.000Z&timezone=Etc/Gmt' \
      -H 'cache-control: no-cache' \
      -H 'content-type: application/json' 
    

    Required Auth Parameters: APP ID, CLIENT KEY

    GET /med/adherence
    Query Parameters
    startDate {ISODate}
    required

    The start date of the summary (Inclusive).

    If the startDate is specified without the time, i.e YYYY-MM-DD then it is converted to the start of the day specified based on the the timezone parameter.

    For example if the startDate is specified as “2017-04-03” and the timezone is set to “America/Los_Angeles” then the start date would be set to “2017-04-03T00:00:00.000-07:00”
    endDate {ISODate}
    required

    The start date of the summary (Inclusive).

    If the startDate is specified without the time, i.e YYYY-MM-DD then it is converted to the start of the day specified based on the the timezone parameter.

    For example if the startDate is specified as “2017-04-03” and the timezone is set to “America/Los_Angeles” then the start date would be set to “2017-04-03T23:59:59.999-07:00”
    timezone {String}
    required

    Name of the requester’s timezone. See the TZ column here
    med {String}

    Identifier for medication to generate the adherence report for. A comma separated list of medication ids can also be passed to generate adherence for multiple medications, see examples below:

    GET /med/adherence?med=dulera_20
    GET /med/adherence?med=dulera_20,rescue_inhaler_30

    default: all associated drugs
    bucketSize {String}

    Grouping for adherence report. This determines the number of periods returned, for example if month is specified then each item in the “periods” array will represent a month’s worth of data.

    Supported values are: day, month, or year
    filter {JSON}

    Owner Id to filter shared objects
    e.g. { “where”: { “ownerId”: OWNER_ID }}

    Note: Currently, only an owner filter will work for this endpoint, other filters are ignored
    shared {Boolean}

    Enable sharing context
    Sample Response
    {
      "class": "MedicationAdministration",
      "timeframe": {
        "units": {
          "taken": 6,
          "expected": 16,
          "adherencePercentage": 37.5,
          "average": 1.5
        },
        "doses": {
          "taken": 2,
          "expected": 8,
          "adherencePercentage": 25,
          "average": 0.5
        },
        "administrations": {
          "taken": 4,
          "expected": 8,
          "adherencePercentage": 50,
          "average": 1
        },
        "lastDate": "2017-04-24T15:00:00.000Z",
        "startDate": "2017-04-22T14:00:00.000Z",
        "endDate": "2017-04-25T15:00:00.000Z",
        "periods": 4,
        "strength": [
          {
            "value": 1200,
            "unit": "ug"
          }
        ]
      },
      "periods": [
        {
          "units": {
            "taken": 2,
            "expected": 4,
            "adherencePercentage": 50,
          },
          "doses": {
            "taken": 1,
            "expected": 2,
            "adherencePercentage": 50
          },
          "administrations": {
            "taken": 1,
            "expected": 2,
            "adherencePercentage": 50
          },
          "date": "2017-04-22",
          "strength": [
            {
              "value": 400,
              "unit": "ug"
            }
          ]
        },
        ...
      ],
      "prescription": {
        "id": "46bc99ee-5409-42bd-9f3f-e4cb175a9d24",
        "description": "100 mcg/5 mcg, 2 inhalations twice daily",
        "medication": {...},
        ...
      }
    }
    
    Timezone not Specified
    {
      "error": {
        "name": "BadRequestError",
        "message": "Invalid timezone; timezone param is 
                    required and must be a string",
        "statusCode": 400
      }
    }
    
    Response Properties
    results {Array}

    List of adherence reports for each medication specified in the request or for all medications for a user
    results[n] {Object}

    Adherence report for a specific medication
    results[n].class {String}

    The class used to generate the adherence report; “MedicationAdministration” is always returned.
    results[n].timeframe {Object}

    Summary of the user’s adherence over the period specified, see example below:

    GET /med/adherence?startDate=2017-03-01&endDate=2017-03-04…
    results[0] = {
      timeframe: {
        units: {
          // # of individual medications taken (eg. 2 tablets) for period
          taken: 4,
          expected: 8, // expected # of medications for period
          adherencePercentage: 50,
          average: 1.3333 // average # of units taken over period
        },
        doses: {
          // # of full doses taken for period
          // given that a prescription dosage is 2 pills, twice a day
          // taking 1 pill twice a day = 0 doses
          // taking 2 pills once a day = 0 doses
          // taking 2 pills twice a day = 1 dose
          taken: 2,
          expected: 4, // expected # of doses that for period
          adherencePercentage: 50,
          average: 66.6666 // average # of doses taken over period
        },
        administrations: {
          // # of times medication was taken for period
          // for example taking 3 pills twice a day = 2 administrations
          // and taking 2 pills once a day = 1 administration
          taken: 2,
          expected: 4, // expected # of administrations for period
          adherencePercentage: 50,
          average: 1 // average number of units taken over period
        },
        strength: [
          // strength of medication consumed over period
          {
            value: 800,
            unit: mg
          }
          ...
        ]
        ...
      }
      ...
    }
          
    results[n].periods {Array}

    Breakdown of the user’s adherence for each period (determined by bucket size). For example, if the bucket size is set to day then each item in the periods array represents a day’s worth of data, for example:

    GET /med/adherence?startDate=2017-03-01&endDate=2017-03-04…
    results[0] = {
      periods: [
        date: '2017-03-01',
        units: {
          // # of individual medications taken (eg. 2 tablets
          taken: 2,
          expected: 4, // expected # of medications
          adherencePercentage: 50
        },
        doses: {
          // # of full doses taken
          // given that a prescription dosage is 2 pills, twice a day
          // taking 1 pill twice a day = 0 doses
          // taking 2 pills once a day = 0 doses
          // taking 2 pills twice a day = 1 dose
          taken: 1,
          expected: 2, // expected # of doses
          adherencePercentage: 50
        },
        administrations: {
          // # of times medication was taken for period
          // for example taking 3 pills twice a day = 2 administrations
          // and taking 2 pills once a day = 1 administration
          taken: 1,
          expected: 2, // expected # of administrations
          adherencePercentage: 50
        },
        strength: [
          // strength of medication consumed
          {
            value: 400,
            unit: mg
          }
          ...
        ]
        ...
    
      ]
    }
    
    results[n].prescription {Object}

    User’s prescription information (including medication) used when generating the adherence report

    Scheduled Jobs

    Allows for the creation of a scheduled task that will execute at stipulated time intervals or only once. This task can either do http requests to remote endpoints or trigger custom logic, all tasks are stored in the main VARA database until complete.

    Fields

    Errors

    curl -X POST \
      /v1/jobs \
      -H 'Client-Key: <Master Key>' \
      -d '{
      "params": {
        "url":"http://jsonplaceholder.typicode.com/posts",
        "payload":{ "title": "foo" },
        "method": "POST"
      },
      "recurrence":"3 days",
      "date":"in 10 seconds",
      "type":"http"
    }'
    
    200 OK
    {
      "result": {
        "id": "57a347e5d64849f5065d6afa",
        "date": "2016-09-16T14:35:46.139Z",
        "recurrence":"3 days"
      }
    }
    

    Create a job

    Required Auth Parameters: APP ID, Master Key

    POST /jobs
    curl -X GET \
      /v1/jobs/57a347e5d64849f5065d6afa \
      -H 'Client-Key: <Master Key>'
    
    200 OK
    {
      "result": {
        "id": "57a347e5d64849f5065d6afa",
        "date": "2016-09-16T14:35:46.139Z",
        "recurrence":"3 days"
      }
    }
    

    Retrieve by Id

    Required Auth Parameters: APP ID, Master Key

    GET /jobs/:id
    curl -X PUT \
      /v1/jobs/57a347e5d64849f5065d6afa \
      -H 'Client-Key: <Master Key>'
      -d '{
      "recurrence": "7 minutes",
      "date": "in 10 minutes",
      "params": {
         "url": "http://foob.ar/raboof",
         "payload": {
           "title": "foobar",
           "body": "bar"
         }
      }
    }'
    
    200 OK
    {
      "result": {
        "id": "57a347e5d64849f5065d6afa"
        "name": "...",
        "data": { ... },
         ...
      }
    }
    

    Update a job

    Required Auth Parameters: APP ID, Master Key

    Modify a job’s scheduled date and/or recurrence interval, and params object using this endpoint. If a params object is provided in the body and not undefined, this value will replace the entire existing params object and no merging or partial update is done.

    PUT /jobs/:id
    curl -X GET \
      /v1/jobs \
      -H 'Client-Key: <Master Key>'
    
    200 OK
    {
      "result": [
        {
          "id": "57a347e5d64849f5065d6afa",
          "date": "2016-09-16T14:35:46.139Z",
          "recurrence":"3 days"
        },
        ...
      ],
      "meta": {
        "count": 12
      }
    }
    

    Required Auth Parameters: APP ID, Master Key

    GET /jobs
    curl -X DELETE \
      /v1/jobs/57a347e5d64849f5065d6afa \
      -H 'Client-Key: <Master Key>'
    
    200 OK
    {
      "result": {
        "deleted": 1
      }
    }
    

    Delete a job

    Required Auth Parameters: APP ID, Master Key

    Delete a job by specifying its id as the parameter. This is a permanent hard delete, i.e there is no undo or way to recover deleted job data.

    DELETE /jobs/:id

    API Keys

    MasterKey

    MasterKey grant’s full data access and privileges to any request that contains its applications masterKey.

    Creation

    A masterKey is automatically generated when an application is created and bypasses model level permissions.

    Usage

    The masterKey is used by substituting it in the request instead of the clientKey. GET /v1/data/class/Person?appId=APPLICATION_ID&clientKey=MASTER_KEY

    Impersonation

    Impersonation allows a user to impersonate/masquerade as a next user, to impersonate a user the following fields must be present in either the header or query string:

    Request

    GET /v1/data/class/Person?appId=APPLICATION_ID&clientKey=MASTER_KEY&filter={ "where":{}}&impersonate=USER_ID

    SDKs

    Android SDK

    JavaDoc

    You can view the Android code api (javadoc) here

    Download the SDK

    Download the platform-{version}.aar file to your local repository of libraries from our private maven repository

    Update build.gradle file

    repositories {
        jcenter()
        flatDir {
            dirs 'libs'
        }
    }
    
    compile(name:'platform-{version}', ext:'aar')
    compile 'com.android.volley:volley:1.0.0'
    compile 'com.google.code.gson:gson:2.6.2'
    

    Include Dependencies

    Include these dependencies in the build.gradle for your app:

    Please ensure that you have progaurd enabled

    Extend EngaugeTxApplication

    Your application will need to extend EngaugeTxApplication.

    Extending the EngaugeTxApplication

    import com.engaugetx.platform.EngaugeTxApplication;
    
    public class MyApp extends EngaugeTxApplication {
    
      @Override
      public void onCreate() {
        super.onCreate();
      }
    ...
    
    }
    

    Update your manifest file

    Update your AndroidManifest file

    <uses-permission 
        android:name="android.permission.INTERNET" />
    
    <application
      android:name=".MyApp"
      ...
      >
    
      ...
      </application>
    
    1. use permission for network access
    2. use the application class

    Configure your application

    Update your strings.xml file

    <string name="etx_base_url">https://api.us1.engaugetx.com/v1</string>
    <string name="etx_app_id">your-app-id</string>
    <string name="etx_client_key">your-client-key</string>
    
    <!--optional-->
    <integer name="etx_login_default_ttl">1200</integer>
    <integer name="etx_login_remember_me_ttl">1209600</integer>
    

    By specifying your appId and clientKey by setting them in your strings.xml file

    Properties that can be specified

    Configurations
    etx_app_id {String}

    Required
    true

    Description
    Your application’s ID
    etx_client_key {String}

    Required
    true

    Description
    Your application’s client key
    etx_base_url {String}

    Required
    true

    Description
    The base URL to the EnguageTx instance.
    Default: https://api.us1.engaugetx.com/v1
    etx_login_default_ttl {Integer}

    Required
    true

    Description
    The default TTL to be set on the access token when logging in a user
    etx_login_remember_me_ttl {Integer}

    Required
    true

    Description
    The TTL to be set on the access token when logging in a user with rememberMe set to true

    Custom TTL must be enabled for your application on the platform

    JSON Serialization

    class SampleClass extends Mappable {
        public String foo;
        public String bar;
        public String another;
        public String one;
        @SerializedName("sName") public String s_name;
        @JsonNullIgnore public String ignored;
    }
    
    // in a function somehwere
    SampleClass test = new SampleClass();
    test.bar = "bah bah";
    test.addJsonNullIgnoredFieldNames(new String[] { "another", "one" });
    
    // when serialized => 
    // {"foo":null,"bar":"bah bah","sName":null}
    

    The following information applies to any model that extends the base model Mappable (including GDOs) and how they are serialized before sending a request to the server. When data is serialized to be sent to the API, null values are serialized by default, with the exception of predefined properties that will not be serialized to be sent to the server.

    Properties that are never serialized to JSON:

    ownerId, dateCreated, lastUpdated

    Properties ignored by serialization when null:

    id, dateLogged

    It is also possible to specify additional properties that will be ignored by the JSON serializer when null. This can be achieved by using the @JsonNullIgnore annotation on the property or using the addJsonNullIgnoredFieldName/addJsonNullIgnoredFieldNames function provided by the Mappable Class

    iOS SDK

    You can view the VARA swift code api here

    CocoaPods Setup

    platform :ios, "8.0"
    source 'https://github.com/CocoaPods/Specs.git'
    source 'https://github.com/medullan/engauge-tx-pod-specs.git'
    
    target 'EnguageTxSampleIosApp' do
      use_frameworks!
      pod 'EngaugeTx', '~> 0.0.39'
    end
    

    Using the SDK

    Create EngaugeTx.plist file with the following contents

    Create EngaugeTx.plist

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>appId</key>
        <string>your-app-id</string>
        <key>clientKey</key>
        <string>your-client-key</string>
        <key>baseUrl</key>
        <string>https://api.eu1.engaugetx.com/v1</string>
        <key>defaultTTL</key>
        <integer>1200</integer>
        <key>rememberMeTTL</key>
        <integer>5000</integer>
    </dict>
    </plist>
    

    Properties that can be specified

    Implement the EngaugeTxAppDelegate

    import EngaugeTx
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate, EngaugeTxAppDelegate {
      var window: UIWindow?
      var engaugeTx: EngaugeTxApplication?
    
      //Then instatiate it with an instance of `EngaugeTxApplication`
      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        engaugeTx = EngaugeTxApplication()
        return true
      }
    }
    
    Configurations Description
    appId {String}

    required
    Your application’s ID
    clientKey {String}

    required
    Your application’s client key
    baseUrl {String}

    required
    The base URL to the EnguageTx instance.
    Default: https://api.us1.engaugetx.com/v1
    defaultTTL {Integer} The default TTL to be set on the access token when logging in a user
    rememberMeTTL {Integer} The TTL to be set on the access token when logging in a user with rememberMe set to true

    Start by implementing the EngaugeTxAppDelegate protocol and conforming to it by defining the engaugeTx instance variable.

    Custom Logic SDK

    Overview

    The goal of the SDK is to make it easy for platform developers to write custom code. The SDK creates a web service to host custom code, implements the Custom Logic protocol and provides interfaces for each type of custom code. The SDK also assists with the configuration of custom code with the VARA API by generating the relevant application configurations.

    Custom Logic Request Flow
    Sample request flow from a mobile app to custom logic code deployed on the VARA platform. The Custom Logic SDK communicates with the VARA API via the Custom Logic Protocol which allows platform developers to focus on writing custom code.

    Registering Custom Code

    const sdk = require('custom-logic-sdk');
    
    sdk.registerStandaloneAPIResponder(config, (context, done) => {
      ...
      context.setBody({...});
      done();
    });
    
    // Register other functions
    ...
    
    // Start the web service
    sdk.run();
    

    By default, executing the above code will create a function registry and start the application server.

    In order to expose custom code to the VARA API, it must be registered with the SDK. Custom code can be added to the SDK’s function registry by calling the appropriate registration method, see the code snippet on the right.

    Once custom code is added to the SDK’s function registry and the web service is started, the custom logic configurations for the application should be updated (as needed), see deploying custom logic for instructions on how to retrieve and update custom logic configurations.

    Writing Custom Logic Code

    API Responders

    An API responder is any custom code that is able to mainipulate the VARA API request or response. Synchronous Pre-hooks, Post-hooks and Standalone functions are all API responders.

    Interface

    API responders are invoked with a context object and an optional done function (done function is optional when the responder is an async method). API responders must have one of the the following signatures:

    The table below provides more details on the parameters passed to API responders:

    //example 1
    //context.requestInfo
    
    {
      body: {...}, // request body
      query: {...}, // parsed query parameters
      headers: {...}, // request headers
      method: '...', // request method POST, PUT, GET, DELETE, etc
      params: {...}, // url parameters
      url: '...' // unparsed request url including protocol, host, path and query params
    }
    
    // example 2
    // context.responseInfo
    
    {
      body: {...}, // response body to be sent to the client
      headers: {...}, // response headers
      statusCode: '...' // http response code
    }
    

    Returns successful response

    //example 3
    responder(context, done) {
      const result = context.body;
      ...
      context.setHeader({...});
      context.setBody({...});
      context.setStatus(200);
      done();
    }
    

    Returns error response

    //example 4
    /**
     * All errors returned from the SDK only contain the following
     * properties:
     *   - name
     *   - message
     *   - code
     *   - statusCode
     *
     * Any additional properties added to an error object will be ignored
     **/
    const MyCustomError = {
      name: 'CustomError',
      message: 'Something went wrong',
      code: 'INTERNAL_ERROR',
      statusCode: '500'
    };
    
    responder(context, done) {
      ...
      const err = new Error(MyCustomError.message);
      object.assign(err, MyCustomError);
      done(err);
    }
    

    Returns result of asynchronous request

    //example 5
    responder(context, done) {
      ...
      setTimeout(() => {
        // build response
        ...
        context.setBody(response);
        done();
      }, 500);
    }
    
    API Responder Arguments
    context {Object}

    Used to retrieve values that were sent by the VARA API (request body, access tokens, application Id, etc.) or to manipulate the response that will be sent back by the VARA API (e.g change the query parameters for a request, return a file, add response headers, etc.).
    context.api {Object}

    The VARA API Client which can be used to interact with VARA API from within the Custom Logic environment. The details of the API can be found here.
    context.appId {String}

    Application identifier sent in original API request.
    context.clientKey {String}

    Client key (API key) sent in the request.
    context.accessToken {String}

    Access token query parameter/header sent in the request.
    context.user {Object}

    Information about the user making the request. The information contained here is the same as the context.user property for custom functions and lifecycle hooks.
    context.userRoles {Array}

    List of roles the requester (user) has been assigned. The format of each role in this list is the same as the context.userRoles property for custom functions and lifecycle hooks.
    context.body {Object}

    Request or response body from API request. For pre-hooks and standalone functions, this is the request body while for post-hooks, it is the response body.
    context.requestInfo {Object}

    Contains information about the request that was sent to the VARA API, for example the URL, the HTTP method, query and path parameters, headers, etc.
    context.responseInfo {Object}

    Contains the response information intended to be sent from the VARA API, for example the status code, response headers, response body, etc.

    Note: This is only applicable for post-hooks.
    context.metadata {Object}

    This contains metadata about the custom logic function being executed. For hooks (pre and post-hooks), additional information is sent such as the Model name and method name executed, for example:

    { "modelName": "Medication", "method": "find", ... }

    context.setBody(keyOrBody: Any [,value: Any]): context {Function}

    Updates or replaces the body property in the response body sent to VARA.

    // method is chainable as context is returned context.setBody({ some: 'json' }) .setBody('some', 'updated json'); context.setBody('<p>some html</p>'); context.setBody(new Buffer('hello world!'));

    context.setQuery(keyOrQueryParams: String|Object [,value: Any]): context {Function}

    Updates or replaces the query property in the response body sent to VARA.

    context.setQuery('foo', 'bar'); context.setBody({ foo: 'bar' });

    context.setHeader(keyOrHeaders: String|Object [,value: Any]): context {Function}

    Alias: context.setHeaders

    Updates or replaces the query property in the response body sent to VARA.

    context.setHeader('content-type', 'application/json'); context.setHeader({ 'content-type': 'application/octet-stream' });

    context.setStatus(statusCode: Number): context {Function}

    Updates the statusCode property in the response body sent to VARA. Calling this method for a pre-hook sets the stopProcessing property to true, which tells VARA that a response should be returned immediately, see custom code output for more details.

    context.stopProcessing(stop: Boolean): context {Function}

    Updates the stopProcessing property in the response body sent to VARA. This property is only used when processing pre-hooks, see custom code output for more details.

    context.getCustomState(): Object {Function}

    Returns the customState property sent by VARA.

    context.setCustomState(keyOrCustomState: String|Object [,value: Any]): context {Function}

    Updates or replaces the customState property in the response body sent to VARA.

    context.setCustomState('content-type', 'application/json'); context.setCustomState({ 'content-type': 'application/octet-stream' });

    context.mergeCustomState(customState: Object): context {Function}

    Merges the customState parameter with the existing custom state; the resulting customState will be sent to VARA.

    context.log {Object}

    Logger created from node-bunyan; contains the following methods, where all log methods write to stdout:

    • trace([data: Any] [,...[args: Any]]) - capture detailed logs that occur at a very high frequency.

    • debug([data: Any] [,...[args: Any]]) - capture detailed logs that occur at a high frequency.

    • info([data: Any] [,...[args: Any]]) - capture high level information on regular operations. Example usage:

      context.log.info(logContext, 'Request received for route: POST /functions/createReport');

    • warn([data: Any] [,...[args: Any]]) - capture well-known errors resulting from improper usage. Example usage:

      const validationError = validate(requestBody); if (validationError) { logContext.err = validationError; // ensure standard error properties are serialized context.log.warn(logContext, validationError.message); ... }

    • logWarning(err: Error, logContext: Object, logPrefix: String[, messagePrefix: String]) - helper method to capture well-known errors in a particular format. Standard error properties are serialized (e.g., name, message, code, etc.) and log messages are tagged with the logPrefix. An optional message prefix can be used to add more context to the error that occurred. Example usage:

      // the form of log message will be: `{logPrefix}:${err.name} ${messagePrefix} ${err.message}` context.log.logWarning(err, logContext, 'StandaloneCode#createReport', 'Failed to create adherence report;');

    • error([data: Any] [,...[args: Any]]) - capture errors where the application is in an inconsistent state or other unknown errors. Example usage:

      fs.readFile('./assets/report.pdf', (err, report) => ( if (err) { context.log.error({ ...logContext, err }, err.message); ... } ... ))
    • logError(err: Error, logContext: Object, logPrefix: String[, messagePrefix: String]) - helper method to capture errors where application is in an inconsistent state or other unknown errors. Standard error properties are serialized (e.g., name, message, code, etc.) and log messages are tagged with the logPrefix. An optional message prefix can be used to add more context to the error that occurred. Example usage:

      fs.readFile('./assets/terms.pdf', (err, report) => ( if (err) { // the form of log message will be: `{logPrefix}:${err.name} ${messagePrefix} ${err.message}` context.log.logError(err, logContext, 'StandaloneCode#sendTerms', 'Failed to retrieve terms and conditions file;'); ... } ... ))

    done([err: Error]) {Function}

    When an API responder uses the callback function it can be called with:

    • no parameters - indicates processing was successful
    • an Error object - indicates that an Error occurred

    Custom Logic (CL) Actions

    Example Custom logic action

    async function sendEmailToInactiveUser(context) {
      const email = context.user.email;
      // send email
      ...
      context.log.info(`Email to sent inactive user: ${email}`);
    }
    
    sdk.registerAction(config, sendEmailToInactiveUser);
    sdk.run();
    

    A custom logic action can be configured to execute whenever a set of conditions are met for an engagement engine rule. CL actions cannot affect VARA API requests/responses as they are executed outside of the request cycle. HTTP responses (successful or failed) received from CL actions are logged by the platform.

    Interface

    Custom logic actions, like API responders, are invoked with a context object and an optional done function. CL actions must have one of the the following signatures:

    The table below provides more details on the parameters passed to CL actions:

    Custom Logic Action Arguments
    context {Object}

    Contains information about the event that triggered the CL action as well as utilities for logging and making VARA API calls.
    context.api {Object}

    The VARA API Client which can be used to interact with VARA API from within the Custom Logic environment. The details of the API can be found here.
    context.event {String}

    Details about the event that triggered the CL action, see CL action input for more details.
    context.user {Object}

    Information about the user the event was triggered for. The information contained here is the same as the context.user property for custom functions and lifecycle hooks.
    context.userRoles {Array}

    List of roles the requester (user) has been assigned. The format of each role in this list is the same as the context.userRoles property for custom functions and lifecycle hooks.
    context.log {Object}

    Utility to capture application logs; the same logger is provided to API responders.

    done([err: Error]) {Function}

    When a CL action uses the callback function it can be called with:

    • no parameters - indicates action was successful
    • an Error object - indicates that an Error occurred

    VARA API Client

    The VARA API Client is meant to make it easier to interact with the VARA Platform from within the Custom Logic environment. The client is passed into the responders as part of the context object. It is also only scoped to the context of the VARA API that the Custom Logic is deployed to.

    Scheduling Standalone Functions

    Schedule the Standalone function testfunction to be called every hour starting ten minutes from now.

    sdk.registerStandaloneAPIResponder(config, (context, done) => {
      context.api.jobs.scheduleFunction('testfunction', {
        date: 'in 10 minutes',
        recurrence: '1 hour',
        payload: {
        value: 123,
        }
      }, (err, result) => {
        done(err);
      });
    });
    
    sdk.run();
    

    The signature for scheduling Standalone Functions:

    context.api.jobs.scheduleFunction(functionName: string, options: object, callbackFn: function)

    The table below provides more details on the parameters passed:

    Parameter Name
    functionName {string}

    Example Value
    customLogicFunctionName

    Description The name of the Standalone Function
    Parameter Name
    options {object}

    Parameter Name
    options.recurrence {string}

    Example Value
    30 minutes

    Description The recurrence as a human readable string
    Parameter Name
    options.date {string}

    Example Value
    in 1 hour

    Description The schedule date as a date string or human readable string
    Parameter Name
    options.method {string}

    Example Value
    POST

    Description The method to use when executing the Standalone Functions. Defaults to ‘POST’
    Parameter Name
    options.payload {object [string]}

    Example Value
    `{value: 123}`

    Description The body of the request that the deferred standalone function will receive when it is triggered
    Parameter Name
    callbackFn {function}

    Example Value
    (err, result) => {}

    Description The callback function which will be called with err, result params

    Environment Variables

    The following environment variables are injected into the application when run in the VARA API environment. During the writing of your custom functions it may be useful to export these variables while testing to simulate the VARA API environment.

    Variable
    PORT

    Example Value
    3000

    Description This is the port that the SDK will bind to. In the VARA environment this value is usually 3000.
    Variable
    TX_PORT

    Example Value
    3000

    Description Same value as PORT
    Variable
    TX_APP_ID

    Example Value
    4b876gd6d78d3f31a0dacb54295128b

    Description The Application ID.
    Variable
    TX_CLIENT_KEY [Deprecated, Do Not Use]

    Example Value
    e48d3a704c5f4cda9b47ea92f9cb3412

    Description The Client Key for the Application the CL will run against. [Deprecated, Do Not Use]
    Variable
    TTX_MASTER_KEY [Deprecated, Do Not Use]

    Description The Master Key for the Application the CL will run against. [Deprecated, Do Not Use]

    Deploying Custom Logic

    1. Request deploy access to the desired application by submitting a help desk ticket with your Public SSH Key:

      Support ticket for sumbitting Public SSH Key
    2. Install the custom logic sdk: npm i @vara/custom-logic-sdk --save

    3. Include a Procfile with a web process type that launches the application. For e.g. web: node index.js

    4. Add the git remote for the Application. The format of the remote is git@git.<<region>>.engaugetx.com:<<appID>>, for e.g. git remote add deploy git@git.us1.engaugetx.com:c61af606ca52a722308f7e4e5ad02a83

    5. Commit the application code to git and push your local master branch to the master on the CL remote: git push deploy master. Alternatively, if you are working on a local branch other than master you can push the local branch to the remote master: git push deploy local-branch:master.

    6. Retrieve the custom logic configurations by running the SDK locally via the sdk.run() method. This configuration is written to stdout but may be retrieved from the configuration endpoint, i.e. http://localhost:5000/configs. The configuration has the following structure:

      { customLogic: { preHooks: [...], postHooks: {...}, functions: {...}, actions: {...} } }
    7. Update your custom logic configurations with the previously retrieved configurations. See updating custom code configurations for more details.

    Sample Custom Logic Application

    A sample application that uses the custom logic SDK can be found here.

    Note: Persistent storage is not provided for deployed custom code. Additionally, custom code is deployed as cluster where multiple nodes are used to process requests, therefore platform developers shoud not expect dynamically created files to be available in subsequent requests. Platform developers should either bundle re-usable assets together with the application source code, see the generateReport standalone function, or dynamically create the desired assets for each request.

    API Specification

    sdk.registerStandaloneAPIResponder()

    Syntax

    sdk.registerStandaloneAPIResponder(config: Object, responder: Function)

    Description

    When configuring a standalone responder, in addition to specifying the name of the standalone function, either the httpMethods or the supportAllMethods property should be specified. This configuration represents the set of HTTP request methods (GET, PUT, POST, DELETE) that the responder should be invoked for, see standalone code configuration for more details.

    The clProtocolVersion property should be set to one of the supported versions, otherwise it defaults to the latest version.

    If the config object is invalid or the responder argument is not a function an InvalidAPIResponderError is thrown.

    /** * Throws @InvalidAPIResponderError - thrown when the configurations * are invalid or the responder argument is not a function **/ sdk.registerStandaloneAPIResponder( config: { name: String, [httpMethods]: Array, [supportAllMethods]: Boolean, [clProtocolVersion]: String }, responder: Function )

    sdk.registerPostHookAPIResponder()

    Syntax

    sdk.registerPostHookAPIResponder(config: Object, responder: Function)

    Description

    When configuring a post-hook responder, the following configuration properties must be specified:

    The clProtocolVersion property should be set to one of the supported versions, otherwise it defaults to the latest version.

    If the config object is invalid or the responder argument is not a function an InvalidAPIResponderError is thrown.

    /** * Throws @InvalidAPIResponderError - thrown when the configurations * are invalid or the responder argument is not a function **/ sdk.registerPostHookAPIResponder( config: { name: String, modelName: String, modelMethod: String, [clProtocolVersion]: String }, responder: Function )

    sdk.registerPreHookAPIResponder()

    Syntax

    sdk.registerPreHookAPIResponder(config: Object, responder: Function)

    Description

    When configuring a pre-hook responder, the following configuration properties must be specified:

    The clProtocolVersion property should be set to one of the supported versions, otherwise it defaults to the latest version.

    If the config object is invalid or the responder argument is not a function an InvalidAPIResponderError is thrown.

    /** * Throws @InvalidAPIResponderError - thrown when the configurations * are invalid or the responder argument is not a function **/ sdk.registerPreHookAPIResponder( config: { name: String, urlPath: String, httpMethods: Array, [clProtocolVersion]: String }, responder: Function )

    sdk.registerAction()

    Syntax

    sdk.registerAction(config: Object, action: Function)

    Description

    When configuring a custom logic action, the name property must be specified. If the config object is invalid or the action argument is not a function an InvalidClActionError is thrown.

    sdk.run()

    Syntax

    sdk.run()

    Description

    Starts the web service and generates custom logic configurations for the VARA Platform.

    Glossary

    Appendix

    Application Configuration

    config {Object}

    Contains custom logic configurations i.e preHooks, postHooks and standalone functions, see lifecycle hook registration and standalone function registration
    emailConfig {Object}

    Configurations for sending emails (e.g SMTP server and port)
    emailConfig.transports {Array}

    Contains SMTP server configurations and authentication credentials.

    Example config:

        tansports: [
          {
            type: 'smtp',
            host: awsSesServer,
            post: awsSesPort,
            secure: true,
            tls: {
              rejectUnauthorized: true,
            },
            auth: {
              user: awsSesUser,
              pass: awsSesPwd,
            },
          }
        ]
    
    emailConfig.adminEmail {String}

    Email address that should show up as the sender for outgoing emails. Additionally emails will be sent to this address when important events occur, for example when a user account is closed.
    modelConfig {Object}

    Stores various model configurations such as model validation requirements, authentication constraints (permitted # of failed attempts, account lock duration, access token expiry, etc.), email templates, etc. for more details see each model.
    modelConfig.gdo {Object}

    Configurations related to generic object such as collection records that are not to be deleted during user account removal or configurations for enabling trends on generic objects.
    modelConfig.gdo.ignoreDelete {Array}

    A list of GDO models that should be ignored during deletion of a user account, for example:

        {
          ...
          modelConfig: {
            ...
            gdo: {
              ...
              ignoreDelete: [
                 'StudyFeedback'
              ]
            }
          }
        }
    
    modelConfig.gdo.trends {Object}

    Configurations for enabling trends on generic objects.
    modelConfig.gdo.trends.models {Object}

    Collection of generic objects that trends may be generated for.
    modelConfig.gdo.trends.models.{className} {Object}

    Key that maps to the name of Generic Object class that trends may be generated for, see example below:

        {
          ...
          trends: {
             models: {
                Sleep: {
                   ...
                }
             }
          }
        }
    
    modelConfig.gdo.trends.models.{className}.dateField {String}

    The date property on the GDO class that should be used for generating trends for the class specified, see example below:
        {
          ...
          trends: {
             models: {
                Sleep: {
                   ...
                   dateField: 'dateRecorded'
                }
             }
          }
        }
    
    modelConfig.gdo.trends.models.{className}.trendField {String}

    The property on the GDO class that should be analyzed and/or processed for gaining insights when generating trends for the class specified, see example below:
        {
          ...
          trends: {
             models: {
                Sleep: {
                   ...
                   trendField: 'duration'
                }
             }
          }
        }
    
    modelConfig.gdo.trends.models.{className}.trend {String}

    The type of analysis and/or processing to perform when generating trends for the class specified, see example below:
        {
          ...
          trends: {
             models: {
                Sleep: {
                   ...
                   trend: 'avg'
                }
             }
          }
        }
    
    logging {Object}

    Configurations for application logging (e.g request tracing)
    logging.traceReqLog {Boolean}

    Flag for enabling request tracing, it is disabled by default. This allows the request object to be logged with copious amounts of details (request body, headers, url, query parameters, response headers, status code, etc.) to allow developers to trace application logs and recreate requests to analyze the application behavior.