# Junction API Source: https://docs.junction.com/api-details/junction-api You use Junction API to: * manage users and demographics information in your Team with the [Core API](/api-reference/user/create-user); * manages user connections, pull ingested device data, inspect connection backfill statuses through the [Devices API](/wearables/connecting-providers/introduction); * order lab tests, manage appointments and pull the results through the [Lab Testing API](/lab/overview/introduction); and * aggregate ingested device data with the [Junction Sense API](/sense/overview); For managing Team configuration and Org resources programmatically, see [Junction Management API](/api-details/junction-management-api). ## Environments Junction provides two Sandbox environments and two Production environments today: | Environment | Base URL | Team API Key Prefix | | ------------------ | -------------------------------------- | ------------------- | | πŸ‡ΊπŸ‡Έ Production US | `https://api.us.junction.com/` | `pk_us_*` | | πŸ‡ͺπŸ‡Ί Production EU | `https://api.eu.junction.com/` | `pk_eu_*` | | πŸ‡ΊπŸ‡Έ Sandbox US | `https://api.sandbox.us.junction.com/` | `sk_us_*` | | πŸ‡ͺπŸ‡Ί Sandbox EU | `https://api.sandbox.eu.junction.com/` | `sk_eu_*` | If you are using a `*.tryvital.io` Base URL, they are still supported alongside `*.junction.com`. Sandbox environments are functionally identical to Production environments, except that: 1. Each Team can have only up to 50 Users in Sandbox; 2. You can create [Synthetic Device connections](/wearables/providers/test_data) connections; and 3. You can [simulate and transition an order through its lifecycle](/lab/overview/sandbox). ## Authentication Junction API accepts **Team API Key**. You can provision a Team API Key through: 1. the Team Config page in the [Junction Dashboard](https://app.junction.com); or 2. the [Create Team API Key](api-reference/org-management/team-api-keys/create-team-api-key) endpoint of the [Junction Management API](/api-details/junction-management-api). Your API requests should present the Team API Key under the `X-Vital-API-Key` header. For example: ```bash cURL theme={null} curl --request GET --url 'https://api.tryvital.io/v2/providers' --header 'X-Vital-API-Key: ' ``` A [Management Key](/api-details/junction-management-api) used by Junction Management API does not function as Team API Key. However, you can manage Team API Keys through a Management Key. ## Junction Mobile SDKs [Junction Mobile SDKs](/wearables/sdks/) supports two authentication methods: | Scheme | Authorization | Recommended use case | | ------------------------------------------------------------------- | --------------------- | ---------------------- | | [Sign-In Token](/wearables/sdks/authentication#vital-sign-in-token) | User-scoped access | Production mobile apps | | [Team API Keys](/wearables/sdks/authentication#vital-api-keys) | Full Team data access | Proof-of-concept | Refer to the [Mobile SDK Authentication](/wearables/sdks/authentication) documentation for more information. # Junction Management API Source: https://docs.junction.com/api-details/junction-management-api [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. You use Junction Management API to: * manage Junction Org resources, such as [Teams](/api-reference/org-management/team/create-team) and [Members](/api-reference/org-management/member/list-members); * manage Team configurations across both Sandbox and Production environments, such as: * [Team features and brand information](/api-reference/org-management/team/update-team); * [Team ETL Pipelines](/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines); * [Team Custom Credentials](/api-reference/org-management/team-custom-credentials/upsert-team-custom-credentials); and * [Team Continuous Query](/api-reference/sense/continuous-query/create). Note that you can access a lot of Junction Management API functionalities through the [Junction Dashboard](https://app.junction.com/) as well. For device and lab testing access, see [Junction API](/api-details/junction-api). ## Environments There is one production environment for Junction Management API: | Environment | Base URL | Management Key Prefix | | ------------- | -------------------------------------- | --------------------- | | πŸ€– Production | `https://api.management.junction.com/` | `mg_*` | ## Authentication Junction Management API accepts **Management Key**. Enterprise customers can request a Management Key through your Junction Customer Success manager. Your API requests should present the Management Key under the `X-Management-Key` header. For example: ```bash cURL theme={null} curl --request GET --url "https://api.management.junction.com/v1/org/${ORG_ID}" --header 'X-Management-Key: ${YOUR_MANAGEMENT_KEY}' ``` A [Team API Key](/api-details/junction-api#authentication) does not work with Junction Management API. Junction enforces the use of separate API credentials based on the principle of least privilege, especially considering the administrative power of Junction Management API. # Rate Limiting Source: https://docs.junction.com/api-details/rate_limiting Junction does not impose a per-customer rate limit on server-to-server API calls at this time. We adjust the elastic capacity and overprovisioning rate of our API servers regularly, in response to your usage pattern changes and product launch plans. However, when Junction API servers are under stress, there is a possibility in which our infrastructure may abort a number of your API requests with a **429 Too Many Requests** or **503 Service Unavailable** response. For idempotent API requests, consider retrying the API request at least once, upon receiving a 429 or 503 response. Optionally, if you are making the API request in an asynchronous job context, consider having more retry attempts with an expontential backoff strategy. ## Targeted rate limits You may encounter **429 Too Many Requests** in the these specific scenarios: | Endpoint | Authentication Scheme | Rate Limit | | ------------------------------ | --------------------- | ------------------- | | `POST /user/refresh/{user_id}` | Junction API Key | 8 per hour per user | Junction may adjust rate limiting based on real-world usage patterns. We will give you sufficient notices if a new enforcement would impact your existing usage patterns. # Regions Source: https://docs.junction.com/api-details/regions Junction Team is a **regional** resource. All data of the Team are stored in the region you selected. This cannot be changed afterwards. Make sure you choose a region that is suitable to your data residency requirements. | Regions | Data Residency | | ------- | ----------------------------------------------- | | πŸ‡ΊπŸ‡Έ US | Data stored in United States (Subject to HIPAA) | | πŸ‡ͺπŸ‡Ί EU | Data stored in Belgium (Subject to GPDR) | Your Junction Org is a **global** resource β€” you can start with a Team in one region and create more in a different region as you expand. # Raw Source: https://docs.junction.com/api-reference/data/activity/get-raw GET /v2/summary/activity/{user_id}/raw Get raw activity summary for user_id ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/activity/{user_id}/raw \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.activity.get_raw( user_id="*", start_date="2022-05-01", end_date="2022-06-01" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { ActivityGetRawRequest } from '@tryvital/vital-node/api/resources/activity'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: ActivityGetRawRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.activity.getRaw( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.activity.requests.ActivityGetRawRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); ActivityGetRawRequest request = ActivityGetRawRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.activity().getRaw("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.ActivityGetRawRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Activity.GetRaw(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Summary Source: https://docs.junction.com/api-reference/data/activity/get-summary GET /v2/summary/activity/{user_id} Get activity summary for user_id ```bash Shell theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/activity/{user_id}/raw \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.activity.get( user_id="*", start_date="2022-05-01", end_date="2022-06-01" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { ActivityGetRequest } from '@tryvital/vital-node/api/resources/activity'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: ActivityGetRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.activity.get( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.activity.requests.ActivityGetRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); ActivityGetRequest request = ActivityGetRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.activity().get("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.ActivityGetRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Activity.Get(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Raw Source: https://docs.junction.com/api-reference/data/body/get-raw GET /v2/summary/body/{user_id}/raw Get raw Body summary for user_id ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/body/{user_id}/raw \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.body.get_raw( user_id="*", start_date="2022-05-01", end_date="2022-06-01" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { BodyGetRawRequest } from '@tryvital/vital-node/api/resources/body'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: BodyGetRawRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.body.getRaw( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.body.requests.BodyGetRawRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); BodyGetRawRequest request = BodyGetRawRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.body().getRaw("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.BodyGetRawRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Body.GetRaw(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Summary Source: https://docs.junction.com/api-reference/data/body/get-summary GET /v2/summary/body/{user_id} Get Body summary for user_id ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/body/{user_id} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.body.get( user_id="*", start_date="2022-05-01", end_date="2022-06-01" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { BodyGetRequest } from '@tryvital/vital-node/api/resources/body'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: BodyGetRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.body.get( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.body.requests.BodyGetRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); BodyGetRequest request = BodyGetRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.body().get("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.BodyGetRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Body.Get(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Get User Device Source: https://docs.junction.com/api-reference/data/device/get-device GET /v2/user/{user_id}/device/{device_id} ```bash Shell theme={null} curl --request GET \ --url {{BASE_URL}}/v2/user/{user_id}/device/{device_id} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.get_devices( user_id="", device_id="" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.getDevices( '', '' ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().getDevices("", ""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.GetDevices(context.TODO(), "", "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Get User Devices Source: https://docs.junction.com/api-reference/data/device/get-devices GET /v2/user/{user_id}/device ```bash Shell theme={null} curl --request GET \ --url {{BASE_URL}}/v2/user/{user_id}/device \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.get_devices( user_id="" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.getDevices( '' ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().getDevices(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.GetDevices(context.TODO(), "user_id") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Summary Source: https://docs.junction.com/api-reference/data/electrocardiogram/get-summary GET /v2/summary/electrocardiogram/{user_id} Get electrocardiogram summary for user_id The Electrocardiogram summary type does not embed the voltage measurements. Use the [Get Electrocardiogram Voltage](/api-reference/data/timeseries/electrocardiogram-voltage) endpoint with the `session_start` and `session-end` timestamps to query the voltage measurements. ```bash Shell theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/electrocardiogram/{user_id} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.electrocardiogram.get( user_id="*", start_date="2022-05-01", end_date="2022-06-01" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { ElectrocardiogramGetRequest } from '@tryvital/vital-node/api/resources/electrocardiogram'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: ElectrocardiogramGetRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.electrocardiogram.get( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.electrocardiogram.requests.ElectrocardiogramGetRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); ElectrocardiogramGetRequest request = ElectrocardiogramGetRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.electrocardiogram().get("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.ElectrocardiogramGetRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Electrocardiogram.Get(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Introspect Historical Pulls Source: https://docs.junction.com/api-reference/data/introspection/historical-pulls GET /v2/introspect/historical_pull ## Overview Diagnose user historical data unavailability through the User Resources Introspection API. This API offers detailed insights into all provider connections and collected historical resources of all users in your team, empowering you to perform an initial diagnosis of any user connection issue with ease. Please note that `user_limit` is an upper bound and the endpoint can return data for fewer users. This would be the case if any of the top selected users had no available resource information. # Introspect Resources Source: https://docs.junction.com/api-reference/data/introspection/user-resources GET /v2/introspect/resources ## Overview Diagnose user data unavailability through the User Resources Introspection API. This API offers detailed insights into all provider connections and collected resources of all users in your team, empowering you to perform an initial diagnosis of any user connection issue with ease. Please note that `user_limit` is an upper bound and the endpoint can return data for fewer users. This would be the case if any of the top selected users had no available resource information. # Summary Source: https://docs.junction.com/api-reference/data/meal/get-summary GET /v2/summary/meal/{user_id} Get user's meals ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/meal/{user_id}?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.meal.get( user_id="", start_date="2021-01-01", end_date="2021-01-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { MealGetRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: MealGetRequest = { startDate: '2021-01-01', endDate: '2021-01-02', }; const data = await client.meal.get('user_id', request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.meal.requests.MealGetRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); MealGetRequest request = MealGetRequest .builder() .startDate("2021-01-01") .endDate("2021-01-02") .build(); var data = vital.meal().get("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) EndDate := "2021-01-02" request := &vital.MealGetRequest{ StartDate: "2021-01-01", EndDate: &EndDate, } response, err := client.Meal.Get( context.TODO(), "", request ) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Summary Source: https://docs.junction.com/api-reference/data/menstrual-cycle/get-summary GET /v2/summary/menstrual_cycle/{user_id} ```bash Shell theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/menstrual_cycle/{user_id} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.menstrual_cycle.get( user_id="*", start_date="2022-05-01", end_date="2022-06-01" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { MenstrualCycleGetRequest } from '@tryvital/vital-node/api/resources/menstrualCycle'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: MenstrualCycleGetRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.menstrualCycle.get( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.menstrualCycle.requests.MenstrualCycleGetRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); MenstrualCycleGetRequest request = MenstrualCycleGetRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.menstrualCycle().get("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.MenstrualCycleGetRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.MenstrualCycle.Get(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Raw Source: https://docs.junction.com/api-reference/data/profile/get-raw GET /v2/summary/profile/{user_id}/raw Get raw profile for user_id ## Parameters ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/profile/{user_id}/raw \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.profile.get_raw("") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.profile.getRaw('') ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.profile().getRaw(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.Profile.GetRaw(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Summary Source: https://docs.junction.com/api-reference/data/profile/get-summary GET /v2/summary/profile/{user_id} Get profile for user_id ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/profile/{user_id} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.profile.get("") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.profile.get('') ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.profile().get(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.Profile.Get(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Summary Source: https://docs.junction.com/api-reference/data/sleep-cycle/get-summary GET /v2/summary/sleep_cycle/{user_id} Get sleep cycle for user_id ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/sleep_cycle/{user_id}?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.sleep_cycle.get( user_id="", start_date="2021-01-01", end_date="2021-01-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { SleepCycleGetRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: SleepCycleGetRequest = { startDate: '2021-01-01', endDate: '2021-01-02', } const data = await client.sleepCycle.get('user_id', request) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.sleep_cycle.requests.SleepCycleGetRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); SleepCycleGetRequest request = SleepCycleGetRequest .builder() .startDate("2021-01-01") .endDate("2021-01-02") .build(); var data = vital.sleepCycle().get("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) EndDate := "2021-01-02" request := &vital.SleepCycleGetRequest{ StartDate: "2021-01-01", EndDate: &EndDate, } response, err := client.SleepCycle.Get( context.TODO(), "", request ) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Raw Source: https://docs.junction.com/api-reference/data/sleep/get-raw GET /v2/summary/sleep/{user_id}/raw Get raw sleep summary for user_id ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/sleep/{user_id}/raw \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.sleep.get_raw( user_id="*", start_date="2021-01-01", end_date="2021-01-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { SleepGetRawRequest } from '@tryvital/vital-node/api/resources/sleep'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: SleepGetRawRequest = { startDate: '2021-01-01', endDate: '2021-01-02', } const data = await client.sleep.getRaw("", request) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.sleep.requests.SleepGetRawRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); SleepGetRawRequest request = SleepGetRawRequest .builder() .startDate("2021-01-01") .endDate("2021-01-02") .build(); var data = vital.sleep().getRaw("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.SleepGetRawRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Sleep.GetRaw(context.TODO(), "", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Summary Source: https://docs.junction.com/api-reference/data/sleep/get-summary GET /v2/summary/sleep/{user_id} Get sleep summary for user_id ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/sleep/{user_id}?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.sleep.get( user_id="", start_date="2021-01-01", end_date="2021-01-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { SleepGetRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: SleepGetRequest = { startDate: '2021-01-01', endDate: '2021-01-02', } const data = await client.sleep.get('user_id', request) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.sleep.requests.SleepGetRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); SleepGetRequest request = SleepGetRequest .builder() .startDate("2021-01-01") .endDate("2021-01-02") .build(); var data = vital.sleep().get("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) EndDate := "2021-01-02" request := &vital.SleepGetRequest{ StartDate: "2021-01-01", EndDate: &EndDate, } response, err := client.Sleep.Get( context.TODO(), "", request ) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Afib Burden Source: https://docs.junction.com/api-reference/data/timeseries/afib-burden GET /v2/timeseries/{user_id}/afib_burden/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/afib_burden/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.afib_burden_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsAfibBurdenGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsAfibBurdenGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.afibBurdenGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsAfibBurdenGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsAfibBurdenGroupedRequest request = VitalsAfibBurdenGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().afibBurdenGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsAfibBurdenGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.AfibBurdenGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:30:52+00:00", "start": "2023-02-13T14:30:52+00:00", "unit": "%", "value": 3 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Basal Body Temperature Source: https://docs.junction.com/api-reference/data/timeseries/basal-body-temperature GET /v2/timeseries/{user_id}/basal_body_temperature/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/basal_body_temperature/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.basal_body_temperature_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsBasalBodyTemperatureGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsBasalBodyTemperatureGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.basalBodyTemperatureGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsBasalBodyTemperatureGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsBasalBodyTemperatureGroupedRequest request = VitalsBasalBodyTemperatureGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().basalBodyTemperatureGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsBasalBodyTemperatureGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.BasalBodyTemperatureGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "\u00b0C", "value": 36.7 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Blood Oxygen Source: https://docs.junction.com/api-reference/data/timeseries/blood-oxygen GET /v2/timeseries/{user_id}/blood_oxygen/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/blood_oxygen/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.blood_oxygen_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsBloodOxygenGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsBloodOxygenGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.bloodOxygenGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsBloodOxygenGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsBloodOxygenGroupedRequest request = VitalsBloodOxygenGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().bloodOxygenGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsBloodOxygenGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.BloodOxygenGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "timestamp": "2023-02-13T14:30:52+00:00", "unit": "%", "value": 98 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Blood Pressure Source: https://docs.junction.com/api-reference/data/timeseries/blood-pressure GET /v2/timeseries/{user_id}/blood_pressure/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/blood_pressure/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.blood_pressure_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsBloodPressureGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsBloodPressureGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.bloodPressureGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsBloodPressureGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsBloodPressureGroupedRequest request = VitalsBloodPressureGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().bloodPressureGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsBloodPressureGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.BloodPressureGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "diastolic": 75, "systolic": 125, "timestamp": "2026-03-09T22:11:10+00:00", "unit": "mmHg" } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Body Fat Source: https://docs.junction.com/api-reference/data/timeseries/body-fat GET /v2/timeseries/{user_id}/body_fat/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/body_fat/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.body_fat_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsBodyFatGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsBodyFatGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.bodyFatGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsBodyFatGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsBodyFatGroupedRequest request = VitalsBodyFatGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().bodyFatGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsBodyFatGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.BodyFatGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "timestamp": "2023-02-13T14:30:52+00:00", "unit": "%", "value": 50 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Body Mass Index Source: https://docs.junction.com/api-reference/data/timeseries/body-mass-index GET /v2/timeseries/{user_id}/body_mass_index/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/body_mass_index/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.body_mass_index_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsBodyMassIndexGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsBodyMassIndexGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.bodyMassIndexGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsBodyMassIndexGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsBodyMassIndexGroupedRequest request = VitalsBodyMassIndexGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().bodyMassIndexGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsBodyMassIndexGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.BodyMassIndexGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "index", "value": 21 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Body Temperature Source: https://docs.junction.com/api-reference/data/timeseries/body-temperature GET /v2/timeseries/{user_id}/body_temperature/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/body_temperature/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.body_temperature_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsBodyTemperatureGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsBodyTemperatureGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.bodyTemperatureGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsBodyTemperatureGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsBodyTemperatureGroupedRequest request = VitalsBodyTemperatureGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().bodyTemperatureGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsBodyTemperatureGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.BodyTemperatureGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:30:52+00:00", "sensor_location": "eardrum", "start": "2023-02-13T14:30:52+00:00", "unit": "\u00b0C", "value": 65 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Body Temperature Delta Source: https://docs.junction.com/api-reference/data/timeseries/body-temperature-delta GET /v2/timeseries/{user_id}/body_temperature_delta/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/body_temperature_delta/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.body_temperature_delta_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsBodyTemperatureDeltaGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsBodyTemperatureDeltaGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.bodyTemperatureDeltaGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsBodyTemperatureDeltaGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsBodyTemperatureDeltaGroupedRequest request = VitalsBodyTemperatureDeltaGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().bodyTemperatureDeltaGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsBodyTemperatureDeltaGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.BodyTemperatureDeltaGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "sensor_location": "wrist", "start": "2023-02-13T14:30:52+00:00", "unit": "\u00b0C", "value": -1.0 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Body Weight Source: https://docs.junction.com/api-reference/data/timeseries/body-weight GET /v2/timeseries/{user_id}/body_weight/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/body_weight/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.body_weight_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsBodyWeightGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsBodyWeightGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.bodyWeightGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsBodyWeightGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsBodyWeightGroupedRequest request = VitalsBodyWeightGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().bodyWeightGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsBodyWeightGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.BodyWeightGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "timestamp": "2023-02-13T14:30:52+00:00", "unit": "kg", "value": 65 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Caffeine Source: https://docs.junction.com/api-reference/data/timeseries/caffeine GET /v2/timeseries/{user_id}/caffeine/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/caffeine/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.caffeine_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsCaffeineGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsCaffeineGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.caffeineGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsCaffeineGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsCaffeineGroupedRequest request = VitalsCaffeineGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().caffeineGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsCaffeineGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.CaffeineGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "unit": "g", "value": 42 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Calories Active Source: https://docs.junction.com/api-reference/data/timeseries/calories-active GET /v2/timeseries/{user_id}/calories_active/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/calories_active/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.calories_active_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsCaloriesActiveGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsCaloriesActiveGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.caloriesActiveGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsCaloriesActiveGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsCaloriesActiveGroupedRequest request = VitalsCaloriesActiveGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().caloriesActiveGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsCaloriesActiveGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.CaloriesActiveGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "unit": "kcal", "value": 184 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Calories Basal Source: https://docs.junction.com/api-reference/data/timeseries/calories-basal GET /v2/timeseries/{user_id}/calories_basal/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/calories_basal/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.calories_basal_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsCaloriesBasalGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsCaloriesBasalGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.caloriesBasalGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsCaloriesBasalGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsCaloriesBasalGroupedRequest request = VitalsCaloriesBasalGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().caloriesBasalGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsCaloriesBasalGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.CaloriesBasalGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "unit": "kcal", "value": 22.8 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Carbohydrates Source: https://docs.junction.com/api-reference/data/timeseries/carbohydrates GET /v2/timeseries/{user_id}/carbohydrates/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/carbohydrates/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.carbohydrates_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsCarbohydratesGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsCarbohydratesGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.carbohydratesGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsCarbohydratesGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsCarbohydratesGroupedRequest request = VitalsCarbohydratesGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().carbohydratesGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsCarbohydratesGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.CarbohydratesGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:30:52+00:00", "start": "2023-02-13T14:30:52+00:00", "unit": "g", "value": 30 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Daylight Exposure Source: https://docs.junction.com/api-reference/data/timeseries/daylight-exposure GET /v2/timeseries/{user_id}/daylight_exposure/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/daylight_exposure/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.daylight_exposure_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsDaylightExposureGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsDaylightExposureGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.daylightExposureGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsDaylightExposureGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsDaylightExposureGroupedRequest request = VitalsDaylightExposureGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().daylightExposureGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsDaylightExposureGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.DaylightExposureGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "min", "value": 45 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Distance Source: https://docs.junction.com/api-reference/data/timeseries/distance GET /v2/timeseries/{user_id}/distance/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/distance/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.distance_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsDistanceGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsDistanceGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.distanceGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsDistanceGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsDistanceGroupedRequest request = VitalsDistanceGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().distanceGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsDistanceGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.DistanceGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "unit": "m", "value": 5.6 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Electrocardiogram Voltage Source: https://docs.junction.com/api-reference/data/timeseries/electrocardiogram-voltage GET /v2/timeseries/{user_id}/electrocardiogram_voltage/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/electrocardiogram_voltage/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.electrocardiogram_voltage_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsElectrocardiogramVoltageGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsElectrocardiogramVoltageGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.electrocardiogramVoltageGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsElectrocardiogramVoltageGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsElectrocardiogramVoltageGroupedRequest request = VitalsElectrocardiogramVoltageGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().electrocardiogramVoltageGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsElectrocardiogramVoltageGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.ElectrocardiogramVoltageGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "timestamp": "2023-02-13T14:30:52+00:00", "type": "lead_1", "unit": "mV", "value": -373 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Fall Source: https://docs.junction.com/api-reference/data/timeseries/fall GET /v2/timeseries/{user_id}/fall/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/fall/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.fall_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsFallGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsFallGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.fallGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsFallGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsFallGroupedRequest request = VitalsFallGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().fallGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsFallGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.FallGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "count", "value": 3 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Floors Climbed Source: https://docs.junction.com/api-reference/data/timeseries/floors-climbed GET /v2/timeseries/{user_id}/floors_climbed/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/floors_climbed/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.floors_climbed_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsFloorsClimbedGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsFloorsClimbedGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.floorsClimbedGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsFloorsClimbedGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsFloorsClimbedGroupedRequest request = VitalsFloorsClimbedGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().floorsClimbedGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsFloorsClimbedGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.FloorsClimbedGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "unit": "count", "value": 2 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Forced Expiratory Volume 1 Source: https://docs.junction.com/api-reference/data/timeseries/forced-expiratory-volume-1 GET /v2/timeseries/{user_id}/forced_expiratory_volume_1/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/forced_expiratory_volume_1/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.forced_expiratory_volume_1_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsForcedExpiratoryVolume1GroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsForcedExpiratoryVolume1GroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.forcedExpiratoryVolume1Grouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsForcedExpiratoryVolume1GroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsForcedExpiratoryVolume1GroupedRequest request = VitalsForcedExpiratoryVolume1GroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().forcedExpiratoryVolume1Grouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsForcedExpiratoryVolume1GroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.ForcedExpiratoryVolume1Grouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "L", "value": 3.5 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Forced Vital Capacity Source: https://docs.junction.com/api-reference/data/timeseries/forced-vital-capacity GET /v2/timeseries/{user_id}/forced_vital_capacity/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/forced_vital_capacity/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.forced_vital_capacity_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsForcedVitalCapacityGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsForcedVitalCapacityGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.forcedVitalCapacityGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsForcedVitalCapacityGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsForcedVitalCapacityGroupedRequest request = VitalsForcedVitalCapacityGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().forcedVitalCapacityGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsForcedVitalCapacityGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.ForcedVitalCapacityGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "L", "value": 4.2 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Glucose Source: https://docs.junction.com/api-reference/data/timeseries/glucose GET /v2/timeseries/{user_id}/glucose/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/glucose/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.glucose_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsGlucoseGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsGlucoseGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.glucoseGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsGlucoseGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsGlucoseGroupedRequest request = VitalsGlucoseGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().glucoseGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsGlucoseGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.GlucoseGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "timestamp": "2023-02-13T14:30:52+00:00", "type": "automatic | manual_scan", "unit": "mmol/L", "value": 0.5 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Handwashing Source: https://docs.junction.com/api-reference/data/timeseries/handwashing GET /v2/timeseries/{user_id}/handwashing/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/handwashing/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.handwashing_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsHandwashingGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsHandwashingGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.handwashingGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsHandwashingGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsHandwashingGroupedRequest request = VitalsHandwashingGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().handwashingGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsHandwashingGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.HandwashingGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "count", "value": 1 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Heart Rate Alert Source: https://docs.junction.com/api-reference/data/timeseries/heart-rate-alert GET /v2/timeseries/{user_id}/heart_rate_alert/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/heart_rate_alert/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.heart_rate_alert_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsHeartRateAlertGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsHeartRateAlertGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.heartRateAlertGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsHeartRateAlertGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsHeartRateAlertGroupedRequest request = VitalsHeartRateAlertGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().heartRateAlertGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsHeartRateAlertGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.HeartRateAlertGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:30:52+00:00", "start": "2023-02-13T14:30:52+00:00", "type": "irregular_rhythm", "unit": "count", "value": 1 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Heart Rate Recovery One Minute Source: https://docs.junction.com/api-reference/data/timeseries/heart-rate-recovery-one-minute GET /v2/timeseries/{user_id}/heart_rate_recovery_one_minute/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/heart_rate_recovery_one_minute/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.heart_rate_recovery_one_minute_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsHeartRateRecoveryOneMinuteGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsHeartRateRecoveryOneMinuteGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.heartRateRecoveryOneMinuteGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsHeartRateRecoveryOneMinuteGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsHeartRateRecoveryOneMinuteGroupedRequest request = VitalsHeartRateRecoveryOneMinuteGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().heartRateRecoveryOneMinuteGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsHeartRateRecoveryOneMinuteGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.HeartRateRecoveryOneMinuteGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "count", "value": 37 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Heart Rate Source: https://docs.junction.com/api-reference/data/timeseries/heartrate GET /v2/timeseries/{user_id}/heartrate/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/heartrate/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.heartrate_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsHeartrateGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsHeartrateGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.heartrateGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsHeartrateGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsHeartrateGroupedRequest request = VitalsHeartrateGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().heartrateGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsHeartrateGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.HeartrateGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "timestamp": "2023-02-13T14:30:52+00:00", "unit": "bpm", "value": 70 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Heart Rate Variability Source: https://docs.junction.com/api-reference/data/timeseries/hrv GET /v2/timeseries/{user_id}/hrv/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/hrv/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.hrv_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsHrvGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsHrvGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.hrvGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsHrvGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsHrvGroupedRequest request = VitalsHrvGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().hrvGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsHrvGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.HrvGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "timestamp": "2023-02-13T14:30:52+00:00", "unit": "rmssd", "value": 48 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Inhaler Usage Source: https://docs.junction.com/api-reference/data/timeseries/inhaler-usage GET /v2/timeseries/{user_id}/inhaler_usage/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/inhaler_usage/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.inhaler_usage_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsInhalerUsageGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsInhalerUsageGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.inhalerUsageGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsInhalerUsageGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsInhalerUsageGroupedRequest request = VitalsInhalerUsageGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().inhalerUsageGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsInhalerUsageGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.InhalerUsageGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "count", "value": 2 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Insulin Injection Source: https://docs.junction.com/api-reference/data/timeseries/insulin-injection GET /v2/timeseries/{user_id}/insulin_injection/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/insulin_injection/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.insulin_injection_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsInsulinInjectionGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsInsulinInjectionGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.insulinInjectionGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsInsulinInjectionGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsInsulinInjectionGroupedRequest request = VitalsInsulinInjectionGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().insulinInjectionGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsInsulinInjectionGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.InsulinInjectionGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:30:52+00:00", "start": "2023-02-13T14:30:52+00:00", "type": "long_acting", "unit": "unit", "value": 60 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Lean Body Mass Source: https://docs.junction.com/api-reference/data/timeseries/lean-body-mass GET /v2/timeseries/{user_id}/lean_body_mass/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/lean_body_mass/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.lean_body_mass_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsLeanBodyMassGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsLeanBodyMassGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.leanBodyMassGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsLeanBodyMassGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsLeanBodyMassGroupedRequest request = VitalsLeanBodyMassGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().leanBodyMassGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsLeanBodyMassGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.LeanBodyMassGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "kg", "value": 50 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Mindfulness Minutes Source: https://docs.junction.com/api-reference/data/timeseries/mindfulness-minutes GET /v2/timeseries/{user_id}/mindfulness_minutes/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/mindfulness_minutes/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.mindfulness_minutes_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsMindfulnessMinutesGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsMindfulnessMinutesGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.mindfulnessMinutesGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsMindfulnessMinutesGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsMindfulnessMinutesGroupedRequest request = VitalsMindfulnessMinutesGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().mindfulnessMinutesGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsMindfulnessMinutesGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.MindfulnessMinutesGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2026-03-09T22:16:10.210767+00:00", "start": "2023-02-13T14:57:24+00:00", "unit": "min", "value": 42 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Note Source: https://docs.junction.com/api-reference/data/timeseries/note GET /v2/timeseries/{user_id}/note/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/note/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.note_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsNoteGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsNoteGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.noteGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsNoteGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsNoteGroupedRequest request = VitalsNoteGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().noteGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsNoteGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.NoteGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:30:52+00:00", "start": "2023-02-13T14:30:52+00:00", "tags": [ "food", "exercise" ], "unit": "text", "value": "Lorem ipsum dolor sit amet" } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Peak Expiratory Flow Rate Source: https://docs.junction.com/api-reference/data/timeseries/peak-expiratory-flow-rate GET /v2/timeseries/{user_id}/peak_expiratory_flow_rate/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/peak_expiratory_flow_rate/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.peak_expiratory_flow_rate_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsPeakExpiratoryFlowRateGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsPeakExpiratoryFlowRateGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.peakExpiratoryFlowRateGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsPeakExpiratoryFlowRateGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsPeakExpiratoryFlowRateGroupedRequest request = VitalsPeakExpiratoryFlowRateGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().peakExpiratoryFlowRateGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsPeakExpiratoryFlowRateGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.PeakExpiratoryFlowRateGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "L/min", "value": 450 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Respiratory Rate Source: https://docs.junction.com/api-reference/data/timeseries/respiratory-rate GET /v2/timeseries/{user_id}/respiratory_rate/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/respiratory_rate/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.respiratory_rate_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsRespiratoryRateGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsRespiratoryRateGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.respiratoryRateGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsRespiratoryRateGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsRespiratoryRateGroupedRequest request = VitalsRespiratoryRateGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().respiratoryRateGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsRespiratoryRateGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.RespiratoryRateGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "timestamp": "2023-02-13T14:30:52+00:00", "unit": "bpm", "value": 15.5 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Sleep Apnea Alert Source: https://docs.junction.com/api-reference/data/timeseries/sleep-apnea-alert GET /v2/timeseries/{user_id}/sleep_apnea_alert/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/sleep_apnea_alert/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.sleep_apnea_alert_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsSleepApneaAlertGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsSleepApneaAlertGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.sleepApneaAlertGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsSleepApneaAlertGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsSleepApneaAlertGroupedRequest request = VitalsSleepApneaAlertGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().sleepApneaAlertGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsSleepApneaAlertGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.SleepApneaAlertGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "count", "value": 1 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Sleep Breathing Disturbance Source: https://docs.junction.com/api-reference/data/timeseries/sleep-breathing-disturbance GET /v2/timeseries/{user_id}/sleep_breathing_disturbance/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/sleep_breathing_disturbance/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.sleep_breathing_disturbance_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsSleepBreathingDisturbanceGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsSleepBreathingDisturbanceGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.sleepBreathingDisturbanceGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsSleepBreathingDisturbanceGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsSleepBreathingDisturbanceGroupedRequest request = VitalsSleepBreathingDisturbanceGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().sleepBreathingDisturbanceGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsSleepBreathingDisturbanceGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.SleepBreathingDisturbanceGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "type": "elevated", "unit": "count", "value": 12 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Stand Duration Source: https://docs.junction.com/api-reference/data/timeseries/stand-duration GET /v2/timeseries/{user_id}/stand_duration/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/stand_duration/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.stand_duration_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsStandDurationGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsStandDurationGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.standDurationGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsStandDurationGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsStandDurationGroupedRequest request = VitalsStandDurationGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().standDurationGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsStandDurationGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.StandDurationGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "min", "value": 15 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Stand Hour Source: https://docs.junction.com/api-reference/data/timeseries/stand-hour GET /v2/timeseries/{user_id}/stand_hour/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/stand_hour/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.stand_hour_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsStandHourGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsStandHourGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.standHourGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsStandHourGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsStandHourGroupedRequest request = VitalsStandHourGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().standHourGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsStandHourGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.StandHourGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "count", "value": 1 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Steps Source: https://docs.junction.com/api-reference/data/timeseries/steps GET /v2/timeseries/{user_id}/steps/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/steps/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.steps_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsStepsGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsStepsGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.stepsGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsStepsGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsStepsGroupedRequest request = VitalsStepsGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().stepsGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsStepsGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.StepsGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "unit": "count", "value": 123 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Stress Level Source: https://docs.junction.com/api-reference/data/timeseries/stress-level GET /v2/timeseries/{user_id}/stress_level/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/stress_level/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.stress_level_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsStressLevelGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsStressLevelGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.stressLevelGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsStressLevelGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsStressLevelGroupedRequest request = VitalsStressLevelGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().stressLevelGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsStressLevelGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.StressLevelGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "timestamp": "2023-02-13T14:30:52+00:00", "unit": "%", "value": 35 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Uv Exposure Source: https://docs.junction.com/api-reference/data/timeseries/uv-exposure GET /v2/timeseries/{user_id}/uv_exposure/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/uv_exposure/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.uv_exposure_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsUvExposureGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsUvExposureGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.uvExposureGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsUvExposureGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsUvExposureGroupedRequest request = VitalsUvExposureGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().uvExposureGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsUvExposureGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.UvExposureGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "index", "value": 5 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Vo2 Max Source: https://docs.junction.com/api-reference/data/timeseries/vo2-max GET /v2/timeseries/{user_id}/vo2_max/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/vo2_max/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.vo2_max_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsVo2MaxGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsVo2MaxGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.vo2MaxGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsVo2MaxGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsVo2MaxGroupedRequest request = VitalsVo2MaxGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().vo2MaxGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsVo2MaxGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.Vo2MaxGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "unit": "mL/kg/min", "value": 48 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Waist Circumference Source: https://docs.junction.com/api-reference/data/timeseries/waist-circumference GET /v2/timeseries/{user_id}/waist_circumference/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/waist_circumference/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.waist_circumference_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsWaistCircumferenceGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsWaistCircumferenceGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.waistCircumferenceGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsWaistCircumferenceGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsWaistCircumferenceGroupedRequest request = VitalsWaistCircumferenceGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().waistCircumferenceGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsWaistCircumferenceGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.WaistCircumferenceGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "cm", "value": 90 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Water Source: https://docs.junction.com/api-reference/data/timeseries/water GET /v2/timeseries/{user_id}/water/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/water/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.water_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsWaterGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsWaterGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.waterGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsWaterGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsWaterGroupedRequest request = VitalsWaterGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().waterGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsWaterGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.WaterGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "unit": "ml", "value": 400 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Wheelchair Push Source: https://docs.junction.com/api-reference/data/timeseries/wheelchair-push GET /v2/timeseries/{user_id}/wheelchair_push/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/wheelchair_push/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.wheelchair_push_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsWheelchairPushGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsWheelchairPushGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.wheelchairPushGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsWheelchairPushGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsWheelchairPushGroupedRequest request = VitalsWheelchairPushGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().wheelchairPushGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsWheelchairPushGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.WheelchairPushGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "count", "value": 52 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Workout Distance Source: https://docs.junction.com/api-reference/data/timeseries/workout-distance GET /v2/timeseries/{user_id}/workout_distance/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/workout_distance/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.workout_distance_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsWorkoutDistanceGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsWorkoutDistanceGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.workoutDistanceGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsWorkoutDistanceGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsWorkoutDistanceGroupedRequest request = VitalsWorkoutDistanceGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().workoutDistanceGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsWorkoutDistanceGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.WorkoutDistanceGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "m", "value": 37 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Workout Duration Source: https://docs.junction.com/api-reference/data/timeseries/workout-duration GET /v2/timeseries/{user_id}/workout_duration/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/workout_duration/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.workout_duration_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsWorkoutDurationGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsWorkoutDurationGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.workoutDurationGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsWorkoutDurationGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsWorkoutDurationGroupedRequest request = VitalsWorkoutDurationGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().workoutDurationGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsWorkoutDurationGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.WorkoutDurationGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:30:52+00:00", "intensity": "medium", "start": "2023-02-13T14:30:52+00:00", "unit": "min", "value": 48 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Workout Swimming Stroke Source: https://docs.junction.com/api-reference/data/timeseries/workout-swimming-stroke GET /v2/timeseries/{user_id}/workout_swimming_stroke/grouped The Response section on the page still needs work. Click here to check out the complete schema. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/timeseries/{user_id}/workout_swimming_stroke/grouped?start_date={{START_DATE}}&end_date={{END_DATE}} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.vitals.workout_swimming_stroke_grouped( user_id="", start_date="2021-10-01", end_date="2021-10-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { VitalsWorkoutSwimmingStrokeGroupedRequest } from '@tryvital/vital-node/api/resources/vitals'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: VitalsWorkoutSwimmingStrokeGroupedRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.vitals.workoutSwimmingStrokeGrouped( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.vitals.requests.VitalsWorkoutSwimmingStrokeGroupedRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); VitalsWorkoutSwimmingStrokeGroupedRequest request = VitalsWorkoutSwimmingStrokeGroupedRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.vitals().workoutSwimmingStrokeGrouped("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.VitalsWorkoutSwimmingStrokeGroupedRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Vitals.WorkoutSwimmingStrokeGrouped(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Example theme={null} { "groups": { "oura": [ { "data": [ { "end": "2023-02-13T14:57:24+00:00", "start": "2023-02-13T14:30:52+00:00", "timestamp": "2023-02-13T14:30:52+00:00", "unit": "count", "value": 37 } ], "source": { "provider": "oura", "type": "ring" } } ] } } ``` # Raw Source: https://docs.junction.com/api-reference/data/workouts/get-raw GET /v2/summary/workouts/{user_id}/raw Get raw workout summary for user_id ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/workouts/{user_id}/raw \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.workouts.get_raw("", start_date="2021-01-01", end_date="2021-01-02") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { WorkoutsGetRawRequest } from '@tryvital/vital-node/api/resources/workouts'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: WorkoutsGetRawRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.workouts.getRaw( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.workouts.requests.WorkoutsGetRawRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); WorkoutsGetRawRequest request = WorkoutsGetRawRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.workouts().getRaw("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.WorkoutsGetRawRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Workouts.GetRaw(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Stream Source: https://docs.junction.com/api-reference/data/workouts/get-stream GET /v2/timeseries/workouts/{workout_id}/stream ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/workouts/{workout_id}/stream \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.workouts.get_by_workout_id("") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.workouts.getByWorkoutId("") ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.workouts().getByWorkoutId(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.Workouts.GetByWorkoutId( context.TODO(), "" ) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Summary Source: https://docs.junction.com/api-reference/data/workouts/get-summary GET /v2/summary/workouts/{user_id} Get workout summary for user_id ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/summary/workouts/{user_id} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.workouts.get( user_id="", start_date="2022-05-01", end_date="2022-06-01" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { WorkoutsGetRequest } from '@tryvital/vital-node/api/resources/workouts'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: WorkoutsGetRequest = { startDate: "2022-05-01", endDate: "2022-06-01" } const data = await client.workouts.get( '', request ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.workouts.requests.WorkoutsGetRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); WorkoutsGetRequest request = WorkoutsGetRequest.builder() .startDate("2022-05-01") .endDate("2022-06-01") .build(); var data = vital.workouts().get("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) EndDate := "2022-06-01" request := &vital.WorkoutsGetRequest{ StartDate: "2022-05-01", EndDate: &EndDate, } response, err := client.Workouts.Get(context.TODO(), "*", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Get ABN Form PDF Source: https://docs.junction.com/api-reference/lab-testing/abn-pdf GET /v3/order/{order_id}/abn_pdf GET ABN pdf for an order Retrieving ABNs is currently in closed beta. When [getting an order](/api-reference/lab-testing/get-order#response-has-abn), you can view the `has_abn` field to determine if an ABN form is available for this order. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order//abn_pdf' \ --header 'accept: application/pdf' \ --header 'x-vital-api-key: {YOUR_KEY}' --output file.pdf ``` ```python Python theme={null} from vital import Client client = Client( api_key=, environment="sandbox", region="us" ) data = client.LabTests.get_order_abn_pdf(order_id=''); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getOrderAbnPdf(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getOrderAbnPdf(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.getOrderAbnPdf(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Get Area Info Source: https://docs.junction.com/api-reference/lab-testing/area-info GET /v3/order/area/info GET information about an area with respect to lab-testing. Information returned: * Whether a given zip code is served by our Phlebotomy network. * List of Lab locations in the area. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order/area/info?zip_code=85004' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_area_info(zip_code="85004") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { LabTestsGetAreaInfoRequest } from '@tryvital/vital-node/api/resources/labTests'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: LabTestsGetAreaInfoRequest = { zipCode: "85004" } const data = await client.labTests.getAreaInfo(request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.LabTestsGetAreaInfoRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); LabTestsGetAreaInfoRequest request = LabTestsGetAreaInfoRequest.builder() .zipCode("85004") .build(); var data = vital.labTests().getAreaInfo(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.LabTestsGetAreaInfoRequest{ ZipCode: "85004", } response, err := client.LabTests.GetAreaInfo(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "zip_code": "85004", "phlebotomy": { "is_served": true, "providers": [ { "name": "getlabs", "tier": ["appointment-ready"] }, { "name": "phlebfinders", "tier": ["appointment-request"] } ] }, "central_labs": { "labcorp": { "patient_service_centers": { "within_radius": 5, # number of PSC's within radius of provided zip code "radius": "25", "capabilities": ["stat"] } } } } ``` # Get Order Appointment Availability Source: https://docs.junction.com/api-reference/lab-testing/at-home-phlebotomy/appointment-availability POST /v3/order/phlebotomy/appointment/availability Return the available time slots to book an appointment with a phlebotomist for the given address and order. ```bash cURL theme={null} curl --request POST \ --url '{{BASE_URL}}/v3/order/phlebotomy/appointment/availability' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --data ' { "first_line": "256 West Lincoln Street", "second_line": "14", "city": "Phoenix", "state": "AZ", "zip_code": "85004", } ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.us_address import UsAddress client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) request = UsAddress( first_line="123 Main St", second_line="", city="San Francisco", state="CA", zip_code="94111", unit="", ) data = client.lab_tests.get_phlebotomy_appointment_availability(request=request) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { UsAddress } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: UsAddress = { firstLine: "123 Main St", city: "San Francisco", state: "CA", zipCode: "94105", } const data = await client.labTests.getPhlebotomyAppointmentAvailability(request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.types.UsAddress; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); UsAddress request = UsAddress .builder() .firstLine("") .state("") .zipCode("") .build(); var data = vital.labTests().getPhlebotomyAppointmentAvailability(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.UsAddress{ FirstLine: "123 Main St", City: "New York", State: "NY", ZipCode: "10001", } response, err := client.LabTests.GetPhlebotomyAppointmentAvailability(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "timezone":"America/Phoenix", "slots": [ { "date":"2023-05-09", "slots": [ { "booking_key": "foo123", "start": "2023-05-09T17:00:00+00:00", "end": "2023-05-09T19:00:00+00:00", "expires_at": "2023-05-09T12:39:57.827000+00:00", "price": 3500, "is_priority": true, "num_appointments_available": 5 }, ... ], }, { "date":"2023-05-10", "slots": [ { "booking_key": "bar456", "start": "2023-05-10T12:00:00+00:00", "end": "2023-05-10T14:00:00+00:00", "expires_at": "2023-05-09T12:39:57.852000+00:00", "price": 7900, "is_priority": true, "num_appointments_available": 5 }, ... ], }, ] } ``` # Book Phlebotomy Appointment Source: https://docs.junction.com/api-reference/lab-testing/at-home-phlebotomy/appointment-booking POST /v3/order/{order_id}/phlebotomy/appointment/book Book an at-home phlebotomy appointment. ```bash cURL theme={null} curl --request POST \ --url '{{BASE_URL}}/v3/order/413d7205-f8a9-42ed-aa4a-edb99e481ca0/phlebotomy/appointment/book' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --data ' { "booking_key": "foo123" } ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.book_phlebotomy_appointment(order_id="", booking_key="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { AppointmentBookingRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: AppointmentBookingRequest = { bookingKey: "", } const data = await client.labTests.bookPhlebotomyAppointment("", request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.AppointmentBookingRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); AppointmentBookingRequest request = AppointmentBookingRequest .builder() .bookingKey("") .build(); var data = vital.labTests().bookPhlebotomyAppointment("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.AppointmentBookingRequest{ BookingKey: "", } response, err := client.LabTests.BookPhlebotomyAppointment(context.TODO(), "", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "413d7205-f8a9-42ed-aa4a-edb99e481ca0", "user_id": "202b2c2f-fb4c-44dc-a4f8-621186fde227", "address": { "first_line": "West Lincoln Street", "second_line": "", "city": "Phoenix", "state": "AZ", "zip_code": "85004", "unit": "14" }, "location": { "lng": -112.0772235, "lat": 33.4421912 }, "start_at": "2023-05-15T16:00:00+00:00", "end_at": "2023-05-15T18:00:00+00:00", "iana_timezone": "America/Phoenix", "type": "phlebotomy", "provider": "getlabs", "status": "pending", "provider_id": "e89eb489-7382-4966-bb14-7ab4763eba6c", "can_reschedule":true } ``` # Get Phlebotomy Appointment Cancellation Reasons Source: https://docs.junction.com/api-reference/lab-testing/at-home-phlebotomy/appointment-cancellation-reasons GET /v3/order/phlebotomy/appointment/cancellation-reasons Get the list of reasons for cancelling an at-home phlebotomy appointment. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order/phlebotomy/appointment/cancellation-reasons' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_phlebotomy_appointment_cancellation_reason(); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getPhlebotomyAppointmentCancellationReason(); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getPhlebotomyAppointmentCancellationReason(); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetPhlebotomyAppointmentCancellationReason(context.TODO()) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json theme={null} [ { "id": "5c0257ef-6fea-4a22-b20a-3ddab573d5c9", "name": "Did not fast for appointment", "is_refundable": true }, { "id": "448c519c-64b4-4497-ae73-622fa93371b3", "name": "Do not trust company", "is_refundable": true }, { "id": "d378e152-12d1-433e-9dd1-e0410f9331dc", "name": "Getlabs cannot deliver to my preferred lab", "is_refundable": true }, { "id": "5330c863-ac80-4316-901b-d305d0df74d5", "name": "No longer interested", "is_refundable": true }, { "id": "2b9f23fd-163e-4483-bb10-90c74a67e0dc", "name": "Other", "is_refundable": true }, { "id": "98f861dd-fe61-4817-a9d6-1b99b19cd0fb", "name": "Provider asked me to cancel", "is_refundable": true }, { "id": "7dfd7da5-ed6e-40bb-a7e4-c8003f0c10a9", "name": "Scheduled for wrong patient", "is_refundable": true }, { "id": "796da8c8-e654-4347-8ded-1026410c1976", "name": "Scheduled time no longer works", "is_refundable": true }, { "id": "ba02af35-a34f-4a7a-abe5-5f766e8f6cd1", "name": "Unable to get lab order from provider", "is_refundable": true }, { "id": "0c9425db-f11e-49c5-b976-3c0d1d4a4ea8", "name": "Wanted to book in-person appointment", "is_refundable": true }, { "id": "2599a0ea-0b4a-42fe-8df6-d8f7c68182e7", "name": "Went to lab for appointment", "is_refundable": true } ] ``` # Cancel Phlebotomy Appointment Source: https://docs.junction.com/api-reference/lab-testing/at-home-phlebotomy/appointment-cancelling PATCH /v3/order/{order_id}/phlebotomy/appointment/cancel Cancel a previously booked at-home phlebotomy appointment. ```bash cURL theme={null} curl --request PATCH \ --url '{{BASE_URL}}/v3/order/413d7205-f8a9-42ed-aa4a-edb99e481ca0/phlebotomy/appointment/cancel' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --data ' { "cancellation_reason_id": "7dfd7da5-ed6e-40bb-a7e4-c8003f0c10a9" } ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_phlebotomy_appointment_cancellation_reason(); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { AppointmentCancelRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: AppointmentCancelRequest = { cancellationReasonId: "", } const data = await client.labTests.cancelPhlabotomyAppointment("", request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.AppointmentCancelRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); AppointmentCancelRequest request = AppointmentCancelRequest .builder() .cancellationReasonId("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.AppointmentCancelRequest{ CancellationReasonId: "", } response, err := client.LabTests.CancelPhlabotomyAppointment(context.TODO(), "", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "413d7205-f8a9-42ed-aa4a-edb99e481ca0", "user_id": "202b2c2f-fb4c-44dc-a4f8-621186fde227", "address": { "first_line": "West Lincoln Street", "second_line": "", "city": "Phoenix", "state": "AZ", "zip_code": "85004", "unit": "14" }, "location": { "lng": -112.0772235, "lat": 33.4421912 }, "start_at": "2023-05-17T20:00:00+00:00", "end_at": "2023-05-17T22:00:00+00:00", "iana_timezone": "America/Phoenix", "type": "phlebotomy", "provider": "getlabs", "status": "cancelled", "provider_id": "e89eb489-7382-4966-bb14-7ab4763eba6c", "can_reschedule": true } ``` # Request Phlebotomy Appointment Source: https://docs.junction.com/api-reference/lab-testing/at-home-phlebotomy/appointment-request POST /v3/order/{order_id}/phlebotomy/appointment/request Request an at-home phlebotomy appointment. ```bash cURL theme={null} curl --request POST \ --url '{{BASE_URL}}/v3/order/413d7205-f8a9-42ed-aa4a-edb99e481ca0/phlebotomy/appointment/request' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --data ' { "address": { "first_line": "256 West Lincoln Street", "second_line": "14", "city": "Phoenix", "state": "AZ", "zip_code": "85004", } "provider": "phlebfinders" } ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) request = RequestAppointmentRequest( address= UsAddress( first_line="123 Main St", second_line="", city="San Francisco", state="CA", zip_code="94111", unit="", ), provider="phlebfinders" ) data = client.lab_tests.request_phlebotomy_appointment(order_id="", request=request) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { RequestAppointmentRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const address: UsAddress = { firstLine: "123 Main St", city: "San Francisco", state: "CA", zipCode: "94105", } const request: RequestAppointmentRequest = { address: address, provider: "phlebfinders" } const data = await client.labTests.requestPhlebotomyAppointment("", request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.RequestAppointmentRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); UsAddress address = UsAddress .builder() .firstLine("") .state("") .zipCode("") .build(); RequestAppointmentRequest request = RequestAppointmentRequest .builder() .address(address) .provider("phlebfinders") .build(); var data = vital.labTests().requestPhlebotomyAppointment("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) address := &vital.UsAddress{ FirstLine: "123 Main St", City: "New York", State: "NY", ZipCode: "10001", } request := &vital.RequestAppointmentRequest{ Address: address, Provider: "phlebfinders", } response, err := client.LabTests.RequestPhlebotomyAppointment(context.TODO(), "", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "413d7205-f8a9-42ed-aa4a-edb99e481ca0", "user_id": "202b2c2f-fb4c-44dc-a4f8-621186fde227", "address": { "first_line": "West Lincoln Street", "second_line": "", "city": "Phoenix", "state": "AZ", "zip_code": "85004", "unit": "14" }, "location": { "lng": -112.0772235, "lat": 33.4421912 }, "type": "phlebotomy", "provider": "phlebfinders", "status": "pending", "provider_id": "e89eb489-7382-4966-bb14-7ab4763eba6c", "can_reschedule":false } ``` # Reschedule Phlebotomy Appointment Source: https://docs.junction.com/api-reference/lab-testing/at-home-phlebotomy/appointment-rescheduling PATCH /v3/order/{order_id}/phlebotomy/appointment/reschedule Reschedule a previously booked at-home phlebotomy appointment. ```bash cURL theme={null} curl --request PATCH \ --url '{{BASE_URL}}/v3/order/413d7205-f8a9-42ed-aa4a-edb99e481ca0/phlebotomy/appointment/reschedule' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --data ' { "booking_key": "bar456" } ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.reschedule_phlebotomy_appointment(order_id="", booking_key=""); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { AppointmentRescheduleRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: AppointmentRescheduleRequest = { bookingKey: "", } const data = await client.labTests.reschedulePhlebotomyAppointment("", request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.AppointmentRescheduleRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); AppointmentRescheduleRequest request = AppointmentRescheduleRequest .builder() .bookingKey("") .build(); var data = vital.labTests().reschedulePhlebotomyAppointment("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.AppointmentRescheduleRequest{ BookingKey: "", } response, err := client.LabTests.ReschedulePhlebotomyAppointment(context.TODO(), "", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "413d7205-f8a9-42ed-aa4a-edb99e481ca0", "user_id": "202b2c2f-fb4c-44dc-a4f8-621186fde227", "address": { "first_line": "West Lincoln Street", "second_line": "", "city": "Phoenix", "state": "AZ", "zip_code": "85004", "unit":"14" }, "location": { "lng": -112.0772235, "lat": 33.4421912 }, "start_at": "2023-05-17T20:00:00+00:00", "end_at": "2023-05-17T22:00:00+00:00", "iana_timezone": "America/Phoenix", "type": "phlebotomy", "provider": "getlabs", "status": "pending", "provider_id": "e89eb489-7382-4966-bb14-7ab4763eba6c", "can_reschedule": true } ``` # Get Phlebotomy Appointment Source: https://docs.junction.com/api-reference/lab-testing/at-home-phlebotomy/get-appointment GET /v3/order/{order_id}/phlebotomy/appointment Get the appointment associated with an order. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order/413d7205-f8a9-42ed-aa4a-edb99e481ca0/phlebotomy/appointment' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_phlebotomy_appointment(order_id="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getPhlebotomyAppointment(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getPhlebotomyAppointment(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetPhlebotomyAppointment(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "413d7205-f8a9-42ed-aa4a-edb99e481ca0", "user_id": "202b2c2f-fb4c-44dc-a4f8-621186fde227", "address": { "first_line": "West Lincoln Street", "second_line": "", "city": "Phoenix", "state": "AZ", "zip_code": "85004", "unit": "14" }, "location": { "lng": -112.0772235, "lat": 33.4421912 }, "start_at": "2023-05-17T20:00:00+00:00", "end_at": "2023-05-17T22:00:00+00:00", "iana_timezone": "America/Phoenix", "type": "phlebotomy", "provider": "getlabs", "status": "pending", "provider_id": "e89eb489-7382-4966-bb14-7ab4763eba6c", "can_reschedule": true } ``` # Get Available Biomarkers Source: https://docs.junction.com/api-reference/lab-testing/biomarkers GET /v3/lab_tests/markers List active and orderable markers for a given Lab. Note that reflex markers are not included. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/lab_tests/markers' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_markers() ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getMarkers(); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getMarkers(); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetMarkers(context.TODO()) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "markers": [ { "id": 202, "name": "Acetylcholine Receptor (AChR) Antibodies, Complete Profile with Reflex to MuSK Antibodies", "slug": "acetylcholine-receptor-achr-antibodies-complete-profile-with-reflex-to-musk-antibodies", "description": "Acetylcholine Receptor (AChR) Antibodies, Complete Profile with Reflex to MuSK Antibodies", "lab_id": 6, "provider_id": "165605", "type": "biomarker", "unit": null, "price": "N/A", "expected_results": [ { "id": 2938, "name": "AChR Blocking Abs, Serum", "slug": "achr-blocking-abs-serum", "lab_id": 6, "provider_id": "085927", "loinc": { "id": 3514, "name": "Acetylcholine receptor blocking Ab Qn (S)", "slug": "acetylcholine-receptor-blocking-ab-qn-s", "code": "11561-8", "unit": "%{inhibition}" } }, { "id": 2939, "name": "AChR Binding Abs, Serum", "slug": "achr-binding-abs-serum", "lab_id": 6, "provider_id": "085904", "loinc": { "id": 3174, "name": "Acetylcholine receptor binding Ab (S) [Moles/Vol]", "slug": "acetylcholine-receptor-binding-ab-s-moles-vol", "code": "11034-6", "unit": "nmol/L" } }, { "id": 2940, "name": "AChR-modulating Ab", "slug": "achr-modulating-ab", "lab_id": 6, "provider_id": "505199", "loinc": { "id": 61121, "name": "Acetylcholine receptor modulation Ab FC Ql (S)", "slug": "acetylcholine-receptor-modulation-ab-fc-ql-s", "code": "99062-2", "unit": null } } ] }, ], "total": 2, "page": 1, "size": 2 } ``` # Cancel Order Source: https://docs.junction.com/api-reference/lab-testing/cancel-order POST /v3/order/{order_id}/cancel POST cancel order ```python Python theme={null} from vital import Client client = Client( api_key=, environment="sandbox", region="us" ) data = client.LabTests.cancel_order(order_id=''); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.cancelOrder(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().cancelOrder(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.CancelOrder(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} [ { "order": { "title": "ClientFacingOrder", "required": [ "user_id", "id", "team_id", "patient_details", "patient_address", "lab_test", "details", "created_at", "updated_at", "events" ], "type": "object", "properties": { "user_id": { "title": "User Id", "type": "string", "description": "User id returned by vital create user request. This id should be stored in your database against the user and used for all interactions with the vital api.", "format": "uuid" }, "user_key": { "title": "User Key", "type": "string", "description": "User key returned by vital create user key request. This key should be stored in your database against the user and used for all interactions with the vital api.", "format": "uuid", "deprecated": true }, "id": { "title": "Id", "type": "string", "description": "The Vital Order ID", "format": "uuid" }, "team_id": { "title": "Team Id", "type": "string", "description": "Your team id.", "format": "uuid" }, "patient_details": { "title": "Patient Details", "allOf": [ { "title": "PatientDetails", "required": ["dob", "gender"], "type": "object", "properties": { "dob": { "title": "Dob", "type": "string", "format": "date-time" }, "gender": { "title": "Gender", "type": "string" }, "email": { "title": "Email", "type": "string" } } } ], "description": "Patient Details" }, "patient_address": { "title": "Patient Address", "allOf": [ { "title": "PatientAddress", "required": [ "receiver_name", "street", "city", "state", "zip", "country", "phone_number" ], "type": "object", "properties": { "receiver_name": { "title": "Receiver Name", "type": "string" }, "street": { "title": "Street", "type": "string" }, "street_number": { "title": "Street Number", "type": "string" }, "city": { "title": "City", "type": "string" }, "state": { "title": "State", "type": "string" }, "zip": { "title": "Zip", "type": "string" }, "country": { "title": "Country", "type": "string" }, "phone_number": { "title": "Phone Number", "type": "string" } } } ], "description": "Patient Address" }, "lab_test": { "title": "Lab Test", "allOf": [ { "title": "LabTestInDB", "required": [ "slug", "name", "sample_type", "method", "lab_id", "price", "is_active", "created_at", "updated_at", "id" ], "type": "object", "properties": { "slug": { "title": "Slug", "type": "string" }, "name": { "title": "Name", "type": "string" }, "description": { "title": "Description", "type": "string" }, "sample_type": { "title": "LabTestSampleType", "enum": ["dried_blood_spot"], "type": "string", "description": "The type of sample used to perform a lab test." }, "method": { "title": "LabTestMethod", "enum": ["testkit", "walk_in_test"], "type": "string", "description": "The method used to perform a lab test." }, "lab_id": { "title": "Lab Id", "type": "integer" }, "skus": { "title": "Skus", "type": "array", "items": { "type": "object" } }, "price": { "title": "Price", "type": "number" }, "is_active": { "title": "Is Active", "type": "boolean" }, "turnaround_time_lower": { "title": "Turnaround Time Lower", "type": "integer" }, "turnaround_time_upper": { "title": "Turnaround Time Upper", "type": "integer" }, "created_at": { "title": "Created At", "type": "string", "format": "date-time" }, "updated_at": { "title": "Updated At", "type": "string", "format": "date-time" }, "id": { "title": "Id", "type": "string", "format": "uuid" }, "lab": { "title": "LabInDB", "required": [ "slug", "name", "first_line_address", "city", "zipcode", "id" ], "type": "object", "properties": { "slug": { "title": "Slug", "type": "string" }, "name": { "title": "Name", "type": "string" }, "first_line_address": { "title": "First Line Address", "type": "string" }, "city": { "title": "City", "type": "string" }, "zipcode": { "title": "Zipcode", "type": "string" }, "clia": { "title": "Clia", "type": "string" }, "created_at": { "title": "Created At", "type": "string", "format": "date-time" }, "updated_at": { "title": "Updated At", "type": "string", "format": "date-time" }, "id": { "title": "Id", "type": "integer" } } }, "markers": { "title": "Markers", "type": "array", "items": { "title": "MarkerInDB", "required": ["name", "slug", "description", "id"], "type": "object", "properties": { "name": { "title": "Name", "type": "string" }, "slug": { "title": "Slug", "type": "string" }, "description": { "title": "Description", "type": "string" }, "created_at": { "title": "Created At", "type": "string", "format": "date-time" }, "updated_at": { "title": "Updated At", "type": "string", "format": "date-time" }, "id": { "title": "Id", "type": "integer" } } } } }, "description": "Schema for a LabTest in the database." } ], "description": "The Vital Test associated with the order" }, "details": { "title": "Details", "anyOf": [ { "title": "ClientFacingWalkInOrderDetails", "required": ["type"], "type": "object", "properties": { "type": { "title": "Type", "enum": ["walk_in_test"], "type": "string" }, "data": { "title": "ClientFacingWalkInTestOrder", "required": ["id", "created_at", "updated_at"], "type": "object", "properties": { "id": { "title": "Id", "type": "string", "description": "The Vital walk-in test Order ID", "format": "uuid" }, "created_at": { "title": "Created At", "type": "string", "format": "date-time" }, "updated_at": { "title": "Updated At", "type": "string", "format": "date-time" } }, "description": "Schema for a walk-in test order in the client facing API.\n\nTo be used as part of a ClientFacingOrder.", "example": { "id": "0651ee15-31a1-461b-9c10-86aa960da6c9", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } } } }, { "title": "ClientFacingTestKitOrderDetails", "required": ["type"], "type": "object", "properties": { "type": { "title": "Type", "enum": ["testkit"], "type": "string" }, "data": { "title": "ClientFacingTestkitOrder", "required": ["id", "created_at", "updated_at"], "type": "object", "properties": { "id": { "title": "Id", "type": "string", "description": "The Vital TestKit Order ID", "format": "uuid" }, "shipment": { "title": "Shipment", "allOf": [ { "title": "ClientFacingShipment", "required": [ "id", "outbound_tracking_number", "outbound_tracking_url", "inbound_tracking_number", "inbound_tracking_url", "outbound_courier", "inbound_courier", "notes" ], "type": "object", "properties": { "id": { "title": "Id", "type": "string", "description": "The Vital Shipment ID", "format": "uuid" }, "outbound_tracking_number": { "title": "Outbound Tracking Number", "type": "string", "description": "Tracking number for delivery to customer" }, "outbound_tracking_url": { "title": "Outbound Tracking Url", "type": "string", "description": "Tracking url for delivery to customer" }, "inbound_tracking_number": { "title": "Inbound Tracking Number", "type": "string", "description": "Tracking number for delivery to lab" }, "inbound_tracking_url": { "title": "Inbound Tracking Url", "type": "string", "description": "Tracking url for delivery to lab" }, "outbound_courier": { "title": "Outbound Courier", "type": "string", "description": "Courier used for delivery to customer" }, "inbound_courier": { "title": "Inbound Courier", "type": "string", "description": "Courier used for delivery to lab" }, "notes": { "title": "Notes", "type": "string", "description": "Notes associated to the Vital shipment" } }, "description": "Schema for a Shipment in the client facing API.\n\nTo be used as part of a ClientFacingTestkitOrder.", "example": { "id": "dcab86c6-a315-493d-97aa-2fd0fa649130", "outbound_tracking_number": "", "outbound_tracking_url": "", "inbound_tracking_number": "", "inbound_tracking_url": "", "outbound_courier": "usps", "inbound_courier": "usps", "notes": "" } } ], "description": "Shipment object" }, "created_at": { "title": "Created At", "type": "string", "format": "date-time" }, "updated_at": { "title": "Updated At", "type": "string", "format": "date-time" } }, "description": "Schema for a testkit order in the client facing API.\n\nTo be used as part of a ClientFacingOrder.", "example": { "id": "43697a79-298f-43db-9703-e4cf16006c66", "shipment": { "id": "51caeb91-cc5c-4f82-a4c2-6f23b68e54c5", "outbound_tracking_number": "", "outbound_tracking_url": "", "inbound_tracking_number": "", "inbound_tracking_url": "", "outbound_courier": "usps", "inbound_courier": "usps", "notes": "", "created_at": "2020-01-01T00:00:00.000Z", "updated_at": "2020-01-01T00:00:00.000Z" }, "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } } } } ] }, "sample_id": { "title": "Sample Id", "type": "string", "description": "Sample ID" }, "notes": { "title": "Notes", "type": "string", "description": "Notes associated with the order" }, "created_at": { "title": "Created At", "type": "string", "description": "When your order was created", "format": "date-time" }, "updated_at": { "title": "Updated At", "type": "string", "description": "When your order was last updated", "format": "date-time" }, "events": { "title": "Events", "type": "array", "items": { "title": "ClientFacingOrderEvent", "required": ["id", "created_at", "status"], "type": "object", "properties": { "id": { "title": "Id", "type": "integer" }, "created_at": { "title": "Created At", "type": "string", "format": "date-time" }, "status": { "title": "OrderV2Status", "enum": [ "received.walk_in_test.requisition_created", "completed.walk_in_test.order_completed", "failed.walk_in_test.sample_error", "received.testkit.ordered", "received.testkit.requisition_created", "collecting_sample.testkit.transit_customer", "collecting_sample.testkit.out_for_delivery", "collecting_sample.testkit.with_customer", "collecting_sample.testkit.transit_lab", "collecting_sample.testkit.problem_in_transit_customer", "collecting_sample.testkit.problem_in_transit_lab", "sample_with_lab.testkit.delivered_to_lab", "completed.testkit.completed", "failed.testkit.failure_to_deliver_to_customer", "failed.testkit.failure_to_deliver_to_lab", "failed.testkit.sample_error", "failed.testkit.lost", "cancelled.testkit.cancelled", "cancelled.testkit.do_not_process" ], "type": "string", "description": "An enumeration." } } } }, "status": { "title": "OrderV2TopLevelStatus", "enum": [ "received", "collecting_sample", "sample_with_lab", "completed", "cancelled", "failed" ], "type": "string", "description": "An enumeration." } }, "example": { "id": "0ee312e2-6773-4a21-a6e1-506882cd98ed", "team_id": "cbb64555-af07-46c1-be09-ef89308e9b60", "user_id": "94e2d9f2-d600-4a23-9f08-536df378e2c7", "patient_details": { "dob": "2020-01-01", "gender": "male" }, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789" }, "details": { "type": "testkit", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "shipment": { "id": "d55210cc-3d9f-4115-8262-5013f700c7be", "outbound_tracking_number": "", "outbound_tracking_url": "", "inbound_tracking_number": "", "inbound_tracking_url": "", "outbound_courier": "usps", "inbound_courier": "usps", "notes": "", "created_at": "2020-01-01T00:00:00.000Z", "updated_at": "2020-01-01T00:00:00.000Z" }, "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "method": "testkit" }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "collecting_sample", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.testkit.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.testkit.requisition_created" }, { "id": 3, "created_at": "2022-01-03T00:00:00Z", "status": "collecting_sample.testkit.transit_customer" } ] } }, "status": { "title": "Status", "type": "string" }, "message": { "title": "Message", "type": "string" } } ] ``` # Compendium Convert Source: https://docs.junction.com/api-reference/lab-testing/compendium/convert POST /v3/compendium/convert This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. # Compendium Search Source: https://docs.junction.com/api-reference/lab-testing/compendium/search POST /v3/compendium/search This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. # Create Order Source: https://docs.junction.com/api-reference/lab-testing/create-order POST /v3/order Patient name fields (`first_name`, `last_name`) must follow specific validation rules due to lab restrictions. See [Patient Name Validation](/lab/workflow/order-requirements#patient-name-validation) for complete details. ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.gender import Gender from vital.types.patient_address_compatible import PatientAddressCompatible from vital.types.patient_details import PatientDetails from datetime import datetime client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.create_order( user_id="63661a2b-2bb3-4125-bb1a-b590f64f057f", lab_test_id="5b41f610-ebc5-4803-8f0c-a61c3bdc7faf", patient_details=PatientDetails( first_name="John", last_name="Doe", dob=datetime.fromisoformat("2020-01-01"), gender=Gender.MALE, phone_number="+1123456789", email="email@email.com" ), patient_address=PatientAddressCompatible( receiver_name="john Doe", first_line="123 Main St.", second_line="Apt. 208", city="San Francisco", state="CA", zip="91189", country="US", phone_number="+1123456789" ), ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { CreateOrderRequestCompatible} from '@tryvital/vital-node/api/resources/labTests'; import { Gender } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: CreateOrderRequestCompatible = { userId: "63661a2b-2bb3-4125-bb1a-b590f64f057f", labTestId: "5b41f610-ebc5-4803-8f0c-a61c3bdc7faf", patientDetails: { firstName: "John", lastName: "Doe", dob: new Date("2020-01-01"), gender: Gender.Male, phoneNumber: "+1123456789", email: "email@email.com" }, patientAddress: { receiverName: "john Doe", first_line: "123 Main St.", second_line: "Apt. 208", city: "San Francisco", state: "CA", zip: "91189", country: "US", phoneNumber: "+1123456789" }, } const data = await client.labTests.createOrder(request) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.LabTestsGetAreaInfoRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); PatientDetails details = PatientDetails.builder() .firstName("John") .lastName("Doe") .dob(OffsetDateTime.parse("2020-01-01")) .gender(Gender.MALE) .phoneNumber("+1123456789") .email("email@email.com") .build(); PatientAddressCompatible address = PatientAddressCompatible .builder() .first_line("123 Main St.") .city("San Francisco") .state("CA") .zip("91189") .country("US") .phoneNumber("+1123456789") .build(); CreateOrderRequestCompatible request = CreateOrderRequestCompatible .builder() .userId("63661a2b-2bb3-4125-bb1a-b590f64f057f") .labTestId("5b41f610-ebc5-4803-8f0c-a61c3bdc7faf") .patientDetails(details) .patientAddress(address) .build(); var data = vital.labTests().createOrder(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) details := &vital.PatientDetails{ FirstName: "John", LastName: "Doe", Dob: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Gender: vital.GenderMale, PhoneNumber: "+1123456789", Email: "email@email.com", } address := &vital.PatientAddressCompatible{ ReceiverName: vital.String("john Doe"), FirstLine: "123 Main St.", SecondLine: vital.String("Apt. 208"), City: "San Francisco", State: "CA", Zip: "91189", Country: "US", PhoneNumber: vital.String("+1123456789"), } request := &vital.CreateOrderRequestCompatible{ UserId: "63661a2b-2bb3-4125-bb1a-b590f64f057f", LabTestId: "5b41f610-ebc5-4803-8f0c-a61c3bdc7faf", PatientDetails: details, PatientAddress: address, } response, err := client.LabTests.CreateOrder(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "order": { "id": "ea7eae96-2c25-404f-b043-bfc08584610d", "team_id": "c26a9cc7-cdff-4f23-a5f6-74d40088c16a", "user_id": "63661a2b-2bb3-4125-bb1a-b590f64f057f", "patient_details": { "dob": "2020-01-01", "gender": "male" }, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789" }, "priority": false, "health_insurance_id": "33ec11aa-d8bf-4f46-950d-c9171be3c22f", "details": { "type": "testkit", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "shipment": { "id": "d55210cc-3d9f-4115-8262-5013f700c7be", "outbound_tracking_number": "", "outbound_tracking_url": "", "inbound_tracking_number": "", "inbound_tracking_url": "", "outbound_courier": "usps", "inbound_courier": "usps", "notes": "", "created_at": "2020-01-01T00:00:00.000Z", "updated_at": "2020-01-01T00:00:00.000Z" }, "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "method": "testkit" }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "collecting_sample", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.testkit.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.testkit.requisition_created" }, { "id": 3, "created_at": "2022-01-03T00:00:00Z", "status": "collecting_sample.testkit.transit_customer" } ] }, "status": "string", "message": "string" } ``` # Create Unregistered Testkit Order Source: https://docs.junction.com/api-reference/lab-testing/create-unregistered-order POST /v3/order/testkit Creates an order for an unregistered testkit ```python Python theme={null} from vital import Client client = Client(api_key, "sandbox") data = client.LabTests.create_unregistered_testkit_order( user_id="63661a2b-2bb3-4125-bb1a-b590f64f057f", lab_test_id="5b41f610-ebc5-4803-8f0c-a61c3bdc7faf", shipping_details={ "receiver_name": "john Doe", "street": "123 Main St.", "street_number": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "US", "phone_number": "+11234567890" } ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { CreateRegistrableTestkitOrderRequest} from '@tryvital/vital-node/api/resources/testkit'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: CreateRegistrableTestkitOrderRequest = { userId: "", labTestId: "", shippingDetails: { receiverName: "", firstLine: "", city: "", state: "", zip: "", country: "", phoneNumber: "", }, }; const data = await client.testkit.createOrder(request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getOrder(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetOrders(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```bash cURL theme={null} curl --request POST \ --url https://api.tryvital.io/v3/order/testkit/ \ --header 'Accept: application/json' --header 'x-vital-api-key: ' \ --data ' { "user_id": "63661a2b-2bb3-4125-bb1a-b590f64f057f", "lab_test_id": "5b41f610-ebc5-4803-8f0c-a61c3bdc7faf", "shipping_details": { "receiver_name": "john Doe", "street": "123 Main St.", "street_number": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "US", "phone_number": "+1123456789" } } ' ``` ```json Response theme={null} "order": { "id": "96edc6ef-3b2c-412b-b9e5-96f361f93aec", "team_id": "b080b20c-e162-4cf1-9c7d-8faee72ee08e", "user_id": "9f1e094e-1641-466b-b668-d4d3300e569f", "shipping_details": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+11234567890" }, "details": { "type": "testkit", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "method": "testkit" }, "sample_id": "123456789", "health_insurace_id": "7695cc28-f9e5-400d-95d2-ec7d9ec580df", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "received", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.testkit.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.testkit.awaiting_registration" }, ] }, "status": "string", "message": "string" } ``` # Get Lab Test Source: https://docs.junction.com/api-reference/lab-testing/get-lab-test GET /v3/lab_tests/{lab_test_id} GET all the lab tests the team has access to. ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_by_id(lab_test_id="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getById(labTestId); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getById(labTestId); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetById(context.TODO(), labTestId) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "5af405fc-fafb-49e8-b0bf-fe9d91df6a7d", "slug": "a94e3005-new-test", "name": "New test", "sample_type": "serum", "method": "at_home_phlebotomy", "price": 0.0, "is_active": true, "status": "active", "fasting": false, "lab": { "id": 26, "slug": "quest", "name": "Quest", "first_line_address": "100 Tri State International #100", "city": "Lincolnshire", "zipcode": "60069", "collection_methods": ["at_home_phlebotomy", "walk_in_test"], "sample_types": ["serum", "saliva", "urine"] }, "markers": [ { "id": 6859, "name": "17-Hydroxyprogesterone", "slug": "17-hydroxyprogesterone", "description": "17-Hydroxyprogesterone", "lab_id": 26, "provider_id": "17180", "type": "biomarker", "unit": null, "price": "N/A", "aoe": null, "a_la_carte_enabled": true }, { "id": 8213, "name": "17-Hydroxypregnenolone", "slug": "17-hydroxypregnenolone", "description": "17-Hydroxypregnenolone", "lab_id": 26, "provider_id": "8352", "type": "biomarker", "unit": null, "price": "N/A", "aoe": null, "a_la_carte_enabled": true } ], "is_delegated": false, "auto_generated": false } ``` # Get Order Source: https://docs.junction.com/api-reference/lab-testing/get-order GET /v3/order/{order_id} GET individual order by ID. ```python Python theme={null} from vital import Client client = Client( api_key=, environment="sandbox", region="us" ) data = client.LabTests.get_order(order_id=''); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getOrder(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getOrder(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetOrders(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "0ee312e2-6773-4a21-a6e1-506882cd98ed", "team_id": "cbb64555-af07-46c1-be09-ef89308e9b60", "user_id": "94e2d9f2-d600-4a23-9f08-536df378e2c7", "patient_details": { "dob": "2020-01-01", "gender": "male" }, "priority": false, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789" }, "details": { "type": "testkit", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "shipment": { "id": "d55210cc-3d9f-4115-8262-5013f700c7be", "outbound_tracking_number": "", "outbound_tracking_url": "", "inbound_tracking_number": "", "inbound_tracking_url": "", "outbound_courier": "usps", "inbound_courier": "usps", "notes": "", "created_at": "2020-01-01T00:00:00.000Z", "updated_at": "2020-01-01T00:00:00.000Z" }, "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "method": "testkit" }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "collecting_sample", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.testkit.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.testkit.requisition_created" }, { "id": 3, "created_at": "2022-01-03T00:00:00Z", "status": "collecting_sample.testkit.transit_customer" } ], "origin": "initial", "order_transaction": { "id": "6424d45-ee1a-49c6-ad0c-5769b8e03fc1", "status": "active", "orders": [ { "id": "0ee312e2-6773-4a21-a6e1-506882cd98ed", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "low_level_status": "transit_customer", "low_level_status_created_at": "2022-01-03T00:00:00Z", "origin": "initial" } ] } } ``` # Get Orders Source: https://docs.junction.com/api-reference/lab-testing/get-orders GET /v3/orders GET many orders with filters. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/orders' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.shipping_address import ShippingAddress client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) shipping_details = ShippingAddress( receiver_name="John Doe", first_line="123 Main St", second_line="", city="San Francisco", state="CA", zip="94103", country="US", phone_number="4155551234" ) data = client.testkit.create_order(user_id="", lab_test_id="", shipping_details=shipping_details) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { CreateRegistrableTestkitOrderRequest} from '@tryvital/vital-node/api/resources/testkit'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: CreateRegistrableTestkitOrderRequest = { userId: "", labTestId: "", shippingDetails: { receiverName: "", firstLine: "", city: "", state: "", zip: "", country: "", phoneNumber: "", }, }; const data = await client.testkit.createOrder(request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.testkit.requests.CreateRegistrableTestkitOrderRequest; import com.vital.api.types.ShippingAddress; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); ShippingAddress address = ShippingAddress .builder() .receiverName("John Doe") .firstLine("") .city("") .state("") .zip("") .country("") .phoneNumber("") .build(); CreateRegistrableTestkitOrderRequest request = CreateRegistrableTestkitOrderRequest .builder() .userId("") .labTestId("", LabTestId: "", ShippingDetails: &vital.ShippingAddress{ ReceiverName: "", FirstLine: "", City: "", State: "", Zip: "", Country: "", }, } response, err := client.Testkit.CreateOrder(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "orders": [ { "id": "0ee312e2-6773-4a21-a6e1-506882cd98ed", "team_id": "cbb64555-af07-46c1-be09-ef89308e9b60", "user_id": "94e2d9f2-d600-4a23-9f08-536df378e2c7", "patient_details": { "dob": "2020-01-01", "gender": "male" }, "priority": false, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789" }, "details": { "type": "testkit", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "shipment": { "id": "d55210cc-3d9f-4115-8262-5013f700c7be", "outbound_tracking_number": "", "outbound_tracking_url": "", "inbound_tracking_number": "", "inbound_tracking_url": "", "outbound_courier": "usps", "inbound_courier": "usps", "notes": "", "created_at": "2020-01-01T00:00:00.000Z", "updated_at": "2020-01-01T00:00:00.000Z" }, "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "method": "testkit" }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "collecting_sample", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.testkit.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.testkit.requisition_created" }, { "id": 3, "created_at": "2022-01-03T00:00:00Z", "status": "collecting_sample.testkit.transit_customer" } ], "origin": "initial", "order_transaction": { "id": "6424d45-ee1a-49c6-ad0c-5769b8e03fc1", "status": "active", "orders": [ { "id": "0ee312e2-6773-4a21-a6e1-506882cd98ed", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "low_level_status": "transit_customer", "low_level_status_created_at": "2022-01-03T00:00:00Z", "origin": "initial" } ] } } ], "total": 1, "page": 1, "size": 50 } ``` # Import Order Source: https://docs.junction.com/api-reference/lab-testing/import-order POST /v3/order/import This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. Patient name fields (`first_name`, `last_name`) must follow specific validation rules due to lab restrictions. See [Patient Name Validation](/lab/workflow/order-requirements#patient-name-validation) for complete details. ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.billing import Billing from vital.types.gender import Gender from vital.types.lab_test_collection_method import LabTestCollectionMethod from vital.types.order_set_request import OrderSetRequest from vital.types.patient_address_compatible import PatientAddressCompatible from vital.types.patient_details import PatientDetails from vital.types.physician_create_request import PhysicianCreateRequest from datetime import datetime client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.import_order( user_id="63661a2b-2bb3-4125-bb1a-b590f64f057f", billing_type=Billing.CLIENT_BILL, order_set=OrderSetRequest(lab_test_ids=[]), collection_method=LabTestCollectionMethod.WALK_IN_TEST, physician=PhysicianCreateRequest(first_name="Jane", last_name="Doe", npi=""), patient_details=PatientDetails( first_name="John", last_name="Doe", dob=datetime.fromisoformat("2020-01-01"), gender=Gender.MALE, phone_number="+1123456789", email="email@email.com" ), patient_address=PatientAddressCompatible( receiver_name="John Doe", first_line="123 Main St.", second_line="Apt. 208", city="San Francisco", state="CA", zip="91189", country="US", phone_number="+1123456789" ), sample_id="1234567890", ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { ImportOrderBody } from '@tryvital/vital-node/api/resources/labTests'; import { Billing, Gender, LabTestCollectionMethod } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: ImportOrderBody = { userId: "63661a2b-2bb3-4125-bb1a-b590f64f057f", billingType: Billing.ClientBill, orderSet: { lab_test_ids=[], }, collectionMethod: LabTestCollectionMethod.WalkInTest, physician: { firstName: "Jane", lastName: "Doe", npi: "", }, patientDetails: { firstName: "John", lastName: "Doe", dob: new Date("2020-01-01"), gender: Gender.Male, phoneNumber: "+1123456789", email: "email@email.com" }, patientAddress: { receiverName: "John Doe", first_line: "123 Main St.", second_line: "Apt. 208", city: "San Francisco", state: "CA", zip: "91189", country: "US", phoneNumber: "+1123456789" }, sampleId: "1234567890", } const data = await client.labTests.importOrder(request) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.types.PatientAddressCompatible; import com.vital.api.types.PatientDetails; import com.vital.api.types.LabTestCollectionMethod; import com.vital.api.types.ImportOrderBody; import com.vital.api.types.PhysicianCreateRequest; import com.vital.api.types.OrderSetRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); PatientDetails details = PatientDetails.builder() .firstName("John") .lastName("Doe") .dob(OffsetDateTime.parse("2020-01-01")) .gender(Gender.MALE) .phoneNumber("+1123456789") .email("email@email.com") .build(); PatientAddressCompatible address = PatientAddressCompatible .builder() .firstLine("123 Main St.") .city("San Francisco") .state("CA") .zip("91189") .country("US") .phoneNumber("+1123456789") .build(); OrderSetRequest orderSet = OrderSetRequest .builder() .labTestIds([]) .build(); PhysicianCreateRequest physician = PhysicianCreateRequest .builder() .firstName("Jane") .lastName("Doe") .npi("") .build(); ImportOrderBody request = ImportOrderBody .builder() .userId("63661a2b-2bb3-4125-bb1a-b590f64f057f") .billingType(Billing.CLIENT_BILL) .orderSet(orderSet) .collectionMethod(LabTestCollectionMethod.WALK_IN_TEST) .physician(physician) .patientDetails(details) .patientAddress(address) .sampleId("1234567890) .build(); var data = vital.labTests().importOrder(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) details := &vital.PatientDetails{ FirstName: "John", LastName: "Doe", Dob: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Gender: vital.GenderMale, PhoneNumber: "+1123456789", Email: "email@email.com", } address := &vital.PatientAddressCompatible{ ReceiverName: vital.String("john Doe"), FirstLine: "123 Main St.", SecondLine: vital.String("Apt. 208"), City: "San Francisco", State: "CA", Zip: "91189", Country: "US", PhoneNumber: vital.String("+1123456789"), } order_set = &vital.OrderSetRequest{ LabTestIds: []string{} } physician = &vital.PhysicianCreateRequest{ FirstName: vital.String("Jane"), LastName: vital.String("Doe), Npi: "", } request := &vital.ImportOrderBody{ UserId: "63661a2b-2bb3-4125-bb1a-b590f64f057f", Billing: vital.BillingClientBill, OrderSet: orderSet, CollectionMethod: vital.LabTestCollectionMethodWalkInTest, Physician: physician, PatientDetails: details, PatientAddress: address, SampleId: vital.String("123456789), } response, err := client.LabTests.ImportOrder(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "order": { "user_id": "7ea94b27-a536-461f-ac4b-1c26c854d180", "id": "d3ec7ac7-2584-4254-9709-fc61d80635c4", "team_id": "59d4dfa1-e7c8-4d96-af15-74ca5ac5d189", "patient_details": { "first_name": "John", "last_name": "Doe", "dob": "1990-01-01T00:00:00+00:00", "gender": "male", "phone_number": "+11231234123", "email": "john@example.com" }, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main Street", "second_line": null, "city": "New York", "state": "NY", "zip": "12345", "country": "US", "phone_number": "+11231234123" }, "lab_test": { "id": "d0b479ec-afcc-4763-985c-4e23f17149f5", "slug": "59d4dfa1-testosterone", "name": "Testosterone", "sample_type": "serum", "method": "at_home_phlebotomy", "price": 0.0, "is_active": true, "status": "active", "fasting": false, "lab": null, "markers": null, "is_delegated": false, "auto_generated": false }, "details": { "type": "at_home_phlebotomy", "data": { "id": "21cb5e3f-25e3-443d-9fee-272de1393310", "appointment_id": null, "created_at": "2025-03-20T13:07:32+00:00", "updated_at": "2025-03-20T13:07:32+00:00" } }, "sample_id": "1234567890", "notes": null, "created_at": "2025-03-20T13:07:32+00:00", "updated_at": "2025-03-20T13:07:32+00:00", "events": [ { "id": 74769, "created_at": "2025-03-20T13:07:32+00:00", "status": "received.at_home_phlebotomy.ordered" }, { "id": 74770, "created_at": "2025-03-20T13:07:32+00:00", "status": "received.at_home_phlebotomy.requisition_bypassed" } ], "status": "received", "physician": { "first_name": "Jane", "last_name": "Doe", "npi": "123" }, "health_insurance_id": null, "requisition_form_url": null, "priority": false, "shipping_details": null, "activate_by": null, "passthrough": null, "billing_type": "client_bill", "icd_codes": null }, "status": "success", "message": "Order created successfully" } ``` # Create Payor Source: https://docs.junction.com/api-reference/lab-testing/insurance/create-payor POST /v3/payor This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. Create a new payor. Allows the creation of new payors that don't already exist in Junction's system. Created payors are tied to the team that created them, so they will only be returned as search results when [searched](search-payor-get) by that same team. # Search Diagnosis Source: https://docs.junction.com/api-reference/lab-testing/insurance/search-diagnosis GET /v3/insurance/search/diagnosis ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/insurance/search/diagnosis?diagnosis_query=glucose' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.insurance.search_diagnosis(diagnosis_query="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { InsuranceSearchDiagnosisRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: InsuranceSearchDiagnosisRequest = { diagnosisQuery: "" } const data = await client.insurance.searchDiagnosis(request) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.insurance.requests.InsuranceSearchDiagnosisRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); InsuranceSearchDiagnosisRequest request = InsuranceSearchDiagnosisRequest .builder() .diagnosisQuery("") .build(); var data = vital.insurance().searchDiagnosis(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.InsuranceSearchDiagnosisRequest{ DiagnosisQuery: "", } response, err := client.Insurance.SearchDiagnosis(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} [ { "diagnosis_code": "D55.0", "description": "ANEMIA DUE TO G6PD DEFICIENCY", }, { "diagnosis_code": "D75.A", "description": "GLUC-6-PHOS DHYDRGNS DEF W/O ANEMIA", }, { "diagnosis_code": "E74.810", "description": "GLUC TRANSPORT PROTEIN TYPE 1 DEFIC", }, { "diagnosis_code": "E74.818", "description": "OTHER DISORDERS GLUCOSE TRANSPORT", } ] ``` # Search Insurance Payor Information Source: https://docs.junction.com/api-reference/lab-testing/insurance/search-payor-get GET /v3/insurance/search/payor ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/insurance/search/payor?insurance_name=AETNA' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ' ``` ```json Response theme={null} [ { "code": "AARPA", "name": "AARP", "aliases": [ "AARP", "AARP" ], "org_address": { "first_line": "PO BOX 740819", "second_line": null, "country": "US", "zip": "30374", "city": "ATLANTA", "state": "GA" } }, ] ``` # Get Lab Report Parser Job Source: https://docs.junction.com/api-reference/lab-testing/lab-report-parsing/get-lab-report-parser-job GET /lab_report/v1/parser/job/{job_id} Retrieves the parse job status and stored result if completed. Returns: ParseLabResultJobResponse with job status and parsed data (if complete) Retrieve the status and results of a lab report parsing job. When the job status is `completed`, the `data` field will contain the extracted lab results with LOINC matches. **Job Status Values:** * `upload_pending` - Job created, waiting for file upload * `started` - File uploaded, parsing in progress * `completed` - Parsing complete, results available in `data` * `failed` - Parsing failed ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/lab_report/v1/parser/job/' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_report.parser_get_job(job_id="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from "@tryvital/vital-node"; const client = new VitalClient({ apiKey: "", environment: VitalEnvironment.Sandbox, }); const data = await client.labReport.parserGetJob(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labReport().parserGetJob(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabReport.ParserGetJob(context.TODO(), "") if err != nil { return err } fmt.Printf("Status: %s\n", response.Status) ``` ```json Response theme={null} { "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "data": { "metadata": { "patient_first_name": "John", "patient_last_name": "Doe", "dob": "1990-05-15", "lab_name": "Quest Diagnostics", "date_reported": "2024-01-15", "date_collected": "2024-01-14", "specimen_number": "SP123456" }, "results": [ { "test_name": "Glucose", "value": "95", "type": "numeric", "units": "mg/dL", "min_reference_range": 70, "max_reference_range": 100, "interpretation": "normal", "is_above_max_range": false, "is_below_min_range": false, "loinc_matches": [ { "loinc_code": "2345-7", "loinc_name": "Glucose [Mass/volume] in Serum or Plasma", "display_name": "Glucose [Mass/volume] in Serum or Plasma", "aliases": ["Blood Sugar", "Fasting Glucose"], "confidence_score": 0.95 } ] }, { "test_name": "Hemoglobin A1c", "value": "5.4", "type": "numeric", "units": "%", "min_reference_range": 4.0, "max_reference_range": 5.6, "interpretation": "normal", "is_above_max_range": false, "is_below_min_range": false, "loinc_matches": [ { "loinc_code": "4548-4", "loinc_name": "Hemoglobin A1c/Hemoglobin.total in Blood", "display_name": "Hemoglobin A1c/Hemoglobin.total in Blood", "aliases": ["HbA1c", "Glycated Hemoglobin"], "confidence_score": 0.98 } ] } ] }, "needs_human_review": false, "is_reviewed": false } ``` # Create Lab Report Parser Job Source: https://docs.junction.com/api-reference/lab-testing/lab-report-parsing/post-lab-report-parser-job POST /lab_report/v1/parser/job Creates a parse job, uploads the file(s) to provider, persists the job row, and starts the ParseLabReport. Returns a generated job_id. Upload a lab report file (PDF, JPEG, or PNG) to create a parsing job. The job will extract lab results and match them to LOINC codes. ```bash cURL theme={null} curl --request POST \ --url '{{BASE_URL}}/lab_report/v1/parser/job' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --header 'Content-Type: multipart/form-data' \ --form 'file=@/path/to/lab_report.pdf' \ --form 'needs_human_review=false' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) with open("lab_report.pdf", "rb") as f: data = client.lab_report.parser_create_job( file=f, needs_human_review=False ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from "@tryvital/vital-node"; import * as fs from "fs"; const client = new VitalClient({ apiKey: "", environment: VitalEnvironment.Sandbox, }); const file = fs.createReadStream("lab_report.pdf"); const data = await client.labReport.parserCreateJob(file, { needsHumanReview: false, }); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import java.io.File; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); File file = new File("lab_report.pdf"); var data = vital.labReport().parserCreateJob(file); ``` ```go Go theme={null} import ( "context" "os" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) file, _ := os.Open("lab_report.pdf") defer file.Close() response, err := client.LabReport.ParserCreateJob(context.TODO(), file, nil) if err != nil { return err } fmt.Printf("Job ID: %s\n", response.JobId) ``` ```json Response theme={null} { "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "started", "data": null, "needs_human_review": false, "is_reviewed": false } ``` # Get Collection Instructions PDF for Lab Test Source: https://docs.junction.com/api-reference/lab-testing/lab-test-collection-instructions-pdf GET /v3/lab_test/{lab_test_id}/collection_instruction_pdf This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/lab_test//collection_instruction_pdf' \ --header 'accept: application/pdf' \ --header 'x-vital-api-key: {YOUR_KEY}' --output file.pdf ``` # Get Markers for Lab Test Source: https://docs.junction.com/api-reference/lab-testing/lab-test-markers GET /v3/lab_tests/{lab_test_id}/markers List all markers for a given Lab Test, as well as any associated reflex markers. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/lab_tests/{lab_test_id}/markers' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_markers_for_lab_test(""); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getMarkersForLabTest(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getMarkersForLabTest(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetMarkersForLabTest(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "markers": [ { "id": 202, "name": "Acetylcholine Receptor (AChR) Antibodies, Complete Profile with Reflex to MuSK Antibodies", "slug": "acetylcholine-receptor-achr-antibodies-complete-profile-with-reflex-to-musk-antibodies", "description": "Acetylcholine Receptor (AChR) Antibodies, Complete Profile with Reflex to MuSK Antibodies", "lab_id": 6, "provider_id": "165605", "type": "biomarker", "unit": null, "price": "N/A", "expected_results": [ { "id": 2938, "name": "AChR Blocking Abs, Serum", "slug": "achr-blocking-abs-serum", "lab_id": 6, "provider_id": "085927", "loinc": { "id": 3514, "name": "Acetylcholine receptor blocking Ab Qn (S)", "slug": "acetylcholine-receptor-blocking-ab-qn-s", "code": "11561-8", "unit": "%{inhibition}" } }, { "id": 2939, "name": "AChR Binding Abs, Serum", "slug": "achr-binding-abs-serum", "lab_id": 6, "provider_id": "085904", "loinc": { "id": 3174, "name": "Acetylcholine receptor binding Ab (S) [Moles/Vol]", "slug": "acetylcholine-receptor-binding-ab-s-moles-vol", "code": "11034-6", "unit": "nmol/L" } }, { "id": 2940, "name": "AChR-modulating Ab", "slug": "achr-modulating-ab", "lab_id": 6, "provider_id": "505199", "loinc": { "id": 61121, "name": "Acetylcholine receptor modulation Ab FC Ql (S)", "slug": "acetylcholine-receptor-modulation-ab-fc-ql-s", "code": "99062-2", "unit": null } } ] }, ], "total": 2, "page": 1, "size": 2 } ``` # Get Lab Accounts Source: https://docs.junction.com/api-reference/lab-testing/lab_accounts GET /v3/lab_test/lab_account Get available Lab Accounts ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/lab_test/lab_account' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` # Get Labels PDF Source: https://docs.junction.com/api-reference/lab-testing/labels-pdf GET /v3/order/{order_id}/labels/pdf This endpoint returns the printed labels for the order. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order//labels/pdf' \ --header 'accept: application/pdf' \ --header 'x-vital-api-key: {YOUR_KEY}' --output file.pdf ``` # Get All Available Labs Source: https://docs.junction.com/api-reference/lab-testing/labs GET /v3/lab_tests/labs GET all the labs. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/lab_tests/labs' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_labs(); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getLabs(); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getLabs(); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetLabs(context.TODO()) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} [ { "id": 1, "slug": "labcorp", "name": "LabCorp", "first_line_address": "123 Main St", "city": "San Francisco", "zipcode": "91789", "collection_methods": ["at_home_phlebotomy", "walk_in_test"], "sample_types": ["saliva", "serum"] } ] ``` # Get Collection Instructions PDF for Order Source: https://docs.junction.com/api-reference/lab-testing/order-collection-instructions-pdf GET /v3/order/{order_id}/collection_instruction_pdf GET collection instructions for an order This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order//collection_instruction_pdf' \ --header 'accept: application/pdf' \ --header 'x-vital-api-key: {YOUR_KEY}' --output file.pdf ``` # Get Order Psc Info Source: https://docs.junction.com/api-reference/lab-testing/order-psc-info GET /v3/order/{order_id}/psc/info ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order/{{ORDER_ID}}/psc/info' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_order_psc_info("order_id") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getOrderPscInfo("order_id"); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getOrderPscInfo("order_id); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetOrderPscInfo(context.TODO(), "order_id) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "lab_id": 27, "slug": "labcorp", "patient_service_centers": [ { "metadata": { "name": "LABCORP", "state": "AZ", "city": "Phoenix", "zip_code": "85006", "first_line": "1300 N 12th St", "second_line": "Ste 300", "phone_number": "480-878-3988", "fax_number": "844-346-5903", "hours": null }, "distance": "25", "capabilities": ["stat"] }, ] } ``` # Get Markers for Order Set Source: https://docs.junction.com/api-reference/lab-testing/order-set-marker POST /v3/lab_tests/list_order_set_markers ```bash cURL theme={null} curl --request POST \ --url '{{BASE_URL}}/v3/lab_tests/list_order_set_markers' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' --d ' { "lab_test_ids": ["id"], "add_on": { "provider_ids": ["123"] } } ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_markers_for_order_set(OrderSetRequest( lab_test_ids=["id"], add_on=AddOnOrder( provider_ids=["123] ) )); ``` ```json Response theme={null} { "markers": [ { "id": 202, "name": "Acetylcholine Receptor (AChR) Antibodies, Complete Profile with Reflex to MuSK Antibodies", "slug": "acetylcholine-receptor-achr-antibodies-complete-profile-with-reflex-to-musk-antibodies", "description": "Acetylcholine Receptor (AChR) Antibodies, Complete Profile with Reflex to MuSK Antibodies", "lab_id": 6, "provider_id": "165605", "type": "biomarker", "unit": null, "price": "N/A", "expected_results": [ { "id": 2938, "name": "AChR Blocking Abs, Serum", "slug": "achr-blocking-abs-serum", "lab_id": 6, "provider_id": "085927", "loinc": { "id": 3514, "name": "Acetylcholine receptor blocking Ab Qn (S)", "slug": "acetylcholine-receptor-blocking-ab-qn-s", "code": "11561-8", "unit": "%{inhibition}" } }, { "id": 2939, "name": "AChR Binding Abs, Serum", "slug": "achr-binding-abs-serum", "lab_id": 6, "provider_id": "085904", "loinc": { "id": 3174, "name": "Acetylcholine receptor binding Ab (S) [Moles/Vol]", "slug": "acetylcholine-receptor-binding-ab-s-moles-vol", "code": "11034-6", "unit": "nmol/L" } }, { "id": 2940, "name": "AChR-modulating Ab", "slug": "achr-modulating-ab", "lab_id": 6, "provider_id": "505199", "loinc": { "id": 61121, "name": "Acetylcholine receptor modulation Ab FC Ql (S)", "slug": "acetylcholine-receptor-modulation-ab-fc-ql-s", "code": "99062-2", "unit": null } } ] }, ], "total": 2, "page": 1, "size": 2 } ``` # Get Order Transaction Source: https://docs.junction.com/api-reference/lab-testing/order-transactions/get-order-transaction GET /v3/order_transaction/{transaction_id} ```python Python theme={null} from vital import Client client = Client( api_key=, environment="sandbox", region="us" ) data = client.LabTests.get_transaction(transaction_id=''); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.get_transaction(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().get_transaction(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetTransaction(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "0ee312e2-6773-4a21-a6e1-506882cd98ed", "team_id": "cbb64555-af07-46c1-be09-ef89308e9b60", "status": "active", "orders": [ { "id": "0ee312e2-6773-4a21-a6e1-506882cd98ed", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "low_level_status": "transit_customer", "low_level_status_created_at": "2022-01-03T00:00:00Z", "origin": "initial" } ] } ``` # Update a Test Source: https://docs.junction.com/api-reference/lab-testing/patch-lab-test PATCH /v3/lab_tests/{lab_test_id} ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.update_lab_test( lab_test_id="", name="", active=, ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { UpdateLabTestRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: UpdateLabTestRequest = { name: '', active: , } const data = await client.labTests.updateLabTest(labTestId, request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.UpdateLabTestRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); UpdateLabTestRequest request = UpdateLabTestRequest .builder() .name("") .active() .build(); var data = vital.labTests().updateLabTest(labTestId, request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.UpdateLabTestRequest{ Name: "", Active: , } response, err := client.LabTests.UpdateLabTest(context.TODO(), labTestId, request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "5af405fc-fafb-49e8-b0bf-fe9d91df6a7d", "slug": "a94e3005-new-test", "name": "New test", "sample_type": "serum", "method": "at_home_phlebotomy", "price": 0.0, "is_active": true, "status": "active", "fasting": false, "lab": { "id": 26, "slug": "quest", "name": "Quest", "first_line_address": "100 Tri State International #100", "city": "Lincolnshire", "zipcode": "60069", "collection_methods": ["at_home_phlebotomy", "walk_in_test"], "sample_types": ["serum", "saliva", "urine"] }, "markers": [ { "id": 6859, "name": "17-Hydroxyprogesterone", "slug": "17-hydroxyprogesterone", "description": "17-Hydroxyprogesterone", "lab_id": 26, "provider_id": "17180", "type": "biomarker", "unit": null, "price": "N/A", "aoe": null, "a_la_carte_enabled": true }, { "id": 8213, "name": "17-Hydroxypregnenolone", "slug": "17-hydroxypregnenolone", "description": "17-Hydroxypregnenolone", "lab_id": 26, "provider_id": "8352", "type": "biomarker", "unit": null, "price": "N/A", "aoe": null, "a_la_carte_enabled": true } ], "is_delegated": false, "auto_generated": false } ``` # Create a Test Source: https://docs.junction.com/api-reference/lab-testing/post-test POST /v3/lab_tests ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.lab_test_collection_method import LabTestCollectionMethod from vital.types.lab_test_sample_type import LabTestSampleType from vital.types.us_address import UsAddress client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.create( provider_ids=["000110"], name="", method=LabTestCollectionMethod.TESTKIT, description="" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { CreateLabTestRequest, LabTestCollectionMethod, LabTestSampleType, } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: CreateLabTestRequest = { providerIds: ['000110'], name: '', method: LabTestCollectionMethod.Testkit, description: '', }; const data = await client.labTests.create(request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.CreateLabTestRequest; import com.vital.api.types.LabTestCollectionMethod; import com.vital.api.types.LabTestSampleType; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); CreateLabTestRequest request = CreateLabTestRequest .builder() .name("") .method(LabTestCollectionMethod.TESTKIT) .description(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.CreateLabTestRequest{ ProviderIds: []int{"001100"}, Name: "", Method: vital.LabTestCollectionMethodTestkit, Description: "", } response, err := client.LabTests.Create(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "2e82a1d4-e2e6-421c-9c54-2210d667ce48", "slug": "a94e3005-labcorp-panel", "name": "Labcorp panel", "sample_type": "serum", "method": "at_home_phlebotomy", "price": 0.0, "is_active": true, "status": "active", "fasting": false, "lab": { "id": 27, "slug": "labcorp", "name": "Labcorp", "first_line_address": "labcorp address", "city": "Lincolnshire", "zipcode": "60069", "collection_methods": ["at_home_phlebotomy", "walk_in_test"], "sample_types": ["serum", "saliva", "urine"] }, "markers": [ { "id": 195, "name": "Abnormal Bleeding Profile", "slug": "abnormal-bleeding-profile", "description": "Abnormal Bleeding Profile", "lab_id": 27, "provider_id": "116004", "type": "biomarker", "unit": null, "price": "N/A", "aoe": null, "a_la_carte_enabled": true } ], "is_delegated": false, "auto_generated": false } ``` # Get Psc Info Source: https://docs.junction.com/api-reference/lab-testing/psc-info GET /v3/order/psc/info ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order/psc/info?zip_code=85004&lab_id=27' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_psc_info(zip_code="85004", lab_id=27) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { LabTestsGetPscInfoRequest } from '@tryvital/vital-node/api/resources/labTests'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: LabTestsGetPscInfoRequest = { zipCode: "85004", labId: 27, } const data = await client.labTests.getPscInfo(request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.LabTestsGetPscInfoRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); LabTestsGetPscInfoRequest request = LabTestsGetPscInfoRequest.builder() .zipCode("85004") .labId(27) .build(); var data = vital.labTests().getPscInfo(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.LabTestsGetPscInfoRequest{ ZipCode: "85004", LabId: 27, } response, err := client.LabTests.GetPscInfo(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "lab_id": 27, "slug": "labcorp", "patient_service_centers": [ { "metadata": { "name": "LABCORP", "state": "AZ", "city": "Phoenix", "zip_code": "85006", "first_line": "1300 N 12th St", "second_line": "Ste 300", "phone_number": "480-878-3988", "fax_number": "844-346-5903", "hours": null }, "distance": "25", "capabilities": ["stat"] }, ] } ``` # PSC Appointment Availability Source: https://docs.junction.com/api-reference/lab-testing/psc-schedulling/appointment-psc-availability POST /v3/order/psc/appointment/availability Use the `allow_stale` parameter for faster responses via the Availability Cache. This feature is currently in closed beta. See [PSC Appointment Scheduling](/lab/walk-in/psc-appointment-scheduling#availability-cache) for more info. ```bash cURL theme={null} curl --request POST \ --url '{{BASE_URL}}/v3/order/psc/appointment/availability' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --data ' { "lab": "quest", "zip_code": "85004", "radius": "25", } ' ``` ```json Response theme={null} { "slots": [ { "location": { "location": { "lng": -112.0568538, "lat": 33.4631386 }, "address": { "first_line": "1300 N 12th St", "second_line": "Ste 300", "city": "Phoenix", "state": "AZ", "zip_code": "85006", }, "code": "23070", "name": "QUEST", "iana_timezone": "America/Los_Angeles" }, "date":"2023-05-09", "slots": [ { "booking_key": "foo123", "start": "2023-05-09T17:00:00+00:00", "end": "2023-05-09T19:00:00+00:00", "expires_at": "2023-05-09T12:39:57.827000+00:00", "price": 0.0, "is_priority": true, "num_appointments_available": 1 }, ... ], }, { "location": { "location": { "lng": -112.0568538, "lat": 33.4631386 }, "address": { "first_line": "1300 N 12th St", "second_line": "Ste 300", "city": "Phoenix", "state": "AZ", "zip_code": "85006", }, "code": "23070", "name": "QUEST" }, "date":"2023-05-10", "slots": [ { "booking_key": "bar456", "start": "2023-05-10T12:00:00+00:00", "end": "2023-05-10T14:00:00+00:00", "expires_at": "2023-05-09T12:39:57.852000+00:00", "price": 0.0, "is_priority": true, "num_appointments_available": 1 }, ... ], }, ], "timezone": null } ``` # Book PSC Appointment Source: https://docs.junction.com/api-reference/lab-testing/psc-schedulling/appointment-psc-booking POST /v3/order/{order_id}/psc/appointment/book This endpoint supports optional `idempotency_key` and Async Confirmation features. These features are currently in closed beta. See [PSC Appointment Scheduling](/lab/walk-in/psc-appointment-scheduling#beta-features) for more info. ```bash cURL theme={null} curl --request POST \ --url '{{BASE_URL}}/v3/order/413d7205-f8a9-42ed-aa4a-edb99e481ca0/psc/appointment/book' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --data ' { "booking_key": "foo123" } ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.book_psc_appointment(order_id="", booking_key="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { AppointmentBookingRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: AppointmentBookingRequest = { bookingKey: "", } const data = await client.labTests.bookPscAppointment("", request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.AppointmentBookingRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); AppointmentBookingRequest request = AppointmentBookingRequest .builder() .bookingKey("") .build(); var data = vital.labTests().bookPscAppointment("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.AppointmentBookingRequest{ BookingKey: "", } response, err := client.LabTests.BookPscAppointment(context.TODO(), "", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "413d7205-f8a9-42ed-aa4a-edb99e481ca0", "user_id": "202b2c2f-fb4c-44dc-a4f8-621186fde227", "address": { "first_line": "West Lincoln Street", "second_line": "", "city": "Phoenix", "state": "AZ", "zip_code": "85004", "unit": "14" }, "location": { "lng": -112.0772235, "lat": 33.4421912 }, "start_at": "2023-05-15T16:00:00+00:00", "end_at": "2023-05-15T18:00:00+00:00", "iana_timezone": "America/Phoenix", "type": "patient_service_center", "provider": "quest", "status": "pending", "provider_id": "e89eb489-7382-4966-bb14-7ab4763eba6c", "can_reschedule": true, "external_id": "ABCDEF" } ``` # PSC Appointment Cancellation Reasons Source: https://docs.junction.com/api-reference/lab-testing/psc-schedulling/appointment-psc-cancellation-reasons GET /v3/order/psc/appointment/cancellation-reasons ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order/psc/appointment/cancellation-reasons' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_psc_appointment_cancellation_reason(); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getPscAppointmentCancellationReason(); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getPscAppointmentCancellationReason(); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetPscAppointmentCancellationReason(context.TODO()) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json theme={null} [ { "id": "5c0257ef-6fea-4a22-b20a-3ddab573d5c9", "name": "Other", "is_refundable": true }, ] ``` # Cancel PSC Appointment Source: https://docs.junction.com/api-reference/lab-testing/psc-schedulling/appointment-psc-cancelling PATCH /v3/order/{order_id}/psc/appointment/cancel ```bash cURL theme={null} curl --request PATCH \ --url '{{BASE_URL}}/v3/order/413d7205-f8a9-42ed-aa4a-edb99e481ca0/psc/appointment/cancel' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --data ' { "cancellation_reason_id": "7dfd7da5-ed6e-40bb-a7e4-c8003f0c10a9" } ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_psc_appointment_cancellation_reason(); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { AppointmentCancelRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: AppointmentCancelRequest = { cancellationReasonId: "", } const data = await client.labTests.cancelPscAppointment("", request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.AppointmentCancelRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); AppointmentCancelRequest request = AppointmentCancelRequest .builder() .cancellationReasonId("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.AppointmentCancelRequest{ CancellationReasonId: "", } response, err := client.LabTests.CancelPscAppointment(context.TODO(), "", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "413d7205-f8a9-42ed-aa4a-edb99e481ca0", "user_id": "202b2c2f-fb4c-44dc-a4f8-621186fde227", "address": { "first_line": "West Lincoln Street", "second_line": "", "city": "Phoenix", "state": "AZ", "zip_code": "85004", "unit": "14" }, "location": { "lng": -112.0772235, "lat": 33.4421912 }, "start_at": "2023-05-17T20:00:00+00:00", "end_at": "2023-05-17T22:00:00+00:00", "iana_timezone": "America/Phoenix", "type": "patient_service_center", "provider": "quest", "status": "cancelled", "provider_id": "e89eb489-7382-4966-bb14-7ab4763eba6c", "can_reschedule": true } ``` # Reschedule PSC Appointment Source: https://docs.junction.com/api-reference/lab-testing/psc-schedulling/appointment-psc-rescheduling PATCH /v3/order/{order_id}/psc/appointment/reschedule ```bash cURL theme={null} curl --request PATCH \ --url '{{BASE_URL}}/v3/order/413d7205-f8a9-42ed-aa4a-edb99e481ca0/psc/appointment/reschedule' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --data ' { "booking_key": "bar456" } ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.reschedule_psc_appointment(order_id="", booking_key=""); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { AppointmentRescheduleRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: AppointmentRescheduleRequest = { bookingKey: "", } const data = await client.labTests.reschedulePscAppointment("", request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.AppointmentRescheduleRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); AppointmentRescheduleRequest request = AppointmentRescheduleRequest .builder() .bookingKey("") .build(); var data = vital.labTests().reschedulePscAppointment("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.AppointmentRescheduleRequest{ BookingKey: "", } response, err := client.LabTests.ReschedulePscAppointment(context.TODO(), "", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "413d7205-f8a9-42ed-aa4a-edb99e481ca0", "user_id": "202b2c2f-fb4c-44dc-a4f8-621186fde227", "address": { "first_line": "West Lincoln Street", "second_line": "", "city": "Phoenix", "state": "AZ", "zip_code": "85004", "unit":"14" }, "location": { "lng": -112.0772235, "lat": 33.4421912 }, "start_at": "2023-05-17T20:00:00+00:00", "end_at": "2023-05-17T22:00:00+00:00", "iana_timezone": "America/Phoenix", "type": "patient_service_center", "provider": "quest", "status": "confirmed", "provider_id": "e89eb489-7382-4966-bb14-7ab4763eba6c", "can_reschedule": true } ``` # Get PSC Appointment Source: https://docs.junction.com/api-reference/lab-testing/psc-schedulling/get-psc-appointment GET /v3/order/{order_id}/psc/appointment Get the appointment associated with an order. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order/413d7205-f8a9-42ed-aa4a-edb99e481ca0/psc/appointment' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_psc_appointment(order_id="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getpscAppointment(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getpscAppointment(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetpscAppointment(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "413d7205-f8a9-42ed-aa4a-edb99e481ca0", "user_id": "202b2c2f-fb4c-44dc-a4f8-621186fde227", "address": { "first_line": "West Lincoln Street", "second_line": "", "city": "Phoenix", "state": "AZ", "zip_code": "85004", "unit": "14" }, "location": { "lng": -112.0772235, "lat": 33.4421912 }, "start_at": "2023-05-17T20:00:00+00:00", "end_at": "2023-05-17T22:00:00+00:00", "iana_timezone": "America/Phoenix", "type": "patient_service_center", "provider": "quest", "status": "confirmed", "provider_id": "e89eb489-7382-4966-bb14-7ab4763eba6c", "can_reschedule": true } ``` # Register Testkit Order Source: https://docs.junction.com/api-reference/lab-testing/register-order POST /v3/order/testkit/register Patient name fields (`first_name`, `last_name`) must follow specific validation rules due to lab restrictions. See [Patient Name Validation](/lab/workflow/order-requirements#patient-name-validation) for complete details. ```python Python theme={null} from datetime import datetime from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.gender import Gender from vital.types.patient_address_compatible import PatientAddressCompatible from vital.types.patient_details import PatientDetails client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) patient_details = PatientDetails( first_name="John", last_name="Doe", dob=datetime.fromisoformat("1990-01-01"), gender=Gender.MALE, email="email@email.com", phone_number="123-456-7890", ) patient_address = PatientAddressCompatible( receiver_name="John Doe", first_line="Main St", second_line="123", city="San Francisco", state="CA", zip="94111", country="US", phone_number="" ) data = client.testkit.register(user_id="", sample_id="", patient_details=patient_details, patient_address=patient_address) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { Gender } from '@tryvital/vital-node/api'; import { RegisterTestkitRequest} from '@tryvital/vital-node/api/resources/testkit'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: RegisterTestkitRequest = { userId: "", sampleId: "", patientDetails: { firstName: "", lastName: "", dob: new Date("1990-01-01"), gender: Gender.Female, phoneNumber: "", email: "", }, patientAddress: { receiverName: "", FirstLine: "", city: "", state: "", zip: "", country: "", phoneNumber: "", }, }; const data = await client.testkit.register(request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.testkit.requests.RegisterTestkitRequest; import com.vital.api.types.Gender; import com.vital.api.types.PatientAddressCompatible; import com.vital.api.types.PatientDetails; import java.time.OffsetDateTime; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); PatientDetails patientDetails = PatientDetails .builder() .firstName("") .lastName("") .dob(OffsetDateTime.parse("1990-01-01")) .gender(Gender.MALE) .phoneNumber("") .email("") .build(); PatientAddressCompatible patientAddress = PatientAddressCompatible .builder() .firstLine("") .city("") .state("") .zip("") .country("") .build(); RegisterTestkitRequest request = RegisterTestkitRequest .builder() .userId("") .sampleId("") .patientDetails(patientDetails) .patientAddress(patientAddress) .build(); var data = vital.testkit().register(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) request := &vital.RegisterTestkitRequest{ UserId: "", SampleId: "", PatientDetails: &vital.PatientDetails{ FirstName: "", LastName: "", Email: "", PhoneNumber: "", Dob: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC), Gender: vital.GenderMale, }, PatientAddress: &vital.PatientAddressCompatible{ ReceiverName: vital.String(""), FirstLine: "", City: "", State: "", Zip: "", Country: "", }, } response, err := client.Testkit.Register(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```bash cURL theme={null} curl --request POST \ --url https://api.tryvital.io/v3/order/testkit/register \ --header 'Accept: application/json' --header 'x-vital-api-key: ' \ --data ' { "user_id":"63661a2b-2bb3-4125-bb1a-b590f64f057f", "sample_id":"123123123", "patient_details":{ "first_name": "John", "last_name": "Doe", "dob": "2020-01-01", "gender": "male", "phone_number": "+1123456789", "email": "email@email.com" }, "patient_address":{ "receiver_name": "john Doe", "street": "123 Main St.", "street_number": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "US" }, "consents":[ {"consentType": "terms-of-use"}, {"consentType": "telehealth-informed-consent"}, {"consentType": "notice-of-privacy-practices"}, ], "physician":{ "first_name": "Doctor", "last_name": "Doc", "npi": "123123123", } } ' ``` ```json Response theme={null} { "order": { "id": "96edc6ef-3b2c-412b-b9e5-96f361f93aec", "team_id": "b080b20c-e162-4cf1-9c7d-8faee72ee08e", "user_id": "9f1e094e-1641-466b-b668-d4d3300e569f", "patient_details": { "first_name": "John", "last_name": "Doe", "phone_number": "+11234567890", "email": "doe@email.com" "dob": "2020-01-01", "gender": "male" }, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+11234567890" }, "shipping_details": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+11234567890" }, "details": { "type": "testkit", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "shipment": { "id": "d55210cc-3d9f-4115-8262-5013f700c7be", "outbound_tracking_number": "", "outbound_tracking_url": "", "inbound_tracking_number": "", "inbound_tracking_url": "", "outbound_courier": "usps", "inbound_courier": "usps", "notes": "", "created_at": "2020-01-01T00:00:00.000Z", "updated_at": "2020-01-01T00:00:00.000Z" }, "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "method": "testkit" }, "sample_id": "123456789", "health_insurace_id": "7695cc28-f9e5-400d-95d2-ec7d9ec580df", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "received", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.testkit.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.testkit.awaiting_registration" }, { "id": 3, "created_at": "2022-01-02T00:00:00Z", "status": "received.testkit.requisition_created" }, { "id": 4, "created_at": "2022-01-03T00:00:00Z", "status": "received.testkit.testkit_registered" } ] }, "status": "string", "message": "string" } ``` # Get Requisition Form PDF Source: https://docs.junction.com/api-reference/lab-testing/requisition-pdf GET /v3/order/{order_id}/requisition/pdf GET requisition pdf for an order ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order//requisition/pdf' \ --header 'accept: application/pdf' \ --header 'x-vital-api-key: {YOUR_KEY}' --output file.pdf ``` ```python Python theme={null} from vital import Client client = Client( api_key=, environment="sandbox", region="us" ) data = client.LabTests.get_order_requisition_pdf(order_id=''); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getOrderRequistionPdf(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getOrderRequistionPdf(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetOrderRequistionPdf(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Get Order Transaction Results Source: https://docs.junction.com/api-reference/lab-testing/results/get-order-transaction-results GET /v3/order_transaction/{transaction_id}/result ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order_transaction//result' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_transaction_result(transaction_id="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getTransactionResult(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getTransactionResult(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetTransactionResult(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "metadata": { "age": 19, "dob": "18/08/1993", "clia_number": "12331231", "patient": "Bob Smith", "provider": "Dr. Jack Smith", "laboratory": "LabCorp", "date_reported": "2020-01-01", "date_collected": "2022-02-02", "specimen_number": "123131", "date_received": "2022-01-01", "status": "final", "interpretation": "abnormal" }, "results": [ { "name": "Sex Horm Binding Glob, Serum", "slug": "sex-horm-binding-glob-serum", "value": 30.4, "result": "30.4", "type": "numeric", "unit": "nmol/L", "timestamp": "2024-10-31T09:08:00+00:00", "notes": "Final", "min_range_value": 24.6, "max_range_value": 122, "is_above_max_range": false, "is_below_min_range": false, "interpretation": "normal", "loinc": "13967-5", "loinc_slug": "sex-hormone-binding-globulin-moles-vol", "provider_id": "082016", "source_markers": [ { "marker_id": 229, "name": "Testosterone Free, Profile I", "slug": "testosterone-free-profile-i", "provider_id": "140226" } ] } ], "order_transaction": { "id": "6424d45-ee1a-49c6-ad0c-5769b8e03fc1", "status": "completed", "orders": [ { "id": "84d96c03-6b1c-4226-ad8f-ef44a6bc08af", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2022-01-04T00:00:00Z", "low_level_status": "redraw_available", "low_level_status_created_at": "2022-01-04T00:00:00Z", "origin": "initial" }, { "id": "15db29d7-ffeb-4540-9513-3c38f26b87aa", "created_at": "2020-01-10T00:00:00Z", "updated_at": "2022-01-14T00:00:00Z", "low_level_status": "completed", "low_level_status_created_at": "2022-01-14T00:00:00Z", "origin": "redraw" } ] } } ``` # Get Order Transaction Results PDF Source: https://docs.junction.com/api-reference/lab-testing/results/get-order-transaction-results-pdf GET /v3/order_transaction/{transaction_id}/result/pdf ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order_transaction//result/pdf' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_transaction_result_pdf(transaction_id="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getTransactionResultPdf(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getTransactionResultPdf(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetTransactionResultPdf(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} "PDF with results" ``` # Get Results Source: https://docs.junction.com/api-reference/lab-testing/results/get-results GET /v3/order/{order_id}/result Return both metadata and raw json test data ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order//result' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_result_raw(order_id="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getResultRaw(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getResultRaw(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetResultRaw(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "metadata": { "age": 19, "dob": "18/08/1993", "clia_number": "12331231", "patient": "Bob Smith", "provider": "Dr. Jack Smith", "laboratory": "LabCorp", "date_reported": "2020-01-01", "date_collected": "2022-02-02", "specimen_number": "123131", "date_received": "2022-01-01", "status": "final", "interpretation": "abnormal" }, "results": [ { "name": "Sex Horm Binding Glob, Serum", "slug": "sex-horm-binding-glob-serum", "value": 30.4, "result": "30.4", "type": "numeric", "unit": "nmol/L", "timestamp": "2024-10-31T09:08:00+00:00", "notes": "Final", "min_range_value": 24.6, "max_range_value": 122, "is_above_max_range": false, "is_below_min_range": false, "interpretation": "normal", "loinc": "13967-5", "loinc_slug": "sex-hormone-binding-globulin-moles-vol", "provider_id": "082016", "source_markers": [ { "marker_id": 229, "name": "Testosterone Free, Profile I", "slug": "testosterone-free-profile-i", "provider_id": "140226" } ] } ], "order_transaction": { "id": "6424d45-ee1a-49c6-ad0c-5769b8e03fc1", "status": "completed", "orders": [ { "id": "84d96c03-6b1c-4226-ad8f-ef44a6bc08af", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2022-02-04T00:00:00Z", "low_level_status": "completed", "low_level_status_created_at": "2022-02-04T00:00:00Z", "origin": "initial" } ] } } ``` # Get Results Metadata Source: https://docs.junction.com/api-reference/lab-testing/results/get-results-metadata GET /v3/order/{order_id}/result/metadata Return metadata related to order results, such as lab metadata, provider and sample dates. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order//result/metadata' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_result_metadata(order_id="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getResultMetadata(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getResultMetadata(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetResultMetadata(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "age": 19, "dob": "18/08/1993", "clia_number": "12331231", "patient": "Bob Smith", "provider": "Dr. Jack Smith", "laboratory": "Quest Diagnostics", "date_reported": "2020-01-01", "date_collected": "2022-02-02", "specimen_number": "123131", "date_received": "2022-01-01", "status": "final", "interpretation": "normal" } ``` # Get Results PDF Source: https://docs.junction.com/api-reference/lab-testing/results/get-results-pdf GET /v3/order/{order_id}/result/pdf This endpoint returns the lab results for the order. ```bash cURL theme={null} curl --request GET \ --url '{{BASE_URL}}/v3/order//result/pdf' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_result_pdf(order_id="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getResultPdf(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getResultPdf(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetResultPdf(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} "Returns PDF of test data - .pdf" ``` # Simulate Order Source: https://docs.junction.com/api-reference/lab-testing/simulate-order POST /v3/order/{order_id}/test Simulate order lifecycle - Sandbox Only ```bash cURL theme={null} curl --request POST \ --url '{{BASE_URL}}/v3/order//test' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` # Get Available Tests Source: https://docs.junction.com/api-reference/lab-testing/tests GET /v3/lab_tests GET all the lab tests the team has access to. > **Deprecated:** This endpoint is deprecated. Please use the paginated version `/v3/lab_test` instead. ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get(); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.get(); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().get(); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.Get(context.TODO()) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} [ { "lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "sample_type": "dried blood spot", "method": "testkit", "price": 10, "is_active": true, "lab": { "slug": "USSL", "name": "US Specialty Lab", "first_line_address": "123 Main St", "city": "New York", "zipcode": "10001" }, "markers": [ { "name": "Thyroid Stimulating Hormone", "slug": "tsh", "description": "" } ] } } ] ``` # Get Available Tests Source: https://docs.junction.com/api-reference/lab-testing/tests-paginated GET /v3/lab_test GET lab tests the team has access to as a paginated list. ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get_paginated(); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.getPaginated(); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().getPaginated(); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" ) client := vitalclient.NewClient( vitalclient.WithApiKey(""), vitalclient.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.GetPaginated(context.TODO()) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "data": [ { "lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "sample_type": "dried blood spot", "method": "testkit", "price": 10, "is_active": true, "lab": { "slug": "USSL", "name": "US Specialty Lab", "first_line_address": "123 Main St", "city": "New York", "zipcode": "10001" }, "markers": [ { "name": "Thyroid Stimulating Hormone", "slug": "tsh", "description": "" } ] } } ], "next_cursor": null } ``` # Update Order Draw Completed Source: https://docs.junction.com/api-reference/lab-testing/update-order-draw-completed PATCH /v3/order/{order_id}/draw_completed PATCH update on site collection order when draw is completed This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. Update an on-site collection order when the draw has been completed. ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.gender import Gender from vital.types.patient_address_compatible import PatientAddressCompatible from vital.types.patient_details import PatientDetails from datetime import datetime client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.update_on_site_collection_order_draw_completed( order_id="order_id", ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.updateOnSiteCollectionOrderDrawCompleted("order_id") ``` ```json Response theme={null} { "order": { "id": "ea7eae96-2c25-404f-b043-bfc08584610d", "team_id": "c26a9cc7-cdff-4f23-a5f6-74d40088c16a", "user_id": "63661a2b-2bb3-4125-bb1a-b590f64f057f", "patient_details": { "dob": "2020-01-01", "gender": "male" }, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789" }, "priority": false, "health_insurance_id": "33ec11aa-d8bf-4f46-950d-c9171be3c22f", "details": { "type": "testkit", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "shipment": { "id": "d55210cc-3d9f-4115-8262-5013f700c7be", "outbound_tracking_number": "", "outbound_tracking_url": "", "inbound_tracking_number": "", "inbound_tracking_url": "", "outbound_courier": "usps", "inbound_courier": "usps", "notes": "", "created_at": "2020-01-01T00:00:00.000Z", "updated_at": "2020-01-01T00:00:00.000Z" }, "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "method": "testkit" }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "collecting_sample", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.on_site_collection.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.on_site_collection.requisition_created" }, { "id": 3, "created_at": "2022-01-03T00:00:00Z", "status": "sample_with_lab.on_site_collection.draw_completed" } ] }, "status": "string", "message": "string" } ``` # Bulk Export Source: https://docs.junction.com/api-reference/link/bulk-export POST /v2/link/bulk_export If you are planning to migrate connections, please [chat with us through support channels](/home/getting-support). Link Migration endpoints are disabled by default. You should [pause the connections](/api-reference/link/bulk-pause) before bulk exporting them. Pausing stops Vital systems from refreshing the connections, ensuring the exported data are most up-to-date. # Bulk Import Source: https://docs.junction.com/api-reference/link/bulk-import POST /v2/link/bulk_import If you are planning to migrate connections, please [chat with us through support channels](/home/getting-support). Link Migration endpoints are disabled by default. Import existing provider connections to your [Bring Your Own OAuth](/wearables/connecting-providers/bring-your-own-oauth/overview) application credentials. Before migrating any connection through this endpoint, you must first [configure your BYOO app credential](/wearables/connecting-providers/bring-your-own-oauth/overview#setting-up-your-oauth-credentials) for the data provider on your Vital Team. Note that these connections cannot be migrated: 1. Any connection that is bound to an OAuth application credential not in your possession 2. Any connection that is not bound to the OAuth application credential set on your Vital Team. ### Asynchronous execution The Bulk Import endpoint enqueues all connections you submitted to a persistent background operation. It then responds *202 Created* immediately afterwards. You can inspect the status of the resulting background operation through the [List Bulk Ops](/api-reference/link/list-bulk-ops) endpoint. Optionally, you may opt into the `wait_for_completion` mode, which would respond 200 OK only if the operation does complete within 20 seconds. Otherwise, the endpoint responds 202 Created. You can submit any number of β€” or even all β€” connections through the Bulk Import API within a short period of time. Enqueuing is fast and does not disrupt the progress of the background operation. # Bulk Pause Source: https://docs.junction.com/api-reference/link/bulk-pause POST /v2/link/bulk_pause If you are planning to migrate connections, please [chat with us through support channels](/home/getting-support). Link Migration endpoints are disabled by default. # Bulk Trigger Historical Pull Source: https://docs.junction.com/api-reference/link/bulk-trigger-historical-pull POST /v2/link/bulk_trigger_historical_pull If you are planning to migrate connections, please [chat with us through support channels](/home/getting-support). Link Migration endpoints are disabled by default. ### Asynchronous execution The Bulk Trigger Historical Pull endpoint enqueues all trigger requests you submitted to a persistent background operation. It then responds *202 Created* immediately afterwards. You can inspect the status of the resulting background operation through the [List Bulk Ops](/api-reference/link/list-bulk-ops) endpoint. Optionally, you may opt into the `wait_for_completion` mode, which would respond 200 OK only if the operation does complete within 20 seconds. Otherwise, the endpoint responds 202 Created. You can trigger historical pull on any number of β€” or even all β€” connections through the Bulk Trigger Historical Pull API within a short period of time. Enqueuing is fast and does not disrupt the progress of the background operation. #### Managing provider API rate limits Junction transparently batches and throttles the trigger requests. This is to avoid exhausting the provider API rate limits of your Bring Your Own OAuth custom credentials. You can submit as many trigger requests as needed. | Provider | Historical Pull triggering Rate | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Garmin | 2 connections every 1 minute | | Fitbit, Polar | 250 connections at a time; no time-based throttling | | Strava | 1 connections every 15 minutes | | Oura | 150 connections every 5 minutes | | Withings | 8 connections every 1 minute, with the assumption of your app using [Withings Enterprise Plan](https://developer.withings.com/developer-guide/v3/withings-solutions/withings-api-plans/). | | All other providers | 8 connections every 1 minute | #### Historical Pull Range Bulk Trigger Historical Pull respects any restricting [User Ingestion Bounds](/wearables/providers/data-ingestion-bounds) and/or any [Team Data Pull Preferences](/wearables/providers/introduction#customizing-historical-data-pull-range) you have specified. If you have specified a large number of days to pull, you should be aware that: 1. The background operation will take longer time to run to completion. 2. It may still cause temporary disruption to data polling and webhook processing, even after the triggering having been throttled. # Complete Password Provider MFA Source: https://docs.junction.com/api-reference/link/complete-password-provider-mfa POST /v2/link/provider/password/{provider}/complete_mfa This connects auth providers that are password based. ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/link/provider/password/{provider}/complete_mfa \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --header 'x-vital-link-token: ' \ --data ' { "mfa_code": "012345" } ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { CompletePasswordProviderMfaBody, PasswordProviders } from '@tryvital/vital-node/api'; const request: CompletePasswordProviderMfaBody = { mfaCode: '', } const data = await client.link.completePasswordProviderMfa(PasswordProviders., request) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.password_providers import PasswordProviders client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.link.complete_password_provider_mfa( PasswordProviders., mfa_code="", ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.CompletePasswordProviderMfaBody; import com.vital.api.types.PasswordProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); IndividualProviderData request = CompletePasswordProviderMfaBody .builder() .mfaCode("") .build(); var data = vital.link().completePasswordProviderMfa(PasswordProviders., request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.CompletePasswordProviderMfaBody{ MfaCode: "", } response, err := client.Link.CompletePasswordProviderMfa( context.TODO(), vital.PasswordProviders, request ) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Create Code Source: https://docs.junction.com/api-reference/link/create-code POST /v2/link/code/create Generate a token to invite a user of Vital mobile app to your team Used for Vital iOS Apps to share Apple HealthKit with you. Please refer [here](/wearables/vital-app/introduction) for more information. ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/link/code/create\?user_id\=1875c190-0cd6-46c1-8670-5b56a7794b78 \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --header 'x-vital-api-key: ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { LinkCodeCreateRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: LinkCodeCreateRequest = { userId: '', } const data = await client.link.codeCreate(request); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.link.code_create(user_id='') ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.LinkCodeCreateRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); LinkCodeCreateRequest request = LinkCodeCreateRequest .builder() .userId("") .build(); var data = vital.link().codeCreate(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.LinkCodeCreateRequest{ UserId: "", } response, err := client.Link.CodeCreate(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Generate a link token Source: https://docs.junction.com/api-reference/link/generate-link-token POST /v2/link/token Endpoint to generate a user link token, to be used throughout the vital link process. The vital link token is a one time use token, that expires after 10 minutes. If you would like vital-link widget to launch with a specific provider, pass in a provider in the body. If you would like to redirect to a custom url after successful or error connection, pass in your own custom redirect_url parameter. ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/link/token \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --header 'x-vital-api-key: ' \ --data ' { "user_id": "1875c190-0cd6-46c1-8670-5b56a7794b78" } ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { LinkTokenExchange } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: LinkTokenExchange = { userId: "", provider: "oura", } const data = await client.link.token(request) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.providers import Providers client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.link.token(user_id="", provider=Providers.OURA) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.LinkTokenExchange; import com.vital.api.types.Providers; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); LinkTokenExchange request = LinkTokenExchange .builder() .userId("") .provider(Providers.OURA) .build(); var data = vital.link().token(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) provider := vital.ProvidersOura request := &vital.LinkTokenExchange{ UserId: "", Provider: &provider, } response, err := client.Link.Token(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Link Demo provider Source: https://docs.junction.com/api-reference/link/link-demo-provider POST /v2/link/connect/demo POST Connect the given Vital user to a demo provider. ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/link/connect/demo \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --header 'x-vital-link-token: ' \ --data ' { "user_id": "1875c190-0cd6-46c1-8670-5b56a7794b78" "provider": "fitbit" } ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { DemoConnectionCreationPayload, DemoProviders } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: DemoConnectionCreationPayload = { userId: "", provider: DemoProviders., } const data = await client.link.connectDemoProvider(request); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.link.connect_demo_provider(user_id="", provider=DemoProviders.) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.DemoConnectionCreationPayload; import com.vital.api.types.DemoProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); DemoConnectionCreationPayload request = DemoConnectionCreationPayload .builder() .userId("") .provider(DemoProviders.) .build(); var data = vital.link().connectDemoProvider(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.DemoConnectionCreationPayload{ UserId: "", Provider: vital.DemoProviders, } response, err := client.Link.ConnectDemoProvider(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Link Email provider Source: https://docs.junction.com/api-reference/link/link-email-provider POST /v2/link/provider/email/{provider} This connects auth providers that are email based. ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/link/provider/email/{provider} \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --header 'x-vital-link-token: ' \ --data ' { "email": "test@email.com" "region": "us" } ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { EmailProviderAuthLink, LinkTokenExchange } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: LinkTokenExchange = { userId: "", } const tokenResponse = await client.link.token(request) const linkRequest: EmailProviderAuthLink = { vitalLinkToken: tokenResponse.linkToken, email: "", region: Region. } const connectEmailResponse = await client.link.connectEmailAuthProvider( "freestyle_libre", linkRequest ) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) token_response = client.link.token(user_id="") linkResponse = client.link.connect_email_auth_provider( provider="freestyle_libre", email="", vital_link_token=token_response.link_token ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.EmailProviderAuthLink; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); LinkTokenExchange request = LinkTokenExchange .builder() .userId("") .build(); var tokenResponse = vital.link().token(request); EmailProviderAuthLink request = EmailProviderAuthLink .builder() .vitalLinkToken(tokenResponse.getLinkToken()) .email("") .build(); var data = vital.link().connectEmailAuthProvider(""), option.WithBaseURL(vital.Environments.Sandbox), ) provider := vital.ProvidersOura request := &vital.LinkTokenExchange{ UserId: "", Provider: &provider, } response, err := client.Link.Token(context.TODO(), request) if err != nil { return err } emailAuthRequest := &vital.EmailProviderAuthLink{ VitalLinkToken: response.LinkToken, Email: "", } emailAuth, err := client.Link.ConnectEmailAuthProvider(context.TODO(), "freestyle_libre", emailAuthRequest) if err != nil { return err } fmt.Printf("Received data %s\n", emailAuth) ``` # Link OAuth provider Source: https://docs.junction.com/api-reference/link/link-oauth-provider GET /v2/link/provider/oauth/{oauth_provider} This endpoint generates an OAuth link for oauth provider ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/link/provider/oauth/{oauth_provider} \ --header 'Accept: application/json' --header 'Content-Type: application/json' \ --header 'x-vital-link-token: ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { LinkTokenExchange, LinkGenerateOauthLinkRequest, OAuthProviders } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: LinkTokenExchange = { userId: "", } const tokenResponse = await client.link.token(request) const linkRequest: LinkGenerateOauthLinkRequest = { vitalLinkToken: tokenResponse.linkToken, } const auth = await client.link.generateOauthLink( OAuthProviders., linkRequest ) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.o_auth_providers import OAuthProviders client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) token_response = client.link.token(user_id="") oauth = client.link.generate_oauth_link( oauth_provider=OAuthProviders., vital_link_token=token_response.link_token, ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.LinkGenerateOauthLinkRequest; import com.vital.api.resources.link.requests.LinkTokenExchange; import com.vital.api.types.OAuthProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); LinkTokenExchange request = LinkTokenExchange .builder() .userId("") .build(); var tokenResponse = vital.link().token(request); LinkGenerateOauthLinkRequest link_request = LinkGenerateOauthLinkRequest .builder() .vitalLinkToken(tokenResponse.getLinkToken()) .build(); var oauth = vital.link().generateOauthLink(OAuthProviders.OURA, link_request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) provider := vital.ProvidersOura request := &vital.LinkTokenExchange{ UserId: "", Provider: &provider, } response, err := client.Link.Token(context.TODO(), request) if err != nil { return err } generateLinkRequest := &vital.LinkGenerateOauthLinkRequest{ VitalLinkToken: response.LinkToken, } oauth, err := client.Link.GenerateOauthLink(context.TODO(), vital.OAuthProvidersOura, generateLinkRequest) if err != nil { return err } fmt.Printf("Received data %s\n", oauth) ``` # Link Password provider Source: https://docs.junction.com/api-reference/link/link-password-provider POST /v2/link/provider/password/{provider} This connects auth providers that are password based. ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/link/provider/password/{provider} \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --header 'x-vital-link-token: ' \ --data ' { "username": "test", "password": "test" } ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { IndividualProviderData, PasswordProviders } from '@tryvital/vital-node/api'; const request: IndividualProviderData = { username: '', password: '', } const data = await client.link.connectPasswordProvider(PasswordProviders., request) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.password_providers import PasswordProviders client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.link.connect_password_provider( PasswordProviders., username="", password="" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.IndividualProviderData; import com.vital.api.types.PasswordProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); IndividualProviderData request = IndividualProviderData .builder() .username("") .password("") .build(); var data = vital.link().connectPasswordProvider(PasswordProviders., request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.IndividualProviderData{ Username: "", Password: "", } response, err := client.Link.ConnectPasswordProvider( context.TODO(), vital.PasswordProviders, request ) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # List Bulk Ops Source: https://docs.junction.com/api-reference/link/list-bulk-ops GET /v2/link/bulk_op If you are planning to migrate connections, please [chat with us through support channels](/home/getting-support). Link Migration endpoints are disabled by default. # Create or Resend Invite Source: https://docs.junction.com/api-reference/org-management/invite/create-or-resend-invite post /v1/org/{org_id}/invite [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request POST \ --url https://api.management.junction.com/v1/org/{org_id}/invite \ --header 'Content-Type: application/json' \ --header 'X-Vital-Org-Key: ' \ --data '{ "email": "" }' ``` # List Invites Source: https://docs.junction.com/api-reference/org-management/invite/list-invites get /v1/org/{org_id}/invite [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/invite \ --header 'X-Vital-Org-Key: ' ``` # Revoke Invite Source: https://docs.junction.com/api-reference/org-management/invite/revoke-invite delete /v1/org/{org_id}/invite/{invite_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request DELETE \ --url https://api.management.junction.com/v1/org/{org_id}/invite/{invite_id} \ --header 'X-Vital-Org-Key: ' ``` # Get Lab Accounts Source: https://docs.junction.com/api-reference/org-management/lab-accounts/get-lab-accounts get /v1/org/{org_id}/lab_account/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/lab_account/{env}/{region} \ --header 'X-Vital-Org-Key: ' ``` # Update Lab Account Teams Source: https://docs.junction.com/api-reference/org-management/lab-accounts/update-lab-account-teams patch /v1/org/{org_id}/lab_account/{env}/{region}/{account_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request PATCH \ --url https://api.management.junction.com/v1/org/{org_id}/lab_account/{env}/{region}/{account_id} \ --header 'X-Vital-Org-Key: ' --data '{ "team_id_allowlist": "" }' ``` # Create Management Key Source: https://docs.junction.com/api-reference/org-management/management-keys/create-management-key post /v1/org/{org_id}/management_key [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request POST \ --url https://api.management.junction.com/v1/org/{org_id}/management_key \ --header 'Content-Type: application/json' \ --header 'X-Vital-Org-Key: ' \ --data '{ "label": "", "team_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a" }' ``` # Delete Management Key Source: https://docs.junction.com/api-reference/org-management/management-keys/delete-management-key delete /v1/org/{org_id}/management_key/{key_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request DELETE \ --url https://api.management.junction.com/v1/org/{org_id}/management_key \ --header 'X-Vital-Org-Key: ' ``` # List Management Keys Source: https://docs.junction.com/api-reference/org-management/management-keys/list-management-keys get /v1/org/{org_id}/management_key [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/management_key \ --header 'X-Vital-Org-Key: ' ``` # List Members Source: https://docs.junction.com/api-reference/org-management/member/list-members get /v1/org/{org_id}/member [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/member \ --header 'X-Vital-Org-Key: ' ``` # Delete Member Source: https://docs.junction.com/api-reference/org-management/member/remove-member delete /v1/org/{org_id}/member/{member_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request DELETE \ --url https://api.management.junction.com/v1/org/{org_id}/member/{member_id} \ --header 'X-Vital-Org-Key: ' ``` # Get Org Source: https://docs.junction.com/api-reference/org-management/org/get-org get /v1/org/{org_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id} \ --header 'X-Vital-Org-Key: ' ``` # Update Org Source: https://docs.junction.com/api-reference/org-management/org/update-org patch /v1/org/{org_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request PATCH \ --url https://api.management.junction.com/v1/org/{org_id} \ --header 'X-Vital-Org-Key: ' ``` # Create Team API Key Source: https://docs.junction.com/api-reference/org-management/team-api-keys/create-team-api-key post /v1/org/{org_id}/team_api_keys/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request POST \ --url https://api.management.junction.com/v1/org/{org_id}/team_api_keys/{env}/{region} \ --header 'Content-Type: application/json' \ --header 'X-Vital-Org-Key: ' \ --data '{ "label": "", "team_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a" }' ``` # Delete Team API Keys Source: https://docs.junction.com/api-reference/org-management/team-api-keys/delete-team-api-keys delete /v1/org/{org_id}/team_api_keys/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request DELETE \ --url https://api.management.junction.com/v1/org/{org_id}/team_api_keys/{env}/{region} \ --header 'X-Vital-Org-Key: ' ``` # List Team API Keys Source: https://docs.junction.com/api-reference/org-management/team-api-keys/list-team-api-keys get /v1/org/{org_id}/team_api_keys/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/team_api_keys/{env}/{region} \ --header 'X-Vital-Org-Key: ' ``` # Delete Team Custom Credentials Source: https://docs.junction.com/api-reference/org-management/team-custom-credentials/delete-team-custom-credentials delete /v1/org/{org_id}/team_custom_credentials/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request DELETE \ --url https://api.management.junction.com/v1/org/{org_id}/team_custom_credentials/{env}/{region} \ --header 'X-Vital-Org-Key: ' ``` # Get Team Custom Credentials Source: https://docs.junction.com/api-reference/org-management/team-custom-credentials/get-team-custom-credentials get /v1/org/{org_id}/team_custom_credentials/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/team_custom_credentials/{env}/{region} \ --header 'X-Vital-Org-Key: ' ``` # Prepare Team Custom Credentials Source: https://docs.junction.com/api-reference/org-management/team-custom-credentials/prepare-team-custom-credentials POST /v1/org/{org_id}/prepare_team_custom_credentials/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/prepare_team_custom_credentials/{env}/{region} \ --header 'X-Vital-Org-Key: ' ``` # Set Team Custom Credentials Source: https://docs.junction.com/api-reference/org-management/team-custom-credentials/upsert-team-custom-credentials post /v1/org/{org_id}/team_custom_credentials/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ### OAuth Providers Bring Your Own OAuth (BYOO) is available for [the Grow and Scale plans](https://tryvital.io/pricing). WHOOP BYOO is available for [the Launch, Grow and Scale plans](https://tryvital.io/pricing). See [our WHOOP Guide](/wearables/guides/whoop) for more information. While most credentials are active immediately once set through this endpoint, some credentials may not. Check out the [Bring Your Own OAuth](/wearables/connecting-providers/bring_your_own_oauth#through-the-org-management-api) documentation for more information. ```bash cURL theme={null} curl --request POST \ --url https://api.management.junction.com/v1/org/{org_id}/team_custom_credentials/{env}/{region} \ --header 'Content-Type: application/json' \ --header 'X-Vital-Org-Key: ' \ --data '{ "team_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "provider": "oura", "client_id": "", "client_secret": "", "details": { "fitbit": { "is_hr_enabled": true, "workout_verify_token": "", "sleep_verify_token": "", "body_verify_token": "" } } }' ``` # Delete Team Data Pull Preferences Source: https://docs.junction.com/api-reference/org-management/team-data-pull-preferences/delete-team-data-pull-preferences delete /v1/org/{org_id}/team_data_pull_preferences/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. Deleting preferences for a provider will revert to the default settings. ```bash cURL theme={null} curl --request DELETE \ --url https://api.management.junction.com/v1/org/{org_id}/team_data_pull_preferences/{env}/{region} \ --header 'X-Vital-Org-Key: ' ``` # Get Team Data Pull Preferences Source: https://docs.junction.com/api-reference/org-management/team-data-pull-preferences/get-team-data-pull-preferences get /v1/org/{org_id}/team_data_pull_preferences/{env}/{region} ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/team_data_pull_preferences/{env}/{region} \ --header 'X-Vital-Org-Key: ' ``` The endpoint will return the default vital settings if no custom preferences are set. [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Post Team Data Pull Preferences Source: https://docs.junction.com/api-reference/org-management/team-data-pull-preferences/upsert-team-data-pull-preferences post /v1/org/{org_id}/team_data_pull_preferences/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request POST \ --url https://api.management.junction.com/v1/org/{org_id}/team_data_pull_preferences/{env}/{region} \ --header 'X-Vital-Org-Key: ' --data '{ "team_ids": [ "" ], "providers": { "oura": { "default": { "historical_days_to_pull": 120 }, "resource_overrides": { "sleep": { "historical_days_to_pull": 180 } } } } }' ``` By setting a higher value, you acknowledge your awareness of *the facts of API life* that: 1. historical pulls may take longer to complete; 2. historical pulls may run into provider rate limiting; and 3. regular data polling may consequentially be delayed as a knock-on effect. Provider rate limiting typically happens at OAuth application level. Therefore, Vital recommends [Bring Your Own OAuth](/wearables/connecting-providers/bring_your_own_oauth) if you want to set an extended historical pull range. This creates a rate limit quota dedicated to your Vital Team, not being shared with other Vital customers. The Team Data Pull Preferences you specified is *advisory*. There are scenarios in which Vital systems may not adhere strictly to your stated preferences. ## Supported Providers | Provider | Default | Configurable | Remarks | | ---------------------------------------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Abbott LibreView](https://www.libreview.com) | 90 days | ❌ | - | | [Fitbit](https://www.fitbit.com/global/uk/home) | 90 days | ⚠️ | Activity and heartrate timeseries data are fixed to 14 days. | | [Garmin](https://www.garmin.com) | 90 days | βœ… | - | | [Google Fit](https://www.google.com/fit/) | 90 days | βœ… | - | | [Oura](https://ouraring.com) | 180 days | βœ… | - | | [Peloton](https://www.onepeloton.com) | 180 days | βœ… | - | | [Strava](https://www.strava.com) | 14 days | βœ… | - | | [Wahoo](https://wahoofitness.com) | 180 days | βœ… | - | | [WHOOP](https://www.whoop.com) | 180 days | βœ… | - | | [Zwift](https://zwift.com) | 270 days | βœ… | - | | [Withings](https://www.withings.com) | 90 days | βœ… | - | | [8Sleep](https://www.eightsleep.com) | 90 days | βœ… | - | | [Apple HealthKit](https://www.apple.com/uk/ios/health/) (SDK) | 30 days | βœ… | - | | [Android Health Connect](https://developer.android.com/health-connect) (SDK) | 30 days | ❌ | Google restricts access to historical data to [30 days before the first successful permission request](https://developer.android.com/health-and-fitness/guides/health-connect/develop/frequently-asked-questions#time-range). | | [Hammerhead](https://www.hammerhead.io) | 30 days | βœ… | - | | [Dexcom](https://www.dexcom.com) | 30 days | βœ… | - | | [Dexcom (G6 And Older)](https://www.dexcom.com) | 1 day | ❌ | - | | [MyFitnessPal](https://www.myfitnesspal.com) | 14 days | βœ… | - | | [Polar](https://www.polar.com/accesslink-api/#polar-accesslink-api) | 28 days | ❌ | Polar only supports historical backfill for Sleep and Sleep Stream resources. | | [Cronometer](https://www.cronometer.com) | 28 days | βœ… | - | The configurable maximum is 365 days at this time. # Delete Team ETL Pipelines Source: https://docs.junction.com/api-reference/org-management/team-etl-pipeline/delete-team-etl-pipelines delete /v1/org/{org_id}/team_etl_pipelines/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request DELETE \ --url https://api.management.junction.com/v1/org/{org_id}/team_etl_pipelines/{env}/{region} \ --header 'X-Vital-Org-Key: ' ``` # Get Team ETL Pipelines Source: https://docs.junction.com/api-reference/org-management/team-etl-pipeline/get-team-etl-pipelines get /v1/org/{org_id}/team_etl_pipelines/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/team_etl_pipelines/{env}/{region} \ --header 'X-Vital-Org-Key: ' ``` # Set Team ETL Pipelines Source: https://docs.junction.com/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines post /v1/org/{org_id}/team_etl_pipelines/{env}/{region} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request POST \ --url https://api.management.junction.com/v1/org/{org_id}/team_etl_pipelines/{env}/{region} \ --header 'Content-Type: application/json' \ --header 'X-Vital-Org-Key: ' \ --data '{ "team_ids": [ "3c90c3cc-0d44-4b50-8888-8dd25736052a" ], "push_historical_data": true, "provider_raw_data": true, "preferences": { "preferred": "cloud_pubsub", "enabled": [ "cloud_pubsub" ] }, "cloud_pubsub": { "project": "", "topic": "", "message_ordering": true }, "rabbitmq": { "uri": "", "exchange": "" }, "svix": { "regional": true }, "event_type_prefixes": [ "" ] }' ``` # Get Team Scope Requirements Source: https://docs.junction.com/api-reference/org-management/team-scope-requirements/get-team-scope-requirements get /v1/org/{org_id}/team/{team_id}/scope_requirements [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/team/{team_id}/scope_requirements \ --header 'X-Vital-Org-Key: ' ``` # Set Team Scope Requirements Source: https://docs.junction.com/api-reference/org-management/team-scope-requirements/upsert-team-scope-requirements post /v1/org/{org_id}/team/{team_id}/scope_requirements [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request POST \ --url https://api.management.junction.com/v1/org/{org_id}/team/{team_id}/scope_requirements \ --header 'Content-Type: application/json' \ --header 'X-Vital-Org-Key: ' \ --data '{ "fitbit": null, "oura": { "user_must_grant": ["daily"], "user_may_grant": ["workout"] } }' ``` # Create Webhook Source: https://docs.junction.com/api-reference/org-management/team-webhook/create post /v1/org/{org_id}/team/{team_id}/{environment}/webhook [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Delete Webhook Source: https://docs.junction.com/api-reference/org-management/team-webhook/delete delete /v1/org/{org_id}/team/{team_id}/{environment}/webhook/{webhook_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Get Webhook Source: https://docs.junction.com/api-reference/org-management/team-webhook/get get /v1/org/{org_id}/team/{team_id}/{environment}/webhook/{webhook_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Get Webhook Headers Source: https://docs.junction.com/api-reference/org-management/team-webhook/get-headers get /v1/org/{org_id}/team/{team_id}/{environment}/webhook/{webhook_id}/headers [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Get Webhook Secret Source: https://docs.junction.com/api-reference/org-management/team-webhook/get-secret get /v1/org/{org_id}/team/{team_id}/{environment}/webhook/{webhook_id}/secret [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # List Webhooks Source: https://docs.junction.com/api-reference/org-management/team-webhook/list get /v1/org/{org_id}/team/{team_id}/{environment}/webhook [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Patch Webhook Source: https://docs.junction.com/api-reference/org-management/team-webhook/patch patch /v1/org/{org_id}/team/{team_id}/{environment}/webhook/{webhook_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Rotate Webhook Secret Source: https://docs.junction.com/api-reference/org-management/team-webhook/rotate-secret post /v1/org/{org_id}/team/{team_id}/{environment}/webhook/{webhook_id}/secret/rotate [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Update Webhook Headers Source: https://docs.junction.com/api-reference/org-management/team-webhook/update-headers put /v1/org/{org_id}/team/{team_id}/{environment}/webhook/{webhook_id}/headers [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Create Team Source: https://docs.junction.com/api-reference/org-management/team/create-team post /v1/org/{org_id}/team [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request POST \ --url https://api.management.junction.com/v1/org/{org_id}/team \ --header 'Content-Type: application/json' \ --header 'X-Vital-Org-Key: ' \ --data '{ "name": "", "region": "us", "lab_tests_patient_sms_communication_enabled": true, "lab_tests_patient_email_communication_enabled": true, "event_type_prefixes": [ "" ] }' ``` # Delete Team Source: https://docs.junction.com/api-reference/org-management/team/delete-team delete /v1/org/{org_id}/team/{team_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request DELETE \ --url https://api.management.junction.com/v1/org/{org_id}/team/{team_id} \ --header 'X-Vital-Org-Key: ' ``` # Get Team Source: https://docs.junction.com/api-reference/org-management/team/get-team get /v1/org/{org_id}/team/{team_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/team/{team_id} \ --header 'X-Vital-Org-Key: ' ``` # List Teams Source: https://docs.junction.com/api-reference/org-management/team/list-teams get /v1/org/{org_id}/team [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request GET \ --url https://api.management.junction.com/v1/org/{org_id}/team \ --header 'X-Vital-Org-Key: ' ``` # Update Team Source: https://docs.junction.com/api-reference/org-management/team/update-team patch /v1/org/{org_id}/team/{team_id} [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. ```bash cURL theme={null} curl --request PATCH \ --url https://api.management.junction.com/v1/org/{org_id}/team/{team_id} \ --header 'Content-Type: application/json' \ --header 'X-Vital-Org-Key: ' \ --data '{ "name": "", "sandbox": { "lab_tests_patient_sms_communication_enabled": true, "lab_tests_patient_email_communication_enabled": true, "event_type_prefixes": [ "" ] }, "production": { "lab_tests_patient_sms_communication_enabled": true, "lab_tests_patient_email_communication_enabled": true, "event_type_prefixes": [ "" ] } }' ``` # List of Providers Source: https://docs.junction.com/api-reference/providers GET /v2/providers Get Provider list ```bash cURL theme={null} curl --request GET \ --url https://api.tryvital.io/v2/providers/ \ --header 'Accept: application/json' --header 'x-vital-api-key: ' \ ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.providers.getAll(); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.providers.get_all() ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.providers().getAll(); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.Providers.GetAll(context.TODO()) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Create a Continuous Query Source: https://docs.junction.com/api-reference/sense/continuous-query/create post /v1/org/{org_id}/team/{team_id}/{environment}/continuous_query Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Get User Result Table Source: https://docs.junction.com/api-reference/sense/continuous-query/get-result-table get /aggregate/v1/user/{user_id}/continuous_query/{query_id_or_slug}/result_table Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. # Get Continuous Query Task History Source: https://docs.junction.com/api-reference/sense/continuous-query/get-task-history get /aggregate/v1/user/{user_id}/continuous_query/{query_id_or_slug}/task_history Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. # List all Continuous Queries Source: https://docs.junction.com/api-reference/sense/continuous-query/list get /v1/org/{org_id}/team/{team_id}/{environment}/continuous_query Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Update a Continuous Query Source: https://docs.junction.com/api-reference/sense/continuous-query/update patch /v1/org/{org_id}/team/{team_id}/{environment}/continuous_query/{query_id} Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). The base URL of this endpoint is `https://api.management.junction.com/`. The endpoint accepts only [Management Key](/api-details/junction-management-api#authentication) (`X-Management-Key`). Team API Key is not accepted. # Query a single user Source: https://docs.junction.com/api-reference/sense/query-one post /aggregate/v1/user/{user_id}/query Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. JSON is the default output format. Each query instruction outputs one entry containing a dataframe to the `$.results` array, in the declaration order of the query instructions. ```json theme={null} { "results": [ "table": { "index": [...], "min": [...], "max": [...], "mean": [...], "newest": [...] } ] } ``` Specify `Accept: application/vnd.vital.tar+gzip+parquet` in your request header. The response body is a gzipped Tarball of multiple Parquet files, namely `0.parquet`, `1.parquet`, `2.parquet`, etc. The numbering corresponds to the declaration order of the query instructions. # Create Insurance Source: https://docs.junction.com/api-reference/user/create-insurance-beta POST /v2/user/{user_id}/insurance If insurance is covered by Medicare or Medicaid, we have closed beta support for inferring the plan to use based on a patient's address. This endpoint supports `payor_code`s with values of `MEDFED` (Medicare) and `MAIDFED` (Medicaid) to do this. For example, supplying a `payor_code` of `MEDFED` for a patient that lives in Arizona will create insurance data using Arizona's Medicare plan payor code. ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/user/{user_id}/insurance \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' \ --data ' { "payor_code": "UNITE", "member_id": "test", "group_id": "123", "relationship": "Self", "insured": { "first_name": "John", "last_name": "Doe", "email": "john@email.com", "phone_number": "+1123123123", "gender": "Male", "dob": "1999-01-01", "address": { "first_line": "Some Street", "second_line": null, "zip": "85004", "state": "AZ", "city": "Phoenix", "country": "US" } } } ' ``` # Create Portal URL Source: https://docs.junction.com/api-reference/user/create-portal-url POST /v2/user/{user_id}/create_portal_url This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. Create a URL to Junction User Portal pre-authorized to a particular User. Junction User Portal requires demographics having been specified for the User: * Use the [Get User Demographics](/api-reference/user/get-info-latest) endpoint to review currently set demographics; * Use the [Update User Demographics](/api-reference/user/upsert-info) endpoint to update the user's demographics. If you attempt to create a Portal URL for a user without demographics, you will get a **422 Unprocessable Entity** response. ```cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/user/{{USER_ID}}/create_portal_url \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' \ --data '{"context":"launch"}' ``` ```python Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.createPortalUrl( '' ); ``` ```javascript Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.user import CreateUserPortalUrlBodyContext client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.create_portal_url( user_id="", context=CreateUserPortalUrlBodyContext.LAUNCH, ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.user.requests.CreateUserPortalUrlBody; import com.vital.api.resources.user.types.CreateUserPortalUrlBodyContext; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); CreateUserPortalUrlBody body = CreateUserPortalUrlBody.builder() .context(CreateUserPortalUrlBodyContext.LAUNCH) .build(); var data = vital.user().createPortalUrl("", body); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) body := &vital.CreateUserPortalUrlBody{ Context: vital.CreateUserPortalUrlBodyContextLaunch, } response, err := client.User.CreatePortalUrl(context.TODO(), "", body) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Create Sign-In Token Source: https://docs.junction.com/api-reference/user/create-sign-in-token POST /v2/user/{user_id}/sign_in_token Create a [Vital Sign-In Token](/wearables/sdks/authentication#vital-sign-in-token) for your mobile apps to sign in with [Vital Mobile SDKs](/wearables/sdks/authentication). Avoid requesting Vital Sign-In Token every time your mobile app relaunches. Your mobile app only needs to sign-in once with the Vital Mobile SDK. A signed-in session is persistent on device. ```cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/user/{{USER_ID}}/sign_in_token \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' \ ``` ```python Python theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.get_user_sign_in_token( '' ); ``` ```javascript Node theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.getUserSignInToken( user_id="" ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().getUserSignInToken(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.GetUserSignInToken(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Create User Source: https://docs.junction.com/api-reference/user/create-user POST /v2/user POST Create a Vital user given a client_user_id and returns the user_id. When the supplied `client_user_id` conflicts with an existing user, the 400 Bad Request error response includes the Vital User ID (`user_id`) and the creation date (`created_on`) of the conflicting user. ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/user/ \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' \ --data ' { "client_user_id": "your_unique_id" } ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.create( '' ); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.create( client_user_id="" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().create(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.Create(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Delete User Source: https://docs.junction.com/api-reference/user/delete-user DELETE /v2/user/{user_id} Deleting a user would also [deregister their all provider connections](/api-reference/user/deregister-a-provider) immediately. Vital erases the user data after a 7-day grace period. If you wish to undo a user deletion for any reason, you can undo the deletion through the [Undo User Deletion](/api-reference/user/undo-delete-user) API. Note that undoing would not restore any provider connections. ```bash cURL theme={null} curl --request DELETE \ --url {{BASE_URL}}/v2/user/{user_id} \ --header 'x-vital-api-key: ' \ --header 'Accept: application/json' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.delete( '' ); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.delete( user_id="" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().delete(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.Delete(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Deregister Connection Source: https://docs.junction.com/api-reference/user/deregister-a-provider DELETE /v2/user/{user_id}/{provider} Deregistration has well-defined behaviour only on [cloud-based providers](/wearables/providers/introduction#cloud-based-providers). If your Junction Health SDK uses [the default Auto Connect mode](/wearables/sdks/health/connection-policies#auto-connect-mode-default), deregistering an Apple HealthKit or a Health Connect connection has no effect. For explicit connection and disconnection of Apple HealthKit and Health Connect connections, see the [the opt-in Explicit Connect mode](/wearables/sdks/health/connection-policies#auto-connect-mode-default). You can also pause [Health SDK data sync](/wearables/sdks/vital-health#pausing-data-synchronization) client-side, or [sign-out the user](/wearables/sdks/vital-core#reset-the-sdk-sign-out) from the SDK. ```bash cURL theme={null} curl --request DELETE \ --url {{BASE_URL}}/v2/user/{user_id}/{provider} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.deregisterProvider( '', '' ); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.deregister_provider( user_id="", provider="" ); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().deregisterProvider( "", "" ); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.DeregisterProvider( context.TODO(), "", "", ) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Get User Demographics Source: https://docs.junction.com/api-reference/user/get-info-latest GET /v2/user/{user_id}/info/latest This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/user/{user_id}/info/latest \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' \ ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.get_latest_user_info( '' ); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.get_latest_user_info( user_id="" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().getLatestUserInfo(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.GetLatestUserInfo(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "first_name": "John", "last_name": "Doe", "email": "john@email.com", "phone_number":"+1123123123", "gender": "Male", "dob": "1999-01-01", "address": { "first_line": "Some Street", "second_line": null, "zip_code": "85004", "state": "AZ", "city": "Phoenix", } } ``` # Get Latest Insurance Source: https://docs.junction.com/api-reference/user/get-latest-insurance GET /v2/user/{user_id}/insurance/latest This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/user/{user_id}/insurance/latest \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' \ ``` ```json Response theme={null} { "payor_code": "UNITE", "member_id": "test", "group_id": "123", "relationship": "Self", "insured": { "first_name": "John", "last_name": "Doe", "email": "john@email.com", "phone_number":"+1123123123", "gender": "Male", "dob": "1999-01-01", "address": { "first_line": "Some Street", "second_line": null, "zip_code": "85004", "state": "AZ", "city": "Phoenix", } } } ``` # Get User Source: https://docs.junction.com/api-reference/user/get-user GET /v2/user/{user_id} ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/user/{user_id} \ --header 'x-vital-api-key: ' \ --header 'Accept: application/json' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.get( '' ); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.get( user_id="" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().get(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.Get(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Get Teams Users Source: https://docs.junction.com/api-reference/user/get-users GET /v2/user GET All users for team. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/user/ \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.getAll(); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.get_all() ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().getAll(); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.GetAll(context.TODO(), nil) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Get User Connections Source: https://docs.junction.com/api-reference/user/get-users-connected-providers GET /v2/user/providers/{user_id} GET Users connected providers ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/user/providers/{user_id} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: API_KEY' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.getConnectedProviders(""); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.get_connected_providers("") ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().getConnectedProviders(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.GetConnectedProviders(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "providers": [ { "name": "Fitbit", "slug": "fitbit", "logo": "https://example.com/fitbit.png", "status": "connected", "created_on": "2010-01-23T12:34:56+00:00", "resource_availability": { "body": { "status": "available", "scope_requirements": { "user_granted": { "required": [ "weight" ], "optional": [] }, "user_denied": { "required": [], "optional": [] } } }, "sleep": { "status": "unavailable", "scope_requirements": { "user_granted": { "required": [], "optional": [] }, "user_denied": { "required": [ "sleep" ], "optional": [ "heartrate", "oxygen_saturation", "respiratory_rate" ] } } } }, "error_details": null } ] } ``` # Patch User Source: https://docs.junction.com/api-reference/user/patch-user PATCH /v2/user/{user_id} ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/user/ \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' \ --data ' { "fallback_time_zone": "Europe/London", "fallback_birth_date" "1980-03-13" } ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.patch( '' ); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.patch( user_id="" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().patch(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.Patch(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Refresh User Data Source: https://docs.junction.com/api-reference/user/refresh-user-data POST /v2/user/refresh/{user_id} Trigger a manual refresh for a specific user ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/user/refresh/{user_id} \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.refresh( '' ); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.refresh( user_id="" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().refresh(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.Refresh(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Get User by Client User ID Source: https://docs.junction.com/api-reference/user/resolve-user GET /v2/user/resolve/{client_user_id} Get User by their `client_user_id`. ```bash cURL theme={null} curl --request GET \ --url {{BASE_URL}}/v2/user/resolve/{client_user_id} \ --header 'x-vital-api-key: ' \ --header 'Accept: application/json' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.getByClientUserId( '' ); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.get_by_client_user_id( client_user_id="" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.user().getByClientUserId(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.User.GetByClientUserId(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Undo User Deletion Source: https://docs.junction.com/api-reference/user/undo-delete-user POST /v2/user/undo_delete You can undo any [user deletion](/api-reference/user/delete-user) that is still in their 7-day grace period. Undoing a deletion does not restore any provider connections. You cannot undo a deletion if you have already [created a new user](/api-reference/user/create-user) using the same `client_user_id`. ```bash cURL theme={null} curl --request DELETE \ --url {{BASE_URL}}/v2/user/undo_delete \ --header 'x-vital-api-key: ' \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --data ' { "client_user_id": "8DS6YRVBCSQQ4S0", } ' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.user.undoDelete({ clientUserId: '8DS6YRVBCSQQ4S0', }); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.undo_delete( client_user_id="8DS6YRVBCSQQ4S0" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.user.requests.UserUndoDeleteRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); UserUndoDeleteRequest request = UserUndoDeleteRequest.builder() .clientUserId("8DS6YRVBCSQQ4S0") .build(); var data = vital.user().undoDelete(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request = &vital.UserUndoDeleteRequest{ ClientUserId: "8DS6YRVBCSQQ4S0", } response, err := client.User.UndoDelete(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Update User Demographics Source: https://docs.junction.com/api-reference/user/upsert-info PATCH /v2/user/{user_id}/info Patient name fields (`first_name`, `last_name`) must follow specific validation rules due to lab restrictions. See [Patient Name Validation](/lab/workflow/order-requirements#patient-name-validation) for complete details. ```bash cURL theme={null} curl --request PATCH \ --url {{BASE_URL}}/v2/user/{user_id}/info \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' \ --data ' { "first_name": "John", "last_name": "Doe", "email": "john@email.com", "phone_number":"+1123123123", "gender": "Male", "dob": "1999-01-01", "address": { "first_line": "Some Street", "second_line": null, "zip_code": "85004", "state": "AZ", "city": "Phoenix", } } ' ``` ```json Response theme={null} { "first_name": "John", "last_name": "Doe", "email": "john@email.com", "phone_number":"+1123123123", "gender": "Male", "dob": "1999-01-01", "address": { "first_line": "Some Street", "second_line": null, "zip_code": "85004", "state": "AZ", "city": "Phoenix", } } ``` # API Source: https://docs.junction.com/changelog/core/api ### Error for closed beta endpoint calls is now JSON (Nov 2025) Previously, if you tried to access an endpoint in closed beta and for which your team was not enabled, it would return a plain text error body. It now returns a JSON error body to align with the rest of our API. ### Lab Test Result Interpretation Filtering (June 2025) You can now filter lab test orders by result interpretation using the new `interpretation` query parameter in the [GET /orders](/api-reference/lab/orders/get-orders) endpoint. The new filtering capability allows you to query orders based on their clinical interpretation: **Query Parameter:** * **`interpretation`** - Filter by result interpretation of the lab test * Type: `enum | null` * Available options: `normal`, `abnormal`, `critical` * Note: This enum is non-exhaustive **Response Enhancement:** Order responses now include a new `interpretation` field providing the clinical assessment of the test results: * **`interpretation`** - Interpretation of the order result * Type: `enum | null` * Available options: `normal`, `abnormal`, `critical` * Note: This enum is non-exhaustive This enhancement enables you to programmatically identify and prioritize critical lab results, improving clinical workflow efficiency and patient care monitoring. Check out the [GET /orders](/api-reference/lab/orders/get-orders) endpoint documentation. ### Webhook Management Endpoints (May 2025) You can now programmatically manage your webhooks via the [Webhooks API](/api-reference/org-management/team-webhook/list). The new endpoints allow you to: * **CRUD** (create/read/update/delete) your webhooks * Manage webhook **headers** * Update webhook **secrets** Org Management API is available for [the Scale plan](https://tryvital.io/pricing). ### Team Management Keys (May 2025) [Junction Management API](/api-details/junction-management-api) now supports Management Keys (previously *Org Keys*) that are scoped to one or more Teams. As a recap, there are now two types of Management Keys: | Type | Remarks | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | Org Management Key | Full control of the organization β€” notably can create or delete Teams. | | Team Management Key | Scoped control over one or more Teams. But it cannot access Org-level resources or actions, e.g., creating or deleting Teams. | All Management Keys β€” Org or Team β€” are accepted by the `X-Management-Key` header, as well as the deprecated `X-Vital-Org-Key` header. Selected customers can now manage Management Keys in the [Junction Dashboard](https://app.junction.com/) through: | Type | Remarks | | ------------------- | -------------------------------------------------------------------------- | | Org Management Key | The **Org Config** page; accessible via the top-left corner Dropdown Menu. | | Team Management Key | The **Team Config** page. | Check out: * the [Create Management Key](/api-reference/org-management/management-keys/create-management-key) endpoint documentation; * the [List Management Keys](/api-reference/org-management/management-keys/list-management-keys) endpoint documentation; and * the [Delete Management Key](/api-reference/org-management/management-keys/delete-management-key) endpoint documentation. If you intend to create a Team Management Key that binds to 2 or more Teams, you must use the [Create Management Key](/api-reference/org-management/management-keys/create-management-key) API endpoint. Junction Dashboard does not support creating a key for more than one Team. Note that Management Keys cannot be used as Team API Keys to access the Junction API. However, you can [manage Team API Keys](/api-reference/org-management/team-api-keys/create-team-api-key) through a Management Key. [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). ### `X-Management-Key` header for Junction Management API (April 2025) [Junction Management API](/api-details/junction-management-api) now accepts Management Keys (previously *Org Keys*) in the `X-Management-Key` header, in addition to the `X-Vital-Org-Key` header. We would continue to support the `X-Vital-Org-Key` header. Though we recommend moving over to `X-Management-Key` to avoid confusion, especially if you do plan to adopt Team Management Keys. [Junction Management API](/api-details/junction-management-api) is available for [the Scale plan](https://tryvital.io/pricing). ### Enhanced Historical Data Pull Status Tracking (May 2025) We've added a new "Retrying" state to the Historical Pull Status page and the historical introspection endpoint, helping you distinguish temporary issues from permanent failures. Additionally, failed historical pulls now include extra error information, making troubleshooting easier and more efficient. This applies to connections established after 9th May. ### Prepare Team Custom Credentials endpoint (Aug 2024) The new [Prepare Team Custom Credentials](/api-reference/org-management/team-custom-credentials/prepare-team-custom-credentials) endpoint provides instructions for preparation of [Bring Your Own OAuth](/wearables/connecting-providers/bring_your_own_oauth) custom credentials. You can use the information to configure things like OAuth callback URI and the Webhook URI (if applicable), before activating it on your Junction Team through the [Set Team Custom Credential](/api-reference/org-management/team-custom-credentials/upsert-team-custom-credentials) endpoint. Org Management API is available for [the Scale plan](https://tryvital.io/pricing). ### Azure Event Hub: Flexible routing (Aug 2024) You can now configure your [Azure Event Hub destination in ETL Pipelines](/webhooks/etl-pipelines#azure-event-hub) to route Junction data events to different Event Hubs based on their event type prefix. Check out the [ETL Pipelines - Azure Event Hub](/webhooks/etl-pipelines#multiple-event-hubs) documentation. ETL Pipelines are available for [the Scale plan](https://tryvital.io/pricing). ### Azure Event Hubs as ETL Pipeline destination (Jun 2024) You can now receive [events](/webhooks/introduction) from Junction directly with your Azure Event Hubs. Check out the [ETL Pipelines](/webhooks/etl-pipelines#azure-event-hub) documentation. ETL Pipelines are available for [the Scale plan](https://tryvital.io/pricing). ### Manage Team Brand Information (Apr 2024) You can now manage Brand Information of your Junction Teams through the Org Management API. Team Brand Information is used in: 1. the Junction-hosted [Link widget](/wearables/connecting-providers/introduction); 2. all user communications [Junction Lab Testing](/lab/overview/introduction) sent on your behalf; and 3. the Junction-hosted Appointment Booking page for [Junction Lab Testing](/lab/overview/introduction). Org Management API is available for [the Scale plan](https://tryvital.io/pricing). Check out the [Update Team](/api-reference/org-management/team/update-team) and [Create Team](/api-reference/org-management/team/create-team) endpoint documentation. ### Junction Orgs and Org Management API (Apr 2024) We have introduced Junction Org, a new level that groups all your Junction Teams. Your Junction Teams have been transparently grouped and migrated to the new structure. We introduced this to provide a unified billing and administrative experience for customers having these use cases: 1. multi-region presence; or 2. user organization with diverging team-level configurations. We introduced the Org Management API because customers have asked for the programmatic access to dyanmically create Junction Teams and manage different aspects of their Junction Teams. Org Management API is available for [the Scale plan](https://tryvital.io/pricing). Check out the [Org Management API](/api-reference/org-management) documentation. ### New webhook event top-level fields (Mar 2024) All events now include Team ID, User ID and Client User ID as top-level fields. We introduced this because this helps reduce a Junction User ID -> Client User ID database lookup on your end. Check out the [Webhook Event Structure](/webhooks/event-structure) documentation. #### Before ```json Basic event structure theme={null} { "data": { # ... event specific data }, "event_type": "daily.data.glucose.created", } ``` #### After ```json Basic event structure theme={null} { "data": { # ... event specific data }, "event_type": "daily.data.glucose.created", "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2", "client_user_id": "01HW3FSNVCHC3B2QB5N0ZAAAVG", "team_id": "6b74423d-0504-4470-9afb-477252ccf67a" } ``` ### Improved error response for User creation conflicts (Mar 2024) The [Create User](/api-reference/user/create-user) endpoint has improved handling of conflicts in Client User ID. We introduced this because the user creation endpoint being idempotent can help simplify your application logic. When the supplied `client_user_id` conflicts with an existing user, the 400 Bad Request response now includes the Junction User ID (`user_id`) and the creation date (`created_on`) of the conflicting user. Check out the [Create User](/api-reference/user/create-user) endpoint documentation. ### User Undo Deletion (Feb 2024) You can now undo user deletion that is still in the 7-day grace period. Check out the [User Undo Deletion](/api-reference/user/undo-delete-user) endpoint documentation. # Dashboard Source: https://docs.junction.com/changelog/core/dashboard ### Lab Account Selection in Order Flow (Feb 2026) The order creation flow now includes a lab account selection step. When your team has multiple lab accounts, a new screen lets you choose which account to use before selecting panels. Each account card shows the lab name, provider account ID, geographic coverage, and supported billing types. ## Order Flow Changes * **Lab account selection screen** – Appears after patient selection when your team has 2+ customer lab accounts. If only one account exists, it is auto-selected. * **Filtered panels and markers** – After selecting a lab account, only panels and markers available under that lab are shown. * **Review screen** – The selected lab account is displayed on the order review screen with a dropdown to change it. Switching to a different lab resets the order set and AoE answers; switching within the same lab preserves them. Lab account selection is available in production environments when the feature is enabled for your team. ### Managing Org and Team Management Keys (June 2025) You can now provision and manage Management Keys through the Junction Dashboard: * Org Management Keys, through the new Org Config page. * Team Management Keys, through the Team Config page of a specific team. You can manage Team Management Keys also through the Management API β€” check out the [Create Management Keys](/api-reference/org-management/management-keys/create-management-key) and [List Management Keys](/api-reference/org-management/management-keys/list-management-keys) endpoint documentation. You must be an Org Admin or using an Org Management Key to manage Team Management Keys. Only Org Admins can managed Org Management Keys, and it must be done through the Junction Dashboard. ### Managing Brand Information (June 2025) We have updated the **Branding and Communcations** tab (previously *White-Labelling*) in the Team Config page. You can now review and update your Team's brand information through this page. Team Brand Information is used in: 1. All patient communication [Junction Lab Testing](/lab/overview/introduction) sent on your behalf; 2. the Junction-hosted Appointment Booking page for [Junction Lab Testing](/lab/overview/introduction); and 3. the Junction-hosted [Link Widget](/wearables/connecting-providers/introduction); You can also manage this information programmatically through the Management API β€” check out the [Update Team](/api-reference/org-management/team/update-team) and [Create Team](/api-reference/org-management/team/create-team) endpoint documentation. ### Introducing Team Admin Roles (May 2025) Introducing **Team Admin** roles for the Junction Dashboard users. Choose between organization-wide and team-specific admin privileges when adding members. ## What’s New * **Two Permission Levels** – When inviting new members to your organization, you can now choose between **Org Admin** and **Team Admin** roles. * **Team-Specific Access** – **Team Admins** can be granted access to specific teams by providing a list of team IDs during invitation or role modification. ## Role Capabilities ### Org Admin * Full access to all organization resources and teams * Can view and edit roles of all organization members * Complete access to billing information and organization settings ### Team Admin * Access limited to specifically assigned teams * Cannot view or edit roles of other organization members * No access to teams outside their allowed list * Cannot view or modify billing information or other organization-level settings ### Improved Order Search experience (Aug 2024) We have improved the search and filtering capabilities in the Orders page, helping you quickly locate specific orders and information. #### Basic filters Order searching now supports these filtering criteria: * Patient or Recipient (in the case of at-home kits) β€” First or Last name * Patient Email * Patient Date of Birth * in the YYYY-MM-DD format. * e.g., β€œ1988” will filter for all patients born in 1988. * e.g., β€œ1988-03” will filter for all patients born in March of 1988 and β€œ1988-03-07” will filter for all patients born on March 7th, 1988. * User ID * Client User ID * Order ID * Order Status Only one Basic filter can be active at any given time. This is because Basic filters are mutually exclusive with each other. ### Date filters On top of the basic filters, you can now filter by date range based on either: * the Order Creation date * For scheduled tests, the order creation date is considered instead of the scheduled date. * the Order Last Updated date * This allows you to filter orders based on the date at which the order status was last updated. Dates are currently filtered with respect to the UTC timezone. ### Status filters Filter based on the most recent status of the order. Note that some statuses are unique to a collection modality such as Mobile Phlebotomy or Self-Collected Test Kits. You can apply multiple Status filters at the same time. These stack as a logical OR among themselves. ### Order Type filters Filter based on the order type: At-Home Phlebotomy, Walk-In Test, At-Home Test Kit. You can apply multiple Order Type filters at the same time. These stack as a logical OR among themselves. ### Activation Type filters Filter for: 1. Scheduled Orders that would not begin execution until the specified target date; or 2. Current Orders that have been executed and are being fulfilled. ### Improved User Search and Introspection experience (Jun 2024) The Users page in the Junction Dashboard is now a search-first experience with more filtering and sorting options available. You can now also click on specific users to inspect device connection status and the patient details. Find your user by typing their Client User ID or Junction User ID into the search bar. For lab testing users: Name, Email and Phone Number are also supported. Click on a user to reveal the new User Details page: #### The General tab Shows an overview of all patient details and user settings associated with the Junction User ID. #### The Connections tab Provides device connection availability and data ingestion insights of the Junction User. You can also obtain this same information programmatically through the [Introspection API](/api-reference/introspection/user-resources). # API Source: https://docs.junction.com/changelog/lab-testing/api ### Lab Report Parsing API (Feb 2026) Introducing the Lab Report Parsing API – extract structured biomarker data from lab report files and match results to standardized LOINC codes. ## Overview The Lab Report Parsing API enables you to digitize lab results from any source. Upload a PDF, JPEG, or PNG lab report, and Junction extracts structured data with LOINC standardization. ## New Endpoints * **POST /lab\_report/v1/parser/job** – Upload a lab report to create a parsing job * **GET /lab\_report/v1/parser/job/** – Retrieve job status and extracted results ## Key Features * **Multi-format support** – Parse PDF, JPEG, and PNG lab reports up to 10 MB * **LOINC matching** – Extracted biomarkers are matched to standardized LOINC codes for cross-lab comparability * **Human review option** – Flag jobs for manual verification before completion * **Structured output** – Get biomarker name, value, unit, reference range, and interpretation ## Webhook Events * **lab\_report.parsing\_job.created** – Triggered when a new parsing job is submitted * **lab\_report.parsing\_job.updated** – Triggered when a parsing job status changes (completed, failed, pending review) ## Use Cases * **Patient uploads** – Allow patients to submit lab reports from other providers * **Historical data import** – Digitize paper records and legacy PDFs * **Multi-source aggregation** – Combine results from various labs using LOINC standardization This feature is currently in beta. Contact your account manager to enable it. Check out the [Lab Report Parsing documentation](/lab/report-parsing/overview) to get started. ### Lab Account Selection in Ordering Flow (Feb 2026) You can now scope orders to a specific lab account during creation. A new team-level lab accounts endpoint lets you list available accounts, and a new `lab_account_id` parameter on the order creation endpoint lets you specify which account to use. ## New Endpoint * **GET /v3/lab\_test/lab\_account** – List lab accounts accessible to your team. Supports optional `lab_account_id` and `status` query parameters. ## Updated Endpoints * **POST /v3/order/create** – New optional `lab_account_id` parameter to specify which lab account to use. If omitted, the backend resolves the appropriate account automatically. * **POST /v3/order/import** – Now accepts lab accounts in `PENDING` status, enabling order imports while accounts are still being set up. * **GET /v3/lab\_tests/markers** – New optional `lab_slug` query parameter to filter markers by lab. When a lab account is selected, you can use this to retrieve only the markers available under that lab. ## Behavior * When `lab_account_id` is provided, available panels and markers are filtered to those supported by the selected account's lab. * When recreating an order with `update_reason=INCORRECT_LAB`, the backend now resolves a fresh lab account rather than carrying forward the incorrect one. ### Idempotency of Ordering (Sep 2025) We’ve added idempotency behavior on the order endpoint, to prevent duplicate orders Check out the [documentation](/lab/overview/idempotency). ### Enhanced Lab Order Management with Result Interpretation Filtering (June 2025) We've enhanced the Orders page with new filtering and visualization capabilities for lab test result interpretations, making it easier to identify and prioritize critical patient results. ## What's New * **Interpretation Filter** – Filter orders by clinical result interpretation using the new "Interpretation" dropdown filter * **Critical Value Indicators** – The orders table now displays visual indicators for orders with critical results * **Enhanced Order Details** – Order detail views now include interpretation badges highlighting critical findings ## Filter Options The new Interpretation filter allows you to quickly locate orders based on their clinical assessment: * **Normal** – Orders with results within normal reference ranges * **Abnormal** – Orders with results outside normal ranges but not critically urgent * **Critical** – Orders with results requiring immediate clinical attention ## Visual Enhancements ### Orders Table * Critical value indicators help you quickly spot high-priority results that need immediate attention * Clear visual distinction between normal, abnormal, and critical interpretations ### Order Details View * Prominent badges display the interpretation status for easy identification * Critical results are highlighted with distinctive styling to ensure they don't go unnoticed You can combine the Interpretation filter with existing filters like Order Status, Date Range, and Order Type for more precise result filtering. This enhancement improves clinical workflow efficiency by enabling healthcare teams to quickly identify and prioritize orders requiring immediate attention. ### Introducing Panel Renaming & Archiving (May 2025) We’ve added more control to how you manage test panels. Now you can skip the support email and work directly in the Test Catalog to: * Rename panel names * Archive outdated or unused panels to keep things clean * View archived panels using the "Status" filter and "unarchive" to make panels active again Existing orders tied to archived panels will remain active and processable. [Full product guide β†’](https://support.tryvital.com/articles/1278554980-managing-lab-panels) ### Problem in Transit Order Statuses (Mar 2025) Testkit orders now support two new statuses representing problems in transit. Refer to the [lab test lifecycle documentation](/lab/workflow/lab-test-lifecycle). We’re introducing two new statuses for the `OrderLowLevelStatus` enum (`problem_in_transit_lab` and `problem_in_transit_customer`) and two for the `OrderStatus` enum (`collecting_sample.testkit.problem_in_transit_customer` and `collecting_sample.testkit.problem_in_transit_lab`)β€”and we may make further changes in the future. To ensure future compatibility, we ask that you avoid exhaustive matching on enum values. Code that assumes all current values are exhaustive could break or fail to compile with SDK upgrades. Here’s how to verify and ensure you benefit from future enhancements: 1. Look for areas in your code where you take an action based on the values of a Junction-defined enum. For example, you might have Python code like this: ```python Python theme={null} match status: case OrderStatus.TESTKIT_ORDERED: handle_ordered() case OrderStatus.TESTKIT_AWAITING_REGISTRATION: handle_awaiting_registration() # other cases... ``` 2. Check unknown values in your code paths are handled gracefullyβ€”for example, by using default cases. Logging unknown values can help you stay informed. ```python Python theme={null} match status: # previous cases... case unknown_status: logger.warning(f"Unknown status received: {repr(unknown_status)}") ``` 3. Make sure you’re running the latest version of our SDK. 4. You’re good to go. Once you’ve checked the code paths won’t break if Junction-defined enums start including new values, no further action is needed. ### Source Marker Identification for Results (Nov 2024) Results now feature the source, orderable marker from which they originated. Refer to the [documentation](/lab/results/result-formats) ### PSC Appointment Scheduling API (Oct 2024) Walk-in Phlebotomy orders now allow for appointment booking directly with Junction. Refer to the [documentation](/lab/walk-in/order-lifecycle) ### Phlebotomy Availability API - Start Date Query (Sep 2024) You can now supply a `start_date` to the [Appointment Availability API](/api-reference/lab-testing/at-home-phlebotomy/appointment-availability). The API always responds with 14 days worth of slots. ### Γ€ La Carte Ordering (Sep 2024) Junction now supports ordering Γ -la-carte, as well as a revamped ordering flow. Check out the [Ordering](/lab/workflow/ordering) documentation. ### Partial Results Webhook (Jul 2024) Junction now supports sending webhooks for partial results, on a team-by-team configuration. Check out the [Partial Results Notifications](/lab/workflow/partials) documentation. ### Patient Service Center (PSC) Availability API (Jul 2024) It is now possible to verify lab PSC availability in regards to a zip code, radius or order. Check out the [Patient Service Center](/lab/walk-in/locations) documentation. ### Create Lab Tests With Provider IDs (Jun 2024) It is now possible to create lab tests using the Laboratory's unique provider id. This allows payloads to be shared across sandbox and production. Check out the [Create a Lab Test](/lab/workflow/create-test) documentation. ### Ask on Order Entry (AOE) (Jun 2024) You can now order panels with AOE requirements via the API. Check out the [AOE](/lab/workflow/aoe) documentation. # Deprecations Source: https://docs.junction.com/changelog/lab-testing/deprecations ### `lab_test_id` in the [POST /v3/order](/api-reference/lab-testing/create-order) endpoint. `lab_test_id` is deprecated in favor of the `order_set` field. More information [here](/lab/workflow/ordering). In the `order` payload, remove the usage of `lab_test_id` ```json theme={null} { "lab_test_id": "some_id" # Remove this } ``` In the `order` payload, add the `order_set` object. ```json theme={null} { "order_set": { "lab_test_ids": ["some_id"] } } ``` ### `value` field in the `results` object We are removing the `value` field from the [results object](/api-reference/results/get-results), as it does not accuratly capture all possible result values. In turn, we've introduced the `result` field. Parse the `result` field in accordance to the `result_type` field. [More information here.](/lab/results/result-formats#resulttype) ### `requisition_form_url` in the `order` object This field is a signed GCP bucket URL, which is only active for 7 days. It will be removed in favor of the [download PDF](/api-reference/lab-testing/requisition-pdf) endpoint. ### `GET /v3/lab_tests` in favor of `GET /v3/lab_test` The endpoint `GET /v3/lab_tests` is deprecated in favor of `GET /v3/lab_test`. The new endpoint will return a paginated list of lab tests and supports two new parameters: `lab_test_limit` and `next_cursor`. # API Source: https://docs.junction.com/changelog/wearables/api ### Junction Sense WHERE clause (September 2025) Junction Sense now accepts a `where` clause so you can filter input rows before aggregation. * Compose SQL-style predicates with `>`, `>=`, `<`, `<=`, `=`, `!=`, `NOT`, `AND`, `OR`, and parenthesis grouping. * Filters always run before `group_by` evaluation when both clauses are present. * Check out the [WHERE clause](/sense/query-dsl/where-clause) documentation for more details. ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.group_key("*"), va.Sleep.score().mean(), va.Sleep.col("stage_asleep_second").sum(), ).group_by( va.date_trunc(va.Sleep.index(), 1, "day"), va.Sleep.col("state"), ).where( "type = 'long_sleep'" ) ``` ```python JSON DSL theme={null} { "select": [ { "group_key": "*" }, { "func": "mean", "arg": { "value_macro": "sleep_score" } }, { "func": "sum", "arg": { "sleep": "stage_asleep_second" } } ], "group_by": [ { "date_trunc": { "value": 1, "unit": "day" }, "arg": { "index": "sleep" } }, { "sleep": "state" } ], "where": "type = 'long_sleep'" } ``` ### Junction Sense sleep macros (September 2025) Junction Sense now exposes two new Sleep Macros to help you pinpoint when users fall asleep and wake up within a session. Check out [Sleep Analysis](/sense/query-dsl/sleep-analysis) for more details. * `asleep_at` returns the first instance at which the user is considered to be asleep, i.e., session start + sleep latency. * `awake_at` returns the time at which the user has stopped sleeping, sans any brief awakening. Use these macros in the Select clause of Junction Sense queries to surface consistent bedtime and wake-up timestamps across providers. Syntax: ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.Sleep.asleep_at(), va.Sleep.awake_at() ) ``` ```python JSON DSL theme={null} { "select": [ { "value_macro": "asleep_at" }, { "value_macro": "awake_at" } ] } ``` ### Device metadata and source device tracking (August 2025) We have added device tracking with new API endpoints and enhanced data attribution. Eligible data resources now include `source.device_id` when the originating device can be determined, enabling better tracking and device-specific analytics. Check out the [Device ID attribution documentation](/wearables/providers/data-attributions#device-id) on what data resources are eligible for Device ID attributions in this initial release. The new [Get User Devices](/api-reference/data/device/get-devices) and [Get Device Details](/api-reference/data/device/get-device) endpoints allow you to retrieve detailed device information for connected users. We've also added [Provider Device Created](/event-catalog/provider.device.created) and [Provider Device Updated](/event-catalog/provider.device.updated) webhook events to notify you when device information becomes available or changes. ### Detecting password expiration in password-based connections (July 2025) The [Link Password Provider endpoint](/api-reference/link/link-password-provider), the [Get User Connections endpoint](/api-reference/user/get-users-connected-providers) and the [Provider Connection Error events](/event-catalog/provider.connection.error) would now report password expiration using the `provider_password_expired` error type. This mainly affects [Abbott LibreView patient-based connections](/wearables/guides/abbott-libreview#abbott-libreview-for-patient-based-connections). ### Introducing Universal Group By Support to Junction Sense (June 2025) Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. Junction Sense can now group and aggregate the data by any [Table Column expression](/sense/query-dsl/column-expressions#table-column-expression) and/or any [Source Column expression](/sense/query-dsl/column-expressions#source-column-expression). This gives you more options on how the data should be dissected β€” not just by calendar units, but now also by Data Source fields and categorical values from the data itself. Check out the [Group By clause](/sense/query-dsl/group-by-clause) documentation. If both the *Provider* and *Source Type* Source Columns are present in the [Group By clause](/sense/query-dsl/group-by-clause), the implicit [Data Prioritization](/sense/data-prioritization) behaviour would be disabled. #### Example queries ```python Python DSL theme={null} # Group Summary Data by Week, Provider and Source Type va.select(...).group_by( va.date_trunc(va.Sleep.index(), 1, "week"), va.Source.col("source_provider"), va.Source.col("source_type"), ) # Group Timeseries Data by Day, Provider and Source Type va.select(...).group_by( va.date_trunc(va.Timeseries.index(), 1, "day"), va.Source.col("source_provider"), va.Source.col("source_type"), ) # Group Electrocardiogram Voltage Data by Hour and Lead Type va.select(...).group_by( va.date_trunc(va.Timeseries.index(), 1, "hour"), va.Timeseries.col("electrocardiogram_voltage").field("type"), ) # Group Sleep Breathing Disturbance by Hour and Elevated vs. Not Elevated va.select(...).group_by( va.date_trunc(va.Timeseries.index(), 1, "hour"), va.Timeseries.col("sleep_breathing_disturbance").field("type"), ) ``` ```json JSON DSL theme={null} # Group Summary Data by Week, Provider and Source Type { "group_by": [ { "date_trunc": { "value": 1, "unit": "week" }, "arg": { "index": "sleep" } }, { "source": "source_provider" }, { "source": "source_type" } ] } # Group Timeseries Data by Day, Provider and Source Type { "group_by": [ { "date_trunc": { "value": 1, "unit": "day" }, "arg": { "index": "timeseries" } }, { "source": "source_provider" }, { "source": "source_type" } ] } # Group Electrocardiogram Voltage Data by Hour and Lead Type { "group_by": [ { "date_trunc": { "value": 1, "unit": "hour" }, "arg": { "index": "timeseries" } }, { "timeseries": "electrocardiogram_voltage", "field": "type" }, ] } # Group Sleep Breathing Disturbance by Hour and Elevated vs. Not Elevated { "group_by": [ { "date_trunc": { "value": 1, "unit": "hour" }, "arg": { "index": "timeseries" } }, { "timeseries": "sleep_breathing_disturbance", "field": "type" }, ] } ``` ### Introducing Timeseries Data Support to Continuous Query (June 2025) Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. We have expanded Continuous Query to support **timeseries resources** in addition to the previously available summary resources. With a few simple configuration steps, Continuous Query enables you to: 1. Extract aggregations of high‑volume timeseries data collected across all your active user device connections; and 2. Offload this concern to Junction, allowing you to simplify your data pipeline and focus on extracting actionable signals. You can now select timeseries resources in a Query: * For each resource, you can select all of specific fields of interest. * You can select multiple timeseries resources simultaneously, even though they are of different types (discrete, interval of blood pressure). Check out the [Table Column expression](/sense/query-dsl/column-expressions) documentation. #### Examples of Timeseries Table Column expressions ```python Python DSL theme={null} select( # Blood Pressure Timeseries.col("blood_pressure").field("systolic"), Timeseries.col("blood_pressure").field("diastolic"), Timeseries.col("blood_pressure").field("timezone_offset"), # Discrete Samples Timeseries.col("heartrate").field("value"), Timeseries.col("heartrate").field("timezone_offset"), # Interval Samples Timeseries.col("body_temperature_delta").field("value"), Timeseries.col("body_temperature_delta").field("duration"), Timeseries.col("body_temperature_delta").field("sensor_location"), Timeseries.col("body_temperature_delta").field("timezone_offset"), # Workout Interval Samples Timeseries.col("workout_distance").field("value"), Timeseries.col("workout_distance").field("duration"), Timeseries.col("workout_distance").field("sport"), Timeseries.col("workout_distance").field("workout_id"), ) ``` ```json JSON DSL: theme={null} [ # Blood Pressure { "timeseries": "blood_pressure", "field": "systolic" }, { "timeseries": "blood_pressure", "field": "diastolic" }, { "timeseries": "blood_pressure", "field": "timezone_offset" }, # Discrete Samples { "timeseries": "heartrate", "field": "value" }, { "timeseries": "heartrate", "field": "timezone_offset" }, { "timeseries": "heartrate", "field": "type" }, # Interval Samples { "timeseries": "body_temperature_delta", "field": "value" }, { "timeseries": "body_temperature_delta", "field": "duration" }, { "timeseries": "body_temperature_delta", "field": "sensor_location" }, { "timeseries": "body_temperature_delta", "field": "timezone_offset" }, # Workout Interval Samples { "timeseries": "workout_distance", "field": "value" }, { "timeseries": "workout_distance", "field": "duration" }, { "timeseries": "workout_distance", "field": "sport" }, { "timeseries": "workout_distance", "field": "workout_id" }, ] ``` ### External User ID of User Connections (April 2025) We now expose the *External User ID* of User Connections in the [Provider Connection Created](/event-catalog/provider.connection.created) event and [Get User Connections](/api-reference/user/get-users-connected-providers) endpoint. The External User ID is the unique user identifier supplied by the provider, e.g., the Fitbit User ID for Fitbit connections. | Connection type | Content | | ----------------------------- | ------------------------------------------------- | | OAuth Providers | User unique identifier; provider-specific formats | | Password Providers | Username | | Email Providers | Email | | Junction Mobile SDK Providers | null (not available at this time) | Check out the [Provider Connection Created](/event-catalog/provider.connection.created) event and [Get User Connections](/api-reference/user/get-users-connected-providers) endpoint documentation. ### Link API Improvements (Jan 2025) We have increased Link Token expiry from 15 minutes to 60 minutes. We have also added an `on_error=redirect` Link Token option which forces any error scenario to redirect to your specified `redirect_url` URL, disabling any in-built error handling behaviour in the Link Widget. Check out the [Link Error](/wearables/connecting-providers/errors) format and the [Generate a Link Token](/api-reference/link/generate-link-token) endpoint documentation. ### Extendable Historical Date Ranges (Jul 2024) We now offer the ability to set custom historical data ranges for wearables providers, with resource-level granularity. You can now extend the historical pull range for a number of supported providers using the [Org Management API](/api-reference/org-management/team-data-pull-preferences/upsert-team-data-pull-preferences) Org Management API is available for [the Scale plan](https://tryvital.io/pricing). Check out the [Set Data Pull Preferences](/api-reference/org-management/team-data-pull-preferences/upsert-team-data-pull-preferences), [Get Data Pull Preferences](/api-reference/org-management/team-data-pull-preferences/get-team-scope-requirements), [Delete Data Pull Preferences](/api-reference/org-management/team-data-pull-preferences/delete-team-scope-requirements) endpoint documentation for more details. ### Introspection: Historical Pull Timeline (May 2024) The Historical Pull Introspection endpoint now reports the execution timeline of historical pulls. The timeline tracks when the historical pull was scheduled, started, and eventually ended (success or failure). This improves the visibility around the latency of historical pulls, as well as providing more indicator of an incomplete execution. Check out the [Historical Pull Introspection](/api-reference/introspection/historical-pulls) endpoint documentation. ### Team Scope Requirements (Apr 2024) You can now tailor what scopes Junction would request from your Users using the [Org Management API](/api-reference/org-management/team-scope-requirements/upsert-team-scope-requirements). This applies when your user connects to an [OAuth provider](/wearables/connecting-providers/auth_types) which supports scopes. Junction ensures that any new provider connection can be established only when the user has granted all the scopes specified in the `user_must_grant` scope list. The scopes specified as `user_may_grant` would be requested alongside the `user_must_grant` scopes. However, unlike `user_must_grant`, they do not prevent the connection from being established. Org Management API is available for [the Scale plan](https://tryvital.io/pricing). Check out the [Set Team Scope Requirements](/api-reference/org-management/team-scope-requirements/upsert-team-scope-requirements) and [Get Team Scope Requirements](/api-reference/org-management/team-scope-requirements/get-team-scope-requirements) endpoint documentation on how to enable the setting. ### Reject Duplicate Connections (Apr 2024) You can now configure your Junction Team through the [Org Management API](/api-reference/org-management/team/update-team) to reject duplicate wearable connections. When the `reject_duplicate_connection` setting is enabled on the Team, Junction checks whether or not the provider-reported user ID is already connected to an existing User in your Team. If it does, the Link API would report the [`duplicate_connection` error](/wearables/connecting-providers/errors). Org Management API is available for [the Scale plan](https://tryvital.io/pricing). Check out the [Update Team](/api-reference/org-management/team/update-team) and [Create Team](/api-reference/org-management/team/create-team) endpoint documentation on how to enable the setting. Check out the [Link Errors](/wearables/connecting-providers/errors) documentation on how to catch the `duplicate_connection` error. ### Link Error reporting (Apr 2024) The Link API now reports errors in terms of a predefined set of [Error Types](/wearables/connecting-providers/errors) on which your application logic can depend. We introduce this because there has not been a dependable way for your application logic to understand why a connection attempt has failed, and in turn this prevents your application from providing actionable messages to your end users. Depending on how you initiate the Link flow, the Link Error would be reported either as a URL query parameter, or as part of the JSON response. Check out the [Link Errors](/wearables/connecting-providers/errors) documentation for the detailed guidance. ### Understanding Resource Availability (Apr 2024) When a user connection to a provider is established, the webhook event now includes a resource availability report of the connection. We introduce this because this helps you understand what resources would and would not be available on a new connection. We also provide insights into how partial consents from users during the OAuth authentication flow can influence the resource availability, so that you can take actions accordingly. This resource availability report is based on the permissions *(also known as API access scopes)* the user has granted during the authentication process. In some cases, a provider resource may be available, but some information could be absent due to some optional scopes having been denied by the user. The availability report includes a full breakdown of granted and denied scopes by their optionality. If the provider has no concept of API access scopes, we report all resources as available. You can also query this information at any time through the [Get User Connections](/api-reference/user/get-users-connected-providers) endpoint. Check out the [Provider Connection Created](/event-catalog/provider.connection.created) (`provider.connection.created`) event schema and the [Get User Connections](/api-reference/user/get-users-connected-providers) endpoint documentation. ### Fallback Birth Date for Heart Rate Zones (Feb 2024) You can now set a Fallback Birth Date on a user. Junction can use this to compute a more accurate workout Heart Rate Zones, when the provider exposes neither heart rate zones nor user age to Junction. Check out the [Heart Rate Zones](/wearables/providers/heart-rate-zones#fallback-birth-date) documentation. ### Grouped Timeseries (Feb 2024) You can now get grouped timeseries data. This initial release groups data by [Source Type](/wearables/providers/data-attributions#source-type) from [supported providers](/wearables/providers/data-attributions#supported-providers). Check out the [Blood Oxygen](/api-reference/timeseries-grouped/blood-oxygen) endpoint documentation for an example. ### Historical Pull Introspection (Dec 2023) You can now introspect the status of all one-off user historical data pulls. It also provides the pulled date-time range, as well as a rough estimate of the amount of data ingested (in terms of "days with data"). Check out the [Historical Pull Introspection](/api-reference/introspection/historical-pulls) endpoint documentation. ### User Resources Introspection (Dec 2023) You can now introspect user data ingestion statistics. For example, the endpoint provides: 1. Oldest and newest data timestamp 2. The number of objects sent in `*.created` events 3. The status and time of the last ingestion attempt (polling or push) Check out the [User Resources Introspection](/api-reference/introspection/user-resources) endpoint documentation. ### Junction Sign-In Token for Mobile SDKs (Nov 2023) Junction Sign-In Token is a new, user-scoped Authentication scheme for Junction Mobile SDKs. It grants only user-scoped access to your mobile app sign-ins. This allows you to keep your Junction Team API Keys as server-side secrets. We encourage all customers using Junction Team API Keys in their production mobile apps to migrate to the Junction Sign-In Token scheme. Check out the [SDK Authentication](/wearables/sdks/authentication#vital-sign-in-token) guide on how to migrate to this scheme. Check out the [Create Sign-In Token](/api-reference/user/create-sign-in-token) endpoint documentation on how to generate tokens for your mobile app sign-ins. # Deprecations Source: https://docs.junction.com/changelog/wearables/deprecations ### The `hypnogram` timeseries type The removal will happen on or after **31 January 2025**. We have introduced a new Sleep Cycle summary type (`sleep_cycle`) which captures the detailed hypnogram of a sleep session. It replaces the existing `hypnogram` timeseries type. More details can be found here: [Sleep Cycle Summary](/api-reference/data/sleep-cycle/get-summary) Start processing the new `historical.data.sleep_cycle.created`, `daily.data.sleep_cycle.created` and `daily.data.sleep_cycle.updated` events. Stop processing the `historical.timeseries.hypnogram.created`, `daily.timeseries.hypnogram.created` and `daily.timeseries.hypnogram.updated` events. ### Redefining Source and Provider The stated removals in this section will happen on or after **31 July 2024**. Junction has redefined what *Source* and *Provider* means across our API and event schemas: | Entity | Definition | | -------- | ---------------------------------------------------------------------------------- | | Source | The source context of a specific summary or a specific timeseries value group. | | Provider | A wearable data provider (an app, a platform, a service, or Junction Mobile SDKs). | A *Source* context comprises of: * the Provider (slug only) * the [Source Type](/wearables/providers/data-attributions#source-type) * the [App ID](/wearables/providers/data-attributions#app-id) (*optional*) We have also plans for the *Source* context to include source device metadata where available. A *Provider* object is a short description of a wearable data provider. It comprises of the familiar `name`, `logo` and `slug` trio you have been receiving in many data events. To migrate to the new definitions, Junction is announcing the following deprecations: Inside the Source context located at `$.data.source`, the `name`, `logo`, and `slug` fields are now deprecated. Junction no longer embeds these *Provider* object fields in every data event, except for the provider slug. Using the [Steps data event](/event-catalog/daily.data.steps.created) as an illustration, originally the `source` field describes the wearable data provider: ```json daily.data.steps.created (Original) theme={null} { "event_type": "daily.data.steps.created", "data": { "data": [...], "source": { "name": "Oura", "slug": "oura", "logo": "https://example.com/oura.svg" } }, "user_id": "fb58770e-8b7b-4416-a5bd-8433786d10dc" } ``` As part of this redefinition, the `source` field is redefined to mean the *Source* context of this steps timeseries value group. So Junction has introduced a few new *Source* context fields: ```json daily.data.steps.created (Current) theme={null} { "event_type": "daily.data.steps.created", "data": { "data": [...], "source": { "provider": "oura", "type": "ring", "app_id": null, /** BEGIN: deprecated Provider fields **/ "name": "Oura", "slug": "oura", "logo": "https://example.com/oura.svg" /** END: deprecated Provider fields **/ } }, "user_id": "fb58770e-8b7b-4416-a5bd-8433786d10dc" } ``` Once the deprecation cycle ends, Junction will remove the deprecated fields: ```json daily.data.steps.created (Future) theme={null} { "event_type": "daily.data.steps.created", "data": { "data": [...], "source": { "provider": "oura", "type": "ring", "app_id": null } }, "user_id": "fb58770e-8b7b-4416-a5bd-8433786d10dc" } ``` Stop parsing `name`, `logo` and `slug` when processing the `$.data.source` field in **all Data Events** (daily.data.\*). If you need the Provider information, you can obtain it through the [Get Providers](/api-reference/providers) endpoint. This applies to: * Get Activity: `GET /v2/summary/activity/*` * Get Body: `GET /v2/summary/body/*` * Get Meal: `GET /v2/summary/meal/*` * Get Profile: `GET /v2/summary/profile/*` * Get Sleep: `GET /v2/summary/sleep/*` * Get Workouts: `GET /v2/summary/workouts/*` Similar to changes to the Data Events, the `name`, `logo`, and `slug` sub-fields under the `source` field in each and every summary are now deprecated. Junction no longer embeds these *Provider* object fields in every summary, except for the provider slug. Using the [Get Profile endpoint](/api-reference/profile/get-summary) as an illustration, originally the `source` field describes the wearable data provider: ```json Profile (Original) theme={null} { "id": "eadba0c7-4e81-4d17-962c-9ebe0629c08f", "date": "2022-08-04", "height": 183, "source": { "name": "Oura", "slug": "oura", "logo": "https://logo_url.com" }, "user_id": "71937dd3-aebe-46b7-ab64-c287bd75b2a6" } ``` As part of this redefinition, the `source` field is redefined to mean the *Source* context of this Profile summary. So Junction has introduced a few new *Source* context fields: ```json Profile (Current) theme={null} { "id": "eadba0c7-4e81-4d17-962c-9ebe0629c08f", "date": "2022-08-04", "height": 183, "source": { "provider": "oura", "type": "app", "app_id": null, /** BEGIN: deprecated Provider fields **/ "name": "Oura", "slug": "oura", "logo": "https://example.com/oura.svg" /** END: deprecated Provider fields **/ }, "user_id": "71937dd3-aebe-46b7-ab64-c287bd75b2a6" } ``` Once the deprecation cycle ends, Junction will remove the deprecated fields: ```json Profile (Future) theme={null} { "id": "eadba0c7-4e81-4d17-962c-9ebe0629c08f", "date": "2022-08-04", "height": 183, "source": { "provider": "oura", "type": "app", "app_id": null }, "user_id": "71937dd3-aebe-46b7-ab64-c287bd75b2a6" } ``` Stop parsing `name`, `logo` and `slug` fields in **all Get Summary endpoint** responses. If you need the Provider information, you can obtain it through the [Get Providers](/api-reference/providers) endpoint. This applies to: * Provider Connection Created: `provider.connection.created`. Junction has renamed the `source` field in this event to `provider`, aligning with the redefinition of *Source* and *Provider*. To illustrate the change, originally the `source` field describes the wearable data provider: ```json provider.connection.created (Original) theme={null} { "event_type": "provider.connection.created", "data": { "source": { "name": "Oura", "slug": "oura", "logo": "https://logo_url.com" }, "user_id": "71937dd3-aebe-46b7-ab64-c287bd75b2a6", "resource_availability": {...} } } ``` As part of this redefinition, `source` is no longer applicable here. Junction has introduced an identical `provider` field as its replacement. ```json provider.connection.created (Current) theme={null} { "event_type": "provider.connection.created", "data": { "provider": { "name": "Oura", "slug": "oura", "logo": "https://logo_url.com" }, "user_id": "71937dd3-aebe-46b7-ab64-c287bd75b2a6", "resource_availability": {...}, /** BEGIN: deprecated field **/ "source": { "name": "Oura", "slug": "oura", "logo": "https://logo_url.com" } /** END: deprecated field **/ } } ``` Once the deprecation cycle ends, Junction will remove the deprecated `source` field: ```json provider.connection.created (Future) theme={null} { "event_type": "provider.connection.created", "data": { "provider": { "name": "Oura", "slug": "oura", "logo": "https://logo_url.com" }, "user_id": "71937dd3-aebe-46b7-ab64-c287bd75b2a6", "resource_availability": {...} } } ``` Update your `provider.connection.created` event parsing logic to parse the `provider` field, and stop parsing the `source` field. ### The `is_final` flag in `historical.data.*.created` events The removal will happen on or after **31 June 2024**. We are removing the `is_final` flag from the Historical Pull Completed (`historical.data.*.created`) events. For each resource, Junction now signals one Historical Pull Completed event, only after we have finished fetching all data chunks. This means `is_final` no longer has a meaning, since the event itself is *final*. Stop parsing `is_final` as a required field when processing `historical.data.*.created` events. # Provider and Resources Source: https://docs.junction.com/changelog/wearables/providers ### Health SDK: Explicit Connect mode (Sep 2025) The Health SDK now supports an opt-in **[Explicit Connect mode](https://docs.junction.com/wearables/sdks/health/connection-policies#explicit-connect-mode)**, which enables your application to explicitly control the moment of Health Connect connection creation and disconnection. The opt-in Explicit Connect mode also allows remote disconnection through the [Deregister Connection](https://docs.junction.com/api-reference/user/deregister-a-provider) endpoint, which was not supported in the default [Auto Connect](https://docs.junction.com/wearables/sdks/health/connection-policies#auto-connect-mode-default) mode. If your device connection management UX is built upon an explicit notion of connecting and disconnecting Apple HealthKit and Health Connect connections β€” as if they are like their cloud-based counterparts β€” you might find the [Explicit Connect](/wearables/sdks/health/connection-policies#explicit-connect-mode) mode more appealling. Check out the [Health SDK Connection Policies](/wearables/sdks/health/connection-policies) documentation for further details. This feature is available on iOS SDK 1.8.0+, Android SDK 4.2.0+, React Native SDK 5.4.0+ and Flutter SDK 4.6.0+. ### Health Connect sync progress logs in Junction Dashboard (Sep 2025) Health Connect integrations using Android SDK 4.2.0 or above would now report client-side sync progress regularly to the Junction Dashboard. Similar to the iOS SDK equivalent, this report would include metadata of any exception that had disrupted a sync attempt. We hope that this brings more visibility into Health Connect sync issues. This feature is available on Android SDK 4.2.0+, React Native SDK 5.4.0+ and Flutter SDK 4.6.0+. ### Expanded Apple HealthKit data type coverage (April 2025) Junction iOS SDK 1.6.0 has expanded the HealthKit data type coverage: | Type | Remarks | `VitalResource` | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------- | | Workout Stream timeseries | Workout Distance β€” including cycling, swimming, rowing, cross country skiing, downhill snow sports, paddle sports, skating sports, walking, running and wheelchair.
Workout Swimming Stroke | As part of `Workout` | | Timeseries | Heart Rate Recovery One Minute | `HeartRateRecoveryOneMinute` | Workout Stream timeseries data always have [Workout ID](/wearables/providers/data-attributions#workout-id) and [Sport](/wearables/providers/data-attributions#sport) attributions. For more information, check out: #### Data Events * the [Workout Distance data events](/event-catalog/daily.data.workout_distance.created) in the Event Catalog; * the [Workout Swimming Stroke data events](/event-catalog/daily.data.workout_swimming_stroke.created) in the Event Catalog; * the [Heart Rate Recovery One Minute data events](/event-catalog/daily.data.heart_rate_recovery_one_minute.created) in the Event Catalog; #### Data access API * the [Get Workout Distance](/api-reference/data/timeseries/workout-distance) endpoint documentation; * the [Get Workout Swimming Stroke](/api-reference/data/timeseries/workout-swimming-stroke) endpoint documentation; and * the [Get Heart Rate Recovery One Minute](/api-reference/data/timeseries/heart-rate-recovery-one-minute) endpoint documentation. ### Summary Ingestion Timestamps (April 2025) Activity, Body, Sleep, Sleep Cycle, Workout, Meal and Menstrual Cycle now expose ingestion timestamps: | Field | Remarks | | ------------ | --------------------------------------------------------------------------- | | `created_at` | The time at which Junction first ingested this summary. | | `updated_at` | The time at which Junction ingested the most recent update to this summary. | For more information, check out: #### Data Events * the [Activity data events](/event-catalog/daily.data.activity.created) in the Event Catalog; * the [Body data events](/event-catalog/daily.data.body.created) in the Event Catalog; * the [Sleep data events](/event-catalog/daily.data.sleep.created) in the Event Catalog; * the [Sleep Cycle data events](/event-catalog/daily.data.sleep_cycle.created) in the Event Catalog; * the [Workout data events](/event-catalog/daily.data.workouts.created) in the Event Catalog; * the [Menstrual Cycle data events](/event-catalog/daily.data.menstrual_cycle.created) in the Event Catalog; and * the [Meal data events](/event-catalog/daily.data.meal.created) in the Event Catalog. #### Data access API * the [Get Activity](/api-reference/data/activity/get-summary) endpoint documentation; * the [Get Body](/api-reference/data/body/get-summary) endpoint documentation; * the [Get Sleep](/api-reference/data/sleep/get-summary) endpoint documentation; * the [Get Sleep Cycle](/api-reference/data/sleep-cycle/get-summary) endpoint documentation; * the [Get Workout](/api-reference/data/workouts/get-summary) endpoint documentation; * the [Get Menstrual Cycle](/api-reference/data/menstrual-cycle/get-summary) endpoint documentation; and * the [Get Meal](/api-reference/data/meal/get-summary) endpoint documentation. ### Expanded Apple HealthKit data type coverage (March 2025) Junction iOS SDK 1.5.0 has expanded the HealthKit data type coverage: | Type | Remarks | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Profile summary | Wheelchair Mode usage | | Activity summary | Wheelchair Mode support (distance and push count) | | Body summary | Lean Body Mass, Body Mass Index, Waist Circumference | | Timeseries resources | Lean Body Mass, Body Mass Index, Waist Circumference, Wheelchair Push, Distance (Wheelchair Mode support), Stand Hour, Stand Duration, Sleep Apnea Alert, Sleep Breathing Disturbance, FEV1, Forced Vital Capacity, Peak Expiratory Flow Rate, Inhaler Usage, Fall, UV Exposure, Daylight Exposure, Handwashing, Basal Body Temperature | For more information, check out: #### Data Events * the [Profile data events](/event-catalog/daily.data.profile.created) in the Event Catalog; * the [Activity data events](/event-catalog/daily.data.activity.created) in the Event Catalog; * the [Body data events](/event-catalog/daily.data.body.created) in the Event Catalog; * the [Lean Body Mass data events](/event-catalog/daily.data.lean_body_mass.created) in the Event Catalog; * the [Body Mass Index data events](/event-catalog/daily.data.body_mass_index.created) in the Event Catalog; * the [Waist Circumference data events](/event-catalog/daily.data.waist_circumference.created) in the Event Catalog; * the [Wheelchair Push data events](/event-catalog/daily.data.wheelchair_push.created) in the Event Catalog; * the [Distance data events](/event-catalog/daily.data.distance.created) in the Event Catalog; * the [Stand Hour data events](/event-catalog/daily.data.stand_hour.created) in the Event Catalog; * the [Stand Duration data events](/event-catalog/daily.data.stand_duration.created) in the Event Catalog; * the [Sleep Apnea Alert data events](/event-catalog/daily.data.sleep_apnea_alert.created) in the Event Catalog; * the [Sleep Breathing Disturbance data events](/event-catalog/daily.data.sleep_breathing_disturbance.created) in the Event Catalog; * the [FEV1 data events](/event-catalog/daily.data.forced_expiratory_volume_1.created) in the Event Catalog; * the [Forced Vital Capacity data events](/event-catalog/daily.data.forced_vital_capacity.created) in the Event Catalog; * the [Peak Expiratory Flow Rate data events](/event-catalog/daily.data.peak_expiratory_flow_rate.created) in the Event Catalog; * the [Inhaler Usage data events](/event-catalog/daily.data.inhaler_usage.created) in the Event Catalog; * the [Fall data events](/event-catalog/daily.data.fall.created) in the Event Catalog; * the [UV Exposure data events](/event-catalog/daily.data.uv_exposure.created) in the Event Catalog; * the [Daylight Exposure data events](/event-catalog/daily.data.daylight_exposure.created) in the Event Catalog; * the [Handwashing data events](/event-catalog/daily.data.handwashing.created) in the Event Catalog; and * the [Basal Body Temperature data events](/event-catalog/daily.data.basal_body_temperature.created) in the Event Catalog. #### Data access API * the [Get Profile](/api-reference/data/profile/get-summary) endpoint documentation; * the [Get Activity](/api-reference/data/activity/get-summary) endpoint documentation; * the [Get Body](/api-reference/data/body/get-summary) endpoint documentation; * the [Get Lean Body Mass](/api-reference/data/timeseries/lean-body-mass) endpoint documentation; * the [Get Body Mass Index](/api-reference/data/timeseries/body-mass-index) endpoint documentation; * the [Get Waist Circumference](/api-reference/data/timeseries/waist-circumference) endpoint documentation; * the [Get Wheelchair Push](/api-reference/data/timeseries/wheelchair-push) endpoint documentation; * the [Get Distance](/api-reference/data/timeseries/distance) endpoint documentation; * the [Get Stand Hour](/api-reference/data/timeseries/stand-hour) endpoint documentation; * the [Get Stand Duration](/api-reference/data/timeseries/stand-duration) endpoint documentation; * the [Get Sleep Apnea Alert](/api-reference/data/timeseries/sleep-apnea-alert) endpoint documentation; * the [Get Sleep Breathing Disturbance](/api-reference/data/timeseries/sleep-breathing-disturbance) endpoint documentation; * the [Get FEV1](/api-reference/data/timeseries/forced-expiratory-volume-1) endpoint documentation; * the [Get Forced Vital Capacity](/api-reference/data/timeseries/forced-vital-capacity) endpoint documentation; * the [Get Peak Expiratory Flow Rate](/api-reference/data/timeseries/peak-expiratory-flow-rate) endpoint documentation; * the [Get Inhaler Usage](/api-reference/data/timeseries/inhaler-usage) endpoint documentation; * the [Get Fall](/api-reference/data/timeseries/fall) endpoint documentation; * the [Get UV Exposure](/api-reference/data/timeseries/uv-exposure) endpoint documentation; * the [Get Daylight Exposure](/api-reference/data/timeseries/daylight-exposure) endpoint documentation; * the [Get Handwashing](/api-reference/data/timeseries/handwashing) endpoint documentation; and * the [Get Basal Body Temperature](/api-reference/data/timeseries/basal-body-temperature) endpoint documentation. ### Improved LibreView practice connection process (Jan 2025) You can now connect LibreView patients in to [46 regions in the EU environment](/wearables/guides/abbott-libreview#vital-eu-region) and [8 regions in the US environment](/wearables/guides/abbott-libreview#vital-us-region). Previously, the Link API and Link Widget listed only the regions to which our practice account was registered. However, these practice accounts can often connect to patients in a larger geographical area beyond the region of registration. For example, patients in most EMEA countries can connect through our Netherlands practice account. Therefore, we have now changed: 1. Link Widget to display an exhaustive list of regions which our practice account can reach; and 2. Link API to accept any ISO 3166-1 code that is on the list. In other words, say if you have a patient domiciled in Belgium, it is no longer necessary to teach them to choose "Netherlands" when connecting through the Link Widget. They can now intuitively choose "Belgium" when asked by the Link Widget. Check out the [Abbott LibreView](/wearables/guides/abbott-libreview) guide and the [Connect Email Provider](/api-reference/link/link-email-provider) endpoint for more information. ### Electrocardiogram, Heart Rate Alert and AFib Burden (Dec 2024) Junction now supports collecting Electrocardiogram (ECG) data and a couple of related data points from a set of providers: | Resource | Apple HealthKit \[3] | Fitbit | Withings | Kardia | | ------------------------------------------------------- | ------------------------------- | ----------------- | ----------------- | ----------------- | | Electrocardiogram Summary
`electrocardiogram` | βœ… | - | βœ… | βœ… | | ECG Voltage timeseries
`electrocardiogram_voltage` | βœ… | - | βœ… | βœ… | | Heart Rate Alert
`heart_rate_alert` | βœ… \[1] | βœ… \[2] | βœ… \[2] | βœ… \[2] | | AFib Burden
`afib_burden` | βœ… | - | - | - | \[1] Irregular rhythm alerts, high heart rate alerts and low heart rate alerts.
\[2] Irregular rhythm alerts only.
\[3] Requires Junction iOS SDK 1.3.0+, Junction Flutter SDK 4.4.0+ or Junction React Native SDK 5.1.0+.
For more information, check out: * the [Get Electrocardiogram](/api-reference/data/electrocardiogram/get-summary) endpoint documentation; * the [Get Electrocardiogram Voltage](/api-reference/data/timeseries/electrocardiogram-voltage) endpoint documentation; * the [Get Heart Rate Alert](/api-reference/data/timeseries/heart-rate-alert) endpoint documentation; and * the [Get AFib Burden](/api-reference/data/timeseries/afib-burden) endpoint documentation; * the [Electrocardiogram data events](/event-catalog/daily.data.electrocardiogram.created) in the Event Catalog; * the [Electrocardiogram Voltage data events](/event-catalog/daily.data.electrocardiogram_voltage.created) in the Event Catalog; * the [Heart Rate Alert data events](/event-catalog/daily.data.heart_rate_alert.created) in the Event Catalog; and * the [AFib Burden data events](/event-catalog/daily.data.afib_burden.created) in the Event Catalog. ### Sleep Cycle Summary Type (Nov 2024) We are introducing a new Sleep Cycle summary type (`sleep_cycle`) which captures the detailed hypnogram of a sleep session. The existing hyponogram timeseries type is deprecated in favour of this new summary type. Check out the [Get Sleep Cycle](/api-reference/data/sleep-cycle/get-summary) endpoint and the [Sleep Cycle data events](/event-catalog/daily.data.sleep_cycle.created) in the Event Catalog for more information. ### Body Summary model extension (Nov 2024) The Body Summary model now includes 4 new fields: * `water_percentage` - The percentage of water in the body. * `muscle_mass_percentage` - The percentage of muscle mass in the body. * `visceral_fat_index` - Provider's score of visceral fat levels. * `bone_mass_percentage` - The percentage of bone mass in the body. At the time of writing all four are only available from the Withings provider. Check out the [Get Body Summary](/api-reference/body/get-summary) endpoint and the [Body Created](/event-catalog/daily.data.body.created) event for more information. ### Sleep Type (Nov 2024) New Sleep summaries now have a new *Sleep Type* field. Junction maps the Sleep Type from the source provider whenever possible. If this is unavailable at source, Junction infers the Sleep Type based on the sleep session duration. | Sleep Type | Description | | ------------------ | -------------------------------------------------------- | | `long_sleep` | >=3 hours of sleep | | `short_sleep` | \<3 hours of sleep | | `acknowledged_nap` | User-acknowledged naps, typically under 3 hours of sleep | | `unknown` | The sleep session recording is ongoing. | Check out the [Get Sleep endpoint](/api-reference/data/sleep/get-summary) and the [Sleep data events](/event-catalog/daily.data.sleep.created) in the Event Catalog for more information. ### Apple HealthKit data sync improvements (Aug 2024) An overhauled Apple HealthKit sync engine is now available across all Junction Mobile SDK platforms. The changes bring historical data sync incrementality, prioritization, network resiliency and reduced resource usage. 1. Most resources now use a resumable sync & upload process. * This enables the SDK to make constant forward progress, especially when you have specified a very large historical pull stage (60-365 days) through the [Team Data Pull Preferences](/api-reference/org-management/team-data-pull-preferences/upsert-team-data-pull-preferences) API. 2. The SDK now prioritises syncing of summary types over timeseries data. * During the historical pull stage, all timeseries resources would be suppressed until all the summary types have been successfully uploaded. * After the historical pull stage, the SDK would make forward progress on summary types, before it proceeds to deal with the more CPU-time consuming timeseries data that are more prone to operating system throttling. 3. With an updated iOS App Target configuration, the SDK would now self-register as a `BGProcessingTask` or `BGHealthResearchTask` (iOS 17+) to the iOS BackgroundTasks framework. * This would provide the SDK an additional opportunity to complete any resource sync that is not able to complete during normal HealthKit background delivery. * Please refer to the [Apple HealthKit guide](https://docs.tryvital.io/wearables/guides/apple-healthkit#1-setup-app-entitlements) on how to update your iOS App Target configuration in Xcode. | Platform | Release Note | | ------------ | ------------------------------------------------------------------------------------------------- | | Native iOS | [vital-ios 1.2.2](https://github.com/tryVital/vital-ios/releases/tag/1.2.2) | | Flutter | [vital-flutter 4.2.1](https://github.com/tryVital/vital-flutter/releases/tag/vital_health-v4.2.1) | | React Native | [vital-react-native 4.2.1](https://github.com/tryVital/vital-react-native/releases/tag/4.2.1) | ### Menstrual Cycle Tracking (July 2024) Menstrual cycle tracking data can now be collected through the Apple HealthKit and Android Health Connect integrations. Ask for data permission on the `MenstrualCycle` resource from the user through the Junction Health SDK. Check out the [Get Menstrual Cycles](/api-reference/menstrual-cycle/get-summary) endpoint and the [Menstrual Cycle data events](/event-catalog/daily.data.menstrual_cycle.created) in the Event Catalog for more information. ### Steps count in workouts (May 2024) Workouts now include the total steps count during the session (if available). Check out the [Get Workouts](/api-reference/workouts/get-summary) endpoint and the [Workout Created](/event-catalog/daily.data.workouts.created) event for more information. ### New Abbott LibreView integration (May 2024) Junction has introduced an new Abbott LibreView provider (`abbott_libreview`). It uses password authentication, accepting LibreView patient account credentials. This is an alternative option to our practice-based Freestyle Libre provider (`freestyle_libre`). With `abbott_libreview`, Junction uses the provided LibreView patient account credentials to connect directly to LibreView. There is no involvement of a LibreView practice in this integration. Note that both practice-based and patient-based connections would continue to be supported in parallel. The `abbott_libreview` integration does not replace the `freestyle_libre` integration. Check out the [Abbott LibreView / Freestyle Libre](/wearables/guides/abbott-libreview) guide for more information. ### Removed Fitbit and Oura required scopes (May 2024) The Fitbit profile scope and Oura email scope are no longer required for establishing connections. For Fitbit specifically, please be aware of the [updated Fitbit Time Zone resolution precedences](/wearables/guides/fitbit#time-zone-resolution). When your user refuses to grant both the activity and the profile scope, Fitbit data are more likely be timestamped incorrectly in UTC time basis due to inaccurate or unavailable time zone information. We have now updated our Fitbit and Oura integrations not to depend on these scopes to be **minimally functional**. If you are interested in requiring your users to grant certain scopes when connecting an OAuth provider, check out the [Team Scope Requirements](/changelog/wearables/api#team-scope-requirements-apr-24) changelog entry and the [Set Team Scope Requirements](/api-reference/org-management/team-scope-requirements/get-team-scope-requirements) endpoint for more information. ### Freestyle Libre: India region (Apr 2024) Our Freestyle Libre integration now supports the LibreView India region. Check out the [Freestyle Libre](/wearables/guides/freestyle#custom-practice-enterprise-only) documentation for more information. ### Health Connect Background Sync (Mar 2024) Junction Mobile SDK now includes an experimental Background Sync feature for Android Health Connect. The feature is available through the following Junction Mobile SDK releases: | Platform | Version | | -------------- | ------- | | Native Android | 2.0.0+ | | Flutter | 3.2.0+ | | React Native | 3.1.0+ | Check out the following documentation for information, integration guidance as well as caveats of the experimental Background Sync : * [Junction Health SDK: Automatic Data Sync](/wearables/sdks/vital-health#automatic-data-sync) for a general overview * [Android Health Connect integration guide](/wearables/guides/android-health-connect#background-sync-experimental), which includes guidance on the experimental Background Sync feature. ### Expanded Source Type attribution (Feb 2024) Data from Fitbit, Oura, Garmin and Freestyle Libre now comes with Source Type attributions. Check out the [Data Attributions](/wearables/providers/data-attributions#source-type) documentation for more information. # continuous_query.result_table.changed Source: https://docs.junction.com/event-catalog/continuous_query.result_table.changed # daily.data.activity.created Source: https://docs.junction.com/event-catalog/daily.data.activity.created # daily.data.activity.updated Source: https://docs.junction.com/event-catalog/daily.data.activity.updated # daily.data.basal_body_temperature.created Source: https://docs.junction.com/event-catalog/daily.data.basal_body_temperature.created # daily.data.basal_body_temperature.updated Source: https://docs.junction.com/event-catalog/daily.data.basal_body_temperature.updated # daily.data.blood_oxygen.created Source: https://docs.junction.com/event-catalog/daily.data.blood_oxygen.created # daily.data.blood_oxygen.updated Source: https://docs.junction.com/event-catalog/daily.data.blood_oxygen.updated # daily.data.blood_pressure.created Source: https://docs.junction.com/event-catalog/daily.data.blood_pressure.created # daily.data.blood_pressure.updated Source: https://docs.junction.com/event-catalog/daily.data.blood_pressure.updated # daily.data.body.created Source: https://docs.junction.com/event-catalog/daily.data.body.created # daily.data.body.updated Source: https://docs.junction.com/event-catalog/daily.data.body.updated # daily.data.body_mass_index.created Source: https://docs.junction.com/event-catalog/daily.data.body_mass_index.created # daily.data.body_mass_index.updated Source: https://docs.junction.com/event-catalog/daily.data.body_mass_index.updated # daily.data.body_temperature.created Source: https://docs.junction.com/event-catalog/daily.data.body_temperature.created # daily.data.body_temperature.updated Source: https://docs.junction.com/event-catalog/daily.data.body_temperature.updated # daily.data.body_temperature_delta.created Source: https://docs.junction.com/event-catalog/daily.data.body_temperature_delta.created # daily.data.body_temperature_delta.updated Source: https://docs.junction.com/event-catalog/daily.data.body_temperature_delta.updated # daily.data.caffeine.created Source: https://docs.junction.com/event-catalog/daily.data.caffeine.created # daily.data.caffeine.updated Source: https://docs.junction.com/event-catalog/daily.data.caffeine.updated # daily.data.calories_active.created Source: https://docs.junction.com/event-catalog/daily.data.calories_active.created # daily.data.calories_active.updated Source: https://docs.junction.com/event-catalog/daily.data.calories_active.updated # daily.data.calories_basal.created Source: https://docs.junction.com/event-catalog/daily.data.calories_basal.created # daily.data.calories_basal.updated Source: https://docs.junction.com/event-catalog/daily.data.calories_basal.updated # daily.data.carbohydrates.created Source: https://docs.junction.com/event-catalog/daily.data.carbohydrates.created # daily.data.carbohydrates.updated Source: https://docs.junction.com/event-catalog/daily.data.carbohydrates.updated # daily.data.daylight_exposure.created Source: https://docs.junction.com/event-catalog/daily.data.daylight_exposure.created # daily.data.daylight_exposure.updated Source: https://docs.junction.com/event-catalog/daily.data.daylight_exposure.updated # daily.data.distance.created Source: https://docs.junction.com/event-catalog/daily.data.distance.created # daily.data.distance.updated Source: https://docs.junction.com/event-catalog/daily.data.distance.updated # daily.data.electrocardiogram_voltage.created Source: https://docs.junction.com/event-catalog/daily.data.electrocardiogram_voltage.created # daily.data.electrocardiogram_voltage.updated Source: https://docs.junction.com/event-catalog/daily.data.electrocardiogram_voltage.updated # daily.data.fall.created Source: https://docs.junction.com/event-catalog/daily.data.fall.created # daily.data.fall.updated Source: https://docs.junction.com/event-catalog/daily.data.fall.updated # daily.data.fat.created Source: https://docs.junction.com/event-catalog/daily.data.fat.created # daily.data.fat.updated Source: https://docs.junction.com/event-catalog/daily.data.fat.updated # daily.data.floors_climbed.created Source: https://docs.junction.com/event-catalog/daily.data.floors_climbed.created # daily.data.floors_climbed.updated Source: https://docs.junction.com/event-catalog/daily.data.floors_climbed.updated # daily.data.forced_expiratory_volume_1.created Source: https://docs.junction.com/event-catalog/daily.data.forced_expiratory_volume_1.created # daily.data.forced_expiratory_volume_1.updated Source: https://docs.junction.com/event-catalog/daily.data.forced_expiratory_volume_1.updated # daily.data.forced_vital_capacity.created Source: https://docs.junction.com/event-catalog/daily.data.forced_vital_capacity.created # daily.data.forced_vital_capacity.updated Source: https://docs.junction.com/event-catalog/daily.data.forced_vital_capacity.updated # daily.data.glucose.created Source: https://docs.junction.com/event-catalog/daily.data.glucose.created # daily.data.glucose.updated Source: https://docs.junction.com/event-catalog/daily.data.glucose.updated # daily.data.handwashing.created Source: https://docs.junction.com/event-catalog/daily.data.handwashing.created # daily.data.handwashing.updated Source: https://docs.junction.com/event-catalog/daily.data.handwashing.updated # daily.data.heart_rate_recovery_one_minute.created Source: https://docs.junction.com/event-catalog/daily.data.heart_rate_recovery_one_minute.created # daily.data.heart_rate_recovery_one_minute.updated Source: https://docs.junction.com/event-catalog/daily.data.heart_rate_recovery_one_minute.updated # daily.data.heartrate.created Source: https://docs.junction.com/event-catalog/daily.data.heartrate.created # daily.data.heartrate.updated Source: https://docs.junction.com/event-catalog/daily.data.heartrate.updated # daily.data.hrv.created Source: https://docs.junction.com/event-catalog/daily.data.hrv.created # daily.data.hrv.updated Source: https://docs.junction.com/event-catalog/daily.data.hrv.updated # daily.data.inhaler_usage.created Source: https://docs.junction.com/event-catalog/daily.data.inhaler_usage.created # daily.data.inhaler_usage.updated Source: https://docs.junction.com/event-catalog/daily.data.inhaler_usage.updated # daily.data.insulin_injection.created Source: https://docs.junction.com/event-catalog/daily.data.insulin_injection.created # daily.data.insulin_injection.updated Source: https://docs.junction.com/event-catalog/daily.data.insulin_injection.updated # daily.data.lean_body_mass.created Source: https://docs.junction.com/event-catalog/daily.data.lean_body_mass.created # daily.data.lean_body_mass.updated Source: https://docs.junction.com/event-catalog/daily.data.lean_body_mass.updated # daily.data.meal.created Source: https://docs.junction.com/event-catalog/daily.data.meal.created # daily.data.meal.updated Source: https://docs.junction.com/event-catalog/daily.data.meal.updated # daily.data.menstrual_cycle.created Source: https://docs.junction.com/event-catalog/daily.data.menstrual_cycle.created # daily.data.menstrual_cycle.updated Source: https://docs.junction.com/event-catalog/daily.data.menstrual_cycle.updated # daily.data.mindfulness_minutes.created Source: https://docs.junction.com/event-catalog/daily.data.mindfulness_minutes.created # daily.data.mindfulness_minutes.updated Source: https://docs.junction.com/event-catalog/daily.data.mindfulness_minutes.updated # daily.data.note.created Source: https://docs.junction.com/event-catalog/daily.data.note.created # daily.data.note.updated Source: https://docs.junction.com/event-catalog/daily.data.note.updated # daily.data.peak_expiratory_flow_rate.created Source: https://docs.junction.com/event-catalog/daily.data.peak_expiratory_flow_rate.created # daily.data.peak_expiratory_flow_rate.updated Source: https://docs.junction.com/event-catalog/daily.data.peak_expiratory_flow_rate.updated # daily.data.profile.created Source: https://docs.junction.com/event-catalog/daily.data.profile.created # daily.data.respiratory_rate.created Source: https://docs.junction.com/event-catalog/daily.data.respiratory_rate.created # daily.data.respiratory_rate.updated Source: https://docs.junction.com/event-catalog/daily.data.respiratory_rate.updated # daily.data.sleep.created Source: https://docs.junction.com/event-catalog/daily.data.sleep.created # daily.data.sleep.updated Source: https://docs.junction.com/event-catalog/daily.data.sleep.updated # daily.data.sleep_apnea_alert.created Source: https://docs.junction.com/event-catalog/daily.data.sleep_apnea_alert.created # daily.data.sleep_apnea_alert.updated Source: https://docs.junction.com/event-catalog/daily.data.sleep_apnea_alert.updated # daily.data.sleep_breathing_disturbance.created Source: https://docs.junction.com/event-catalog/daily.data.sleep_breathing_disturbance.created # daily.data.sleep_breathing_disturbance.updated Source: https://docs.junction.com/event-catalog/daily.data.sleep_breathing_disturbance.updated # daily.data.sleep_cycle.created Source: https://docs.junction.com/event-catalog/daily.data.sleep_cycle.created # daily.data.sleep_cycle.updated Source: https://docs.junction.com/event-catalog/daily.data.sleep_cycle.updated # daily.data.stand_duration.created Source: https://docs.junction.com/event-catalog/daily.data.stand_duration.created # daily.data.stand_duration.updated Source: https://docs.junction.com/event-catalog/daily.data.stand_duration.updated # daily.data.stand_hour.created Source: https://docs.junction.com/event-catalog/daily.data.stand_hour.created # daily.data.stand_hour.updated Source: https://docs.junction.com/event-catalog/daily.data.stand_hour.updated # daily.data.steps.created Source: https://docs.junction.com/event-catalog/daily.data.steps.created # daily.data.steps.updated Source: https://docs.junction.com/event-catalog/daily.data.steps.updated # daily.data.stress_level.created Source: https://docs.junction.com/event-catalog/daily.data.stress_level.created # daily.data.stress_level.updated Source: https://docs.junction.com/event-catalog/daily.data.stress_level.updated # daily.data.uv_exposure.created Source: https://docs.junction.com/event-catalog/daily.data.uv_exposure.created # daily.data.uv_exposure.updated Source: https://docs.junction.com/event-catalog/daily.data.uv_exposure.updated # daily.data.vo2_max.created Source: https://docs.junction.com/event-catalog/daily.data.vo2_max.created # daily.data.vo2_max.updated Source: https://docs.junction.com/event-catalog/daily.data.vo2_max.updated # daily.data.waist_circumference.created Source: https://docs.junction.com/event-catalog/daily.data.waist_circumference.created # daily.data.waist_circumference.updated Source: https://docs.junction.com/event-catalog/daily.data.waist_circumference.updated # daily.data.water.created Source: https://docs.junction.com/event-catalog/daily.data.water.created # daily.data.water.updated Source: https://docs.junction.com/event-catalog/daily.data.water.updated # daily.data.weight.created Source: https://docs.junction.com/event-catalog/daily.data.weight.created # daily.data.weight.updated Source: https://docs.junction.com/event-catalog/daily.data.weight.updated # daily.data.wheelchair_push.created Source: https://docs.junction.com/event-catalog/daily.data.wheelchair_push.created # daily.data.wheelchair_push.updated Source: https://docs.junction.com/event-catalog/daily.data.wheelchair_push.updated # daily.data.workout_distance.created Source: https://docs.junction.com/event-catalog/daily.data.workout_distance.created # daily.data.workout_distance.updated Source: https://docs.junction.com/event-catalog/daily.data.workout_distance.updated # daily.data.workout_duration.created Source: https://docs.junction.com/event-catalog/daily.data.workout_duration.created # daily.data.workout_duration.updated Source: https://docs.junction.com/event-catalog/daily.data.workout_duration.updated # daily.data.workout_stream.created Source: https://docs.junction.com/event-catalog/daily.data.workout_stream.created # daily.data.workout_stream.updated Source: https://docs.junction.com/event-catalog/daily.data.workout_stream.updated # daily.data.workout_swimming_stroke.created Source: https://docs.junction.com/event-catalog/daily.data.workout_swimming_stroke.created # daily.data.workout_swimming_stroke.updated Source: https://docs.junction.com/event-catalog/daily.data.workout_swimming_stroke.updated # daily.data.workouts.created Source: https://docs.junction.com/event-catalog/daily.data.workouts.created # daily.data.workouts.updated Source: https://docs.junction.com/event-catalog/daily.data.workouts.updated # historical.data.activity.created Source: https://docs.junction.com/event-catalog/historical.data.activity.created # historical.data.basal_body_temperature.created Source: https://docs.junction.com/event-catalog/historical.data.basal_body_temperature.created # historical.data.blood_oxygen.created Source: https://docs.junction.com/event-catalog/historical.data.blood_oxygen.created # historical.data.blood_pressure.created Source: https://docs.junction.com/event-catalog/historical.data.blood_pressure.created # historical.data.body.created Source: https://docs.junction.com/event-catalog/historical.data.body.created # historical.data.body_mass_index.created Source: https://docs.junction.com/event-catalog/historical.data.body_mass_index.created # historical.data.body_temperature.created Source: https://docs.junction.com/event-catalog/historical.data.body_temperature.created # historical.data.body_temperature_delta.created Source: https://docs.junction.com/event-catalog/historical.data.body_temperature_delta.created # historical.data.caffeine.created Source: https://docs.junction.com/event-catalog/historical.data.caffeine.created # historical.data.calories_active.created Source: https://docs.junction.com/event-catalog/historical.data.calories_active.created # historical.data.calories_basal.created Source: https://docs.junction.com/event-catalog/historical.data.calories_basal.created # historical.data.carbohydrates.created Source: https://docs.junction.com/event-catalog/historical.data.carbohydrates.created # historical.data.daylight_exposure.created Source: https://docs.junction.com/event-catalog/historical.data.daylight_exposure.created # historical.data.distance.created Source: https://docs.junction.com/event-catalog/historical.data.distance.created # historical.data.electrocardiogram_voltage.created Source: https://docs.junction.com/event-catalog/historical.data.electrocardiogram_voltage.created # historical.data.fall.created Source: https://docs.junction.com/event-catalog/historical.data.fall.created # historical.data.fat.created Source: https://docs.junction.com/event-catalog/historical.data.fat.created # historical.data.floors_climbed.created Source: https://docs.junction.com/event-catalog/historical.data.floors_climbed.created # historical.data.forced_expiratory_volume_1.created Source: https://docs.junction.com/event-catalog/historical.data.forced_expiratory_volume_1.created # historical.data.forced_vital_capacity.created Source: https://docs.junction.com/event-catalog/historical.data.forced_vital_capacity.created # historical.data.glucose.created Source: https://docs.junction.com/event-catalog/historical.data.glucose.created # historical.data.handwashing.created Source: https://docs.junction.com/event-catalog/historical.data.handwashing.created # historical.data.heart_rate_recovery_one_minute.created Source: https://docs.junction.com/event-catalog/historical.data.heart_rate_recovery_one_minute.created # historical.data.heartrate.created Source: https://docs.junction.com/event-catalog/historical.data.heartrate.created # historical.data.hrv.created Source: https://docs.junction.com/event-catalog/historical.data.hrv.created # historical.data.inhaler_usage.created Source: https://docs.junction.com/event-catalog/historical.data.inhaler_usage.created # historical.data.insulin_injection.created Source: https://docs.junction.com/event-catalog/historical.data.insulin_injection.created # historical.data.lean_body_mass.created Source: https://docs.junction.com/event-catalog/historical.data.lean_body_mass.created # historical.data.meal.created Source: https://docs.junction.com/event-catalog/historical.data.meal.created # historical.data.menstrual_cycle.created Source: https://docs.junction.com/event-catalog/historical.data.menstrual_cycle.created # historical.data.mindfulness_minutes.created Source: https://docs.junction.com/event-catalog/historical.data.mindfulness_minutes.created # historical.data.note.created Source: https://docs.junction.com/event-catalog/historical.data.note.created # historical.data.peak_expiratory_flow_rate.created Source: https://docs.junction.com/event-catalog/historical.data.peak_expiratory_flow_rate.created # historical.data.profile.created Source: https://docs.junction.com/event-catalog/historical.data.profile.created # historical.data.respiratory_rate.created Source: https://docs.junction.com/event-catalog/historical.data.respiratory_rate.created # historical.data.sleep.created Source: https://docs.junction.com/event-catalog/historical.data.sleep.created # historical.data.sleep_apnea_alert.created Source: https://docs.junction.com/event-catalog/historical.data.sleep_apnea_alert.created # historical.data.sleep_breathing_disturbance.created Source: https://docs.junction.com/event-catalog/historical.data.sleep_breathing_disturbance.created # historical.data.sleep_cycle.created Source: https://docs.junction.com/event-catalog/historical.data.sleep_cycle.created # historical.data.stand_duration.created Source: https://docs.junction.com/event-catalog/historical.data.stand_duration.created # historical.data.stand_hour.created Source: https://docs.junction.com/event-catalog/historical.data.stand_hour.created # historical.data.steps.created Source: https://docs.junction.com/event-catalog/historical.data.steps.created # historical.data.stress_level.created Source: https://docs.junction.com/event-catalog/historical.data.stress_level.created # historical.data.uv_exposure.created Source: https://docs.junction.com/event-catalog/historical.data.uv_exposure.created # historical.data.vo2_max.created Source: https://docs.junction.com/event-catalog/historical.data.vo2_max.created # historical.data.waist_circumference.created Source: https://docs.junction.com/event-catalog/historical.data.waist_circumference.created # historical.data.water.created Source: https://docs.junction.com/event-catalog/historical.data.water.created # historical.data.weight.created Source: https://docs.junction.com/event-catalog/historical.data.weight.created # historical.data.wheelchair_push.created Source: https://docs.junction.com/event-catalog/historical.data.wheelchair_push.created # historical.data.workout_distance.created Source: https://docs.junction.com/event-catalog/historical.data.workout_distance.created # historical.data.workout_duration.created Source: https://docs.junction.com/event-catalog/historical.data.workout_duration.created # historical.data.workout_swimming_stroke.created Source: https://docs.junction.com/event-catalog/historical.data.workout_swimming_stroke.created # historical.data.workouts.created Source: https://docs.junction.com/event-catalog/historical.data.workouts.created # lab_report.parsing_job.created Source: https://docs.junction.com/event-catalog/lab_report.parsing_job.created # lab_report.parsing_job.updated Source: https://docs.junction.com/event-catalog/lab_report.parsing_job.updated # labtest.appointment.updated Source: https://docs.junction.com/event-catalog/labtest.appointment.updated # labtest.order.created Source: https://docs.junction.com/event-catalog/labtest.order.created # labtest.order.updated Source: https://docs.junction.com/event-catalog/labtest.order.updated # labtest.result.critical Source: https://docs.junction.com/event-catalog/labtest.result.critical # provider.connection.created Source: https://docs.junction.com/event-catalog/provider.connection.created # provider.connection.error Source: https://docs.junction.com/event-catalog/provider.connection.error # provider.device.created Source: https://docs.junction.com/event-catalog/provider.device.created # provider.device.updated Source: https://docs.junction.com/event-catalog/provider.device.updated # Getting Support Source: https://docs.junction.com/home/getting-support ## Support channels When you encounter an issue which you cannot figure out, or an unexpected API error, you are welcome to get in touch with Junction support through these channels: * Email Support ([help@junction.com](mailto:help@junction.com)) * Junction Slack community * Your dedicated Slack support channel (for Scale plan customers). We appreciate as much context as possible when you raise an issue. This helps us understand your issue, enabling a quicker investigation turnaround. ## I have an issue with... ### Anything in general | Item | Remarks | | ------------------------ | ------------------------------------------ | | Your Junction Team ID | - | | Your Junction region | US or EU | | The Junction User ID | ...if the issue occurs on a specific user. | | The Junction environment | Sandbox, Production or both | ### Junction Mobile SDK | Item | Remarks | | --------------------- | --------------------------------------- | | Junction SDK Platform | Native, React Native, Flutter | | Junction SDK Version | - | | Device OS | Android or iOS | | Device OS Version | - | | Auth Scheme | Junction Sign-In Token, or Team API Key | ### A Junction API endpoint If your system has distributed tracing configured that are compliant with W3C Trace Context (e.g., OpenTelemetry tracing), you can provide us the Trace ID of the operation **in your system**. A Trace ID looks like this: `0af7651916cd43dd8448eb211c80319c`. This helps us locate the concerned API request and related contexts. This works only if your distributed tracing setup would propagate Trace Context to your outbound HTTP requests β€” more specifically injecting the standards-based `traceparent` header into the requests. ### A wearables provider connection Before reporting an issue, we encourage you to try out the following steps: The [Get User Connections](https://docs.tryvital.io/api-reference/user/get-users-connected-providers) endpoint reports the resource availability of all the connections. More specifically, it tells you: 1. What resources are available for a given user connection 2. Why each individual resource is available or unavailable, in terms of provider API access scopes. If you are having issues with historical data specifically, the [Historical Pull Introspection](https://docs.tryvital.io/api-reference/introspection/historical-pulls) endpoint provides a track record of historical pulls of all your user connections. If you have troubles with data availability in general, the [User Resource Introspection](https://docs.tryvital.io/api-reference/introspection/user-resources) endpoint is a live data ingestion record of all your user connections. This is inclusive of all historical data covered by the Historical Pull Introspection endpoint. If you are not able to diagnose your issue using these tools, feel free to contact Junction support for further assistance. # Libraries Source: https://docs.junction.com/home/libraries We have created a few libraries to help you integrate with the Junction API. We'll continue to add support for more libraries as we add more devices. Email [support@junction.com](mailto:support@junction.com) for specific library support. ### Typed Bindings of Junction API These typed bindings track the Junction API OpenAPI Schema. | | | | ---------------------------------------------------------------- | --------------------------- | | [vital-java](https://github.com/tryVital/vital-java) | Junction API Java client | | [vital-go](https://github.com/tryVital/vital-go) | Junction API Go client | | [vital-node](https://www.npmjs.com/package/@tryvital/vital-node) | Junction API Node.js client | | [vital-python](https://pypi.org/project/vital) | Junction API Python client | ### Mobile SDK | | | | -------------------------------------------------------------------- | ----------------------------------- | | [vital-ios](https://github.com/tryVital/vital-ios) | Junction iOS Client | | [vital-android](https://github.com/tryVital/vital-android) | Junction Android Client | | [vital-flutter](https://github.com/tryVital/vital-flutter) | Junction Flutter Client | | [vital-react-native](https://github.com/tryVital/vital-react-native) | Junction React Native Client | | [vital-connect](https://github.com/tryVital/vital-connect-rn) | Whitelabel App using Junction's API | ### Web SDK | | | | ---------------------------------------------------------------- | ----------------------------------- | | [vital-link](https://www.npmjs.com/package/@tryvital/vital-link) | React Library for initializing link | # Quickstart Source: https://docs.junction.com/home/quickstart A quick introduction to building with Junction ## 1. API keys Let's test out running Junction locally by cloning the [Quickstart app](https://github.com/tryVital/quickstart). You can get API keys by signing up in the [Dashboard](https://app.junction.com). Once registered, you can create a team. A team is associated with a region (either `EU` or `US`), the region dictates where data is stored. You can learn more at [regions](/api-details/regions). To create a team and get your API keys, you first need to sign up for a Junction account in the [Dashboard](https://app.junction.com). Once registered, you can create a team by hovering over your username at the bottom of the Dashboard sidebar. A team is associated with a region (either `EU` or `US`), the region dictates where data is stored. You can learn more at [regions](/api-details/regions). To create your API keys, go to the configuration section of the Dashboard. For each region, you'll have access to two environments Sandbox and Production. We'll start in the Sandbox environment, so create a new Sandbox API key. If you get stuck at any point in the Quickstart, help is just a click away! Join our Slack channel or send us a message to [support@junction.com](mailto:support@junction.com) | Environment | | | | ------------ | ------------------------------------------- | ----------------------- | | `sandbox` | Testing, Connect up to 10 live users | api.sandbox.tryvital.io | | `production` | Live environment to use with real customers | api.tryvital.io | ## 2. Running Quickstart locally Once you have your API keys, it's time to run the Junction Quickstart locally! The instructions below will guide you through the process of cloning the [Quickstart repository](https://github.com/tryVital/quickstart), customizing the `.env` file with your own Junction `API_KEY` and finally, building and running the app. ```bash 1. Clone quickstart and run locally theme={null} # Note: If on Windows, run # git clone -c core.symlinks=true https://github.com/tryVital/quickstart # instead to ensure correct symlink behavior git clone https://github.com/tryVital/quickstart.git # Create .env, then fill # out VITAL_API_KEY, VITAL_REGION (eu, us) and VITAL_ENV in .env touch .env # Note: must use python 3 # For virtualenv users: # virtualenv venv # source venv/bin/activate poetry install # Start the backend app cd backend/python source ./start.sh ``` Open a new shell and start the frontend app. Your app will be running at `http://localhost:3000`. ```bash 2. Run quickstart frontend theme={null} # Install dependencies cd quickstart/frontend npm install # Open .env.local, then fill # out NEXT_PUBLIC_VITAL_API_KEY, NEXT_PUBLIC_VITAL_ENV and NEXT_PUBLIC_VITAL_REGION # Start the frontend app npm run dev # Go to http://localhost:3000 ``` ## 3. Creating your first User When retrieving data or connecting devices, Junction will require a `user_id` as input. A `user_id` is a unique representation that we hold for a user. It allows you to fetch data for that user. To create a user, you need to pass an unique id (`client_user_id`). This represents the user in your system. Our recommendation is to store the Junction `user_id` in your db against the user row. Personally identifiable information (PII), such as an email address or phone number, should not be used as input for the `user_id` parameter. Enter a new `client_user_id` and tap Create: quickstart This can also be achieved via the API as follows. ```bash Creating a Junction user (bash) theme={null} curl --request POST \ --url {{BASE_URL}}/v2/user/ \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --header 'x-vital-api-key: ' \ --data '{"client_user_id":""}' ``` ```js Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { UserCreateBody } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: UserCreateBody = { clientUserId: "" } const data = await client.user.create(request) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.create(client_user_id="") ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.user.requests.UserCreateBody; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); UserCreateBody request = UserCreateBody .builder() .clientUserId("") .build(); var data = vital.user().create(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.UserCreateBody{ ClientUserId: "", } response, err := client.User.Create(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```swift Swift theme={null} let user = try await VitalClient.shared.user.create(clientUserId) ``` ## 4. Connecting a source A source, at Junction, is a medical device, wearable, or lab. It is a source of information for health data. To connect a source tap the connect button, this will launch the Junction Link Widget for that user. Once you have entered your credentials and moved to the next screen, you have connected your first source! You can now make API calls to retrieve data for that Source. ### How it works As you might have noticed, you use both a server and a client-side component to access the Junction APIs. A more detailed explanation on how linking works can be found in [link flow](/wearables/connecting-providers/link_flow) The first step is to create a new `link_token` by making a `/link/token` request and passing in the required configuration. This `link_token` is a short lived, one-time use token that authenticates your app with Junction Link, our frontend module. ```python Generating a Link Token theme={null} # TEST BACKEND IMPLEMENTATION from vital.client import Vital from vital.environment import VitalEnvironment from vital.client import Vital from fastapi import FastAPI from starlette.middleware.cors import CORSMiddleware client = Vital( api_key=, environment=VitalEnvironment.SANDBOX, timeout=30 ) app = FastAPI() app.add_middleware( # type: ignore CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/token/{user_key}") def get_token(user_key: str): return client.link.token(user_id=user_key) ``` Once you have a `link_token`, you can use it to initialize `Link`. `Link` is a drop-in client-side module available for web, iOS, and Android that handles the authentication process. The Quickstart uses `Link` on the web, which is a pure JavaScript integration that you trigger via your own client-side code. ```javascript Generating a Link Token theme={null} import { Button } from "@chakra-ui/react"; import { useState, useCallback } from "react"; import { useVitalLink } from "@tryvital/vital-link"; export const LinkButton: React.FC<{ userID: string | null }> = ({ userID }) => { const [isLoading, setLoading] = useState(false); const onSuccess = useCallback((metadata) => { // Device is now connected. console.log("onSuccess", metadata); }, []); const onExit = useCallback((metadata) => { // User has quit the link flow. console.log("onExit", metadata); }, []); const onError = useCallback((metadata) => { // Error encountered in connecting device. console.log("onError", metadata); }, []); const config = { onSuccess, onExit, onError, env: "sandbox", region: "us", }; const { open, ready, error } = useVitalLink(config); const handleVitalOpen = async () => { setLoading(true); const token = await getTokenFromBackend(userID); open(token.link_token); setLoading(false); }; return ( ); }; ``` This is what your users see to connect their medical device or wearables: Link specific ## 5. Making your first API request We can now explore what happens when you press the analyze button in the Quickstart to make an API call. As an example, we'll look at the Quickstart's call to `/summary/sleep`, which retrieves sleep summary data for a user. The request is simple and requires the Junction `user_id`, `start_date` and `end_date`. **Getting user sleep data** ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.sleep.get( user_id="", start_date="2021-01-01", end_date="2021-01-02" ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { SleepGetRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: SleepGetRequest = { startDate: '2021-01-01', endDate: '2021-01-02', } const data = await client.sleep.get('user_id', request) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.sleep.requests.SleepGetRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); SleepGetRequest request = SleepGetRequest .builder() .startDate("2021-01-01") .endDate("2021-01-02") .build(); var data = vital.sleep().get("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) EndDate := "2021-01-02" request := &vital.SleepGetRequest{ StartDate: "2021-01-01", EndDate: &EndDate, } response, err := client.Sleep.Get( context.TODO(), "", request ) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```swift Swift theme={null} let sleepData = try await VitalClient.shared.summary.sleep(userId, startDate, endDate) ``` ## 6. SDK's and Libraries We offer different SDKs so you can start building your app right away: | | | | ---------------------------------------------------------------- | ------------------------------------------------------- | | [vital-python](https://pypi.org/project/vital) | Python library for calling Junction API on your backend | | [vital-link](https://www.npmjs.com/package/@tryvital/vital-link) | React Library for initializing link | | [vital-node](https://www.npmjs.com/package/@tryvital/vital-node) | Junction Node Client | | [vital-ios](https://github.com/tryVital/vital-ios) | Junction iOS Client | | Java | Soon to be added | | Go | Soon to be added | You can also download our [API collection](https://www.postman.com/collections/35339909-b0d080b7-3870-4aa8-a68b-a675f93a0533) or by installing postman first and clicking on the below button. [![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/35339909-b0d080b7-3870-4aa8-a68b-a675f93a0533?action=collection%2Ffork\&source=rip_markdown\&collection-url=entityId%3D35339909-b0d080b7-3870-4aa8-a68b-a675f93a0533%26entityType%3Dcollection%26workspaceId%3Ddd82502b-0c9d-4fe4-9760-73a54bc2b8bf) ## Next Steps Congratulations, you have completed the Junction Quickstart! There are a few directions you can go in now: A client-side component your users will interact with in order to link their accounts with Junction. It allows you to access their accounts via the Junction API. Native toolkits to integrate Junction into iOS, Android, and Flutter Webhooks are a way to receive data from Junction. We frequently poll to receive data from the various providers. * [Junction Link](/wearables/vital-link/introduction) * [Junction SDKs](/wearables/sdks/ios) * [Junction Webhooks](/webhooks/Introduction) # Junction Source: https://docs.junction.com/home/welcome Integrate 300+ wearables, streamline lab operations, and build your own lab testing experience spanning all 50 states – with a single API. Access a nationwide lab network. In-person, at-home, or hybrid methods to maximize completion rates. Automatically transforms raw activity and biometric data from 300+ connected devices into structured, aggregated datasets that update as new data arrives. Connect to 300+ devices, and receive data in Junction standardized schemas. User management, Device connection and data access, Lab Testing orders and results, Junction Sense Query and Data. Programmatically manage all Junction regional and global resources in your organization, including Junction Sense Continuous Queries. ## Popular Topics The Quickstart walk-through gives you an overview of how to integrate with Junction. By the end of this guide, you'll have a working app with both backend and frontend. Learn how to integrate with Apple HealthKit through Junction Mobile SDKs, available in Native iOS, React Native and Flutter. Learn how to integrate with Android Health Connect through Junction Mobile SDKs, available in Native Android, React Native and Flutter. Junction Link enables your users to connect their account with with their wearable data providers. Learn how to integrate the Link Widget for a no-fuzz starter integration. Learn how to use the Junction Link API to build your own Link Widget. Learn about timestamp and time zone handling of wearable device data. Junction can push wearable device data as webhook events to you as soon as they are discovered. Learn about the delivery stages, event structure as well as the advanced ETL Pipeline options for high volume needs. Learn how to use Demo Connections β€” which emit synthetic data mimicking selected providers β€” as an alternative way to test your Junction integration. # Communications Source: https://docs.junction.com/lab/at-home-phlebotomy/communications Communication for patients is done via email or SMS, for At-Home Phlebotomy orders. Customers have the following options when setting this up: * `Default` - Email and SMS communications are enabled. * `SMS Only` - Only SMS communication is enabled. * `Disable` - All communications from Junction are disabled. Each option has a different set of content and status changes, depending on what triggered them. You can enable or disable SMS and Email communications individually through the [**Junction Dashboard**](http://app.junction.com/), under the Team Settings section. ## Default Communications ### SMS Messages A table of the **Order Status** and **default SMS** is provided below: | Order Status | Default Message | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `requisition_created` | *"Hey **patient\_first\_name**, it's time to book your at-home phlebotomy draw. You can book a slot using the following link **booking\_link**."* | | `appointment_scheduled` | *"Your appointment with the phlebotomist has been booked at **date**! You can reschedule or cancel using the following link **booking\_link**."* | | `appointment_cancelled` | *"Hey, your at-home-phlebotomy appointment at **date** for the **team\_name** has been cancelled. You can rebook using the following link **booking\_link**."* | | `draw_completed` | *"Your at-home phlebotomy draw is complete! We're delivering your sample to the lab for processing."* | | `completed` | *"The lab has finished processing your blood sample, your results should be ready soon :)"* | | `cancelled` | *"Hey, your order for the **team\_name** at-home-phlebotomy appointment has been cancelled. If this is by accident please contact support."* | SMS Texts are customizable, and can be enabled or disabled individually. ### Emails For emails, the following table describes what information each email contains for each Order Status: | Order Status | Email Content Description | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `requisition_created` | An email with the booking link for **Junction Booking Widget** will be sent to the patient. | | `appointment_scheduled` | An email confirming the appointment date and time will be sent to the patient, with the possibility of rescheduling or cancelling the appointment. | | `appointment_cancelled` | An email confirming the cancellation of the appointment is sent, providing the possibility of scheduling a new appointment. | Emails can be white labelled and sent from your own domain. ## Scheduling Appointments Before A Requisition Has Been Created This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. If the ability to [`schedule appointments before a requisition has been created`](/lab/at-home-phlebotomy/order-appointment-lifecycle#scheduling-appointments-before-a-requisition-has-been-created) is enabled for your team, all appointment-related messages will be triggered by the **appointment** status, not the **order** status. All non appointment-related messages will be triggered by the **order** status e.g. `requisition_created`, `draw_completed` etc. (see above section). ### SMS Messages A table of the **Appointment Status** and **default SMS** is provided below: | Appointment Status | Default Message | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `confirmed` | *"Your appointment with the phlebotomist has been booked at **date**! You can reschedule or cancel using the following link **booking\_link**."* | | `cancelled` | *"Hey, your at-home-phlebotomy appointment at **date** for the **team\_name** has been cancelled. You can rebook using the following link **booking\_link**."* | ### Emails For emails, the following table describes what information each email contains for each Appointment Status: | Appointment Status | Email Content Description | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `confirmed` | An email confirming the appointment date and time will be sent to the patient, with the possibility of rescheduling or cancelling the appointment. | | `cancelled` | An email confirming the cancellation of the appointment is sent, providing the possibility of scheduling a new appointment. | ## Disable Communications In this case, no communications will be sent from Junction. You have the ability to produce completely customized communications using the [`Webhook events`](/lab/at-home-phlebotomy/webhooks) described previously. # Order and Appointment Lifecycle Source: https://docs.junction.com/lab/at-home-phlebotomy/order-appointment-lifecycle The **At-Home Phlebotomy** orders are composed of two different lifecycles, the **Order** lifecycle and the **appointment** lifecycle, detailed in the following sections. ## Order Lifecycle As discussed in [`Lab Test Lifecycle - Statuses`](/lab/workflow/lab-test-lifecycle#statuses), each lab testing modality has the following format `[HIGH-LEVEL STATUS].[TEST MODALITY].[LOW-LEVEL STATUS]` For each modality, there can be multiple **low-level status**, for **At Home Phlebotomy** the possible low-level status are: * `ordered`: Junction received the order, stored it into our system, and started processing it asynchronously. * `requisition_created`: An order requisition form was validated and created with the partner laboratory, making the order available to be carried out. * `requisition_bypassed`: An order requisition form wasn't created when the order was placed with us because it already existed. * `appointment_pending`: An appointment was placed in Junction's system for the order, but doesn't have a scheduled date. * `appointment_scheduled`: An appointment was scheduled or rescheduled for the order. * `draw_completed`: The phlebotomy appointment was completed and the blood was drawn successfuly. * `appointment_cancelled`: The appointment was cancelled, by either the patient, Junction or you. * `partial_results`: The laboratory has started making partial results available. * `completed`: The laboratory processed the blood sample and final results are available. * `sample_error`: The collected sample was unprocessable by the lab. * `cancelled`: The order was cancelled by either the patient, Junction or you. The Finite State Machine that defines the possible transitions for the low-level statuses described above is illustrated in the following diagram.
In **sandbox**, there is no async transition from the `ordered` state to the `requisition_created` state, this must be **manually triggered** via the **Junction Dashboard**. ## Appointment Lifecycle The appointment lifecycle is separate from the order lifecyle, and it corresponds to a single appointment. The possible status are defined as follows: * `pending`: An appointment was placed in the system, and is pending updates from the phlebotomy service. * `reserved`: The time slot has been reserved for the appointment, but the scheduling confirmation is waiting on the creation of order requisition. * `scheduled`: The appointment has been scheduled or rescheduled. * `in-progress`: The phlebotomist is in their way to the patient address. * `cancelled`: The appointment was cancelled by either the patient, Junction or you. * `completed`: The phlebotomy appointment was completed and the blood was drawn successfuly The Finite State Machine that defines the possible transitions for the appointment statuses described above is illustrated in the following diagram. The events are related to a single appointment. An order can have multiple existing appointments in Junction system, although only one appointment will be considered active and returned when using the [GET Appointment endpoint](/api-reference/lab-testing-at-home-phlebotomy/get-appointment). ## Scheduling Appointments Before A Requisition Has Been Created This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. When this feature is enabled on your Team, you can create an At-Home Phlebotomy appointment even before the requisition has been created for the corresponding Order (i.e., reaching the `requisition_created` status). This allows you to build an experience that can order the lab test *and* book an appointment consecutively, without having to wait on the asynchronous requisition creation process that may take an indeterminate amount of time. ### API Journey You create an Appointment for an Order which does not yet have a requisition. For an [Appointment Ready Tier](/lab/at-home-phlebotomy/service-tiers) service, the Appointment starts with the `pending` status and immediately moves to the `reserved` status. For an [Appointment Request Tier](/lab/at-home-phlebotomy/service-tiers) service, the Appointment starts with the `pending` status. Once the service has penciled in the time slot, the Appointment moves to the `reserved` status β€” if the order requisition still has not yet been created by then. If the order requisition has been created on or before the time slot confirmation, the Appointment moves directly to the `confirmed` status. The `reserved` status would be skipped. The requisition is being created asynchronously. The requisition has now been created. For all types of Appointments: 1. The Order will move through the `requisition_created` and `appointment_pending` Order statuses in quick succession. You will receive two `labtest.order.updated` events, one each for the two status transitions. For all [Appointment Ready Tier](/lab/at-home-phlebotomy/service-tiers) Appointments and all the penciled [Appointment Request Tier](/lab/at-home-phlebotomy/service-tiers) Appointments: 1. The Order will move further to the `appointment_scheduled` Order status in quick succession. 2. The Appointment will move to the `confirmed` status automatically, which corresponds to the `scheduled` appointment event. You will additionally receive a `labtest.order.updated` event and a `labtest.appointment.updated` event for the status transition. ## Cancelling An Appointment If an Appointment is cancelled **before** a requisition has been created: * The Appointment will move to the `cancelled` status; and * There is no change to the Order, which will remain in the `ordered` status. If an Appointment is cancelled **after** a requisition has been created: 1. The Appointment will move to the `cancelled` status; and 2. The Order will move from the `appointment_pending` or `appointment_scheduled` status to the `appointment_cancelled` status. ### Auto-cancellation of appointments without requisition In cases where appointments can be scheduled before a requisition has been created, the appointment will be automatically cancelled by Junction if the following happens: 1. More than 8 hours have passed since the order was created, and no requisition has been received. 2. There is less than 2 hours until the appointment start time, and no requisition has been received. When booking an appointment with Getlabs, we strongly recommend that the appointment is booked at least 48 hours in advance to allow sufficient time for the requisition to be received and sent to the phlebotomist. Getlabs automatically reschedules appointments that do not have a requisition form around 24 hours before the appointment start time. They also inform the patient about the rescheduling, directly via SMS. Restricting the scheduling of appointments to at least 48 hours in advance allows Junction to handle the auto-cancellation of the appointment, which triggers an appointment cancelled webhook. You can then use this for patient communications, rather than relying on Getlabs to do so. # Overview Source: https://docs.junction.com/lab/at-home-phlebotomy/overview At-home phlebotomy tests are one of our offered modalities. This modality is focused on patients that don't want to go to a Laboratory, but instead want a phlebotomist to come to their home or office to carry out the phlebotomy draw. The phlebotomist is responsible for bringing the required equipment and tubes and then delivering those to our partner Laboratories. ## Ordering Flow overview To achieve this, a high-level overview of the process is defined as: * An order is placed in Junction's system through our Dashboard or API. * The order is added to a background queue and additional checks are made before a test requisition is created with the chosen Laboratory. * After the requisition is created, some form of communication is carried out with the patient so he can book an appointment with our phlebotomy partners. * At the day of the appointment, our partner phlebotomist will contact the patient directly to assert that everything is correct for carrying out the appointment. * The phlebotomist goes to the appointment, draws the patient's blood and then delivers it to the Laboratory. * The Laboratory processes the patient's blood sample and generates the required results. * Junction exposes the results via API as soon as they are available, both on PDF and structured data via API. ## Constraints * Phlebotomy appointments are not supported for patients under 18 years of age. * The same patient can't have more than one active appointment with a specific **Phlebotomy provider**. * The addresses provided when fetching the appointment availability slots must be reachable by the phlebotomist, containing street number and unit (if exists). To find out more details, see [`Order and Appointment Lifecycle`](/lab/at-home-phlebotomy/order-appointment-lifecycle), [`Communications`](/lab/at-home-phlebotomy/communications) and [`Webhooks`](/lab/at-home-phlebotomy/webhooks). # Phlebotomy Service Tiers Source: https://docs.junction.com/lab/at-home-phlebotomy/service-tiers Junction offers multiple Tiers of phlebotomy services, with different coverage capabilities: * **Appointment Ready:** An appointment is booked in Junction using the patient’s address and the appointment’s date time. The scheduling is completely synchronous and fully controlled by Junction's customers through our API. * **Appointment Request:** An appointment is requested through Junction system using the patient’s address. A phlebotomist will eventually assign themselves to the appointment and define the appointment’s date time with the patient. The following Providers are available for each Tier: | | Getlabs | Phlebfinders (Beta) | | :------------------ | :-----: | :-----------------: | | Appointment Ready | X | | | Appointment Request | | X | `appointment-request` appointments will start in the `pending` status, and won't have any time or date information. ## High Level flow for Appointment Scheduling
The recommend high level flow for selecting an appointment at Junction is: * Place an At-Home Phlebotomy order with the [`POST /v3/order`](/api-reference/lab-testing/create-order) endpoint. * Wait for the `Requisition Ready` status updates through our Webhooks. * Fetch Provider data via the [`GET /v3/order/area/info`](/api-reference/lab-testing/area-info) endpoint, the response payload should look like this: ```json theme={null} { "zip_code": "85004", "phlebotomy": { "is_served": true, "providers": [ { "name": "getlabs", "tier": ["appointment-ready"] }, { "name": "phlebfinders", "tier": ["appointment-request"] } ] } } ``` * Select the Provider that best fit your needs. * If you use the [`POST /v3/order/{order_id}/phlebotomy/appointment/book`](/api-reference/lab-testing-at-home-phlebotomy/appointment-booking) endpoint, an `appointment-ready` provider is chosen on your behalf. * if you use the [`POST /v3/order/{order_id}/phlebotomy/appointment/request`](/api-reference/lab-testing-at-home-phlebotomy/appointment-request) endpoint, you must select a provider that offers an `appointment-request` tier. * Wait for the `Appointment Webhooks` and `Order Webhooks` described in the [Webhooks](/lab/at-home-phlebotomy/webhooks) section. # Webhooks Source: https://docs.junction.com/lab/at-home-phlebotomy/webhooks The following webhook events are of interest, when placing an At-Home Phlebotomy order. Those are described in detail in the following sections. ## Order webhook events Based on the status present in [`Order and Appointment Lifecycle - Order Lifecycle`](/lab/at-home-phlebotomy/order-appointment-lifecycle#order-lifecycle), Junction will trigger two kinds of webhook events, [`labtest.order.created`](/event-catalog/labtest.order.created) and [`labtest.order.updated`](/event-catalog/labtest.order.updated). The `labtest.order.created` event is triggered when an order is created in the system, having the `ordered` status, and all subsequent status changes will trigger a `labtest.order.updated` event in the system. The `partial_results` status does not trigger a Webhook unless specifically requested from Junction. The webhook payload body will have the following information if the Order is in the `appointment_scheduled` status: ```json Phlebotomy Order Updated theme={null} { "id": "84d96c03-6b1c-4226-ad8f-ef44a6bc08af", "team_id": "6353bcab-3526-4838-8c92-063fa760fb6b", "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "patient_details": { "dob": "2020-01-01", "gender": "male" }, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789" }, "details": { "type": "at_home_phlebotomy", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "appointment_id": "d55210cc-3d9f-4115-8262-5013f700c7be", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "collecting_sample", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.at_home_phlebotomy.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.at_home_phlebotomy.requisition_created" }, { "id": 3, "created_at": "2022-01-03T00:00:00Z", "status": "collecting_sample.at_home_phlebotomy.appointment_scheduled" } ], "origin": "initial", "order_transaction": { "id": "6424d45-ee1a-49c6-ad0c-5769b8e03fc1", "status": "active", "orders": [ { "id": "84d96c03-6b1c-4226-ad8f-ef44a6bc08af", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "low_level_status": "appointment_scheduled", "low_level_status_created_at": "2022-01-03T00:00:00Z", "origin": "initial" } ] } } ``` ## Appointment webhook events Based on the status present in [`Order and Appointment Lifecycle - Appointment Lifecycle`](/lab/at-home-phlebotomy/order-appointment-lifecycle#appointment-lifecycle), Junction will trigger the [`labtest.appointment.updated`](/event-catalog/labtest.appointment.updated) webhook event. The `labtest.appointment.updated` event is triggered for all possible appointment statuses, and is the **recommended** way of integrating to fetch all **At-Home Phlebotomy** status updates, together with the [Order Webhooks](#order-webhook-events). If the ability to [schedule appointments before a requisition has been created](/lab/at-home-phlebotomy/order-appointment-lifecycle#scheduling-appointments-before-a-requisition-has-been-created) is enabled for your team, and you intend to send patient communications for appointment updates, use the `labtest.appointment.updated` event. See more on communications for this feature [here](/lab/at-home-phlebotomy/communications#scheduling-appointments-before-a-requisition-has-been-created). The webhook payload body may have the following information if the appointment is in the `scheduled` status, after a **reschedule** has happened: ```json Phlebotomy Appointment Updated theme={null} { "event_type": "labtest.appointment.updated", "data": { "id": "06c2c65b-74a0-4f25-a4a9-44f796296355", "user_id": "acf79a82-0c2c-4ca0-998b-378931793905", "order_id": "1ed9c8d7-e1b4-4d61-8123-0f99de5ae99a", "order_transaction_id": "6424d45-ee1a-49c6-ad0c-5769b8e03fc1", "address": { "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip_code": "91189", "country": "United States" }, "location": { "lng": -122.4194155, "lat": 37.7749295 }, "start_at": "2022-01-01T00:00:00", "end_at": "2022-01-01T00:00:00", "iana_timezone": "America/New_York", "type": "phlebotomy", "provider": "getlabs", "status": "pending", "event_status": "scheduled", "provider_id": "123", "can_reschedule": true, "event_data": { "origin": "patient", "is_reschedule": true }, "events": [ { "created_at": "2022-01-01T00:00:00Z", "data": null, "status": "scheduled" }, { "created_at": "2022-01-01T00:00:00Z", "data": { "origin": "patient", "is_reschedule": true }, "status": "scheduled" }, ] } } ```
The `event_data` field contains relevant information regarding the current appointment status, and may be specific for each `provider`. # Communications Source: https://docs.junction.com/lab/on-site-collection/communications This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. Communication for patients is done via email or SMS, for On-site collection orders. Customers have the following options when setting this up: * `Default` - Email and SMS communications are enabled. * `SMS Only or Email Only` - Only SMS or Email communication is enabled. * `Disable` - All communications from Junction are disabled. Each option has a different set of content and status changes, depending on what triggered them. You can enable or disable SMS and Email communications individually through the [**Junction Dashboard**](http://app.junction.com/), under the Team Settings section. ## Default Communications ### SMS Messages A table of the **Order Status** and **default SMS** is provided bellow: | Order Status | Default Message | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `ordered` | *"Hi, , your order for the on-site collection has been placed! We'll provide you with updates on the status of your order via text messages :)"* | | `requisition_created` | *"Hey , it's time to visit your local collection center. Please check the email you just received for instructions."* | | `draw_completed` | *"Your on-site collection is complete! We're delivering your sample to the lab for processing."* | | `completed` | *"Your results have finished processing and should be ready soon."* | | `cancelled` | *"Hey, your order for the on-site collection has been cancelled. If this is by accident, please contact support."* | SMS Texts are customizable, and can be enabled or disabled individually. ### Emails For emails, the following table describes what information each email contains for each Order Statuses: | Order Status | Email Content Description | | --------------------- | ------------------------------------------------------------------------------------------------------ | | `requisition_created` | An email with confirmation of the partner Lab and additional instructions will be sent to the patient. | Emails can be customised and sent from your own domain. ## Disable Communications In this case, no communications will be sent from Junction. You have the ability to produce completely customized communications using the [`Webhook events`](/lab/on-site-collection/webhooks) described previously. # Order Lifecycle Source: https://docs.junction.com/lab/on-site-collection/order-lifecycle This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. The **On-site Collection** orders are composed of one lifecycle, the **Order** lifecycle, detailed in the following section. ## Order Lifecycle As discussed in [`Lab Test Lifecycle - Statuses`](/lab/workflow/lab-test-lifecycle#statuses), each lab testing modality has the following format `[HIGH-LEVEL STATUS].[TEST MODALITY].[LOW-LEVEL STATUS]` For on-site collections, this will look like: `[HIGH-LEVEL STATUS].on_site_collection.[LOW-LEVEL STATUS]` For each modality, there can be multiple **low-level status**, for **On-site Collection** the possible low-level status are: * `ordered`: Junction received the order, stored it into our system, and started processing it asynchronously. * `requisition_created`: An order requisition form was validated and created with the partner laboratory, making the order available to be carried out. * `requisition_bypassed`: An order requisition form wasn't created when the order was placed with us because it already existed. * `draw_completed`: The phlebotomy was completed and the blood was drawn successfuly. * `partial_results`: The laboratory has started making partial results available. * `completed`: The laboratory processed the blood sample and final results are available. * `cancelled`: The order was cancelled by either the patient, Junction or you. This closely models that of walk-in tests. Refer to that [order lifecycle](/lab/walk-in/order-lifecycle#order-lifecycle) for a diagramatic explanation. # Overview Source: https://docs.junction.com/lab/on-site-collection/overview This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. On-site collections are one of our offered modalities. Their locations are customer-managed settings where blood draws occur outside of traditional lab-owned patient service centers β€” such as primary care clinics, employer health centers, or event-based wellness pop-ups. ## Ordering Flow overview To achieve this, a high-level overview of the process is defined as: * An order is placed in Junction's system through our Dashboard or API. * The order is added to a background queue and additional checks are made before a test requisition is created with the chosen Laboratory. * After the requisition is created, some form of communication is carried out with the patient. * After that, the patient can go at any time they want to the draw location to have their blood drawn. * The Laboratory processes the patient's blood sample and generates the required results. * Junction exposes the results via API as soon as they are available, both on PDF and structured data via API. To find out more details, see [`Order Lifecycle`](/lab/on-site-collection/order-lifecycle), [`Communications`](/lab/on-site-collection/communications) and [`Webhooks`](/lab/on-site-collection/webhooks). # Webhooks Source: https://docs.junction.com/lab/on-site-collection/webhooks This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. The following webhook events are of interest, when placing an On-site collection. Those are described in detail in the following sections. ## Order webhook events Based on the status present in [`Order Lifecycle`](/lab/on-site-collection/order-lifecycle), Junction will trigger two kinds of webhook events, [`labtest.order.created`](/event-catalog/labtest.order.created) and [`labtest.order.updated`](/event-catalog/labtest.order.updated). The `labtest.order.created` event is triggered when an order is created in the system, having the `ordered` status, and all subsequent status changes will trigger a `labtest.order.updated` event in the system. The `partial_results` status does not trigger a Webhook unless specifically requested from Junction. The webhook payload body will have the following information if the Order is in the `completed` status: ```json On-site Collection Order Updated theme={null} { "id": "84d96c03-6b1c-4226-ad8f-ef44a6bc08af", "team_id": "6353bcab-3526-4838-8c92-063fa760fb6b", "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "patient_details": { "dob": "2020-01-01", "gender": "male" }, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789" }, "details": { "type": "on_site_collection", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "completed", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.on_site_collection.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.on_site_collection.requisition_created" }, { "id": 33, "created_at": "2022-01-03T00:00:00Z", "status": "sample_with_lab.on_site_collection.draw_completed" }, { "id": 4, "created_at": "2022-01-04T00:00:00Z", "status": "sample_with_lab.on_site_collection.partial_results" }, { "id": 5, "created_at": "2022-01-05T00:00:00Z", "status": "completed.on_site_collection.completed" } ], "origin": "initial", "order_transaction": { "id": "6424d45-ee1a-49c6-ad0c-5769b8e03fc1", "status": "completed", "orders": [ { "id": "84d96c03-6b1c-4226-ad8f-ef44a6bc08af", "created_at": "2022-01-01T00:00:00Z", "updated_at": "2022-01-05T00:00:00Z", "low_level_status": "completed", "low_level_status_created_at": "2022-01-05T00:00:00Z", "origin": "initial" } ] } } ``` # Compendium Search Source: https://docs.junction.com/lab/overview/compendium-search This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. `POST /v3/compendium/search` searches the compendium in two modes: * `canonical`: find and rank canonical tests only. * `crosswalk`: select one canonical test, then map it into per-lab candidates. ## Request body ```json theme={null} { "mode": "canonical | crosswalk", "query": "string (optional in crosswalk, required in canonical)", "loinc_set_hash": "string (crosswalk only)", "labs": ["labcorp", "quest", "bioreference", "sonora_quest"], "include_related": true, "limit": 3 } ``` ### Validation rules #### Canonical mode * `query` is required and must be non-empty. * `loinc_set_hash` is not allowed. * `limit` must be `1..10`. #### Crosswalk mode * Exactly one of `query` or `loinc_set_hash` is required. * `limit` must be `1..20`. * `include_related` controls whether related canonical candidates are returned. If validation fails, the API returns HTTP `422`. ## Search modes ### 1) `canonical` Purpose: return ranked canonical tests only. Behavior: * Returns: * `selected_canonical`: top candidate (or `null`). * `canonical_candidates`: ranked list (up to `limit`). * `per_lab` and `related` are empty in this mode. ### 2) `crosswalk` Purpose: choose one canonical test and expand it across labs. Entry points: * By text query: pick best canonical from query. * By `loinc_set_hash`: load canonical directly by hash. Behavior: * If no canonical is found, response has: * `selected_canonical = null` * `canonical_candidates = []` * no per-lab candidates. * If canonical is found, returns: * one `selected_canonical` and same item in `canonical_candidates` * `per_lab`: candidates grouped by lab slug * `related`: related canonical tests (if `include_related=true`) ## Canonical tests "Canonical tests" are normalized, cross-lab test concepts. They are used to: * normalize query intent (`display_name`, `aliases`, `loinc_codes`, etc.) * create one canonical anchor (`selected_canonical`) * crosswalk into concrete provider/lab tests (`per_lab`) Canonical ranking combines match quality and popularity score. The selected labs filter which per-lab candidates are returned in crosswalk mode. ## Output schema ```json theme={null} { "mode": "canonical | crosswalk", "selected_canonical": { "loinc_set_hash": "string", "display_name": "string", "aliases": ["string"], "loinc_codes": ["string"], "provider_ids": ["string"], "loinc_components": ["string"], "loinc_groups": ["string"], "popularity_score": 0.0, "confidence": 0.0 }, "canonical_candidates": [ { "loinc_set_hash": "string", "display_name": "string", "aliases": ["string"], "loinc_codes": ["string"], "provider_ids": ["string"], "loinc_components": ["string"], "loinc_groups": ["string"], "popularity_score": 0.0, "confidence": 0.0 } ], "per_lab": { "quest": [ { "marker_id": 0, "lab_id": 7, "lab_slug": "quest", "name": "string", "result_names": ["string"], "provider_id": "string", "loinc_set_hash": "string", "loinc_codes": ["string"], "loinc_components": ["string"], "loinc_groups": ["string"], "relation": "exact | subset | superset | overlap | suggested", "confidence": 0.0, "reason_codes": ["EXACT_LOINC_SET | SUBSET | SUPERSET | OVERLAP | NAME_MATCH | SYNONYM_MATCH"], "marker_popularity_score": 0.0 } ] }, "related": [ { "canonical": { "loinc_set_hash": "string", "display_name": "string", "aliases": ["string"], "loinc_codes": ["string"], "provider_ids": ["string"], "loinc_components": ["string"], "loinc_groups": ["string"], "popularity_score": 0.0, "confidence": 0.0 }, "relation": "exact | subset | superset | overlap | suggested", "confidence": 0.0, "reason_codes": ["EXACT_LOINC_SET | SUBSET | SUPERSET | OVERLAP | NAME_MATCH | SYNONYM_MATCH"] } ] } ``` Notes: * `confidence` is model/service-generated and relative to result quality. * Omitting `labs` defaults to all supported labs (`labcorp`, `quest`, `bioreference`, `sonora_quest`). ## Example requests ### Canonical mode ```bash theme={null} curl -X POST "$BASE_URL/v3/compendium/search" \ -H "Content-Type: application/json" \ -H "x-vital-api-key: $API_KEY" \ -d '{ "mode": "canonical", "query": "hemoglobin a1c", "labs": ["quest", "labcorp"], "limit": 5 }' ``` ### Crosswalk mode by query ```bash theme={null} curl -X POST "$BASE_URL/v3/compendium/search" \ -H "Content-Type: application/json" \ -H "x-vital-api-key: $API_KEY" \ -d '{ "mode": "crosswalk", "query": "comprehensive metabolic panel", "labs": ["quest", "bioreference"], "include_related": true, "limit": 3 }' ``` ### Crosswalk mode by canonical hash ```bash theme={null} curl -X POST "$BASE_URL/v3/compendium/search" \ -H "Content-Type: application/json" \ -H "x-vital-api-key: $API_KEY" \ -d '{ "mode": "crosswalk", "loinc_set_hash": "f0a1b2c3d4", "labs": ["labcorp", "quest"], "include_related": false, "limit": 3 }' ``` For endpoint schema and try-it behavior, see the API reference page: [Compendium Search](/api-reference/lab-testing/compendium/search). # Ordering Idempotency Source: https://docs.junction.com/lab/overview/idempotency Idempotent behavior on the order flow The API supports idempotency for safely retrying requests without accidentally performing the same operation twice. When creating an order via the [POST /v3/order,](/api-reference/lab-testing/create-order) you can supply a `X-Idempotency-Key` header. Then, if a connection error occurs, you can safely repeat the request without risk of creating a second order. This works by saving the resulting status code and body of the first request made for any given idempotency key, regardless of whether it succeeds or fails. Subsequent requests with the same key return the same result. A client generates an idempotency key, which is a unique key that the server uses to recognize subsequent retries of the same request. How you create unique keys is up to you, but we suggest using V4 UUIDs, or another random string with enough entropy to avoid collisions. You can remove keys from the system automatically after they’re at least 24 hours old. We generate a new request if a key is reused after the original is pruned. We save results only after the execution of an endpoint begins. If incoming parameters fail validation, or the request conflicts with another request that’s executing concurrently, we don’t save the idempotent result because the API endpoint does not initiate the execution. You can retry these requests. # Insurance Ordering Source: https://docs.junction.com/lab/overview/insurance This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. Junction supports ordering with commercial insurance billing. To set this up, there are a set of steps required. This document explains the complete flow for ordering via commercial insurance. ## Setting Insurance Data **Medicare and Medicaid location inference closed beta** If insurance is covered by Medicare or Medicaid, we have closed beta support for inferring the plan to use based on a patient's address. This endpoint supports `payor_code`s with values of `MEDFED` (Medicare) and `MAIDFED` (Medicaid) to do this. For example, supplying a `payor_code` of `MEDFED` for a patient that lives in Arizona will create insurance data using Arizona's Medicare plan payor code. Medicare and Medicaid location inference is in closed beta. Get in touch with your Customer Success manager for more information or check out the [API docs](/api-reference/user/create-insurance) on the beta channel. To place an order for a `user` in Junction with commercial insurance, you must first provide Junction with insurance data. This can be done via the [`POST /{user_id}/insurance`](/api-reference/user/create-insurance) endpoint, with the following payload: Note that you can only set insurance data for users that already have patient information set, either via an order or via the [`PATCH /v2/user/{user_id}/info`](/api-reference/user/upsert-info). It is expected that insurance data will become outdated or change with time. It is the customer's responsability to update this data with Junction in order to minimise the risk of a denied claim. ```json theme={null} "insurance": { "payor_code": "string", "member_id": "string", "group_id": "string", // optional "relationship": "Self, Spouse, Other", "insured": { "first_name": "string", "last_name": "string", "dob": "YYYY-MM-DD", "gender": "string", "address": { "first_line": "string", "second_line": "string", // optional "zip_code": "string", "state": "string", "city": "string", }, "phone_number": "string", "email": "string" }, "guarantor": { "first_name": "string", "last_name": "string", "dob": "YYYY-MM-DD", "gender": "string", "address": { "first_line": "string", "second_line": "string", // optional "zip_code": "string", "state": "string", "city": "string", }, "phone_number": "string", "email": "string", } // optional if relationship is Self } ``` ### Insured, Guarantor and Relationship First, let's understand the `relationship`, `insured` and `guarantor` fields. `insured` refers to the insured person, and must always be provided. The `relationship` field, is the relationship between the patient and the holder of the insurance. For example, if the insured person and the insurance holder are the same, then the relationship is `Self`. The `guarantor` is an optional field, that must be provided when the relationship is **NOT** `Self`. The guarantor is the financially responsible party. If provided when the relationship is `Self`, we will store it, but it may not be propagated to all lab partners, as not all partners accept a guarantor in this situation. ### Payor Code The `payor code` is a lab specific identifier for an insurance company. Junction has abstracted this away into our own Junction payor codes, allowing customers to supply one lab-agnostic payor code for all labs. To obtain this code, use the [Search Payor Code](/api-reference/lab-testing-insurance/search-payor-get) endpoint, using the insurance company name, and select the `code` from your insurance company. It is also possible to use this endpoint to search using external provider's payor codes (e.g Change HealthCare or Availity). ```json theme={null} [ { "code": "AARPA", "name": "AARP", "aliases": [ "AARP", "AARP" ], "org_address": { "first_line": "PO BOX 740819", "second_line": null, "country": "US", "zip": "30374", "city": "ATLANTA", "state": "GA" } }, ] ``` ## Ordering Once the above steps are complete, you can place an order as usual, with two differences. In order to trigger the commercial insurance flow, you must supply the following extra fields in the [POST /order](/api-reference/lab-testing/create-order) payload: ```json theme={null} { "icd_codes": ["ICD.10"], "billing_type": "commercial_insurance" } ``` ### Billing type By default, Junction orders are Client Bill. In order to trigger the insurance flow, you must supply the `billing_type` field, with `commercial_insurance`. ### Diagnosis Codes Insurance orders require diagnosis codes to be supplied. You can search for diagnosis codes in our [Search ICD Code](/api-reference/lab-testing-insurance/search-diagnosis) endpoint. ## Insurance Availability Not all labs and not all states are cleared for insurance ordering. You can verify if a zip code is served for insurance via the [`GET /v3/order/area/info`](/api-reference/lab-testing/area-info) endpoint. If a particular lab supports insurance, then you should see `commercial_insurance` in the supported bill types. ```json theme={null} { "zip_code": "85007", "central_labs": { "labcorp": { "patient_service_centers": { "within_radius": 15, "radius": "25" }, "supported_bill_types": [ "commercial_insurance" ] }, ... } } ``` ## Error Cases 1. Order with `commercial_insurance` billing for a lab that does not support it. 2. Order with `commercial_insurance` billing for a state that does not support it. 3. Order with `commercial_insurance` billing and provide no ICD codes or invalid ICD codes. 4. Order with `commercial_insurance` billing and the user has no insurance data. # Introduction Source: https://docs.junction.com/lab/overview/introduction Junction's lab test API allows digital health companies to carry out at-home lab testing. Companies can use the API to order a variety of tests, using multiple sampling methods. We partner with CLIA and CAP certified labs across the United States that cover a range of different diagnostic tests. For example lipids panels, metabolic profiles, hormone tests and more. We collect these test samples via at-home kits, or phlebotomy. ### Features Our lab test API gives you an end-to-end experience, from ordering a test all the way to receiving results, all with one API call! What we offer: * Multiple [test modalities](/lab/overview/testing-modalities) through one unified API. * 50 State Physician network - you can use your own physician if you have one, or we will automatically assign one from our network. * Updates throughout the test lifecycle - we automatically send you webhook updates and SMS updates to your patients so that you both never miss an update. * Test results: we will update you once the test results are ready. In case of abnormal results, we also provide follow-ups through our Physician network. * 1:1 support: after production launch, you will have direct access to Slack and our engineers. ### Start integrating You can sign-up today to our sandbox environment. The [quickstart](/lab/overview/quickstart) guide will help you getting from zero to ordering tests in less than 30 minutes! Once the integration is ready, switching to production will just be a matter of switching API keys. ### Production launch As a first step, we need to define what type of testing you want to offer to your users. We will have an introduction call to go over the test, projected volumes, customizations for kits and other questions you might have. Once we have all this information, we will begin setting up the test kits for distribution. Once the integration is complete, you will have access to the dashboard to start ordering tests via our API. In [this page](https://tryvital.io/labs), you can check out some examples of the test panels we offer, and book an introductory call with us. ### Support We will assist you throughout the whole integration with Junction. After launch, you will have a dedicated account manager with access to Slack, and 1:1 support with our engineers. ### Coverage Our lab test API is available throughout the US. This is our current coverage by test modality: * [At-home test kits](/lab/overview/testing-modalities#at-home-test-kits): 49 states (all excluding NY). * [Walk-in tests](/lab/overview/testing-modalities#walk-in-tests): 49 states (all excluding NY). * [At-home phlebotomy](/lab/overview/testing-modalities#at-home-phlebotomy): 35 states at the moment. Full coverage coming soon. * [On-site collection](/lab/overview/testing-modalities#on-site-collection): In beta testing To find out more about our coverage, you can use the [area info endpoint](/api-reference/lab-testing/area-info) - this endpoint takes a zip code and tells whether that area is covered by our service or not. Or you can contact us at [support@junction.com](mailto:support@junction.com) for more info. # Lab Accounts Source: https://docs.junction.com/lab/overview/lab-accounts Lab accounts encapsulate the information needed to place orders and receive results for a particular lab. They are tied to Orgs and can be dynamically linked to as many Teams within the Org as desired. When an active lab account is linked to a Team, that Team can place orders using that lab account. A Team can have multiple lab accounts linked, even for a single lab, but an individual order must be placed with a single lab account. The [Create Order](/api-reference/lab-testing/create-order) endpoint accepts a `lab_account_id` to specify the lab account. If `lab_account_id` is not provided when placing an order, we will try to determine an appropriate lab account based on available information, but if we cannot unambigously find one lab account that should be used, we might not be able to place the order. We recommend including the `lab_account_id` when possible. Lab accounts for an Org can be viewed using the [Get Lab Accounts (Org-level)](/api-reference/org-management/lab-accounts/get-lab-accounts) endpoint. Teams can fetch their available lab accounts using [Get Lab Accounts (Team-level)](/api-reference/lab-testing/lab_accounts). Team linkage is managed only through the [Update Lab Account Teams (Org-level)](/api-reference/org-management/lab-accounts/update-lab-account-teams) endpoint. ## Which endpoint should I use? Use both endpoints for different jobs. They are complementary, not interchangeable. | Topic | Org-level Get Lab Accounts | Team-level Get Lab Accounts | | ------------------ | --------------------------------------------------------------------------- | ----------------------------------------------------------- | | Scope | Returns lab accounts for the Org, across teams | Returns lab accounts available to one team context | | Typical user | Org admin or platform integrator | Team-scoped integration | | Best use case | Org-wide visibility and management | Team-specific runtime operations | | Filtering behavior | Can be used to review accounts at the Org level and understand team linkage | Returns only accounts available in the current team context | Recommendation: * Use the team-level endpoint when operating within one team context. * Use the org-level endpoint when you need cross-team, org-level management views. * There is no team-level endpoint for updating lab account team linkage. Practical examples: * **Org-level endpoint:** An operations admin audits all lab accounts in the Org and confirms which teams are linked to each account. * **Team-level endpoint:** A team-specific ordering workflow fetches only the lab accounts that team can use before creating an order. You can also view lab accounts tied to your Org on the [Lab Interface Requests page of your Org Config](https://app.junction.com/org/lab-interface-requests) in the Junction dashboard. # Patient Service Centers and Appointments Source: https://docs.junction.com/lab/overview/locations # Finding the Nearest Serviceable Patient Service Centers Verifying if a particular zip code is serviceable is an important step, as not all labs have patient service centers within a state or within a reasonable distance. The recommended high-level flow for verifying PSC availability at Junction is: 1. Fetch PSC location data via the `GET` [Area Info](/api-reference/lab-testing/area-info) endpoint, the response payload should look like this: ```json theme={null} { "zip_code": "85004", "central_labs": { "labcorp": { "within_radius": 5, # the number of PSC's within radius of provided zip code "radius": "25", # miles "capabilities": ["stat", "appointment_scheduling_with_lab"] # aggregate list of capabilities provided by PSCs within the radius } } ... } ``` 2. Then you can query the `GET` [Psc Info](/api-reference/lab-testing/psc-info) endpoint for specific information on the PSCs within the radius of the provided zip code, which will look like this: ```json theme={null} { "lab_id": 27, "slug": "labcorp", "patient_service_centers": [ { "metadata": { "name": "LABCORP", "state": "AZ", "city": "Phoenix", "zip_code": "85006", "first_line": "1300 N 12th St", "second_line": "Ste 300", "phone_number": "480-878-3988", "fax_number": "844-346-5903", "hours": null }, "distance": "25", "site_code": "ABC", "capabilities": ["stat", "appointment_scheduling_with_lab"] }, ] } ``` A **capability** is a specific service that a PSC provides, for example, STAT testing or appointment scheduling. # Booking appointments at Patient Service Centers Currently only available for Quest lab locations. ## **Appointment Scheduling Capability** The ability to schedule appointments is designated with the `capabilities` field in the response from the `GET` [Area Info](/api-reference/lab-testing/area-info) or `GET` [Psc Info](/api-reference/lab-testing/psc-info) endpoints as: * `appointment_scheduling_via_junction`: Indicates that the PSC allows scheduling appointments via Junction’s API. * `appointment_scheduling_with_lab`: Indicates that the PSC allows scheduling appointments directly with the lab, either online or via phone. This capability also indicates that scheduling appointments with this location is **not** available through Junction’s API. If neither scheduling capability is present, that means the PSC either does not offer appointment scheduling, or its scheduling availability is unknown. At this time, Arizona Quest lab locations cannot be scheduled through Junction's API. These labs are operated by Sonora Quest, and their scheduling service does not currently support third-party API scheduling. ## Appointment Availability Use the `POST` [PSC Appointment Availability](/api-reference/lab-testing/psc-schedulling/appointment-psc-availability) endpoint to obtain the available slots. You can provide the `site_code` you obtain from the `GET` [Psc Info](/api-reference/lab-testing/psc-info) endpoint, or supply a zip code directly. * If you provide a zip code, a maximum of 3 PSC locations will be displayed. Note that since this endpoint can return availability for multiple PSCs, there may be multiple time zones associated with the returned data. * ⚠️ The individual start and end times are in UTC, and each location has its own `iana_timezone` key, which should be used to convert these start and end dates to the correct time zone. * The overarching `timezone` key will always be `null` in this endpoint. ## Booking Patient Service Centers have access to the booked appointment, which can be canceled or updated outside of our system. When this occurs, the lab does not expose this to us, and so we do not have visibility, and it will not be reflected in our system or API. In order to book these appointments, you can use the `POST` [PSC Appointment Availability](/api-reference/lab-testing/psc-schedulling/appointment-psc-availability) endpoint to find available appointment slots. You can then [book](/api-reference/lab-testing/psc-schedulling/appointment-psc-booking), [reschedule](/api-reference/lab-testing/psc-schedulling/appointment-psc-rescheduling), or [cancel](/api-reference/lab-testing/psc-schedulling/appointment-psc-cancelling) with Junction as many times as you want. To allow the patient to track their appointment individually, we expose the `external_id` parameter in the appointment endpoints and webhooks. * This code is the unique identifier for the appointment at the provider and should be made available to the patient. * We provide it to the patient via [SMS communications](/lab/walk-in/communications) for the appointment. ### External ID Format The `external_id` field contains the provider's unique appointment identifier: | Provider | Format | Example | Description | | -------- | ------------- | -------- | ------------------------------------------------------ | | Quest | 6-letter code | `ABCDEF` | Used by Quest to identify appointments in their system | # Order and Follow-up Physician Source: https://docs.junction.com/lab/overview/physicians For an order to be valid/complete, both the request and the results need to be validated by a physician. As such, Junction provides three flows: ## 1. Order and Results through Junction Physician Network The validation of both the order and the results is done by Vital’s physician network. For each order, Junction's physician network validates the requisition and, when the results are available, they are uploaded to Vital’s physician network to be evaluated for abnormal/critical results. On the physician's judgement, the patient may receive a phone call regarding their results. ## 2. Order and Results with Customer Physician Network In this flow, the Customer must specify a `physician` when making an order request. Both the order and the results are the responsability of the Customer's chosen physician. ## 3. Order with Junction Physician Network and Results with Customer Physician Network In this flow, the Customer does not specify a `physician` when making an order request - they use Vital’s physician network. However, the results aren’t uploaded to Vital’s physician network and it's the Customer's physicians responsability to validate the results and do the follow ups. When there are critical results, Vital’s physician network will always be notified. # Quickstart Source: https://docs.junction.com/lab/overview/quickstart We have carefully designed Junction’s API to ensure a seamless and hassle-free experience for developers. You can get up and running with just a few lines of code. In this guide, we will walk you through the essential steps to order your first lab test. ### What you need * If you haven't done so yet, you can get API keys by signing up for a Junction account in the [Dashboard](https://app.junction.com). Detailed instructions are available [here](/home/quickstart#1-api-keys). * One of our [client libraries](/home/libraries). The lab testing API is available for the [python](https://pypi.org/project/vital/) and [node](https://www.npmjs.com/package/@tryvital/vital-node) SDKs. If none of those fit your use-case, you can always directly call the API. Email [support@junction.com](mailto:support@junction.com) for specific library support. In the following, replace `{{BASE_URL}}` with your team's [environment URL](/home/environments). ### (1) Creating a user In the context of our API, a user refers to the patient who will be undergoing the lab test. All you need to provide is a string identifying the user on your side (`client_user_id`). The response to this call returns the userId on our side, which you'll be able to use for future requests. You can find more details in our [API quickstart guide](/home/quickstart#3-creating-your-first-user). ```bash Creating a Junction user (bash) theme={null} curl --request POST \ --url {{BASE_URL}}/v2/user/ \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --header 'x-vital-api-key: ' \ --data '{"client_user_id":""}' ``` ```js Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { UserCreateBody } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: UserCreateBody = { clientUserId: "" } const data = await client.user.create(request) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.user.create(client_user_id="") ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.user.requests.UserCreateBody; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); UserCreateBody request = UserCreateBody .builder() .clientUserId("") .build(); var data = vital.user().create(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.UserCreateBody{ ClientUserId: "", } response, err := client.User.Create(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ### (2) Listing Available Tests To retrieve the set of lab tests you have access to, use the `/v3/lab_tests` API endpoint. In Sandbox, you will already have access to a default set of tests. Once you're ready to head to production, we can [setup a call](/lab/overview/introduction#production-launch) and get you ready for launch! ```bash Listing available tests (bash) theme={null} curl --request GET \ --url {{BASE_URL}}/v3/lab_tests/ \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.get(); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.get(); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().get(); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.Get(context.TODO()) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` This will return a list of all the available tests to you. They include a description, turnaround time and price: ```json Response theme={null} { "lab_tests": [ { "id": "e2eaa385-a311-4f17-b33f-2165e3d24dd9" "name": "Lipids Panel", "description": "Cholesterol tests", "sample_type": "dried blood spot", "method": "testkit", "price": 10.0, "is_active": True, "lab": { "slug": "USSL", "name": "US Specialty Lab", "first_line_address": "123 Main St", "city": "New York", "zipcode": "10001", }, "markers": [ { "name": "Thyroid Stimulating Hormone", "slug": "tsh", "description": "", "min_value": 100, "max_value": 200, "unit": "fg/L" } ], } ] } ``` You can then use the test `id` when creating an order. ### (3) Placing an order Ordering a test for your user is as simple as making an API call. Ensure all patient name fields (`first_name`, `last_name`, `receiver_name`) follow our [name validation requirements](/lab/workflow/order-requirements#patient-name-validation): ```bash Ordering a test (bash) theme={null} curl --request POST \ --url {{BASE_URL}}/v3/order/ \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' \ --data ' { "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "patient_details": { "dob": "2022-07-06T22:20:26.796Z", "gender": "male | female", "email": "test@test.com" }, "patient_address": { "receiver_name": "John Doe", "street": "Hazel Road", "street_number": "102", "city": "San Francisco", "state": "CA", "zip": "91789", "country": "U.S.", "phone_number": "+14158180852" }, "lab_test_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "physician": { "first_name": "John", "last_name": "Doe", "email": "john@doe.com", "npi": "123456789", "licensed_states": ["CA", "NY"], "created_at": "2022-07-06T22:20:26.796Z", "updated_at": "2022-07-06T22:20:26.796Z" } } ' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.gender import Gender from vital.types.patient_address_compatible import PatientAddressCompatible from vital.types.patient_details import PatientDetails from datetime import datetime client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.create_order( user_id="63661a2b-2bb3-4125-bb1a-b590f64f057f", lab_test_id="5b41f610-ebc5-4803-8f0c-a61c3bdc7faf", patient_details=PatientDetails( first_name="John", last_name="Doe", dob=datetime.fromisoformat("2020-01-01"), gender=Gender.MALE, phone_number="+1123456789", email="email@email.com" ), patient_address=PatientAddressCompatible( receiver_name="john Doe", first_line="123 Main St.", second_line="Apt. 208", city="San Francisco", state="CA", zip="91189", country="US", phone_number="+1123456789" ), ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { CreateOrderRequestCompatible} from '@tryvital/vital-node/api/resources/labTests'; import { Gender } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: CreateOrderRequestCompatible = { userId: "63661a2b-2bb3-4125-bb1a-b590f64f057f", labTestId: "5b41f610-ebc5-4803-8f0c-a61c3bdc7faf", patientDetails: { firstName: "John", lastName: "Doe", dob: new Date("2020-01-01"), gender: Gender.Male, phoneNumber: "+1123456789", email: "email@email.com" }, patientAddress: { receiverName: "john Doe", firstLine: "123 Main St.", secondLine: "Apt. 208", city: "San Francisco", state: "CA", zip: "91189", country: "US", phoneNumber: "+1123456789" }, } const data = await client.labTests.createOrder(request) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.LabTestsGetAreaInfoRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); PatientDetails details = PatientDetails.builder() .firstName("John") .lastName("Doe") .dob(OffsetDateTime.parse("2020-01-01")) .gender(Gender.MALE) .phoneNumber("+1123456789") .email("email@email.com") .build(); PatientAddressCompatible address = PatientAddressCompatible .builder() .firstLine("123 Main St.") .city("San Francisco") .state("CA") .zip("91189") .country("US") .phoneNumber("+1123456789") .build(); CreateOrderRequestCompatible request = CreateOrderRequestCompatible .builder() .userId("63661a2b-2bb3-4125-bb1a-b590f64f057f") .labTestId("5b41f610-ebc5-4803-8f0c-a61c3bdc7faf") .patientDetails(details) .patientAddress(address) .build(); var data = vital.labTests().createOrder(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) details := &vital.PatientDetails{ FirstName: "John", LastName: "Doe", Dob: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Gender: vital.GenderMale, PhoneNumber: "+1123456789", Email: "email@email.com", } address := &vital.PatientAddressCompatible{ ReceiverName: vital.String("john Doe"), FirstLine: "123 Main St.", SecondLine: vital.String("Apt. 208"), City: "San Francisco", State: "CA", Zip: "91189", Country: "US", PhoneNumber: vital.String("+1123456789"), } request := &vital.CreateOrderRequestCompatible{ UserId: "63661a2b-2bb3-4125-bb1a-b590f64f057f", LabTestId: "5b41f610-ebc5-4803-8f0c-a61c3bdc7faf", PatientDetails: details, PatientAddress: address, } response, err := client.LabTests.CreateOrder(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```json Response theme={null} { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"), "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "patient_details": {"dob": "2020-01-01", "gender": "male"}, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789", }, "details": { "type": "testkit", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "shipment": { "id": "d55210cc-3d9f-4115-8262-5013f700c7be", "outbound_tracking_number": "", "outbound_tracking_url": "", "inbound_tracking_number": "", "inbound_tracking_url": "", "outbound_courier": "usps", "inbound_courier": "usps", "notes": "", "created_at": "2020-01-01T00:00:00.000Z", "updated_at": "2020-01-01T00:00:00.000Z", }, "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", }, }, "diagnostic_lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "method": "testkit", }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "collecting_sample", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.testkit.ordered", } ], } ``` As demonstrated in this Quickstart Guide, integrating with our API is straightforward, allowing you to access a wide range of lab tests with minimal effort. Placing an order for a lab test is as simple as making a request to the appropriate endpoint. With our user-friendly design and clear documentation, you can harness the power of our clinical test API to enhance your application and deliver a seamless experience to your users. # Testing in Sandbox Source: https://docs.junction.com/lab/overview/sandbox To enable Customers to simulate the ordering process in the sandbox environment, Junction provides simulated results and a way to transition an order through its [lifecycle](/lab/workflow/lab-test-lifecycle). ## Creating Lab Tests Every created team comes with 3 standard `Lab Tests` to allow the Customer to test all 4 collection modalities: * **Test Kit**: At-home collection using shipped test kits * **Walk-in Test**: Patient visits a Patient Service Center (PSC) location * **At-Home Phlebotomy**: A Mobile phlebotomist visits the patient's location * **On-Site Collection**: Collection at the customer's facility However, since most customers choose to have their own custom lab tests, you should create your own tests using the [API](/lab/workflow/create-test). This is encouraged, as Junction generates results based on the associated markers. More on this in the [results section](/lab/overview/sandbox#results). ## Lifecycle When an order is placed in the sandbox environment, it is "stuck" in the `ordered` state and will not progress unless triggered to do so. This can be done through the dashboard or through the [API](/api-reference/lab-testing/simulate-order). Initiating the simulation process will progress the order through the expected success states defined in the [lifecycle section](/lab/workflow/lab-test-lifecycle), triggering all expected webhooks, emails, and SMS's. ## Collection Method Restrictions ### At-Home Phlebotomy When testing At-Home Phlebotomy appointments, there are only two specific zip codes allowed: * **85004** (Phoenix, AZ) - Uses GetLabs provider * And only one specific address: `West Lincoln Street, Phoenix, AZ 85004, USA` * **54650** - Uses PhlebFinders provider ### Quest PSC Appointments In the sandbox, Quest appointment slots are generated automatically with these patterns: **Availability:** * Same day: Next hour until 5 PM EST (if before 4 PM) * Future days: 7 AM - 12 PM EST * 20-minute intervals for all slots **Timezone:** All sandbox appointments use `America/New_York` timezone regardless of location (production uses actual PSC timezones). We have a feature request in our backlog to support more representative sandbox appointment slots, including handling time zones specific to locations. We don't have a timeline for this right now, but if you'd like to be notified when it is implemented, please contact our support team. ## Results To allow Customers to test, Junction generates fake results for each lab test based on the [expected markers](/lab/results/result-formats#expected-results). The generated JSON results will cycle through all possible [result types](/lab/results/result-formats#resulttype): * `numeric`: Numerical values with reference ranges * `comment`: Textual commentary results * `range`: Range-based results (e.g., "\<50") **Important Notes:** * Generated values might not match the expected unit * **PDF results are not dynamically generated** - Junction returns an example PDF that does not reflect the ordered test * Account numbers for shipping labels return a hardcoded value `1234567890` in sandbox ## Customizing the Response You can customize the end response of the results via the body of the [Simulate Order](/api-reference/lab-testing/simulate-order) endpoint. There are three avenues of customization currently: ### 1. Interpretation Supplying the `interpretation` field will set the result's status to the supplied value. If you set it to critical, then the critical result workflow will be triggered. ```json theme={null} { "interpretation": "critical" } ``` Available interpretations: * `normal`: Standard normal results (default) * `abnormal`: Results outside normal ranges * `critical`: Triggers critical result notifications and workflows ### 2. Result Types This field controls what result types will be generated. Omitting it will cycle through all available types. ```json theme={null} { "result_types": ["numeric", "comment"] } ``` Available result types: * `numeric`: Numerical results with reference ranges * `comment`: Text-based results and observations * `range`: Range-based results ### 3. Missing Results Supplying this field will generate missing biomarkers in the result to simulate incomplete lab processing. ```json theme={null} { "has_missing_results": true } ``` This simulates scenarios where some biomarkers fail processing or are unavailable, which is common in real-world lab operations. ## Environment-Specific Behavior ### Sandbox vs Production Differences | Feature | Sandbox | Production | | -------------------- | -------------------------------------------- | ----------------------------------------- | | Order Progression | Manual trigger required | Automatic, based on lab processing times. | | PDF Results | Static example PDF | Dynamic lab-generated PDFs | | Account Numbers | Hardcoded `1234567890` | Real lab account numbers | | Phlebotomy Locations | Restricted to specific zip codes | Full coverage area | | Quest PSC Slots | Mock slots, `America/New_York` timezone only | Real Quest API integration | | Appointment Times | Restricted availability (see above) | Based on actual PSC availability | ## Testing Recommendations 1. **Use Custom Lab Tests**: Create tests with your specific biomarkers for realistic result simulation 2. **Test All Collection Methods**: Verify each modality works with your integration 3. **Test Error Scenarios**: Use missing results and different interpretations 4. **Test Appointment Workflows**: Book, reschedule, and cancel appointments to verify full flow # Testing Modalities Source: https://docs.junction.com/lab/overview/testing-modalities By integrating with our API, you will have access to the following testing modalities: * At-home test kit. * At-home phlebotomy visit. * Walk-in test visit. * On-site collection visit (in closed beta). ## At-home test kits This involves sending a kit to a patient's home. They will then be able to collect their sample specimen and send it back to our lab networks via mail. At home testing kits are comprised of a number of components such as:
* Mailer Box * Blade Lancets * Plasters * Gauze * ADX Card * Blood collection card * Saliva Tubes * Collection Supplies * Instruction Booklet Additional components can be included in the test kit box. To fully customize the kit, just contact us [support@junction.com](mailto:support@junction.com).
## At-home phlebotomy At-home phlebotomy requires a phlebotomist to visit a patient's home and carrying out the phlebotomy draw. The sample is then delivered to the lab by the phlebotomist. The flow for at-home phlebotomy orders is as follows: * [Place an order](/lab/overview/quickstart) via the API. * The order is processed in the background. For at-home phlebotomy, this means generating the requisition form for the lab test. * Once the requisition form is ready, a webhook update and an email to the patient are sent. The email will let them know they can now book an appointment. * The patient goes through Junction's appointment booking dashboard, where they can select a location and time from the available slots. * Once the appointment takes place and the sample is drawn, it will be taken to the lab. * Once the lab analyzes the results, the order is complete and the results can be [fetched](/lab/results/result-formats). ## Walk-in tests Walk-in tests require the patient to visit a lab location (often referred to as a Patient Service Center or PSC)β€”such as Quest, Labcorp, or BioReferenceβ€”to have their sample collected. A requisition form is needed to perform the test, which Junction’s API generates if the patient meets the required conditions. This workflow closely mirrors the at-home phlebotomy flow, except that appointment scheduling is not required. With a "walk-in" test, a patient can simply *walk in* to a PSC or they can schedule an appointment (Quest only). ## On-site collection On-site collection also require the patient to visit a Patient Service Center closely mirrors the walk-in test flow. Scheduling an appointment is not necessary. On-site collections are currently in beta testing. Expanded support for on-site or in-clinic draws coming soon! # Turnaround Times Source: https://docs.junction.com/lab/overview/turnaround-times For a comprehensive Dashboard-focused overview of turnaround times, check out [the product guide](https://support.junction.com/articles/9137043704-turnaround-time). This page focuses on the API aspects. We have two different turnaround time metrics: 1. Common turnaround time which 95% of orders should fall within. 2. Worst-case turnaround time which 99% of orders should fall within. Furthermore, each operates on three distinct levels: 1. Markers 2. Panels 3. Orders Turnaround times are calculated from historical data. For some tests, we may not yet have enough data to make a determination on its turnaround time. We won't include turnaround times for these tests in our API responses. We recalculate expected turnaround times regularly using the latest live data. Values might change dramatically from time to time to reflect real-world logistical circumstances. ## Marker turnaround times Marker turnaround times are based on historical orders of each marker. If a marker has been ordered enough such that we can make a statistically-viable determination on its average turnaround time, we will include these in payloads containing marker data. For example, the response body for [retrieving markers for a lab test](https://docs.junction.com/api-reference/lab-testing/lab-test-markers) includes the common and worst-case number of days to receive results for that marker after collection time. If we don't have enough data for that marker, those values will be null. ## Panel turnaround times Panels comprise of one or more markers, and the turnaround time for the panel is the largest turnaround time among its constituent markers. Just like in marker response bodies, lab test response bodies return the common and worst-case turnaround times (in days) for the panel. You can see this when [getting all available tests](https://docs.junction.com/api-reference/lab-testing/tests-paginated), for example. If any of the markers in the panel have null turnaround times, then the panel also has a null turnaround time. ## Order turnaround times An order's turnaround time is the same as its panel, so we don't repeat that information in the top level order response body. You can still find that information in the `lab_test` object within the response body if you need it. Instead, the top level body includes two related fields: 1. Expected result by date 2. Worst-case result by date These will be null unless the order's lab test has a non-null turnaround time *and* the sample collection has already been performed. The time of sample collection is what we add to the panel turnaround times to determine the expected and worst-case result by dates. [Retrieving an order](https://docs.junction.com/api-reference/lab-testing/get-order) is an example of where this is included. # Lab Report Parsing Source: https://docs.junction.com/lab/report-parsing/overview Junction's Lab Report Parsing API extracts structured data from lab report files (PDF, JPEG, or PNG) and matches biomarkers to standardized LOINC codes. This enables you to digitize and integrate lab results from any source into your application. ## How It Works The parsing workflow consists of three steps: 1. **Upload** – Submit a lab report file to create a parsing job 2. **Process** – Junction extracts biomarker data and matches to LOINC codes 3. **Retrieve** – Get structured results with standardized identifiers ```mermaid theme={null} sequenceDiagram participant App as Your App participant API as Junction API participant Parser as Parser Service App->>API: POST /lab_report/v1/parser/job (file) API->>Parser: Queue parsing job API-->>App: { job_id, status: "started" } Parser->>API: Processing complete API-->>App: Webhook: lab_report.parsing_job.updated App->>API: GET /lab_report/v1/parser/job/{job_id} API-->>App: { status: "completed", data: [...] } ``` ## Creating a Parsing Job Upload a lab report file to create a parsing job: ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) with open("lab_report.pdf", "rb") as f: job = client.lab_report.parser_create_job( file=f, needs_human_review=False ) print(f"Job ID: {job.id}") print(f"Status: {job.status}") ``` ### Supported File Formats | Format | Extension | Max Size | | ------ | --------------- | -------- | | PDF | `.pdf` | 10 MB | | JPEG | `.jpg`, `.jpeg` | 10 MB | | PNG | `.png` | 10 MB | ### Human Review Set `needs_human_review=True` if you want Junction to manually verify extracted results before marking the job as complete. This is useful for: * High-stakes clinical decisions * Complex multi-page reports * Reports with handwritten annotations This is not enabled by default, and you need to contact support to enable it. ## Job Statuses | Status | Description | | ---------------- | ------------------------------------- | | `started` | Job created and queued for processing | | `processing` | Extraction in progress | | `pending_review` | Awaiting human review (if requested) | | `completed` | Results ready | | `failed` | Processing failed | ## Retrieving Results Once a job completes, retrieve the extracted data: ```python Python theme={null} job = client.lab_report.parser_get_job(job_id="") if job.status == "completed": for result in job.data.results: print(f"{result.name}: {result.result} {result.unit}") print(f" LOINC: {result.loinc} ({result.loinc_slug})") ``` ### Result Structure Each extracted biomarker includes: * **name** – Biomarker name as it appears on the report * **result** – The value (numeric or text) * **unit** – Unit of measurement * **loinc** – LOINC code for standardization * **loinc\_slug** – Human-readable LOINC identifier * **reference\_range** – Normal range from the report * **interpretation** – `normal`, `abnormal`, or `critical` LOINC matching enables you to compare results across different labs and integrate with other health systems using standardized identifiers. ## Webhooks Subscribe to parsing job events to avoid polling: | Event | Trigger | | -------------------------------- | -------------------------------------------- | | `lab_report.parsing_job.created` | New parsing job submitted | | `lab_report.parsing_job.updated` | Job status changed (completed, failed, etc.) | Example webhook payload: ```json theme={null} { "event_type": "lab_report.parsing_job.updated", "data": { "id": "8eb0217f-4683-4a3c-adca-faf95ac65739", "status": "completed", "failure_reason": None, "data": { "metadata": { "patient_first_name": "Jane", "patient_last_name": "Doe", "dob": "1990-01-01", "lab_name": "Acme Labs", "date_reported": "2025-01-01", "date_collected": "2024-12-30", "specimen_number": "ABC123", }, "results": [ { "test_name": "Glucose", "value": "90", "type": "numeric", "units": "mg/dL", "max_reference_range": 99, "min_reference_range": 70, "source_panel_name": "CMP", "loinc_matches": [ { "loinc_code": "2345-7", "loinc_name": "Glucose [Mass/volume] in Serum or Plasma", "display_name": "Glucose", "aliases": [], "confidence_score": 0.99, } ], "interpretation": "normal", "is_above_max_range": False, "is_below_min_range": False, } ], }, "needs_human_review": False, "is_reviewed": False } } ``` ## Use Cases ### Patient-Uploaded Lab Results Allow patients to upload lab reports from other providers: ```python theme={null} def handle_patient_upload(file, patient_id): job = client.lab_report.parser_create_job(file=file) # Store job_id linked to patient for later retrieval store_pending_job(patient_id, job.id) return {"message": "Processing your lab report..."} ``` ### Historical Data Import Digitize paper records or legacy PDFs: ```python theme={null} import os for filename in os.listdir("historical_reports/"): with open(f"historical_reports/{filename}", "rb") as f: job = client.lab_report.parser_create_job( file=f, needs_human_review=True # Verify historical data ) print(f"Created job {job.id} for {filename}") ``` ## API Reference * [Create Lab Report Parser Job](/api-reference/lab-testing/lab-report-parsing/post-lab-report-parser-job) * [Get Lab Report Parser Job](/api-reference/lab-testing/lab-report-parsing/get-lab-report-parser-job) # Critical Results Source: https://docs.junction.com/lab/results/critical-results When our Lab partners return results to Junction, they may return specific interpretations for each Marker: * `normal`, the marker value is inside the lab reference range. * `abnormal`, the marker value is outside the lab reference range. * `critical`, the marker value falls in the critical range for that marker, which can be life-threatening. These values are returned in an `interpretation` field for results, both in each individual marker and the overall results object: ```json Resuls Object theme={null} { "metadata": { "age": 19, "dob": "18/08/1993", "clia_number": "12331231", "patient": "Bob Smith", "provider": "Dr. Jack Smith", "laboratory": "LabCorp", "date_reported": "2020-01-01", "date_collected": "2022-02-02", "specimen_number": "123131", "date_received": "2022-01-01", "status": "final", "interpretation": "critical" }, "results": [ { "name": "Hemoglobin", "slug": "hemoglobin", "unit": "g/dL", "notes": null, "value": 100.2, "timestamp": null, "max_range_value": 17.7, "min_range_value": 13.0, "is_above_max_range": true, "is_below_min_range": false, "interpretation": "critical" }, { "name": "Sodium", "slug": "unknown", "unit": "mmol/L", "notes": null, "value": 136, "timestamp": null, "max_range_value": 144, "min_range_value": 134, "is_above_max_range": false, "is_below_min_range": false, "interpretation": "normal" } ] } ``` Besides the `interpretation` field, there is also the `status` field, which can assume the values of `final` or `partial`, as the critical results are notified as soon as they're available, not all tests are guaranteed to have been processed by the lab. For critical results, the laboratory will contact a physician, so they can provide further information to the patient. The physician called is the Junction assigned physician. They act as the ordering physician when placing the order. After the call is made, and Junction has received the results from the laboratory, a `labtest.result.critical` event is sent. The webhook payload contains the following information: ```json Critical Results Webhook Payload theme={null} { "data": { "created_at": "2023-09-04T19:02:33.042533+00:00", "updated_at": "2023-09-04T19:02:47.106778+00:00", "order_id": "93ae03a7-626f-4cf4-802b-1fb6b3c339c7", "sample_id": "W4123LKB3VTL", "status": "final", "interpretation": "critical", "team_id": "482gh249-cd9d-4049-bb42-db5682cdf1d2", "user_id": "db5d34d5-bf7b-4e25-b119-9asd53694f45" }, "event_type": "labtest.result.critical" } ``` After you receive the webhook, you can use the [`GET /order/{order_id}/result`](/api-reference/results/get-results) endpoint to fetch the raw results. You can test the webhook event by using the [Junction Dashboard](https://app.junction.com) Webhooks section, where you can register an endpoint and subscribe to the `labtest.result.critical` event. The following images show what the screen should look like after you have an endpoint registered and have selected the `Testing` section inside the endpoint configurations. You can then click the `Send Example` button and should receive a HTTP call in the registered url. # Follow up Source: https://docs.junction.com/lab/results/follow-up When the test results are ready, you will receive a `labtest.order.updated` webhook where the order has a `complete` status. That means the results are ready and can be [fetched](/lab/results/result-formats). In case of abnormal results, our physician will determine if a call with the patient is required, and if so, they will follow up to figure out the next steps. Please keep in mind that the follow up call with the patient only happens for orders processed by our physician network. If you provided your own physician when you placed the order, you will be responsible for following up with your patients. # Galleri Multi-Cancer Early Detection Results (GRAIL) Source: https://docs.junction.com/lab/results/galleri-results ## Overview The Galleri test (by GRAIL) is a multi-cancer early detection blood test available exclusively through the Quest lab provider. Unlike standard lab biomarkers that return numeric values with reference ranges, Galleri results use coded values (enums) to indicate cancer detection status, predicted cancer signal origin, and anatomical subcategories. ## Result Structure Comparison ### Standard Numeric Biomarker Result Typical lab results for biomarkers like cholesterol follow this structure: ```json theme={null} { "name": "Total Cholesterol", "slug": "total-cholesterol", "type": "numeric", "result": "195", "value": 195, "unit": "mg/dL", "reference_range": "< 200", "min_range_value": null, "max_range_value": 200, "is_above_max_range": false, "is_below_min_range": false, "interpretation": "normal", "timestamp": "2024-01-15T10:30:00Z", "loinc": "2093-3", "loinc_slug": "cholesterol-total-serum-plasma", "provider_id": "001347", "notes": null, "source_sample_id": "12345678" } ``` Notice the `type` is `"numeric"`, the `result` contains a numeric string, `value` contains the actual number, and `unit` specifies the measurement unit. ### Galleri Result - No Cancer Signal Detected When no cancer signal is detected, you receive a single result: ```json theme={null} { "name": "Galleri Multi-Cancer Early Detection", "slug": "galleri-cancer-signal", "type": "coded_value", "result": "NOT_DETECTED", "value": -1, "unit": null, "reference_range": null, "min_range_value": null, "max_range_value": null, "is_above_max_range": null, "is_below_min_range": null, "interpretation": "normal", "timestamp": "2024-01-15T10:30:00Z", "loinc": null, "loinc_slug": null, "provider_id": "86038987", "notes": null, "source_sample_id": "12345678" } ``` ### Galleri Results - Cancer Signal Detected When a cancer signal is detected, you receive **multiple related biomarker results** that provide detailed information about the detection: #### 1. Cancer Signal Detection ```json theme={null} { "name": "Galleri Cancer Signal", "slug": "galleri-cancer-signal", "type": "coded_value", "result": "DETECTED", "value": -1, "unit": null, "reference_range": null, "min_range_value": null, "max_range_value": null, "is_above_max_range": null, "is_below_min_range": null, "interpretation": "critical", "timestamp": "2024-01-15T10:30:00Z", "loinc": null, "loinc_slug": null, "provider_id": "86038987", "notes": null, "source_sample_id": "12345678" } ``` #### 2. Cancer Signal Origin ```json theme={null} { "name": "Galleri Cancer Signal Origin", "slug": "galleri-cancer-origin", "type": "coded_value", "result": "HEAD_AND_NECK", "value": -1, "unit": null, "reference_range": null, "min_range_value": null, "max_range_value": null, "is_above_max_range": null, "is_below_min_range": null, "interpretation": "critical", "timestamp": "2024-01-15T10:30:00Z", "loinc": null, "loinc_slug": null, "provider_id": "86038987_1", "notes": null, "source_sample_id": "12345678" } ``` #### 3. Cancer Signal Subcategory ```json theme={null} { "name": "Galleri Cancer Signal Subcategory", "slug": "galleri-cancer-subcategory", "type": "coded_value", "result": "OROPHARYNX_HYPOPHARYNX_NASOPHARYNX_LARYNX_LIP_AND_ORAL_CAVITY_INCLUDING_ORAL_TONGUE_NASAL_CAVITY_PARANASAL_SINUSES_MAJOR_SALIVARY_GLANDS", "value": -1, "unit": null, "reference_range": null, "min_range_value": null, "max_range_value": null, "is_above_max_range": null, "is_below_min_range": null, "interpretation": "critical", "timestamp": "2024-01-15T10:30:00Z", "loinc": null, "loinc_slug": null, "provider_id": "86038987_2", "notes": null, "source_sample_id": "12345678" } ``` #### 4. Additional Information (Comment) ```json theme={null} { "name": "Galleri Additional Information", "slug": "galleri-comment-1", "type": "comment", "result": "Squamous Cell Signal of Head and Neck, Lung, and Esophagus", "value": -1, "unit": null, "reference_range": null, "min_range_value": null, "max_range_value": null, "is_above_max_range": null, "is_below_min_range": null, "interpretation": "critical", "timestamp": "2024-01-15T10:30:00Z", "loinc": null, "loinc_slug": null, "provider_id": "86038987_3", "notes": "Squamous Cell Signal of Head and Neck, Lung, and Esophagus", "source_sample_id": "12345678" } ``` ## Key Differences from Standard Biomarkers Understanding these differences is crucial for proper integration: ### Result Type * **Standard biomarkers**: `type` is `"numeric"` or `"range"` * **Galleri results**: `type` is `"coded_value"` or `"comment"` ### Result Field * **Standard biomarkers**: Contains numeric values as strings (e.g., `"195"`, `"<1.2"`) * **Galleri results**: Contains enum constant names in uppercase with underscores (e.g., `"DETECTED"`, `"HEAD_AND_NECK"`) ### Value Field * **Standard biomarkers**: Contains actual numeric value (e.g., `195`) * **Galleri results**: Always `-1` (placeholder value; actual information is in the `result` field) The `value` field is deprecated and will eventually be removed. For Galleri results, always use the `result` field to extract meaningful information. ### Unit Field * **Standard biomarkers**: Contains measurement units (e.g., `"mg/dL"`, `"mmol/L"`) * **Galleri results**: Always `null` ### Interpretation Field * **Standard biomarkers**: Can be `"normal"`, `"abnormal"`, or `"critical"` based on reference ranges * **Galleri results**: `"normal"` when not detected, `"critical"` when cancer signal detected ### Result Multiplicity * **Standard biomarkers**: Typically one result per biomarker tested * **Galleri positive results**: Multiple related results (signal + origin + subcategory + comments) ### Provider ID Pattern Galleri results use a consistent `provider_id` pattern to distinguish between the different related results: * `"86038987"` - Base cancer signal detection result * `"86038987_1"` - Cancer signal origin * `"86038987_2"` - Cancer signal subcategory * `"86038987_3"` - Additional information/comments ## Enum Value Reference ### Galleri Cancer Signal **Description**: Indicates whether a cancer signal was detected in the Galleri test. **Field**: `BiomarkerResult.result` when `type` is `"coded_value"` and `provider_id` is `"86038987"` **Possible Values**: ``` DETECTED NOT_DETECTED ``` ### Galleri Cancer Origin **Description**: When a cancer signal is detected, indicates the predicted tissue of origin for the cancer signal. This represents the general anatomical location or cell lineage where the cancer may have originated. **Field**: `BiomarkerResult.result` when `type` is `"coded_value"` and `provider_id` is `"86038987_1"` **Possible Values** (21 total): ``` ANUS BLADDER_UROTHELIAL_TRACT BREAST CERVIX COLON_RECTUM HEAD_AND_NECK KIDNEY LIVER_BILE_DUCT LUNG NEUROENDOCRINE_CELLS_OF_LUNG_OR_OTHER_ORGANS LYMPHOID_LINEAGE MELANOCYTIC_LINEAGE MYELOID_LINEAGE OVARY PANCREAS_GALLBLADDER PLASMA_CELL_LINEAGE PROSTATE BONE_AND_SOFT_TISSUE THYROID_GLAND STOMACH_ESOPHAGUS UTERUS ``` ### Galleri Cancer Subcategory **Description**: Provides a more specific anatomical classification of the predicted cancer signal origin, with additional detail about the specific organs or tissue types involved. **Field**: `BiomarkerResult.result` when `type` is `"coded_value"` and `provider_id` is `"86038987_2"` **Possible Values** (21 total): ``` ANUS BLADDER_RENAL_PELVIS_URETER_URETHRA BREAST CERVIX COLON_RECTUM_APPENDIX OROPHARYNX_HYPOPHARYNX_NASOPHARYNX_LARYNX_LIP_AND_ORAL_CAVITY_INCLUDING_ORAL_TONGUE_NASAL_CAVITY_PARANASAL_SINUSES_MAJOR_SALIVARY_GLANDS KIDNEY LIVER_INTRAHEPATIC_BILE_DUCT LUNG_BRONCHUS NEUROENDOCRINE_CELLS_OF_LUNG_OR_OTHER_ORGANS LYMPHOID_LINEAGE MELANOCYTIC_LINEAGE MYELOID_LINEAGE OVARY_FALLOPIAN_TUBE_PRIMARY_PERITONEUM PANCREAS_EXTRAHEPATIC_BILE_DUCT_GALLBLADDER PLASMA_CELL_LINEAGE PROSTATE SKELETAL_MUSCLE_AND_OTHER_CONNECTIVE_TISSUE_VASCULAR_TISSUE_BONE_AND_CARTILAGE THYROID_GLAND STOMACH_ESOPHAGUS UTERUS ``` ## Integration Tips ### Identifying Galleri Results Galleri results can be identified in the API response by: 1. The `type` field will be `"coded_value"` or `"comment"` 2. The `provider_id` will start with `"86038987"` 3. The slug values will be: `"galleri-cancer-signal"`, `"galleri-cancer-origin"`, or `"galleri-cancer-subcategory"` ### Handling Multiple Related Results When a cancer signal is detected, you will receive multiple related biomarker results that should be grouped and displayed together. The `provider_id` field distinguishes between them: * `"86038987"` - The main cancer signal detection result * `"86038987_1"` - The predicted origin of the cancer signal * `"86038987_2"` - The specific subcategory with detailed anatomical information * `"86038987_3"`, `"86038987_4"`, etc. - Additional information and comments # Reference Range Source: https://docs.junction.com/lab/results/reference-range-parsing The Junction API already returns the `interpretation` field (e.g., "normal", "abnormal", "critical") in the [`BiomarkerResult`](/lab/results/result-formats#biomarkerresult) object. This documentation is intended for teams that want to build their own interpretation logic and need to parse the `reference_range` field along with `min_range_value` and `max_range_value` to determine whether results are within normal limits. The `reference_range` field in Order results contains a string representation of the normal range for a lab test result. This field is critical for determining whether a result is within normal limits, but it requires parsing to understand the inclusivity/exclusivity of boundary values. ## Why Parsing is Needed The `min_range_value` and `max_range_value` fields contain the boundary numbers from the lab, but they **do not indicate whether those boundaries are inclusive or exclusive**. This information is only available in the `reference_range` string, which requires parsing to determine the correct comparison operator. ### Example For a result with: * `reference_range`: `"<5.7"` * `max_range_value`: `5.7` * `min_range_value`: None * `result`: `"5.7"` Without knowing the inclusivity, you cannot determine if `5.7` is normal or abnormal. The boundary value `5.7` is the same whether the range is `<=5.7` (inclusive, normal) or `<5.7` (exclusive, abnormal). You must parse the `reference_range` string to know which operator to use. ## Reference Range Formats The `reference_range` field can appear in several formats: ### 1. Comparison Operators Single-bound comparisons using standard operators: * `<5` - Less than 5 (exclusive upper bound) * `<=10.5` - Less than or equal to 10.5 (inclusive upper bound) * `>3` - Greater than 3 (exclusive lower bound) * `>=7.25` - Greater than or equal to 7.25 (inclusive lower bound) ### 2. Range Notation Two-bound ranges using hyphen notation: * `1-10` - Between 1 and 10 (both inclusive) * `3.5-7.8` - Between 3.5 and 7.8 (both inclusive) **Note:** Range notation (with hyphen) typically indicates both bounds are inclusive. ### 3. Alternative Comparison Syntax Some labs use alternative syntax: * `< OR = 9.9` - Less than or equal to 9.9 (inclusive upper bound) * `> OR = 2` - Greater than or equal to 2 (inclusive lower bound) These are case-insensitive (`< or = 9.9` is equivalent). ## Parsing Strategy Parse the `reference_range` string to extract: * Lower bound value (if present) * Upper bound value (if present) * Whether each bound is inclusive or exclusive The parsed information, combined with `min_range_value` and `max_range_value`, determines the correct comparison operators for evaluating whether results are within the normal range. ## Example Implementation Here's an example implementation showing how to parse the `reference_range` field: This example uses Python's `re` module for regular expression matching. ```python theme={null} import re def parse_reference_range(reference_range_string): # Pattern 1: Handle comparison operators: <5, <=10.5, >3, >=7.25 comparison_match = re.match(r"([<>]=?)(\d+(\.\d+)?)", reference_range_string) if comparison_match: comparison_operator, boundary_value, _ = comparison_match.groups() boundary_value_float = float(boundary_value) if comparison_operator in ["<", "<="]: # Upper bound: < or <= return { "lower_bound": None, "upper_bound": boundary_value_float, "lower_included": False, "upper_included": comparison_operator == "<=", # <= is inclusive, < is exclusive } else: # > or >= # Lower bound: > or >= return { "lower_bound": boundary_value_float, "upper_bound": None, "lower_included": comparison_operator == ">=", # >= is inclusive, > is exclusive "upper_included": False, } # Pattern 2: Handle range notation: 1-10, 3.5-7.8 range_match = re.match(r"(\d+(\.\d+)?)-(\d+(\.\d+)?)", reference_range_string) if range_match: lower_bound_str, _, upper_bound_str, _ = range_match.groups() return { "lower_bound": float(lower_bound_str), "upper_bound": float(upper_bound_str), "lower_included": True, # Range notation is always inclusive on both sides "upper_included": True, } # Pattern 3: Handle alternative comparison syntax: < OR = 9.9, > OR = 2 alternative_match = re.match(r"([<>]) OR = (\d+(\.\d+)?)", reference_range_string, re.IGNORECASE) if alternative_match: comparison_operator, boundary_value, _ = alternative_match.groups() boundary_value_float = float(boundary_value) if comparison_operator == "<": # < OR = means <= (inclusive upper bound) return { "lower_bound": None, "upper_bound": boundary_value_float, "lower_included": False, "upper_included": True, } else: # > # > OR = means >= (inclusive lower bound) return { "lower_bound": boundary_value_float, "upper_bound": None, "lower_included": True, "upper_included": False, } # If no pattern matches, return empty bounds return { "lower_bound": None, "upper_bound": None, "lower_included": False, "upper_included": False, } ``` This code should be used as a starting point and not directly used in production. It demonstrates parsing logic for the formats shown in this documentation, but may not handle all edge cases or variations that labs might use. For example, it does not cover signed ranges (e.g., `-5.0 - +2.0`) or other format variations. You should thoroughly test and extend this implementation based on your specific needs. ## Parsing Examples Here are examples of what the above parsing function returns for different `reference_range` formats: ### Example 1: Exclusive Upper Bound ```python theme={null} reference_range_string = "<5.7" parsed = parse_reference_range(reference_range_string) # Returns: { # "lower_bound": None, # "upper_bound": 5.7, # "lower_included": False, # "upper_included": False # < means exclusive # } ``` ### Example 2: Inclusive Upper Bound ```python theme={null} reference_range_string = "<=5.7" parsed = parse_reference_range(reference_range_string) # Returns: { # "lower_bound": None, # "upper_bound": 5.7, # "lower_included": False, # "upper_included": True # <= means inclusive # } ``` ### Example 3: Range with Both Bounds ```python theme={null} reference_range_string = "2.6-24.9" parsed = parse_reference_range(reference_range_string) # Returns: { # "lower_bound": 2.6, # "upper_bound": 24.9, # "lower_included": True, # Range notation is inclusive # "upper_included": True # } ``` ### Example 4: Lower Bound Only ```python theme={null} reference_range_string = ">3.0" parsed = parse_reference_range(reference_range_string) # Returns: { # "lower_bound": 3.0, # "upper_bound": None, # "lower_included": False, # > means exclusive # "upper_included": False # } ``` ### Example 5: Alternative Syntax ```python theme={null} reference_range_string = "< OR = 9.9" parsed = parse_reference_range(reference_range_string) # Returns: { # "lower_bound": None, # "upper_bound": 9.9, # "lower_included": False, # "upper_included": True # < OR = means <= (inclusive) # } ``` # Result Formats Source: https://docs.junction.com/lab/results/result-formats Junction's API returns results in two different formats. * `PDF` * `JSON` ## PDF Results We return the raw results in PDF form, that we receive directly from our partner labs. This can be retrieved as follows: ```bash Get order results PDF theme={null} curl --request GET \ --url {{BASE_URL}}/v3/order/result/pdf \ --header 'Accept: application/json' \ --header 'Content-Type: application/pdf' \ --header 'x-vital-api-key: ' \ ``` Some laboratories do not issue PDFs for partial results, and only provide PDF reports once all results are finalized An example result: ## JSON Results We also return the parsed results in JSON format, so you can use them to generate your own forms. These results are returned in a structured format, which you can find [here](/api-reference/results/get-results). The `results` field, according to the spec, can return either a `list[BiomarkerResult]` or an untyped `dict`. This is due to backwards compatibility, and you can disregard the untyped `dict` ### Result Status The `status` field can be one of the following: 1. `ResultStatus.PARTIAL` - The results are partial. Labs can return results before all biomarkers are available. Junction makes these results available to you as soon as we receive them, but does not send a webhook notification for `partial` results. This means that if you probe the API for results, you might get a `partial` result, even if there was no webhook for a `labtest.update` event. This is done due to the possibility of [critical values](/lab/workflow/lab-test-lifecycle#critical-results) in the results. 2. `ResultStatus.FINAL` - The results are complete. This means that all biomarkers are available, and the results are final. You will receive a `labtest.update` webhook notification for this event. ### BiomarkerResult A `BiomarkerResult` has the following definition: ```python theme={null} name: str slug: str value: float # deprecated result: str type: ResultType unit: str | None timestamp: datetime | None notes: str | None reference_range: str | None min_range_value: float | None max_range_value: float | None is_above_max_range: bool | None is_below_min_range: bool | None interpretation: str = Interpretation.NORMAL loinc: str | None loinc_slug: str | None provider_id: str | None source_markers: List[ParentBiomarkerData] | None ``` #### ResultType Results can fall into one of the following categories: 1. `ResultType.NUMERIC` - A numeric result, e.g. `1.2` In this case, the `result` field will be a string representation of the number, and the `value` field will be a float representation of the number. 2. `ResultType.RANGE` - A range result, e.g. `<1.2` In this case, the `result` field will be a string representation of the range value, and the `value` field will be `-1`. Note that you will also find the `<1.2` value in the `notes` field. A range result will always be a value following the pattern `^([<>]=?\d*(\.\d+)?|(\d*(\.\d+)?-\d*(\.\d+)?))$`. 3. `ResultType.COMMENT` - A text result, e.g. `Positive` In this case, the `result` field will be a string representation of the text, and the `value` field will be `-1`. Note that you will also find the `Positive` value in the `notes` field. 4. `ResultType.CODED_VALUE` - A coded value result using enum constants, e.g. `DETECTED`, `HEAD_AND_NECK` In this case, the `result` field will be an enum constant name, and the `value` field will be `-1`. This type is used for specialized tests like the Galleri multi-cancer early detection test. See the [Galleri Results](/lab/results/galleri-results) documentation for detailed information. The `value` field in deprecated and will eventually be removed. #### Interpretation Interpretation is a string value that can be one of the following: 1. `Interpretation.NORMAL` - The result is within normal parameters. 2. `Interpretation.ABNORMAL` - The result is outside of normal parameters. 3. `Interpretation.CRITICAL` - The result is outside of critical parameters. In this case, refer to the [critical values](/lab/workflow/lab-test-lifecycle#critical-results) section. #### Standardisation - LOINC It's possible to test the same biomarkers across different laboratories. For these to match, we use the [LOINC](https://loinc.org/) standard. In the `BiomarkerResult` object, you can see two fields `loinc_slug` and `loinc`. These fields refer to the LOINC standard. Customers should use this standard, so it's possible to match results across different laboratories. You can expect that the `slug` field is what the laboratory returns to us - and the `loinc_slug` is the standardised version. An example: | Lab | Slug | LOINC | LOINC Slug | | ------- | --------------- | ------ | --------------------------- | | Labcorp | hdl-cholesterol | 2085-9 | cholesterol-in-hdl-mass-vol | | USSL | hdl | 2085-9 | cholesterol-in-hdl-mass-vol | As you can see, the same biomarker `HDL Cholesterol` can have different slugs across different laboratories. However it's represented by the same LOINC value. LOINC codes may be missing in some results due to our LOINC compendium still being expanded and updated, labs providing data in formats our system does not recognize, or labs not including LOINC codes in their data at all. We are actively improving both the completeness of our compendium and our ability to interpret data variations, but missing LOINCs can still occur. Integrations should be built to handle cases where LOINC codes are not guaranteed. #### Expected Results When ordering a `lab_test`, you can see which `markers` each test orders. These can either be `panels` composed of multiple `biomarkers` or just individual `biomarkers`. This means that a `lab_test` with only one associated `marker`, such as `Lipid Panel`, can return multiple `result markers`. We call these expected results. Each `marker` can thus be composed of multiple `expected results` which match to a `loinc`. As an example, here's the expected results for the `Lipid Panel` marker: ```json theme={null} "expected_results":[ { "id":1108, "name":"VLDL Cholesterol Cal", "slug":"vldl-cholesterol-cal", "lab_id":6, "provider_id":"011919", "loinc":{ "id":5062, "name":"Cholesterol in VLDL Calc [Mass/Vol]", "slug":"cholesterol-in-vldl-calc-mass-vol", "code":"13458-5", "unit":"mg/dL" } }, { "id":1109, "name":"Cholesterol, Total", "slug":"cholesterol-total", "lab_id":6, "provider_id":"001065", "loinc":{ "id":11940, "name":"Cholesterol [Mass/Vol]", "slug":"cholesterol-mass-vol", "code":"2093-3", "unit":"mg/dL" } }, { "id":1110, "name":"HDL Cholesterol", "slug":"hdl-cholesterol", "lab_id":6, "provider_id":"011817", "loinc":{ "id":11858, "name":"Cholesterol in HDL [Mass/Vol]", "slug":"cholesterol-in-hdl-mass-vol", "code":"2085-9", "unit":"mg/dL" } }, { "id":1112, "name":"Triglycerides", "slug":"triglycerides", "lab_id":6, "provider_id":"001172", "loinc":{ "id":16384, "name":"Triglyceride [Mass/Vol]", "slug":"triglyceride-mass-vol", "code":"2571-8", "unit":"mg/dL" } }, { "id":1113, "name":"LDL Chol Calc (NIH)", "slug":"ldl-chol-calc-nih", "lab_id":6, "provider_id":"012059", "loinc":{ "id":5060, "name":"Cholesterol in LDL Calc [Mass/Vol]", "slug":"cholesterol-in-ldl-calc-mass-vol", "code":"13457-7", "unit":"mg/dL" } } ] ``` You can use this information to verify if the final results are composed of all expected results. In order to obtain this data, you can use the following endpoints: 1. [GET /v3/lab\_tests/markers](/api-reference/lab-testing/biomarkers) This allows you to search markers based on laboratory or name. 2. [GET /v3/lab\_tests/\{id}/markers](/api-reference/lab-testing/lab-test-markers) This allows you to see all markers associated with a lab test and it's expected results. #### Source Markers As mentioned above, a marker can be composed of one or more results. This means that if you order a `Lipid Panel`, there will be no `Lipid Panel` result returned, but instead a series of markers that originate from the `Lipid Panel`. Junction identifies the source marker via the `source_markers` field. ```json theme={null} { "name": "Sex Horm Binding Glob, Serum", "slug": "sex-horm-binding-glob-serum", "value": 30.4, "result": "30.4", "type": "numeric", "unit": "nmol/L", "timestamp": "2024-10-31T09:08:00+00:00", "notes": "Final", "min_range_value": 24.6, "max_range_value": 122, "is_above_max_range": false, "is_below_min_range": false, "interpretation": "normal", "loinc": "13967-5", "loinc_slug": "sex-hormone-binding-globulin-moles-vol", "provider_id": "082016", "source_markers": [ { "marker_id": 229, "name": "Testosterone Free, Profile I", "slug": "testosterone-free-profile-i", "provider_id": "140226" } ] }, ``` When Junction cannot identify the source, then this field will be `null`, indicating that this is an unsolicited result. There may also be more than one `source` marker, if there are two or more ordered markers that contain the same underlying result, using the same testing method. #### Missing Results At times labs will commit mistakes, and expected results will be missing. Junction identifies these and parses them into a separate structure, named `missing_results`. This data has the following format: ```python theme={null} name: str slug: str inferred_failure_type: FailureType note: str | None = None loinc: str | None = None loinc_slug: str | None = None provider_id: str | None = None source_markers: List[ParentBiomarkerData] | None = None ``` `inferred_failure_type` is the Junction assigned error type. The error type is inferred from the comments received from the lab. They are to help assess possibly root causes of missing results, and aid the customer in identifying issues in aggregate. The way that we infer these error types is subject to change as we continue to refine and achieve more granular understanding of failure modes. 1. `quantity_not_sufficient_failure` The lab could not process this result due to an insufficient quantity of collected sample. This could be due to the patient refusing to collect more, the phlebotomist being to unable to collect the proper volume of blood from the sample, or the phlebotomist not collecting all of the sample they were meant to collect. 2. `collection_process_failure` This is indicative of potential failures to follow the entirety phlebotomy process, often immediately following the collection. For example, improper centrifugation of the collected sample, improper refrigeration. While these are the most likely causes, there are other reasons for why sample quality may have been degraded. 3. `drop_off_failure` This speaks to a specific form of collection process failures. Specifically that the sample was not received by the lab in proper condition. Possible issues correspond to improper freezing, or refrigeration, or was exposed to excessive transport delay. 4. `internal_lab_failure` This speaks to failures that are most likely to have happened internal to the lab. This includes issues such as misplacing a collected sample, or errors that could not be best attributed to any external cause. 5. `order_entry_failure` The test was not performed because it was not properly ordered at the lab. 6. `non_failure` This speaks to a failure that should not impact the patient. For example, it may indicate that a duplicate test was ordered. 7. `unknown_failure` This is a failure mode that could not be properly attributed to any specific failure mode. Potentially the results were simply left out and Junction was not provided any additional information. 8. `patient_condition_failure` This speaks to a failure to result due to specifics of the patient's physical condition. For example, the patient may have had some food very high in fats that immediately prior to a collection. It is possible that improper storage and handling can cause samples to fail in ways that appear to be a patient condition failure. 9. `missing_result_calc_failure` This failure indicates that a calculated field is missing because the underlying tests required to perform the calculation were either unable to be processed, or yielded a result outside of the allowable parameters to perform the calculation 10. `missing_demo_aoe_calc_failure` Some results may require additional information reported in AOE (ask on order entry) in order to yield results, such as age, to be properly calculated. # Communications Source: https://docs.junction.com/lab/testkits/communications Communication for patients is done via SMS, for At-home Teskit orders. Customers have the following options when setting this up: * `Default` - SMS communications are enabled. * `Disable` - All communications from Junction are disabled. Each option has a different set of content and status changes, depending on what triggered them. You can enable or disable SMS individually through the [**Junction Dashboard**](http://app.junction.com/), under the Team Settings section. ## Default Communications ### SMS Messages A table of the **Order Status** and **default SMS** is provided bellow: | Order Status | Default Message | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ordered` | Hey **receiver\_name**, your order for the **team\_name** health kit has been placed! We'll update you with where it is in the process via messages :) | | `transit_customer` | Your order for the **team\_name** test kit is in transit, and should be arriving by **eta**. | | `out_for_delivery` | Your order for the **team\_name** test kit is out for delivery, and should be arriving by today | | `delivered_to_lab` | Your test kit has arrived to our labs we're processing your sample now! | | `completed` | The lab has finished processing your test kit, your results should be ready soon :) | | `cancelled` | Hey, your order for the **team\_name** test kit has been cancelled, If this is by accident please contact support. | SMS Texts are customizable, and can be enabled or disabled individually. ### Emails We do not send emails for At-home Testkits. ## Disable Communications In this case, no communications will be sent from Junction. You have the ability to produce completely customized communications using the [`Webhook events`](/lab/testkits/webhooks) described previously. # Order Lifecycle Source: https://docs.junction.com/lab/testkits/order-lifecycle The **At-Home Testkit** can be composed of two distinct **Order** lifecycles, which we detail bellow. ## Order Lifecycle As discussed in [`Lab Test Lifecycle - Statuses`](/lab/workflow/lab-test-lifecycle#statuses), each lab testing modality has the following format `[HIGH-LEVEL STATUS].[TEST MODALITY].[LOW-LEVEL STATUS]` For each modality, there can be multiple **low-level status**, for **At-Home Testkit** the possible low-level status are: ### Registered Teskits * `ordered`: Junction received the order, stored it into our system, and started processing it asynchronously. * `requisition_created`: An order requisition form was validated and created with the partner laboratory, making the order available to be carried out. * `requisition_bypassed`: An order requisition form wasn't created when the order was placed with us because it already existed. * `transit_customer`: The teskit is shipped and in transit to the customer. * `out_for_delivery`: The teskit is out for delivery. * `with_customer`: The teskit is delivered to the customer. * `transit_lab`: The customer has sent the testkit back to the lab. * `delivered_to_lab`: The lab has received the testkit. * `failure_to_deliver_to_customer`: The shipping company was unable to deliver the testkit to the customer. * `failure_to_deliver_to_lab`: The shipping company was unable to deliver the testkit to the lab. * `problem_in_transit_customer`: The shipping company encountered a problem while delivering the testkit to the customer. * `problem_in_transit_lab`: The shipping company encountered a problem while delivering the testkit to the lab. * `sample_error`: The collected sample was unprocessable by the lab. * `completed`: The laboratory processed the blood sample and final results are available. * `cancelled`: The order was cancelled by either the patient, Junction or you. The Finite State Machine that defines the possible transitions for the low-level statuses described above is illustrated in the following diagram. ### Registrable Testkits * `ordered`: Junction received the order, stored it into our system, and started processing it asynchronously. * `awaiting_registration`: Order is created but no user has been registered yet * `transit_customer`: The teskit is shipped and in transit to the customer. * `out_for_delivery`: The teskit is out for delivery. * `with_customer`: The teskit is delivered to the customer. * `registered`: Order has been registered, and a requisition will be created. * `requisition_created`: An order requisition form was validated and created with the partner laboratory, making the order available to be carried out. * `transit_lab`: The customer has sent the testkit back to the lab. * `delivered_to_lab`: The lab has received the testkit. * `failure_to_deliver_to_customer`: The shipping company was unable to deliver the testkit to the customer. * `failure_to_deliver_to_lab`: The shipping company was unable to deliver the testkit to the lab. * `problem_in_transit_customer`: The shipping company encountered a problem while delivering the testkit to the customer. * `problem_in_transit_lab`: The shipping company encountered a problem while delivering the testkit to the lab. * `sample_error`: The collected sample was unprocessable by the lab. * `completed`: The laboratory processed the blood sample and final results are available. * `cancelled`: The order was cancelled by either the patient, Junction or you. The Finite State Machine that defines the possible transitions for the low-level statuses described above is illustrated in the following diagram.
In **sandbox**, there is no async transition from the `ordered` state to the `requisition_created` state, this must be **manually triggered** via the **Junction Dashboard**. # Overview Source: https://docs.junction.com/lab/testkits/overview At-home Testkits are one of our offered modalities. This modality involves sending a kit to a patient’s home, where the patient will collect their own sample and send the kit to our Lab network through mail. Within this modality, we provide two types of testkits, those which are pre-registered to a user, and those which need to to be (registered by the patient upon receiving the kit)\[lab/workflow/order-registrable-testkit]. ## Ordering Flow overview To achieve this, a high-level overview of the process is defined as: * An order is placed in Junction's system through our Dashboard or API. * The order is added to a background queue and additional checks are made before the kit is shipped to the provided address. * Once the order shipment is created, a requisition is generated. * After the requisition is created, some form of communication is carried out with the patient. * If the kit is not registered, then the user must register the kit before results can be provided. * The patient receives the kit, collects their sample, and sends the kit back through mail. * The Laboratory processes the patient's blood sample and generates the required results. * Junction exposes the results via API as soon as they are available, both on PDF and structured data via API. ## Constraints * If the teskit is un-registered, then the user must register it in order for the results to be processed. To find out more details, see [`Order and Appointment Lifecycle`](/lab/testkits/order-lifecycle), [`Communications`](/lab/testkits/communications) and [`Webhooks`](/lab/testkits/webhooks). # Webhooks Source: https://docs.junction.com/lab/testkits/webhooks The following webhook events are of interest, when placing an At-Home Testkit order. Those are described in detail in the following sections. ## Order webhook events Based on the status present in [`Order Lifecycle`](/lab/testkit/order-lifecycle), Junction will trigger two kinds of webhook events, [`labtest.order.created`](/event-catalog/labtest.order.created) and [`labtest.order.updated`](/event-catalog/labtest.order.updated). The `labtest.order.created` event is triggered when an order is created in the system, having the `ordered` status, and all subsequent status changes will trigger a `labtest.order.updated` event in the system. The webhook payload body will have the following information if the Order is in the `requisition_created` status: ```json Testkit Order Updated theme={null} { "data":{ "created_at":"2023-09-01T18:02:41.210495+00:00", "details":{ "data":{ "created_at":"2023-09-01T18:02:41.247443+00:00", "id":"3c046d74-347e-4e28-9e0d-c5b720a8e219", "shipment":{ "id":"ae0323d4-2c18-4aea-a0e7-d73b377315b1", "inbound_courier":null, "inbound_tracking_number":null, "inbound_tracking_url":null, "notes":null, "outbound_courier":null, "outbound_tracking_number":null, "outbound_tracking_url":null }, "updated_at":"2023-09-01T18:02:42.186312+00:00" }, "type":"testkit" }, "events":[ { "created_at":"2023-09-01T18:02:41.271401+00:00", "id":4056, "status":"received.testkit.ordered" }, { "created_at":"2023-09-01T18:05:12.618156+00:00", "id":4057, "status":"received.testkit.requisition_created" } ], "health_insurance_id":null, "id":"e1c380c1-7df4-487f-869e-f1be0193ca25", "lab_test":{ "fasting":false, "id":"0cb9f34f-c3df-4a13-8ca1-19429a82611b", "is_active":true, "is_delegated":false, "lab":null, "markers":null, "method":"testkit", "name":"Female General Wellness", "price":45, "sample_type":"dried_blood_spot", "slug":"general_wellness_female_002" }, "notes":null, "patient_address":{ ... }, "patient_details":{ ... }, "physician":{ ... }, "priority":false, "requisition_form_url":null, "sample_id": "some_id, "shipping_details":null, "status":"received", "team_id":"f07f59d2-2903-4bcd-a2ac-3e87fa47c4bc", "updated_at":"2023-09-01T18:02:41.210495+00:00", "user_id":"f6cdf185-6815-4b2b-9482-798b75168689", "origin": "initial", "order_transaction": { "id": "6424d45-ee1a-49c6-ad0c-5769b8e03fc1", "status": "active", "orders": [ { "id": "e1c380c1-7df4-487f-869e-f1be0193ca25", "created_at": "2023-09-01T18:02:41.210495+00:00", "updated_at": "2023-09-01T18:02:41.210495+00:00", "low_level_status": "requisition_created", "low_level_status_created_at": "2023-09-01T18:05:12.618156+00:00", "origin": "initial" } ] }, }, "event_type":"labtest.order.updated" } ``` # Communications Source: https://docs.junction.com/lab/walk-in/communications Communication for patients is done via email or SMS, for Walk-in orders. Customers have the following options when setting this up: * `Default` - Email and SMS communications are enabled. * `SMS Only or Email Only` - Only SMS or Email communication is enabled. * `Disable` - All communications from Junction are disabled. Each option has a different set of content and status changes, depending on what triggered them. You can enable or disable SMS and Email communications individually through the [**Junction Dashboard**](http://app.junction.com/), under the Team Settings section. ## Default Communications ### SMS Messages A table of the **Order Status** and **default SMS** is provided below: | Order Status | Default Message | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ordered` | *`"Hi, {patient_first_name}, your order for the {team_name} walk-in-test has been placed! We'll provide you with updates on the status of your order via text messages :)."`* | | `requisition_created` | *`"Hey {patient_first_name}, it's time to visit your local {lab_name} center. Please check the email you just received for instructions."`* | | `appointment_scheduled` | *`"Your appointment with the lab has been booked at {date} over at {address}! Here's your appointment key: {appointment_key}"`* This message is sent only for Quest orders and Quest appointments placed through Junction API. | | `appointment_cancelled` | *`"Hey, your lab appointment at {date} for {team_name} has been cancelled."`* This message is sent only for Quest orders and Quest appointments placed through Junction API. | | `redraw_available` | *`"Hi {patient_first_name}, your {team_name} results have come back with at least one missing biomarker. We recommend a redraw to complete your lab testing. We will send you an email shortly with more information."`* | | `completed` | *`"Your results have finished processing, your results should be ready soon."`* | | `cancelled` | *`"Hey, your order for the {team_name} walk-in-test has been cancelled. If this is by accident please contact support."`* | SMS Texts are customizable, and can be enabled or disabled individually. ### Emails For emails, the following table describes what information each email contains for each Order Status: | Order Status | Email Content Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `requisition_created` | An email with confirmation of the partner Lab and additional instructions will be sent to the patient. | | `redraw_available` | An email with confirmation of the partner Lab, missing biomarker(s) and redraw instructions will be sent to the patient. | Emails can be customised and sent from your own domain. ## Scheduling Appointments Before A Requisition Has Been Created This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. If the ability to [`schedule appointments before a requisition has been created`](/lab/walk-in/order-lifecycle#scheduling-appointments-before-a-requisition-has-been-created) is enabled for your team, all appointment-related messages will be triggered by the **appointment** status, not the **order** status. All non appointment-related messages will be triggered by the **order** status e.g. `requisition_created`, `completed` etc. (see above section). ### SMS Messages A table of the **Appointment Status** and **default SMS** is provided below: | Appointment Status | Default Message | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `confirmed` | *`"Your appointment with the lab has been booked at {date} over at {address}! Here's your appointment key: {appointment_key}"`* This message is sent only for Quest orders and Quest appointments placed through Junction API."\`\`\_ | | `cancelled` | *`"Hey, your lab appointment at {date} for {team_name} has been cancelled."`* This message is sent only for Quest orders and Quest appointments placed through Junction API. | ### Emails No emails are sent for this feature. ## Disable Communications In this case, no communications will be sent from Junction. You have the ability to produce completely customized communications using the [`Webhook events`](/lab/at-home-phlebotomy/webhooks) described previously. # Order and Appointment Lifecycle Source: https://docs.junction.com/lab/walk-in/order-lifecycle The **Walk-In Phlebotomy** orders are composed of two different lifecycles, the **Order** lifecycle and the **appointment** lifecycle, detailed in the following sections. ## Order Lifecycle As discussed in [`Lab Test Lifecycle - Statuses`](/lab/workflow/lab-test-lifecycle#statuses), each lab testing modality has the following format `[HIGH-LEVEL STATUS].[TEST MODALITY].[LOW-LEVEL STATUS]` For each modality, there can be multiple **low-level status**, for **Walk-In Phlebotomy** the possible low-level status are: * `ordered`: Junction received the order, stored it into our system, and started processing it asynchronously. * `requisition_created`: An order requisition form was validated and created with the partner laboratory, making the order available to be carried out. * `requisition_bypassed`: An order requisition form wasn't created when the order was placed with us because it already existed. * `appointment_pending`: An appointment was placed in Junction's system for the order, but doesn't have a scheduled date. * `appointment_scheduled`: An appointment was scheduled or rescheduled for the order. * `appointment_cancelled`: The appointment was cancelled, by either the patient, Junction or you. * `partial_results`: The laboratory has started making partial results available. * `redraw_available`: The result has been marked as final but is missing biomarker results due to lab error. (Currently in beta testing.) * `completed`: The laboratory processed the blood sample and final results are available. * `sample_error`: The collected sample was unprocessable by the lab. * `cancelled`: The order was cancelled by either the patient, Junction or you. The Finite State Machine that defines the possible transitions for the low-level statuses described above is illustrated in the following diagram.
In **sandbox**, there is no async transition from the `ordered` state to the `requisition_created` state, this must be **manually triggered** via the **Junction Dashboard**. ## Appointment Lifecycle The appointment lifecycle is separate from the order lifecyle, and it corresponds to a single appointment. The possible status are defined as follows: * `pending`: An appointment was placed in the system, and is pending updates from the phlebotomy service. * `scheduled`: An appointment was scheduled or rescheduled. The Finite State Machine that defines the possible transitions for the appointment statuses described above is illustrated in the following diagram. The events are related to a single appointment. An order can have multiple existing appointments in Junction system, although only one appointment will be considered active and returned when using the [GET Appointment endpoint](/api-reference/lab-testing/psc-schedulling/get-psc-appointment). ## Scheduling Appointments Before A Requisition Has Been Created This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. When this feature is enabled on your Team, you can create a Patient Service Center (PSC) appointment even before the requisition has been created for the corresponding Order (i.e., reaching the `requisition_created` status). This allows you to build an experience that can order the lab test *and* book an appointment consecutively, without having to wait on the asynchronous requisition creation process that may take an indeterminate amount of time. ### API Journey You create an Appointment for an Order which does not yet have a requisition. The Appointment starts with the `reserved` status. If the order requisition has been created on or before the time slot confirmation, the Appointment moves directly to the `confirmed` status. The `reserved` status will be skipped. The requisition is being created asynchronously. The requisition has now been created. 1. The Order will move through the `requisition_created`, `appointment_pending` and `appointment_scheduled` Order statuses in quick succession. 2. The Appointment will move to the `confirmed` status automatically, which corresponds to the `scheduled` appointment event. You will receive three `labtest.order.updated` events, one each for the three Order status transition. You will also received a `labtest.appointment.updated` event for the Appointment status transition. ## Cancelling An Appointment If an Appointment is cancelled **before** a requisition has been created: * The Appointment will move to the `cancelled` status; and * There is no change to the Order, which will remain in the `ordered` status. If an Appointment is cancelled **after** a requisition has been created: 1. The Appointment will move to the `cancelled` status; and 2. The Order will move from the `appointment_pending` or `appointment_scheduled` status to the `appointment_cancelled` status. ### Auto-cancellation of appointments without requisition In cases where appointments can be scheduled before a requisition has been created, the appointment will be automatically cancelled by Junction if the following happens: 1. More than 8 hours have passed since the order was created, and no requisition has been received. 2. There is less than 2 hours until the appointment start time, and no requisition has been received. ## Arizona Schedulling This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. When this feature is enabled on your Team, you can book appointments with Quest in Arizona (Sonora Quest) ### API Journey You create an Appointment for an Order to a Arizona PSC. The Appointment starts with the `reserved` status. The appointment is being created asynchronously. The appointment has now been created. 1. The Order will move through the `appointment_pending` and `appointment_scheduled` Order statuses in quick succession. 2. The Appointment will move to the `confirmed` status automatically, which corresponds to the `scheduled` appointment event. You will receive two `labtest.order.updated` events, one each for the two Order status transitions. You will also received a `labtest.appointment.updated` event for the Appointment status transition. ### Appointment Booking Failure If an we are unable to book the Appointment asynchronously, then: * The Appointment will move to the `cancelled` status; and * There is no change to the Order, which will remain in it's last status. ### Cancellation and Reschedulling Cancellation and reschedulling for Arizona should be done directly by the patient, via the email they receive from Sonora Quest. It is not possible to cancel/reschedule via Junction. # Overview Source: https://docs.junction.com/lab/walk-in/overview Walk-in tests are one of our offered modalities. This modality is focused on patients that are able to go to a Laboratory to have their blood drawn. ## Ordering Flow overview To achieve this, a high-level overview of the process is defined as: * An order is placed in Junction's system through our Dashboard or API. * The order is added to a background queue and additional checks are made before a test requisition is created with the chosen Laboratory. * After the requisition is created, some form of communication is carried out with the patient so they can go to the assigned partner Laboratory. * After that, the patient can go at any time they want to the laboratory to have their blood drawn. * The Laboratory processes the patient's blood sample and generates the required results. * Junction exposes the results via API as soon as they are available, both on PDF and structured data via API. To find out more details, see [`Order Lifecycle`](/lab/walk-in/order-lifecycle), [`Communications`](/lab/walk-in/communications) and [`Webhooks`](/lab/walk-in/webhooks). # PSC Scheduling API: Errors and Features Source: https://docs.junction.com/lab/walk-in/psc-appointment-scheduling Error handling and optional features for scheduling appointments at Patient Service Centers (PSCs). ## API Endpoints All endpoints require authentication and are prefixed with `/v3/order`: * [`POST /v3/order/psc/appointment/availability`](/api-reference/lab-testing/psc-schedulling/appointment-psc-availability) - Get available time slots * [`POST /v3/order/{order_id}/psc/appointment/book`](/api-reference/lab-testing/psc-schedulling/appointment-psc-booking) - Book an appointment * [`PATCH /v3/order/{order_id}/psc/appointment/reschedule`](/api-reference/lab-testing/psc-schedulling/appointment-psc-reschedule) - Reschedule an appointment * [`PATCH /v3/order/{order_id}/psc/appointment/cancel`](/api-reference/lab-testing/psc-schedulling/appointment-psc-cancel) - Cancel an appointment * [`GET /v3/order/{order_id}/psc/appointment`](/api-reference/lab-testing/psc-schedulling/appointment-psc-get) - Get appointment details * [`GET /v3/order/psc/appointment/cancellation-reasons`](/api-reference/lab-testing/psc-schedulling/appointment-psc-cancellation-reasons) - Get cancellation reasons *** ## Error Handling ### Environment Differences ⚠️ **Important**: Currently, error behavior differs between sandbox and production environments: | Environment | Quest Integration | Slot Conflict Behavior | | -------------- | ----------------- | --------------------------------------------- | | **Sandbox** | Mock client | Returns 400 (mock doesn't simulate conflicts) | | **Production** | Real Quest API | Returns 404 (actual Quest response) | **Recommendation**: For now, handle both 400 and 404 responses as potential slot-unavailability scenarios. ### Frequently Asked Questions #### Q: Do I need to parse error messages to distinguish between different 400 errors? **Yes.** Since multiple error types return 400 status codes, you must examine the error message content to determine the appropriate handling: * **Time in the past**: `"This appointment slot is no longer available. Please select a new time."` β†’ Select new slot * **Invalid booking key**: `"Invalid booking key format"` β†’ Fix request * **Missing parameters**: `"radius must be provided when using zip_code"` β†’ Fix request * **Order state issues**: `"Order is not in a state that allows booking..."` β†’ Contact support HTTP status codes alone are insufficient because our API uses 400 for multiple distinct error categories that require different user experiences. ### Error Categories #### Slot Unavailability Errors *The user should select a different time slot* | Status | Error Message | When It Occurs | User Action | | ---------- | --------------------------------------------------------------------------- | ---------------------------------------- | -------------------------------------- | | 400 | `"This appointment slot is no longer available. Please select a new time."` | Requested time is in the past | Select a future time slot | | 400 or 404 | Quest-specific error messages | Slot already taken or no longer exists | Select a different time slot | | 404 | `"No slots found for zip code"` | No locations/slots in the area | Try a different location or date range | | 404 | `"No slots found. Not all PSCs support scheduling..."` | All locations lack scheduling capability | Use different locations | #### Input Validation Errors *Fix the request parameters* | Status | Error Message | When It Occurs | User Action | | ------ | ------------------------------------------------------------- | ------------------------------------- | ----------------------------------------------- | | 400 | `"Invalid booking key format"` | Malformed or corrupted booking key | Refresh availability and get a new booking key | | 400 | `"Location with site code {code} not found"` | Invalid PSC location code | Use a valid site code from the availability API | | 400 | `"Location with site code {code} is no longer active"` | PSC location disabled | Select a different location | | 400 | `"start_date must be greater or equal than the current date"` | Past date in the availability request | Use current or future date | | 400 | `"site_codes or zip_code must be provided"` | Missing search parameters | Provide either site codes or zip code | | 400 | `"radius must be provided when using zip_code"` | Missing radius with zip code search | Include radius parameter | | 400 | `"site_codes must be less than or equal to 3"` | Too many site codes | Limit to 3 site codes maximum | | 404 | `"This order doesn't exist."` | Invalid order ID | Verify order ID is correct | | 404 | `"This order is not a walk-in phlebotomy order."` | Wrong order type | Use the correct order type | #### Order State Errors *Order workflow or business logic issues* | Status | Error Message | When It Occurs | User Action | | ------ | ------------------------------------------------------- | ------------------------------------ | --------------------------------------- | | 400 | `"Order is not a walkin phlebotomy order"` | Wrong order type for PSC booking | Contact support | | 400 | `"Order does not have a sample ID..."` | Order missing requisition | Wait for requisition or contact support | | 400 | `"Order is not in a state that allows booking..."` | Order workflow prevents booking | Contact support | | 400 | `"Order is not in a state that allows cancelling..."` | Order workflow prevents cancellation | Contact support | | 400 | `"Order is not in a state that allows rescheduling..."` | Order workflow prevents rescheduling | Contact support | | 400 | `"This order does not have a requisition..."` | Missing requisition for appointment | Wait or contact support | | 400 | `"This lab is not supported."` | Order not for Quest lab | Use a supported lab | #### Appointment Management Errors *Appointment-specific business rules* | Status | Error Message | When It Occurs | User Action | | ------ | ----------------------------------------------------- | ------------------------------------------- | --------------------------------- | | 400 | `"This order doesn't have an appointment yet."` | Trying to modify a non-existent appointment | Book an appointment first | | 400 | `"This appointment cannot be rescheduled."` | Appointment marked non-reschedulable | Cancel and book a new appointment | | 400 | `"This appointment has been cancelled or completed."` | Operating on finalized appointment | No further action is possible | | 400 | `"This appointment has already been cancelled."` | Cancelling already cancelled appointment | No action needed | | 404 | `"Appointment not found."` | Appointment doesn't exist | Verify appointment exists | #### Authorization Errors *Access control issues* | Status | Error Message | When It Occurs | User Action | | ------ | -------------------- | ----------------------------------- | --------------- | | 401 | Authentication error | Invalid or expired token | Re-authenticate | | 403 | `"Action forbidden"` | User lacks permission for the order | Contact support | #### Service Availability Errors *System configuration or availability* | Status | Error Message | When It Occurs | User Action | | ------ | ----------------------- | ----------------------- | ---------------------------- | | 503 | `"Feature not enabled"` | PSC scheduling disabled | Contact support or try later | ### Testing Error Scenarios #### Sandbox Testing * Most validation errors can be tested in sandbox * Slot conflicts cannot be reliably tested (mock client always succeeds) * Use invalid parameters to test input validation #### Production Testing * Test with caution using real appointments * Slot conflicts will return actual 404 errors from Quest * Time-based errors can be tested with past dates ### Quest API Limitations * **Timeout**: 15 seconds read timeout, 5 seconds connection timeout * **Slot availability**: Real-time, can change between availability check and booking * **Mock client**: Doesn't simulate all real-world scenarios * **Rate limiting**: Standard Quest API rate limits apply *** ## Beta Features These features are in **closed beta**. Interested in these features? Get in touch with your Customer Success Manager. ### Availability Cache Junction maintains a fast Availability Cache containing the next 21 days of availability for all locations. Each location is refreshed every 5 minutes on average. When you set the `allow_stale` parameter on the [PSC Appointment Availability](/api-reference/lab-testing/psc-schedulling/appointment-psc-availability) endpoint, your request is fulfilled directly from the Availability Cache, provided that: 1. All requested locations have a cache hit; and 2. The cached information is reasonably fresh (less than 15 minutes old). When a request cannot be fulfilled by the Availability Cache, it is routed directly to the PSC Appointment Provider. #### When to use Use `allow_stale` when you need fast response times and can tolerate slightly stale availability dataβ€”for example, when displaying initial availability options to a user before they select a specific slot. ### Idempotency Key Consider specifying an Idempotency Key (`X-Idempotency-Key` header) when submitting a booking request to the [Book PSC Appointment](/api-reference/lab-testing/psc-schedulling/appointment-psc-booking) endpoint. Requests with the same idempotency key within a 14-day retention window will return the original request's response. Note that the response is frozen at the time it is first emitted. For example, if a pending appointment later transitions to confirmed via Async Confirmation, requests with that idempotency key will still return the appointment in its original pending status. #### When to use When a booking request fails on your side (API consumer side), it could be a transient connection issue or a Junction request queueing issue. Your request might have still reached the Junction API server and might have been processed. Using an Idempotency Key allows your system to safely retry booking requests, without the risk of double booking appointments. ### Async Confirmation When calling the [Book PSC Appointment](/api-reference/lab-testing/psc-schedulling/appointment-psc-booking) endpoint, Junction by default returns a 500 Internal Server Error if the PSC Appointment Provider fails to acknowledge the booking request. With Async Confirmation enabled, Junction waits synchronously for the provider to acknowledge the booking request, up to `sync_confirmation_timeout_millisecond` (default: 2.5s, range: 1–10s). * If an acknowledgement is received before the timeout, Junction creates a *reserved* or *confirmed* appointment immediately and returns it in the response. * If no acknowledgement is received before the timeout, Junction creates a placeholder **pending** appointment and returns it in the response. #### Background retries If a pending appointment is created, Junction continues to retry the booking request with the PSC Appointment Provider in the background, up to `async_confirmation_timeout_millisecond` (default: 15min, range: 1min–48hr). * If an acknowledgement is received before the timeout, the appointment transitions to *reserved* or *confirmed* status. * If the provider still fails to acknowledge after the timeout, the pending appointment transitions to *cancelled* status. #### Webhooks You will receive the `labtest.appointment.updated` event for the initial appointment creation, as well as for all state transitions described above. #### When to use Use Async Confirmation when you want to provide a consistently responsive booking experience. When the PSC Appointment Provider is under stress, Async Confirmation allows you to show a pending appointment to the end patient, and manages all the logistics to retrying the booking requests with the provider. # Webhooks Source: https://docs.junction.com/lab/walk-in/webhooks The following webhook events are of interest, when placing a Walk-in order. Those are described in detail in the following sections. ## Order webhook events Based on the status present in [`Order Lifecycle`](/lab/walk-in/order-lifecycle), Junction will trigger two kinds of webhook events, [`labtest.order.created`](/event-catalog/labtest.order.created) and [`labtest.order.updated`](/event-catalog/labtest.order.updated). The `labtest.order.created` event is triggered when an order is created in the system, having the `ordered` status, and all subsequent status changes will trigger a `labtest.order.updated` event in the system. The `partial_results` status does not trigger a Webhook unless specifically requested from Junction. The webhook payload body will have the following information if the Order is in the `completed` status: ```json Walk-in Order Updated theme={null} { "id": "84d96c03-6b1c-4226-ad8f-ef44a6bc08af", "team_id": "6353bcab-3526-4838-8c92-063fa760fb6b", "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "patient_details": { "dob": "2020-01-01", "gender": "male" }, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789" }, "details": { "type": "walk_in_test", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "completed", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.walk_in_test.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.walk_in_test.requisition_created" }, { "id": 3, "created_at": "2022-01-03T00:00:00Z", "status": "collecting_sample.walk_in_test.appointment_pending" }, { "id": 4, "created_at": "2022-01-03T00:00:00Z", "status": "collecting_sample.walk_in_test.appointment_scheduled" }, { "id": 5, "created_at": "2022-01-04T00:00:00Z", "status": "sample_with_lab.walk_in_test.partial_results" }, { "id": 6, "created_at": "2022-01-04T00:00:00Z", "status": "completed.walk_in_test.completed" } ], "origin": "initial", "order_transaction": { "id": "6424d45-ee1a-49c6-ad0c-5769b8e03fc1", "status": "completed", "orders": [ { "id": "84d96c03-6b1c-4226-ad8f-ef44a6bc08af", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2022-01-04T00:00:00Z", "low_level_status": "completed", "low_level_status_created_at": "2022-01-04T00:00:00Z", "origin": "initial" } ] } } ``` ## Appointment webhook events Based on the status present in [`Order and Appointment Lifecycle - Appointment Lifecycle`](/lab/walk-in/order-lifecycle#appointment-lifecycle), Junction will trigger the [`labtest.appointment.updated`](/event-catalog/labtest.appointment.updated) webhook event. If the ability to [schedule appointments before a requisition has been created](/lab/walk-in/order-lifecycle#scheduling-appointments-before-a-requisition-has-been-created) is enabled for your team, and you intend to send patient communications for appointment updates, use the `labtest.appointment.updated` event. See more on communications for this feature [here](/lab/walk-in/communications#scheduling-appointments-before-a-requisition-has-been-created). The webhook payload body may have the following information if the appointment is in the `scheduled` status, after a **reschedule** has happened: ```json Phlebotomy Appointment Updated theme={null} { "event_type": "labtest.appointment.updated", "data": { "id": "06c2c65b-74a0-4f25-a4a9-44f796296355", "user_id": "acf79a82-0c2c-4ca0-998b-378931793905", "order_id": "1ed9c8d7-e1b4-4d61-8123-0f99de5ae99a", "order_transaction_id": "6424d45-ee1a-49c6-ad0c-5769b8e03fc1", "address": { "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip_code": "91189", "country": "United States" }, "location": { "lng": -122.4194155, "lat": 37.7749295 }, "start_at": "2022-01-01T00:00:00", "end_at": "2022-01-01T00:00:00", "iana_timezone": "America/New_York", "type": "patient_service_center", "provider": "quest", "status": "confirmed", "event_status": "scheduled", "provider_id": "123", "can_reschedule": true, "event_data": { "origin": "patient", "is_reschedule": true }, "events": [ { "created_at": "2022-01-01T00:00:00Z", "data": null, "status": "scheduled" }, { "created_at": "2022-01-01T00:00:00Z", "data": { "origin": "patient", "is_reschedule": true }, "status": "scheduled" }, ] } } ```
The `event_data` field contains relevant information regarding the current appointment status, and may be specific for each `provider`. # Ask on Order Entry (AOE) Source: https://docs.junction.com/lab/workflow/aoe Some biomarkers require answers to clinical questions, this is referred to as AOE. These can be as simple as the volume of a particular marker or the patient's ethnicity. Questions that are related to fasting (e.g, code `FSTING`) do not need to be answered. Junction uses the default value you provided for fasting at the [lab test](/api-reference/lab-testing/post-test) creation time. However, if you do answer them, then the answered value overrides the preset test value. There are 4 types of AOE: 1. `choice` 2. `multiple_choice` 3. `numeric` 4. `text` `choice` and `multiple_choice` have a set list of answers to pick from, while `numeric` and `text` don't, and are free-form responses. AOE questions can also be required or optional. ## Where to find the questions As these AOE are directly related to biomarkers, you can find them in the [`GET /v3/lab_tests/markers`](/api-reference/lab-testing/biomarkers) endpoint or in the [`GET /v3/lab_tests/{lab_test_id}/markers`](/api-reference/lab-testing/lab-test-markers) endpoint, in the `aoe` object. As an example, let's look at the `Lead, Blood (Adult)` marker. We have omitted some extra questions for the sake of this doc. ```json theme={null} { "id": 173, "name": "Lead, Blood (Adult)", "provider_id": "007625", ... "questions": [ { "id": 1234567890251, "code": "BLPURP", "type": "choice", "value": "BLOOD LEAD PURPOSE", "constraint": null, "answers": [ { "id": 1234567890252, "code": "LCANS4", "value": "I - INITIAL" }, { "id": 1234567890253, "code": "LCANS5", "value": "R - REPEAT" }, { "id": 1234567890254, "code": "LCANS6", "value": "F - FOLLOW-UP" } ], "required": true, "sequence": 1 }, ] ` ``` Each question has it's own `id`, which will be required to answer it. The types we refer to above are an enum that is represented by `type`. The `answers` list contains a list of possible answers in the case that `type` is one of `choice` or `multiple_choice`, otherwise it is empty. `required` deems whether the questions MUST be answered in order for the order to be valid, otherwise a error will be shown `Missing required questions - "question"`. ## How to answer For more information, refer to our [Knowledge Base article](https://support.junction.com/articles/1804950149-understanding-ask-on-order-entry-aoe-questions). Answering is done at order time, via the [`POST /v3/order`](/api-reference/lab-testing/create-order) endpoint, in the field `aoe_answers`. This field has the following format: ```json theme={null} { "aoe_answers": [ { "marker_id": , "question_id": , "answer": , } ] } ``` You will populate this list with the answers to the questions defined in the `marker` object, as explained [above](/lab/workflow/aoe#where-to-find-the-questions). The `marker_id` refers to the Junction [marker id](/api-reference/lab-testing/biomarkers), and the `question_id` to the id of the question being answered. For example, marker `Leader, Blood (Adult)`, has the following data: ```json theme={null} { "id": 173, "name": "Lead, Blood (Adult)", "provider_id": "007625", ... "questions": [ { "id": 1234567890251, "code": "BLPURP", "type": "choice", "value": "BLOOD LEAD PURPOSE", "constraint": null, "answers": [ { "id": 1234567890252, "code": "LCANS4", "value": "I - INITIAL" }, { "id": 1234567890253, "code": "LCANS5", "value": "R - REPEAT" }, { "id": 1234567890254, "code": "LCANS6", "value": "F - FOLLOW-UP" } ], "required": true, "sequence": 1 }, ] ``` In order to answer the required question, you will need the `marker_id` which is the `id` field, the `question_id` in `questions[0].id` and one of the `code` values in the `question[0].answers` field. ### Answer types The `answer` field contains the actual answer to the question and it's value will depend on the type and question itself. 1. `choice` and `multiple_choice` In this case, the `answer` should contain the `code` field in the list of `answers` provided, as shown [here](/lab/workflow/aoe#where-to-find-the-questions). e.g The question is as follows for marker of id `173`: ```json theme={null} { "id": 173, "questions": [ { "id": 1234567890251, "code": "BLPURP", "type": "choice", "value": "BLOOD LEAD PURPOSE", "constraint": null, "answers": [ { "id": 1234567890252, "code": "LCANS4", "value": "I - INITIAL" }, { "id": 1234567890253, "code": "LCANS5", "value": "R - REPEAT" }, { "id": 1234567890254, "code": "LCANS6", "value": "F - FOLLOW-UP" } ], "required": true, "sequence": 1 }, ] } ``` The answer is: ```json theme={null} { "aoe_answers": [ { "marker_id": 173, "question_id": 1234567890251, "answer": "LCANS4", } ] } ``` 2. `text` This case can encompass many responses, and depends on the question itself. In some instances, the `text` case contains a `constraint` field. This is a string tooltip indicator of the constraints applied by the lab on this particular question. e.g The question is as follows for marker of id `173`: ```json theme={null} { "id": 173, "questions": [ { "id": 1234567890304, "code": "EDDATE", "type": "text", "constraint": null, "value": "EDD/EDC DATE", "answers": [], "required": true, "sequence": 2 }, ] } ``` The answer is: ```json theme={null} { "aoe_answers": [ { "marker_id": 173, "question_id": 1234567890304, "answer": "19900101", } ] } ``` 3. `numeric` In this case, the `answer` should be a numeric string representation of the actual value. e.g The question is as follows for marker of id `173`: ```json theme={null} { "id": 173, "questions": [ { "id": 1234567890240, "code": "COLVOL", "type": "numeric", "constraint": null, "value": "URINE VOLUME (MILLILITERS)", "answers": [], "required": true, "sequence": 1 } ] } ``` The answer is: ```json theme={null} { "aoe_answers": [ { "marker_id": 173, "question_id": 1234567890240, "answer": "1000", } ] } ``` # Cancelling an Order Source: https://docs.junction.com/lab/workflow/cancelling-an-order Order cancellation depends on the current status of your order and its collection method. Orders follow a finite state machine (FSM) pattern with specific rules governing when cancellation is permitted. **Important timing considerations to avoid financial costs:** * **At-home phlebotomy**: Appointments cancelled more than 24 hours in advance will be fully refunded. Cancellations made less than 24 hours before the scheduled appointment are non-refundable. * **Testkits**: Cancel before shipment to avoid fees. Testkits typically ship within the same business day if orders are placed before 1pm EST. * **Walk-in tests**: Can generally be cancelled until sample collection begins * **On-site collection**: Cancel before the collection event ## When Orders Can Be Cancelled ### βœ… Generally Cancellable States All collection methods allow cancellation during these early phases: * `ordered` - Initial order placement * `requisition_created` - After requisition form is generated * `requisition_bypassed` - When requisition is bypassed * `awaiting_registration` - For registrable testkits ### ❌ Never Cancellable (Terminal States) Orders **cannot** be cancelled once they reach these states: * **Already cancelled**: `cancelled`, `do_not_process` * **Completed**: `completed`, `partial_results` * **Failed**: `lost`, `sample_error`, `failure_to_deliver_to_customer`, `failure_to_deliver_to_lab` ## Collection Method Specific Rules **βœ… Can cancel:** * `received.walk_in_test.ordered` * `received.walk_in_test.requisition_created` * `received.walk_in_test.requisition_bypassed` * `collecting_sample.walk_in_test.appointment_scheduled` * `collecting_sample.walk_in_test.appointment_cancelled` * `collecting_sample.walk_in_test.appointment_pending` **❌ Cannot cancel once collection begins:** * `cancelled.walk_in_test.cancelled` - Already cancelled * `completed.walk_in_test.completed` - Order completed * `sample_with_lab.walk_in_test.partial_results` - Has partial results * `collecting_sample.walk_in_test.redraw_available` - Order marked as final but has missing results * `failed.walk_in_test.sample_error` - Sample error occurred Cancelling a walk-in test order does **not** automatically cancel PSC appointments to prevent a poor patient experience, as it could lead to a patient arriving and their appointment being cancelled. Use the [PSC appointment cancellation endpoint](/api-reference/lab-testing/psc-schedulling/appointment-psc-cancelling) if you need to cancel the appointment separately. Orders can be reinstated by our support team if a patient shows up for an appointment related to a cancelled order. **βœ… Can cancel:** * `received.at_home_phlebotomy.ordered` * `received.at_home_phlebotomy.requisition_created` * `received.at_home_phlebotomy.requisition_bypassed` * `collecting_sample.at_home_phlebotomy.appointment_pending` * `collecting_sample.at_home_phlebotomy.appointment_scheduled` * `collecting_sample.at_home_phlebotomy.appointment_cancelled` * `collecting_sample.at_home_phlebotomy.draw_completed` **❌ Cannot cancel once collection begins:** * `cancelled.at_home_phlebotomy.cancelled` - Already cancelled * `completed.at_home_phlebotomy.completed` - Order completed * `sample_with_lab.at_home_phlebotomy.partial_results` - Has partial results * `failed.at_home_phlebotomy.sample_error` - Sample error occurred You can cancel the order directly - the system will automatically cancel any scheduled at-home phlebotomy appointments. Appointments cancelled with less than 24 hours notice are non-refundable. **βœ… Can cancel:** * `received.testkit.ordered` * `received.testkit.awaiting_registration` * `received.testkit.requisition_created` * `received.testkit.requisition_bypassed` * `received.testkit.registered` **❌ Cannot cancel once shipped:** * `collecting_sample.testkit.out_for_delivery` * `collecting_sample.testkit.transit_customer` * `collecting_sample.testkit.with_customer` * `collecting_sample.testkit.transit_lab` * `sample_with_lab.testkit.delivered_to_lab` Testkits become non-cancellable once they enter the shipping phase to avoid logistics costs. Testkits typically ship within the same business day if orders are placed before 1pm EST. **βœ… Can cancel:** * `received.on_site_collection.ordered` * `received.on_site_collection.requisition_created` * `received.on_site_collection.requisition_bypassed` * `sample_with_lab.on_site_collection.draw_completed` **❌ Cannot cancel once collection begins:** * `cancelled.on_site_collection.cancelled` - Already cancelled * `completed.on_site_collection.completed` - Order completed * `sample_with_lab.on_site_collection.partial_results` - Has partial results * `failed.on_site_collection.sample_error` - Sample error occurred ## How to Cancel ### Cancel Appointment (Optional - For Reference Only) **Note**: You can skip this step and go directly to [cancelling the order](#cancel-the-order). The system will automatically cancel at-home phlebotomy appointments when you cancel the order, but **will not** automatically cancel walk-in PSC appointments to avoid a poor patient experience where they arrive and their appointment is cancelled. If you need to cancel only the appointment (without cancelling the entire order), use the appropriate endpoint for your collection method: * **At-home phlebotomy**: [Appointment Cancellation endpoint](/api-reference/lab-testing/at-home-phlebotomy/appointment-cancelling) * **Walk-in tests (PSC)**: [PSC Appointment Cancellation endpoint](/api-reference/lab-testing/psc-schedulling/appointment-psc-cancelling) #### At-Home Phlebotomy Appointment Cancellation ```bash cURL theme={null} curl --request PATCH \ --url '{{BASE_URL}}/v3/order//phlebotomy/appointment/cancel' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' \ --data '{"cancellation_reason_id": "7dfd7da5-ed6e-40bb-a7e4-c8003f0c10a9"}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) client.lab_tests.cancel_phlebotomy_appointment( order_id="", cancellation_reason_id="", ) ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { AppointmentCancelRequest } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: AppointmentCancelRequest = { cancellationReasonId: "", } const data = await client.labTests.cancelPhlabotomyAppointment("", request); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.labtests.requests.AppointmentCancelRequest; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); AppointmentCancelRequest request = AppointmentCancelRequest .builder() .cancellationReasonId("", request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.AppointmentCancelRequest{ CancellationReasonId: "", } response, err := client.LabTests.CancelPhlabotomyAppointment(context.TODO(), "", request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` #### Walk-In Test (PSC) Appointment Cancellation For walk-in tests scheduled at Patient Service Centers, use the PSC appointment cancellation endpoint: ```bash cURL theme={null} curl --request DELETE \ --url '{{BASE_URL}}/v3/order//appointment/psc/cancel' \ --header 'accept: application/json' \ --header 'x-vital-api-key: {YOUR_KEY}' ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) client.lab_tests.cancel_psc_appointment(order_id="") ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.cancelPscAppointment(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().cancelPscAppointment(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.CancelPscAppointment(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ### Cancel the Order Use the [order cancellation endpoint](/api-reference/lab-testing/cancel-order) to cancel any lab test order. **Automatic Appointment Handling**: * **At-home phlebotomy**: Appointments are automatically cancelled when you cancel the order * **Walk-in PSC**: Appointments are **not** automatically cancelled to prevent poor patient experience if they show up. Orders can be reinstated if needed. ```bash cURL theme={null} curl --request DELETE \ --url {{BASE_URL}}/v3/lab_test//cancel \ --header 'Accept: application/json' \ --header 'x-vital-api-key: ' \ --header 'Content-Type: application/json' \ ``` ```python Python theme={null} from vital import Client client = Client( api_key=, environment="sandbox", region="us" ) data = client.LabTests.cancel_order(order_id=''); ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const data = await client.labTests.cancelOrder(""); ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var data = vital.labTests().cancelOrder(""); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.LabTests.CancelOrder(context.TODO(), "") if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ## Error Handling When attempting to cancel an order that cannot be cancelled, you'll receive an error response: ```json Error Response theme={null} { "error": "Bad Request", "message": "Transition from current_status to cancelled is not allowed", "status_code": 400 } ``` **Common reasons for cancellation failure:** * Order is already in a terminal state (completed, failed, or cancelled) * Testkit has already shipped * Sample has been collected or is with the lab * Results are already available (partial or complete) ## Checking Cancellation Eligibility Before attempting cancellation, check the order's current status using the [get order endpoint](/api-reference/lab-testing/get-order): ```bash Check Order Status theme={null} curl --request GET \ --url {{BASE_URL}}/v3/order/ \ --header 'x-vital-api-key: ' ``` Look for these indicators in the response: * `status` field shows the current high-level status * `events` array shows the detailed state progression * Orders in early states (`ordered`, `requisition_created`) are typically cancellable * Orders with `cancelled`, `completed`, or `failed` status cannot be cancelled ## Response Examples ### Successful Cancellation ```json Cancellation Success Response theme={null} { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "patient_details": {"dob": "2020-01-01", "gender": "male"}, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789", }, "details": { "type": "testkit", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "shipment": { "id": "d55210cc-3d9f-4115-8262-5013f700c7be", "outbound_tracking_number": "", "outbound_tracking_url": "", "inbound_tracking_number": "", "inbound_tracking_url": "", "outbound_courier": "usps", "inbound_courier": "usps", "notes": "", "created_at": "2020-01-01T00:00:00.000Z", "updated_at": "2020-01-01T00:00:00.000Z", }, "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", }, }, "diagnostic_lab_test": { "name": "Lipids Panel", "description": "Cholesterol test", "method": "testkit", }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "cancelled", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.testkit.ordered", }, { "id": 2, "created_at": "2022-01-01T00:00:00Z", "status": "cancelled.testkit.cancelled", } ], } ``` ### Successful Appointment Cancellation ```json Appointment Cancellation Response theme={null} { "id": "413d7205-f8a9-42ed-aa4a-edb99e481ca0", "user_id": "202b2c2f-fb4c-44dc-a4f8-621186fde227", "address": { "first_line": "West Lincoln Street", "second_line": "", "city": "Phoenix", "state": "AZ", "zip_code": "85004", "unit": "14" }, "location": { "lng": -112.0772235, "lat": 33.4421912 }, "start_at": "2023-05-17T20:00:00+00:00", "end_at": "2023-05-17T22:00:00+00:00", "iana_timezone": "America/Phoenix", "type": "phlebotomy", "provider": "getlabs", "status": "cancelled", "provider_id": "e89eb489-7382-4966-bb14-7ab4763eba6c", "can_reschedule": true } ``` ## Related Resources * [Order Lifecycle Overview](/lab/workflow/lab-test-lifecycle) * [API Reference: Cancel Order](/api-reference/lab-testing/cancel-order) * [API Reference: Cancel At-Home Appointment](/api-reference/lab-testing/at-home-phlebotomy/appointment-cancelling) * [API Reference: Cancel PSC Appointment](/api-reference/lab-testing/psc-schedulling/appointment-psc-cancelling) * [Webhooks for Order Events](/lab/testkits/webhooks) *** *For questions about specific cancellation scenarios, please contact support at [support@junction.com](mailto:support@junction.com)* # Creating a Lab Test Source: https://docs.junction.com/lab/workflow/create-test Junction provides a way to create your own lab tests. Lab tests are a collection or pre-set of orderable markers. These are made up of: 1. One or more marker IDs or Provider IDs. 2. The collection method: `testkit`, `walk_in_test`, `at_home_phlebotomy`, or `on_site_collection`. Once a Lab Test is created and approved, you can use it when making a order. When you create a Lab Test, it is not immediately available. We need to validate and approve it on our side, before you can use it when making orders. ### Example To begin, make a request to our [`GET /v3/lab_tests/markers`](/api-reference/lab-testing/biomarkers) endpoint, in order to choose the markers you want to test for. ```json Get all markers theme={null} { "markers": [ { "id": 1, "name": "17-OH Progesterone LCMS", "slug": "17-oh-progesterone-lcms", "description": "17-OH Progesterone LCMS", "lab_id": 1, "provider_id": "070085", "type": null, "unit": null, "price": "N/A" }, { "id": 2, "name": "ABO Grouping", "slug": "abo-grouping", "description": "ABO Grouping", "lab_id": 1, "provider_id": "006056", "type": null, "unit": null, "price": "N/A" } ], "total": 2, "page": 1, "size": 2 } ``` With this information, you can create a lab test using [`POST /v3/lab_tests`](/api-reference/lab-testing/post-test). As an example, let's say you wanted a test from `Labcorp`, with markers with provider ids `006056` and `070085`: ```python Create a new test theme={null} from vital import Client client = Client(api_key, "sandbox") data = client.LabTests.create_test( name="Example test", description="Example test", method="at_home_phlebomoty", provider_ids=["006056", "070085"] # or use the marker ids e.g marker_ids=[1, 2], ) ``` It is possible to create a test with either the marker id or provider id. Provider ids are the recommended way, since these are shared accross environments. Not all labs provide these, so in their absence, use the marker id. Once your lab test creation is successful you will receive the following response: ```json theme={null} { "lab_test": { "name": "Example test", "description": "Example test", "sample_type": "serum", "method": "at_home_phlebomoty", "price": 10, "is_active": false, "lab": { "slug": "labcorp", "name": "LabCorp", "first_line_address": "123 Main St", "city": "San Francisco", "zipcode": "91789" }, "markers": [ { "name": "17-OH Progesterone LCMS", "slug": "17-oh-progesterone-lcms", "description": "17-OH Progesterone LCMS" }, { "name": "ABO Grouping", "slug": "abo-grouping", "description": "ABO Grouping" } ] } } ``` Do notice that `is_active` is set to `false`. You can verify its status by calling the [lab tests endpoint](/api-reference/lab-testing/tests). # Importing an Order Source: https://docs.junction.com/lab/workflow/importing-order This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. If you want to track orders with Junction that weren’t originally placed with Junction, you can import an order. It's crucial that imported orders are created with the correct sample ID. This is the **requisition number** or **control number** for Labcorp orders, or the **lab reference ID** for Quest orders. If in doubt, contact your Junction Customer Service Manager to confirm what you should supply for the sample ID. The sample ID must also uniquely identify the order. If you supply an ID that clashes with an existing order, that will result in a validation error. An imported order can be cancelled, receive results, and functions similarly to orders originally placed with Junction, but with one important exception: a requisition will not be created. This means you cannot use the [endpoint to retrieve the requisition](/api-reference/lab-testing/requisition-pdf) for an imported order since Junction does not have access to it. After an order is successfully imported, it will be in the `requisition_bypassed` state (for example, an imported testkit order would have a `received.testkit.requisition_bypassed` status). This status supports transitioning to the same states that `requisition_created` does. Since import skips the regular order creation flow, no communications are sent to patients about the order being created, but communications are sent for subsequent events that might occur (order completion, for example). ### Example ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.billing import Billing from vital.types.gender import Gender from vital.types.lab_test_collection_method import LabTestCollectionMethod from vital.types.order_set_request import OrderSetRequest from vital.types.patient_address_compatible import PatientAddressCompatible from vital.types.patient_details import PatientDetails from vital.types.physician_create_request import PhysicianCreateRequest from datetime import datetime client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.lab_tests.import_order( user_id="63661a2b-2bb3-4125-bb1a-b590f64f057f", billing_type=Billing.CLIENT_BILL, order_set=OrderSetRequest(lab_test_ids=["0e18c2f8-0740-475d-8868-431f7e9c0cfd"]), collection_method=LabTestCollectionMethod.WALK_IN_TEST, physician=PhysicianCreateRequest(first_name="Jane", last_name="Doe", npi=""), patient_details=PatientDetails( first_name="John", last_name="Doe", dob=datetime.fromisoformat("2020-01-01"), gender=Gender.MALE, phone_number="+1123456789", email="email@email.com" ), patient_address=PatientAddressCompatible( receiver_name="John Doe", first_line="123 Main St.", second_line="Apt. 208", city="San Francisco", state="CA", zip="91189", country="US", phone_number="+1123456789" ), sample_id="1234567890", ) ``` This example assumes you provide your own physician. The physician must be provided if your team configuration is set up as delegated. The billing type provided must also be appropriate to your team configuration. These aspects will be validated when making the request. Since the request represents an existing order, we don’t perform strict address validation on the patient address, to make it easier to import orders exactly as they were placed in the original interface. The response object schema is the same as [creating an order](/api-reference/lab-testing/create-order). # Lab Test Lifecycle Source: https://docs.junction.com/lab/workflow/lab-test-lifecycle ## Introduction We encapsulate our lab test modalities in the `order` object. This is the main object you will interact with, as it contains all the information related to that lab test. An order has a high-level status which represents where the order is throughout its lifecycle, e.g. received/delivered/completed/cancelled etc. The high-level status is the same across test modalities. On top of that, each modality has its own sub-statuses, to represents lifecycle stages specific to that modality. For example, test-kits involve shipping of the box, so we have a whole set of statuses to track the shipment. **Please note**: To ensure future compatibility, we ask that you avoid exhaustive matching on enum values such as an order’s status. We may introduce new statuses (and other enum values) over time, and code that assumes all current values are exhaustive could break or fail to compile with SDK upgrades. To stay compatible and benefit from future enhancements, treat unknown values gracefullyβ€”for example, by using default cases or limiting checks to only the values your integration depends on. ## Statuses Statuses are namespaced with the following format: `[HIGH-LEVEL STATUS].[TEST MODALITY].[LOW-LEVEL STATUS]` As an example, the status `collecting_sample.at_home_phlebotomy.appointment_scheduled` means the patient has scheduled an at-home phlebotomy appointment. ### High-level statuses The high-level statuses of an order, are represented by `order.status`: * `received`: we received the order, stored it into our system, and started processing it. * `collecting_sample`: these track collecting the sample from the patient. For test-kits, these track the shipment of the kit. For at-home phlebotomy and walk-in tests, these track appointment scheduling and management. * `sample_with_lab`: the lab received the sample and is currently analyzing it. * `completed`: the order is complete and the results are ready. * `cancelled`: the order has been cancelled, by you or the patient. * `failed`: we failed to process the order. Modality specific sub-statuses are encapsulated in the `order.events` list. These include a list of all the events for that test: ```json Test kit order status theme={null} { ..., "status": "cancelled", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.testkit.ordered", }, { "id": 2, "created_at": "2022-01-01T00:00:00Z", "status": "received.testkit.requisition_created", }, { "id": 3, "created_at": "2022-01-01T00:00:00Z", "status": "collecting_sample.testkit.transit_customer" }, ], } ``` Below is a full list of all the available statuses by test modality. The names should be self-descriptive. Please do not hesitate to contact us if you need any additional clarification! ### Test-kit statuses * `received.testkit.ordered` * `received.testkit.awaiting_registration` * `received.testkit.testkit_registered` * `received.testkit.requisition_created` * `received.testkit.requisition_bypassed` * `collecting_sample.testkit.transit_customer` * `collecting_sample.testkit.out_for_delivery` * `collecting_sample.testkit.with_customer` * `collecting_sample.testkit.transit_lab` * `collecting_sample.testkit.problem_in_transit_customer` * `collecting_sample.testkit.problem_in_transit_lab` * `sample_with_lab.testkit.delivered_to_lab` * `completed.testkit.completed` * `failed.testkit.failure_to_deliver_to_customer` * `failed.testkit.failure_to_deliver_to_lab` * `failed.testkit.sample_error` * `failed.testkit.lost` * `cancelled.testkit.cancelled` * `cancelled.testkit.do_not_process` ### Walk-in visit statuses * `received.walk_in_test.ordered` * `received.walk_in_test.requisition_created` * `received.walk_in_test.requisition_bypassed` * `collecting_sample.walk_in_test.appointment_pending` * `collecting_sample.walk_in_test.appointment_scheduled` * `collecting_sample.walk_in_test.appointment_cancelled` * `collecting_sample.walk_in_test.redraw_available` (currently in beta testing) * `sample_with_lab.walk_in_test.partial_results` * `completed.walk_in_test.completed` * `failed.walk_in_test.sample_error` * `cancelled.walk_in_test.cancelled` ### At-home phlebotomy statuses * `received.at_home_phlebotomy.ordered` * `received.at_home_phlebotomy.requisition_created` * `received.at_home_phlebotomy.requisition_bypassed` * `collecting_sample.at_home_phlebotomy.appointment_pending` * `collecting_sample.at_home_phlebotomy.appointment_scheduled` * `collecting_sample.at_home_phlebotomy.draw_completed` * `collecting_sample.at_home_phlebotomy.appointment_cancelled` * `sample_with_lab.at_home_phlebotomy.partial_results` * `completed.at_home_phlebotomy.completed` * `failed.at_home_phlebotomy.sample_error` * `cancelled.at_home_phlebotomy.cancelled` ### On-site collection statuses (currently in beta testing) * `received.on_site_collection.ordered` * `received.on_site_collection.requisition_created` * `received.on_site_collection.requisition_bypassed` * `collecting_sample.on_site_collection.draw_completed` * `sample_with_lab.on_site_collection.partial_results` * `completed.on_site_collection.completed` * `failed.on_site_collection.sample_error` * `cancelled.on_site_collection.cancelled` # Ordering a Registrable Testkit Source: https://docs.junction.com/lab/workflow/order-registrable-testkit Under the [standard Testkit ordering flow](/api-reference/lab-testing/create-order), you place a Testkit order for a specific patient. This resulting Testkit is bound to that patient, and the Testkit cannot be passed on to someone else. With Registrable Testkits, Junction offers a different option β€” Testkits not bound to any specific patient can be ordered to a specific household address. The patient registration process is deferred until an actual patient intends to use the Testkit. Note that this only applies to Testkits, not Walk-in Phlebotomy or At-home Phlebotomy. This involves two steps: 1. Ordering the Testkit. 2. Registering a patient. After step 1, the order is sent to the requested address and generates all the same webhooks as a regular order would. However it is stuck on the `received.testkit.awaiting_registration` state, meaning that no requisition form is generated for this order, and no results can be obtained until step 2 is done. After step 2, the order flow resumes as normal, the patient sends the `testkit` to the lab, the order is progressed to the `received.testkit.testkit_registered` state and, again, all the same webhooks as in the regular order flow are dispatched. ### Example To make an order, make a request to our [`POST /v3/order/testkit`](/api-reference/lab-testing/create-unregistered-order), to fulfil step 1. ```python Python theme={null} from vital import Client client = Client(api_key, "sandbox") data = client.LabTests.create_unregistered_testkit_order( user_id="63661a2b-2bb3-4125-bb1a-b590f64f057f", lab_test_id="5b41f610-ebc5-4803-8f0c-a61c3bdc7faf", shipping_details={ "receiver_name": "john Doe", "street": "123 Main St.", "street_number": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "US", "phone_number": "+11234567890" } ) ``` The `testkit` is now ordered, and will be sent to the specified address. Once delivered, it can be kept until it's expiration. Once the `testkit` is with it's intended patient, it should be registered (step 2). In order to register, Junction requires a `sample_id`, which is found within the `testkit` itself. This is an unique identifier. Junction also requires the patient details and address. Besides this, if using Junction's physician network, then supplying the consents field is required. If providing your own physician, then the physician information is required. For this example, we will assume the use of your own physician. To register a `testkit` order, make a request to our [`POST /v3/order/testkit/register/`](/api-reference/lab-testing/register-order), to fulfil step 2. ```python Python theme={null} client = Client(api_key, "sandbox") data = client.LabTests.register_testkit_order( user_id="63661a2b-2bb3-4125-bb1a-b590f64f057f", sample_id="123123123", patient_details={ "first_name": "John", "last_name": "Doe", "dob": "2020-01-01", "gender": "male", "phone_number": "+1123456789", "email": "email@email.com" }, patient_address={ "receiver_name": "john Doe", "street": "123 Main St.", "street_number": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "US" }, physician={ "first_name": "Doctor", "last_name": "Doc", "npi": "123123123" } ) ``` Your `testkit` order is now registered, and the order flow should resume as normal. For more information regarding the lifecycle of a test, refer to the [lab test lifecyle](/lab/workflow/lab-test-lifecycle) page. # Order Requirements Source: https://docs.junction.com/lab/workflow/order-requirements ### Consents When you order a lab test through our API, you may have to collect consents from the patient and forward it to us. The following consents may include: * `hipaa-authorization`. * `terms-of-use`. * `telehealth-informed-consent`. ### Physician As mentioned [in the introduction](/lab/overview/introduction#features), you don't need to have your own Physician to place orders with Junction. When you [order a test](/lab/overview/quickstart#3-placing-an-order), you can pass an optional `physician` argument. If provided, the order will use your physician. If not, your order will go through with Junction's physician network. For more information, please check [our lab testing page](https://tryvital.io/labs) and [book an introductory call with us](https://cal.com/team/vital/discovery-call). ### Patient Name Validation Patient names must conform to a specific regex pattern due to lab restrictions. This validation applies to all name fields including `first_name` and `last_name`. **Regex Pattern:** ``` ^([a-zA-Z0-9]{1})([a-zA-Z0-9-.,']*(\s[a-zA-Z0-9-.,']+)*[a-zA-Z0-9-.,']?)$ ``` **Validation Rules:** * Must start with an alphanumeric character (not space, hyphen, or punctuation) * Only allows letters (a-z, A-Z), numbers (0-9), hyphens (-), periods (.), commas (,), apostrophes (') * Spaces are only allowed between words * Cannot start or end with a space * Cannot have consecutive spaces **Valid Name Examples:** * `John` * `Mary-Jane` * `O'Connor` * `Dr. Smith` * `Jean-Pierre` **Invalid Name Examples:** * ` John` (starts with space) * `John ` (ends with space) * `-John` (starts with hyphen) * `John@Smith` (contains @) * `JosΓ©` (contains accented character) ### Minors Ordering for minors (under 18 years old) requires a specific configuration, which you can request with your Junction Customer Success Manager. Once enabled, you are able to order for a minor patient, by providing information regarding the medical proxy. This information can be supplied at [ordering time](/api-reference/lab-testing/create-order#body-patient-details-medical-proxy), or at any time by updating the [user demographics](/api-reference/user/upsert-info#body-medical-proxy). # Ordering Source: https://docs.junction.com/lab/workflow/ordering # Concepts Junction has a series of concepts to grasp regarding ordering: * [Lab Tests](/lab/workflow/create-test) * [Lifecyle of an Order](/lab/workflow/lab-test-lifecycle) * [AoE](/lab/workflow/aoe) * [Registrable Kits](/lab/workflow/order-registrable-testkit) * [Scheduled Orders](/lab/workflow/scheduled-orders) In this document, we will focus on orderable panels/biomarkers. ### Markers [*Markers*](/api-reference/lab-testing/biomarkers) are individual, orderable tests, at the lab level. So, for example, at *Labcorp* you can order a `Lipid Panel` test and a `Vitamin D` test, a `panel` and a `biomarker` respectively. At Junction, these are both refered to as **markers**. ### Lab Tests [*Lab Tests*](/api-reference/lab-testing/post-test) are a collection of markers, or a preset combination of markers, that you can order. Using the example above, you can create a `Labcorp Lipid Panel and Vitamin D` lab test. You can then place orders at Junction using this preset. This is useful for situations where you repeatedly want to order the same markers. # Ordering When placing an [Order](/api-reference/lab-testing/create-order), you will see an `order_set` field. This is what defines what markers will be ordered. There are multiple combinations allowed with this field, so let's explore all of them. ### Ordering a *Lab Test* When ordering from **one** previously created *Lab Test*, the `order_set` field should be populated as follows: ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) client.lab_tests.create_order( order_set=OrderSet( lab_test_ids=["lab_test_id"] ) ... ) ``` ### Ordering from multiple *Lab Tests* You may want to combine two or more existing Lab Tests without creating a new one. This is possible by doing: ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) client.lab_tests.create_order( order_set=OrderSet( lab_test_ids=["lab_test_id1", "lab_test_id_2"] ) ... ) ``` ### Ordering without a *Lab Test* This is what Junction calls `Γ  la carte` ordering. You may want to order from the marker compendium without creating a preset. This is a team level configuration that must be requested from Junction. Not all markers can be ordered in this manner. It is possible to order `a-la-carte` using Junction `marker_ids` or the lab's `provider_id`. ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) client.lab_tests.create_order( order_set=OrderSet( add_on=AddOn( provider_ids=["322022"] # marker_ids=[1] ) ), collection_method="walk_in_test", ... ) ``` ### Order *Lab Tests* with extra *Markers* You may also add extra **markers** to an order. For example, you want to order the `Labcorp Lipid Panel and Vitamin D` but for this particular patient, you also want to order a `CBC Panel`. ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) client.lab_tests.create_order( order_set=OrderSet( lab_test_ids=["lab_test_id"], add_on=AddOn( provider_ids=["322022"] # marker_ids=[1] ) ), collection_method="walk_in_test", ... ) ``` When supplying the `add_on` field, it is always required to provide the `collection_method` field. ## Collection Method Further explored in the documentation, Junction also has the concept of *Collection Method*. Junction currently supports three methods, [`At Home Phlebotomy`](lab/at-home-phlebotomy/overview), [`Walk In Phlebotomy`](lab/walk-in/overview) and [`Testkits`](lab/testkits/overview). When ordering, you must select one of these, either at lab test creation, or at ordering time. ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) client.lab_tests.create_order( order_set=OrderSet( lab_test_ids=["lab_test_id"], ), collection_method="walk_in_test", ... ) ``` ## Γ€ La Carte Markers As mentioned above, not all `markers` are `a la carte` orderable. You can find which one's are orderable via the [GET /v3/lab\_tests/markers](/api-reference/lab-testing/biomarkers). ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) markers = client.lab_tests.get_markers( name="322022", a_la_carte_enabled=True ) if not all([m.a_la_carte_enabled for m in markers.markers]): raise Exception("Markers not a_la_carte_enabled") ``` ## Error Cases With the existance of various allowed combinations with the `order_set` field, there's many validations that are done server side. Here are some of the errors you can expect to encounter: ### 400 Bad Request 1. `collection_method must be set if add_on is set`: When the `add_on` field is supplied, you must provide the `collection_method`. 2. `marker_ids or provider_ids must be set in add_on`: One of `marker_ids` or `provider_ids` must be set, if the `add_on` field is provided. 3. `cannot set both marker_ids and provider_ids in add_on`: Similarly, only one of the former fields can be provided. 4. `cannot order lab_tests from multiple labs`: You can only order multiple lab tests from the same lab. 5. `cannot order with lab tests with multiple collection methods`: You must supply a `collection_method` if ordering multiple lab tests with multiple collection methods. 6. `Lab does not allow gender `: The associated lab restricts which patient genders it accepts. This restriction applies to the entire lab, not to individual lab tests. # Partial Result Notifications Source: https://docs.junction.com/lab/workflow/partials Orders may have partial results, meaning that the lab has made available part of the result, while the full result is not complete. In general, these are short lived and as such, Junction does not expose them via webhooks. Some orders however have long lived partial results, specifically when one ordered marker takes significantly longer to result than the others. In these situations, clients may want to be notified of the existance of partial results. As such, Junction provides a team level configuration that enables the delivery of partial result webhooks. Similarly to other events, these are triggered via a `labtest.order.updated` event in the system. Orders can also experience multiple partials in their lifecycle. In these cases, Junction will send a webhook for each partial update. So if your order experiences two partial results before a final, complete result, you should expect to receive two `labtest.order.updated` webhooks with `partial` status. For example, in the second partial result, you would receive a webhook with the following body: Note that the `events` block contains two `sample_with_lab.walk_in_test.partial_results` events. ```json Walk-in Order Updated theme={null} { "id": "84d96c03-6b1c-4226-ad8f-ef44a6bc08af", "team_id": "6353bcab-3526-4838-8c92-063fa760fb6b", "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "patient_details": { "dob": "2020-01-01", "gender": "male" }, "patient_address": { "receiver_name": "John Doe", "first_line": "123 Main St.", "second_line": "Apt. 208", "city": "San Francisco", "state": "CA", "zip": "91189", "country": "United States", "phone_number": "+1123456789" }, "details": { "type": "walk_in_test", "data": { "id": "a655f0e4-6405-4a1d-80b7-66f06c2108a7", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z" } }, "sample_id": "123456789", "notes": "This is a note", "created_at": "2020-01-01T00:00:00Z", "updated_at": "2020-01-01T00:00:00Z", "status": "sample_with_lab", "events": [ { "id": 1, "created_at": "2022-01-01T00:00:00Z", "status": "received.walk_in_test.ordered" }, { "id": 2, "created_at": "2022-01-02T00:00:00Z", "status": "received.walk_in_test.requisition_created" }, { "id": 3, "created_at": "2022-01-03T00:00:00Z", "status": "sample_with_lab.walk_in_test.partial_results" }, { "id": 4, "created_at": "2022-01-04T00:00:00Z", "status": "sample_with_lab.walk_in_test.partial_results" } ] } ``` # Redraws Source: https://docs.junction.com/lab/workflow/redraws This feature is in **closed beta**. Interested in this feature? Get in touch with your Customer Success Manager. Please note that this feature is only available for walk-in tests with Labcorp, Quest and BioReference. A patient may be eligible for a redraw if their order has missing results due to a lab error. To get a redraw, the patient will need to go back to the PSC with a new requisition form, listing only the missing biomarkers that need to be retested. To facilitate this flow, Junction uses the concept of **order transactions** to group related orders together and provide unified results. An order transaction represents a single testing journey, which will include one initial order and at most, one redraw order. ## Order Transactions and Initial Orders An order transaction is created whenever a new `initial` order is created and a link is established between the two entities. * `initial` orders are orders that are not derived from another order. This is denoted in the order's `origin` field. * Order transactions have a `status` field, which can either be `active`, `completed` or `cancelled` * `active` - one or more orders belonging to the order transaction is in progress * `completed` - all expected lab work for this transaction is completed and no further updates are expected * `cancelled` - the initial order has been cancelled (and there's no redraw order), or both the initial and redraw orders have been cancelled * An order can only ever belong to one order transaction ## Order Transactions and Redraw Orders If an `initial` order is eligible for a redraw, rather than transitioning the [low-level status](/lab/workflow/lab-test-lifecycle) of the order to `completed`, the order status is set to `redraw_available`. * At this point, a new order is created, with `origin` set to `redraw` and it is linked to the same order transaction as the `initial` order ### Webhooks * A `labtest.order.updated` [webhook](/lab/walk-in/webhooks) will be sent for the `initial` order when it transitions to the `redraw_available` status * Additionally, `labtest.order.created` and `labtest.order.updated` [webhooks](/lab/walk-in/webhooks) will be sent for the `redraw` order, when it is created and transitions to the `requisition_created` status upon the requisition being processed * The `labtest.order.created` and `labtest.order.updated` webhook payloads will include origin and order transaction details (including a list of orders that belong to the transaction, ordered by ascending date of order creation) - see the example payload on the [webhooks](/lab/walk-in/webhooks) page for more details * Ordering of webhooks delivery is not guaranteed and should not be relied upon ### Monitoring updates and completion * The results for the `initial` order will be available via the [results API](/lab/results/result-formats) when the `redraw_available` event is received. **No further updates for this order should be expected.** * The `redraw` order will move through the usual [order lifecycle](/lab/walk-in/order-lifecycle) and will emit its own webhooks so that updates can be tracked. * A patient can only go through one redraw per order transaction. This means that a `redraw` order will never transition to a `redraw_available` state. * When results for the `redraw` order have been received and processed, the `redraw` order status will transition to `completed`, regardless of whether there are still missing results. The order transaction status will also be marked as `completed`. ## API Endpoints The following endpoints are available to retrieve order transaction information and combined results for all orders within a transaction: * `GET /v3/order_transaction/{order_transaction_id}` - Get an order transaction's details and list of orders * `GET /v3/order_transaction/{order_transaction_id}/result` - Get combined results for an order transaction in JSON format * `GET /v3/order_transaction/{order_transaction_id}/result/pdf` - Get combined results for an order transaction in PDF format Responses for the following endpoints also include order transaction details: * `GET /v3/orders` - Get a list of filtered orders (including the ability to filter by order transaction ID) * `GET /v3/order/{order_id}` - Get an individual order * `GET /v3/order/{order_id}/result` - Get an individual order's results ## Key things to note * The order transaction should be used as the primary way to reason about all related orders. A workflow for monitoring redraws could be as follows: * Save the order transaction ID associated with an order at the point of its creation * When processing order webhooks, if an initial order is in a `redraw_available` state, check the order transaction details in the payload to retrieve the order ID for the new redraw order * Monitor webhooks for the redraw order to get the latest updates * Once the redraw order is completed, use the order transaction endpoint to retrieve combined results ## Integrating with Order Transactions We recommend that you store both the `order_id` and `order_transaction_id` when placing an order in Junction. This way, if you receive a webhook for an order that you have not seen in your system, you will be able to link it to the initial order via the `order_transaction_id`. From there, you can always use the `order_transaction_id` directly to obtain results, ensuring you always get the complete set of results for a particular order transaction. # Scheduled Orders Source: https://docs.junction.com/lab/workflow/scheduled-orders Junction provides a way to schedule your orders for a future date, both via the API and via the Junction Dashboard. A Scheduled order is one that will only be fulfilled in the future, so it won't be created in the Partner Laboratories until the defined date. This is useful for defining follow-up orders after placing an initial Lab Order for a patient. ## Ordering through the API To place a scheduled order through the API, you should use the [Create Order endpoint](/api-reference/lab-testing/create-order), passing the `activate_by` date parameter. The `activate_by` parameter defines when that order is scheduled for, and it will move from the `ordered` to the `requisition_created` status when the date arrives. If you want to query all placed orders, you may use [Get Orders endpoint](/api-reference/lab-testing/get-orders), passing an `order_activation_types` query parameter, which accept the following values `["current", "scheduled"]`. * If the parameter is not provided, every order is returned. * If `current` is provided, orders with a `null` `activate_by` date or with `activate_by` set before the current date are returned. * If `scheduled` is provided, orders with an `activate_by` date greater than the current date are returned. Placing a scheduled order for 6 of January of 2025 would look like this: ```json Placing a Scheduled Order request theme={null} { "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "patient_details": { "dob": "2022-07-06T22:20:26.796Z", "gender": "male | female", "email": "test@test.com" }, "patient_address": { "receiver_name": "John Doe", "street": "Hazel Road", "street_number": "102", "city": "San Francisco", "state": "CA", "zip": "91789", "country": "U.S.", "phone_number": "+14158180852" }, "lab_test_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "physician": { "first_name": "John", "last_name": "Doe", "email": "john@doe.com", "npi": "123456789", "licensed_states": ["CA", "NY"], "created_at": "2022-07-06T22:20:26.796Z", "updated_at": "2022-07-06T22:20:26.796Z" }, "activate_by": "2025-01-06" } ``` ## Ordering through the Junction Dashboard To place a Scheduled Order through the dashboard, just follow the normal procedure until you reach the Lab Testing selection screen. Notice that there is a checkbox for defining this order as scheduled for the future, after the checkbox is selected you can pass in the desired scheduled date. After that, you can filter out the `Scheduled` and `Current` orders in the dashboard using the filter in the orders listing. # Data Prioritization Source: https://docs.junction.com/sense/data-prioritization ## Overview Data prioritization automatically kicks in whenever you use a [Group By clause](/sense/query-dsl/group-by-clause). Within each group outlined by your [Group By clause](/sense/query-dsl/group-by-clause), the query executor would **implicitly**: * Sub-group the data further by their Provider and their [Source Type](/wearables/providers/data-attributions); * Keep only data from the sub-group that has the highest Source Type priority, or the highest Provider priority when multiple sub-group exists for the same Source Type. Data prioritization works only in a [Group By](/sense/query-dsl/group-by-clause) context. If both the *Provider* and *Source Type* Source Columns are present in the [Group By clause](/sense/query-dsl/group-by-clause), the implicit [Data Prioritization](/sense/data-prioritization) behaviour would be disabled. You are querying Sleep Score grouped by every 1 day: ```json theme={null} { "select": [ { "func": "newest", { "value_macro": "sleep_score" } } ], "group_by": [ { "date_trunc": { "value": 1, "unit": "day }, "arg": { "index": "sleep" } } ] } ``` Assuming this timeline: * Day 1: The user connected Oura. * Day 5: The user connected Apple HealthKit. * Both Oura and HealthKit have been consistently sending sleep data every day since connected. #### If your team priority states that Apple HealthKit > Oura The query result would be: * Day 1-4: Sleep Score computed from Oura sleep data * Day 5-7: Sleep Score computed from Apple HealthKit sleep data ### If your team priority states that Oura > Apple HealthKit The query result would be: * Day 1-7: Sleep Score computed from Oura sleep data Apple HealthKit data are ignored in this case, because Oura is available throughout all days and has higher provider priority. ## Previewing the prioritization effect You can experiment and preview the effect of prioritization using the [Query API](/sense/using-query-api). By specifying a list of provider slugs in `config.provider_priority_overrides`, you instruct the query executor to treat these providers as the highest priority β€” above the team provider priority β€” specifically in this query invocation. ## Managing Provider Priorities You can manage the provider priorities for each summary type through the **Data Priority** section of the [Junction Dashboard](https://app.junction.com/). A larger number indicates a higher priority. ## Source Type Priorities We use pre-assigned, non-configurable Source Type priorities that follow the general expectation of data reliability. *(sorted from highest priority to lowest priority)* * `lab` * `automatic` * `watch` * `ring` * `chest_strap` * `scale` * `cuff` * `fingerprick` * `manual_scan` * `phone` * `app` * `multiple_sources` * `unknown` # Examples Source: https://docs.junction.com/sense/examples A couple of examples to get you started with Continuous Query: ## Daily Sleep Analysis ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.group_key("*"), va.Sleep.col("efficiency").mean(), va.Sleep.score().mean(), va.Sleep.chronotype().newest() ).where( "type = 'long_sleep'" ).group_by( va.date_trunc(va.Sleep.index(), 1, "day") ).finalize() ``` ```python JSON DSL theme={null} { "where": "type = 'long_sleep'", "select": [ { "group_key": "*" }, { "arg": { "sleep": "efficiency" }, "func": "mean" }, { "arg": { "value_macro": "sleep_score", "version": "automatic" }, "func": "mean" }, { "arg": { "value_macro": "chronotype", "version": "automatic" }, "func": "newest" } ], "group_by": [ { "date_trunc": { "value": 1, "unit": "day" }, "arg": { "index": "sleep" } } ] } ``` ## Weekly Insights Into Users Activity ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.group_key("*"), va.Activity.col("heart_rate_resting").mean(), va.Activity.col("calories_total").max(), va.Activity.col("steps").min(), va.Activity.col("duration_active_seconds").mean() ).group_by( va.date_trunc(va.Activity.index(), 1, "week") ).finalize() ``` ```python JSON DSL theme={null} { "select": [ { "group_key": "*" }, { "arg": { "activity": "heart_rate_resting" }, "func": "mean" }, { "arg": { "activity": "calories_total" }, "func": "max" }, { "arg": { "activity": "steps" }, "func": "min" }, { "arg": { "activity": "duration_active_second" }, "func": "mean" }, ], "group_by": [ { "date_trunc": { "value": 1, "unit": "week" }, "arg": { "index": "activity" } } ] } ``` ## First Glucose Measurement Of The Day Grouped By Source Type and Provider ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.group_key("*"), va.Timeseries.col("glucose").field("value").oldest() ).group_by( va.date_trunc(va.Timeseries.index(), 1, "day"), va.Source.col("source_provider"), va.Source.col("source_type") ).finalize() ``` ```python JSON DSL theme={null} { "select": [ { "group_key": "*" }, { "arg": { "field": "value", "timeseries": "glucose" }, "func": "oldest" } ], "group_by": [ { "arg": { "index": "timeseries" }, "date_trunc": {"unit": "day", "value": 1} }, {"source": "source_provider"}, {"source": "source_type"} ] } ``` ## Daily Summaries of Metabolic Biomarkers Grouped By Source Type and Provider ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.group_key("*"), va.Timeseries.col("glucose").field("value").mean(), va.Timeseries.col("heartrate").field("value").mean(), va.Timeseries.col("steps").field("value").sum(), va.Timeseries.col("hrv").field("value").mean(), va.Timeseries.col("calories_active").field("value").sum(), va.Timeseries.col("body_temperature").field("value").mean(), va.Timeseries.col("body_temperature").field("value").min(), va.Timeseries.col("body_temperature").field("value").max(), ).group_by( va.date_trunc(va.Timeseries.index(), 1, "day"), va.Source.col("source_provider"), va.Source.col("source_type"), ).finalize() ``` ```python JSON DSL theme={null} { "select": [ { "group_key": "*" }, { "arg": { "timeseries": "glucose", "field": "value" }, "func": "mean" }, { "arg": { "timeseries": "heartrate", "field": "value" }, "func": "mean" }, { "arg": { "timeseries": "steps", "field": "value" }, "func": "sum" }, { "arg": { "timeseries": "hrv", "field": "value" }, "func": "mean" }, { "arg": { "timeseries": "calories_active", "field": "value" }, "func": "sum" }, { "arg": { "timeseries": "body_temperature", "field": "value" }, "func": "max" } ], "group_by": [ { "arg": { "index": "timeseries" }, "date_trunc": {"unit": "day", "value": 1} }, {"source": "source_provider"}, {"source": "source_type"} ] } ``` ## Weekly Exercise Summary ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.group_key("*"), va.Workout.col("calories").max(), va.Workout.col("calories").min(), va.Workout.col("heart_rate_zone_1").mean(), va.Workout.col("heart_rate_zone_2").mean(), va.Workout.col("heart_rate_zone_3").mean(), va.Workout.col("heart_rate_zone_4").mean(), va.Workout.col("heart_rate_zone_5").mean(), va.Workout.col("heart_rate_zone_6").mean(), va.Workout.col("distance_meter").max(), va.Workout.col("duration_active_second").mean() ).group_by( va.date_trunc(va.Workout.index(), 1, "week") ).finalize() ``` ```python JSON dsl theme={null} { "select": [ { "group_key": "*" }, { "arg": { "workout": "calories" }, "func": "max" }, { "arg": { "workout": "calories" }, "func": "min" }, { "arg": { "workout": "heart_rate_zone_1" }, "func": "mean" }, { "arg": { "workout": "heart_rate_zone_2" }, "func": "mean" }, { "arg": { "workout": "heart_rate_zone_3" }, "func": "mean" }, { "arg": { "workout": "heart_rate_zone_4" }, "func": "mean" }, { "arg": { "workout": "heart_rate_zone_5" }, "func": "mean" }, { "arg": { "workout": "heart_rate_zone_6" }, "func": "mean" }, { "arg": { "workout": "distance_meter" }, "func": "max" }, { "arg": { "workout": "duration_active_second" }, "func": "mean" } ], "group_by": [ { "date_trunc": { "value": 1, "unit": "week" }, "arg": { "index": "workout" } } ] } ``` # Overview Source: https://docs.junction.com/sense/overview Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. Junction Sense consolidates the multitude of user data collected across [wearable data providers](/wearables/providers/introduction) and [source types](/wearables/providers/data-attributions#source-type) into simple tabular outputs. A high-level illustration of the inputs and outputs of the Aggregation API Work with simple tabular outputs, and offload the dirty work to Junction Sense β€” the data gathering, prioritization and sanitization processes across multiple providers and source types. Provider priorities can be configured at team level. Query-level priority overrides are also supported to enable experimentations. Work with time zone agnostic outputs that are indexed in the user's perception of time (i.e., *floating time*). ## Query API and Continuous Query You can use Junction Sense through two mediums: ### Query API * Rapid Insights: Ideal for quick analysis, allowing developers to experiment with individual user data immediately. * Flexible Outputs: Produces easy-to-use DataFrames that work seamlessly with popular data analysis tools. * Cost and Time Efficiency: Get aggregated insights without having to dump all user data from the API. * Build Dynamic Dashboards: Queries are adjustable and executed in real time, suitable for dynamic dashboards that need to support flexible adjustments of query based on user inputs. ### Continuous Query * Automatic Evaluation: Best suited for scenarios requiring ongoing data monitoring and updates, automatically re-evaluating queries when new data is available. * Streamlined Workflows: Minimizes manual effort and efficiently manages large datasets, making it ideal for continuous reporting and collaboration within teams. * Pushed Changes: New and changed query results are pushed to you as webhooks via svix or ETL Pipelines ### Decision Guide Start with Query API if you need quick insights, one-time analysis or query experimentation using an individual user's data. Switch to Continuous Query once you have queries which you wish to turn into a continuously updated, named dataset and to receive change events for. Choose based on your current needs β€” rapid analysis and iteration with Query API or continual data monitoring with Continuous Query. Both features use the same Query DSL, complement each other and can adapt as your product evolves. # Getting Started Source: https://docs.junction.com/sense/python-sdk/getting-started The [`vital` PyPI package](/home/libraries) (Junction API Python SDK) provides typed bindings of the Junction API in Python that tracks our OpenAPI Schema. Junction Sense Python SDK is a standalone product offering, separately from the `vital` PyPI package. **Junction Sense Python SDK** bridges Junction's data aggregation and intelligence capabilities with the rich Python ecosystem for scientific computing and data analysis. ## Authentication Use the same [Vital API Keys](/api-details/authentication#server-to-server) your system used to interact with Junction API. To set the Junction API Key for the Junction Sense Python SDK, you can: * Pass the Junction API Key explicitly to the Junction Sense SDK entity's constructor (e.g., the Query Executor); or * Set the Junction API Key as the `VITAL_API_KEY` environment variable. Sign-in locally on your machine with your [Junction Dashboard](https://app.junction.com/) account. You can do so through the [Junction Sense SDK Command Line Tool](#sdk-command-line-tool) as detailed below. ## Installation ### Aggregation API Add the `vitalx-aggregation` package to your project. ```bash theme={null} poetry add vitalx-aggregation ``` ### SDK Command Line Tool Add the `vitalx-cli` package as a dev dependency to your project. ```bash theme={null} poetry add -G dev vitalx-cli ``` Create the `~/.vitalx` directory, and grant it execute permission. The SDK requires execute permission to maintain a file lock. ```bash theme={null} mkdir -p ~/.vitalx && chmod +x ~/.vitalx ``` Call the `auth login` CLI subcommand on the `vitalx-cli` package to start the authentication process. ```bash theme={null} poetry run vitalx-cli auth login ``` You would be prompted in your default web browser to sign-in to the [Junction Dashboard](https://app.junction.com/). You will then be asked to confirm the device authorization. Device Confirmation prompt You have now signed-in. You can now use any Junction Sense SDK entity without a Junction API Key. Your permanent session is stored at `~/.vitalx/`. ## Example [tryvital/vitalx-notebook](https://github.com/tryVital/vitalx-notebook) is a pre-configured Jupyter Notebook project with dependencies on Aggregation API and SDK Command Line Tool pre-configured. The notebook also includes an example to compute *Chronotypes* using the Aggregation API. # Column expressions Source: https://docs.junction.com/sense/query-dsl/column-expressions ## Table Column expression Your Query Instruction can pull data from [Junction summary and timeseries resources](/wearables/providers/resources) through Table Column expressions. Each Table Column expression can select one column (a field) of the table (the Vital resource). You can use multiple Table Column expressions to select multiple columns in the same query. Cross-table query instruction is not supported β€” you cannot mix Table Column expressions from different tables in the same query instruction. Column expressions for Electrocardiogram, Sleep Cycle, Menstrual Cycle and lab testing biomarkers are planned to be introduced in the future. ### Summaries | Table | Python DSL | JSON DSL | | -------------------------------------- | -------------------- | ---------------------- | | Activity daily summary
`activity` | `Activity.col("$1")` | `{ "activity": "$1" }` | | Body summary
`body` | `Body.col("$1")` | `{ "body": "$1" }` | | Sleep summary
`sleep` | `Sleep.col("$1")` | `{ "sleep": "$1" }` | | Workout summary
`workout` | `Workout.col("$1")` | `{ "workout": "$1" }` | | Meal summary
`meal` | `Meal.col("$1")` | `{ "meal": "$1" }` | | Profile
`profile` | `Profile.col("$1")` | `{ "profile": "$1" }` | ### Column Mappings Map summary resource properties to their Continuous Query column equivalents. #### Sleep | Sleep field | Query Column | | ------------------- | ------------------------ | | `calendar_date` | `index` | | `bedtime_start` | `session_start` | | `bedtime_stop` | `session_end` | | `type` | `type` | | `timezone_offset` | `time_zone` | | `duration` | `duration_second` | | `total` | `stage_asleep_second` | | `awake` | `stage_awake_second` | | `light` | `stage_light_second` | | `rem` | `stage_rem_second` | | `deep` | `stage_deep_second` | | - | `stage_unknown_second` | | `score` | `score` | | `hr_lowest` | `heart_rate_minimum` | | `hr_average` | `heart_rate_mean` | | - | `heart_rate_maximum` | | `hr_resting` | `heart_rate_resting` | | `efficiency` | `efficiency` | | `latency` | `latency_second` | | `temperature_delta` | `skin_temperature_delta` | | `skin_temperature` | `skin_temperature` | | `hr_dip` | `heart_rate_dip` | | `average_hrv` | `hrv_mean_rmssd` | | - | `hrv_mean_sdnn` | | `respiratory_rate` | `respiratory_rate` | | `state` | `state` | | `source.slug` | `source_provider` | | `source.type` | `source_type` | | `source.device_id` | `source_device_id` | | `source.app_id` | `source_app_id` | #### Activity | Activity field | Query Column | | ---------------------------- | ---------------------------- | | `calendar_date` | `index` | | `calories_total` | `calories_total` | | `calories_active` | `calories_active` | | `steps` | `steps` | | `daily_movement` | `duration_active_second` | | `distance` | `distance_meter` | | `floors_climbed` | `floors_climbed` | | `time_zone` | `time_zone` | | `timezone_offset` | `time_zone_offset` | | `heart_rate.avg_bpm` | `heart_rate_mean` | | `heart_rate.min_bpm` | `heart_rate_minimum` | | `heart_rate.max_bpm` | `heart_rate_maximum` | | `heart_rate.resting_bpm` | `heart_rate_resting` | | `heart_rate.avg_walking_bpm` | `heart_rate_mean_walking` | | `wheelchair_use` | `wheelchair_use` | | `wheelchair_push` | `wheelchair_push` | | - | `intensity_sedentary_second` | | `low` | `intensity_low_second` | | `medium` | `intensity_medium_second` | | `high` | `intensity_high_second` | | `source.slug` | `source_provider` | | `source.type` | `source_type` | | `source.device_id` | `source_device_id` | | `source.app_id` | `source_app_id` | #### Workout | Workout field | Query Column | | ------------------------ | ------------------------- | | `title` | `title` | | `timezone_offset` | `time_zone` | | `average_hr` | `heart_rate_mean` | | `max_hr` | `heart_rate_maximum` | | - | `heart_rate_minimum` | | `distance` | `distance_meter` | | `calendar_date` | - | | `time_start` | `session_start` | | `time_end` | `session_end` | | `calories` | `calories` | | `sport.name` | `sport_name` | | `sport.slug` | `sport_slug` | | `hr_zones` | `heart_rate_zone_1..6` | | `moving_time` | `duration_active_second` | | `total_elevation_gain` | `elevation_gain_meter` | | `elev_high` | `elevation_maximum_meter` | | `elev_low` | `elevation_minimum_meter` | | `average_speed` | `speed_mean` | | `max_speed` | `speed_maximum` | | `average_watts` | `power_mean` | | `device_watts` | `power_source` | | `max_watts` | `power_maximum` | | `weighted_average_watts` | `power_weighted_mean` | | `steps` | `steps` | | `map.polyline` | `map_polyline` | | `map.summary_polyline` | `map_summary_polyline` | | `provider_id` | `external_id` | | `source.slug` | `source_provider` | | `source.type` | `source_type` | | `source.device_id` | `source_device_id` | | `source.app_id` | `source_app_id` | #### Body | Body field | Query Column | | -------------------------------- | -------------------------------- | | `calendar_date` | - | | `date` (deprecated) | - | | - | `measured_at` | | `weight` | `weight_kilogram` | | `fat` | `fat_mass_percentage` | | `water_percentage` | `water_percentage` | | `muscle_mass_percentage` | `muscle_mass_percentage` | | `visceral_fat_index` | `visceral_fat_index` | | `bone_mass_percentage` | `bone_mass_percentage` | | `body_mass_index` | `body_mass_index` | | `lean_body_mass_kilogram` | `lean_body_mass_kilogram` | | `waist_circumference_centimeter` | `waist_circumference_centimeter` | | - | `time_zone` | | `source.slug` | `source_provider` | | `source.type` | `source_type` | | `source.device_id` | `source_device_id` | | `source.app_id` | `source_app_id` | #### Meal | Meal field | Query Column | | ---------------------------------- | ---------------------------- | | `timestamp` | index | | `name` | `name` | | `energy.value` | `calories` | | `macros.carbs` | `carbohydrate_gram` | | `macros.protein` | `protein_gram` | | `macros.alcohol` | `alcohol_gram` | | `macros.water` | `water_gram` | | `macros.fibre` | `fibre_gram` | | `macros.sugar` | `sugar_gram` | | `macros.cholesterol` | `cholesterol_gram` | | `macros.fats.saturated` | `saturated_fat_gram` | | `macros.fats.monounsaturated` | `monounsaturated_fat_gram` | | `macros.fats.polyunsaturated` | `polyunsaturated_fat_gram` | | `macros.fats.omega3` | `omega3_fat_gram` | | `macros.fats.omega6` | `omega6_fat_gram` | | `macros.fats.total` | `total_fat_gram` | | `micros.minerals.sodium` | `sodium_milligram` | | `micros.minerals.potassium` | `potassium_milligram` | | `micros.minerals.calcium` | `calcium_milligram` | | `micros.minerals.phosphorus` | `phosphorus_milligram` | | `micros.minerals.magnesium` | `magnesium_milligram` | | `micros.minerals.iron` | `iron_milligram` | | `micros.minerals.zinc` | `zinc_milligram` | | `micros.minerals.fluoride` | `fluoride_milligram` | | `micros.minerals.chloride` | `chloride_milligram` | | `micros.vitamins.vitamin_a` | `vitamin_a_milligram` | | `micros.vitamins.vitamin_b1` | `vitamin_b1_milligram` | | `micros.vitamins.riboflavin` | `riboflavin_milligram` | | `micros.vitamins.niacin` | `niacin_milligram` | | `micros.vitamins.pantothenic_acid` | `pantothenic_acid_milligram` | | `micros.vitamins.vitamin_b6` | `vitamin_b6_milligram` | | `micros.vitamins.biotin` | `biotin_microgram` | | `micros.vitamins.vitamin_b12` | `vitamin_b12_microgram` | | `micros.vitamins.vitamin_c` | `vitamin_c_milligram` | | `micros.vitamins.vitamin_d` | `vitamin_d_microgram` | | `micros.vitamins.vitamin_e` | `vitamin_e_milligram` | | `micros.vitamins.vitamin_k` | `vitamin_k_microgram` | | `micros.vitamins.folic_acid` | `folic_acid_microgram` | | `micros.trace_elements.chromium` | `chromium_microgram` | | `micros.trace_elements.copper` | `copper_milligram` | | `micros.trace_elements.iodine` | `iodine_microgram` | | `micros.trace_elements.manganese` | `manganese_milligram` | | `micros.trace_elements.molybdenum` | `molybdenum_microgram` | | `micros.trace_elements.selenium` | `selenium_microgram` | | `source.slug` | `source_provider` | | `source.type` | `source_type` | | `source.app_id` | `source_app_id` | | `source.device_id` | `source_device_id` | #### Profile | Profile field | Query Column | | ------------------ | ------------------- | | `sex` | `sex` | | `gender` | `gender` | | `height` | `height_centimeter` | | `wheelchair_use` | `wheelchair_use` | | `birth_date` | `birth_date` | | `created_at` | `created_at` | | `updated_at` | `updated_at` | | `source.slug` | `source_provider` | | `source.type` | `source_type` | | `source.app_id` | `source_app_id` | | `source.device_id` | `source_device_id` | ### Timeseries | Table | Python DSL | JSON DSL | | -------------------------- | ---------------------------------- | -------------------------------------- | | Timeseries Resource fields | `Timeseries.col("$1").field("$2")` | `{ "timeseries": "$1", "field": "$2"}` | ## Source Column expression You can use a Source Column expression to refer to Data Source fields common to Junction resources. Note that some Source Column expression may not be valid in the implicit query context. For example, `source_sport` is not a valid Source Column expression in a query that is selecting from the Sleep resource. If both the *Provider* and *Source Type* Source Columns are present in the [Group By clause](/sense/query-dsl/group-by-clause), the implicit [Data Prioritization](/sense/data-prioritization) behaviour would be disabled. | Column | Python DSL | JSON DSL | Available in | | ------------- | --------------------------------- | ---------------------------------- | ------------------------------ | | Provider | `Source.col("source_provider")` | `{ "source": "source_provider" }` | All Tables | | Source Type | `Source.col("source_type")` | `{ "source": "source_type" }` | All Tables | | Source App ID | `Source.col("source_app_id")` | `{ "source": "source_app_id" }` | All summary tables | | Workout ID | `Source.col("source_workout_id")` | `{ "source": "source_workout_id"}` | Workout Stream timeseries only | | Sport | `Source.col("source_sport")` | `{ "source": "source_sport" }` | Workout Stream timeseries only | ## Index Column expression Each Vital resource has a primary datetime index. Your Query Instruction can reference this datetime index using the special Index Column expression: | Table | Index | Python DSL | JSON DSL | | -------------------------------------- | ------------------------- | ------------------ | ------------------------- | | Activity daily summary
`activity` | Calendar date | `Activity.index()` | `{ "index": "activity" }` | | Body summary
`body` | Measurement datetime | `Body.index()` | `{ "index": "body" }` | | Sleep summary
`sleep` | Session end datetime | `Sleep.index()` | `{ "index": "sleep" }` | | Workout summary
`workout` | Session start datetime | `Workout.index()` | `{ "index": "workout" }` | | Profile
`profile` | Updated At (UTC) datetime | `Profile.index()` | `{ "index": "profile" }` | ## Group Key Column expression When your Query Instruction uses [the `group_by` clause](/sense/query-dsl/group-by-clause), each expression in the `group_by` clause creates a temporary Group Key Column (`group_key.$OFFSET`). These temporary columns are then used to facilitate the data grouping and aggregation as specified by your Query Instruction. You can refer to these columns using the special Group Key Column expression: | Group Key Columns | Python DSL | JSON DSL | | ----------------- | ---------------- | ---------------------- | | All | `group_key("*")` | `{ "group_key": "*" }` | | The `$N`-th key | `group_key($N)` | `{ "group_key": $N }` | # GROUP BY clause Source: https://docs.junction.com/sense/query-dsl/group-by-clause ## Overview You specify how a Junction Sense should dissect the selected data through a Group By clause. The [Aggregate expressions](/sense/query-dsl/select-clause#aggregate-a-table-column) you specify would then be applied to the resulting groups. Your Group By clause can contain one or more of the following expressions: * a [Date Truncate expression](#group-by-a-truncated-datetime) * A synchronous [Query](/sense/using-query-api) can have at most one [Date Truncate expression](#group-by-a-truncated-datetime) * A [Continuous Query](/sense/using-continuous-query) must have exactly one [Date Truncate expression](#group-by-a-truncated-datetime). * a [Date Part expression](#group-by-a-date-or-time-component) * a [Table Column expression](#group-by-a-table-column-expression) * a [Source Column expression](#group-by-a-source-column-expression) You can mix and match any expression types, and organize them in any order. [Group Key Column expression](/sense/query-dsl/select-clause#select-the-group-key-columns) tracks your declaration order. ## Group by a truncated datetime Group the input rows based on the specified column expression truncated to the specified granularity. This is similar to the `DATE_TRUNC` function in SQL. | Argument | Remarks | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------- | | date\_trunc | The date or time period to truncate the input datetime to. | | arg | The input expression β€” only an [Index Column expression](/sense/query-dsl/column-expressions#index-column-expression) is accepted. | `group_key.$OFFSET` if this is the N-th expression in the `group_by` clause. See also: [Select the group key columns](/sense/query-dsl/select-clause#select-the-group-key-columns) ```python Python DSL theme={null} import vitalx.aggregation as va # Truncate to every 1 day. va.select(...).group_by( va.date_trunc(va.Sleep.index(), 1, "day"), ) # Truncate to every 3 months. va.select(...).group_by( va.date_trunc(va.Sleep.index(), 3, "month"), ) ``` ```python JSON DSL theme={null} # Truncate to every 1 day. { "group_by": [ { "date_trunc": { "value": 1, "unit": "day" }, "arg": { "index": "sleep" } } ] } # Truncate to every 3 months. { "group_by": [ { "date_trunc": { "value": 3, "unit": "month" }, "arg": { "index": "sleep" } } ] } ``` ## Group by a date or time component Group the input rows based on a specific date or time component extracted from the specified column expression. This is similar to the `DATE_PART` function in SQL. | Argument | Remarks | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------- | | date\_part | The date or time unit to extract. | | arg | The input expression β€” only an [Index Column expression](/sense/query-dsl/column-expressions#index-column-expression) is accepted. | `group_key.$OFFSET` if this is the N-th expression in the `group_by` clause. See also: [Select the group key columns](/sense/query-dsl/select-clause#select-the-group-key-columns) ```python Python DSL theme={null} import vitalx.aggregation as va # Extract the weekday va.select(...).group_by( va.date_part(va.Sleep.index(), "weekday"), ) # Extract the day-of-month va.select(...).group_by( va.date_part(va.Sleep.index(), "day") ) ``` ```python JSON DSL theme={null} # Extract the weekday { "group_by": [ { "date_part": "weekday", "arg": { "index": "sleep" } } ] } # Extract the day-of-month { "group_by": [ { "date_part": "day", "arg": { "index": "sleep" } } ] } ``` ## Group by a Table Column expression Group the input rows based on a [Table Column expression](/sense/query-dsl/column-expressions#table-column-expression). This is similar to the generic `GROUP BY` clause in SQL. Any [Table Column expressions](/sense/query-dsl/column-expressions#table-column-expression) β€” summary or timeseries. `group_key.$OFFSET` if this is the N-th expression in the `group_by` clause. See also: [Select the group key columns](/sense/query-dsl/select-clause#select-the-group-key-columns) ```python Python DSL theme={null} import vitalx.aggregation as va # Group by calendar date and Stand Hour type (idle vs stand) va.select( va.group_key("*"), va.Timeseries.col("stand_hour").field("value").sum() ).group_by( va.date_trunc(va.Timeseries.index(), 1, "day"), va.Timeseries.col("stand_hour").field("type") ) ``` ```python JSON DSL theme={null} # Group by calendar date and Stand Hour type (idle vs stand) { "group_by": [ { "date_trunc": { "value": 1, "unit": "day" }, "arg": { "index": "timeseries" } }, { "timeseries": "stand_hour", "field": "type" } ] } ``` ## Group by a Source Column expression Group the input rows based on a [Source Column expression](/sense/query-dsl/column-expressions#source-column-expression). This is similar to a generic `GROUP BY` clause in SQL. If both the *Provider* and *Source Type* Source Columns are present in the [Group By clause](/sense/query-dsl/group-by-clause), the implicit [Data Prioritization](/sense/data-prioritization) behaviour would be disabled. Any [Source Column expressions](/sense/query-dsl/column-expressions#source-column-expression) valid in the query context. `group_key.$OFFSET` if this is the N-th expression in the `group_by` clause. See also: [Select the group key columns](/sense/query-dsl/select-clause#select-the-group-key-columns) ```python Python DSL theme={null} import vitalx.aggregation as va # Group by the source provider and source type va.select(...).group_by( va.date_trunc(va.Sleep.index(), 1, "week"), va.Source.col("source_provider"), va.Source.col("source_type") ) ``` ```python JSON DSL theme={null} # Group by the source provider and source type { "group_by": [ { "date_trunc": { "value": 1, "unit": "week" }, "arg": { "index": "sleep" } }, { "source": "source_provider" }, { "source": "source_type" } ] } ``` # SELECT clause Source: https://docs.junction.com/sense/query-dsl/select-clause ## Select a table column Select a specific table column using a [Table Column expression](/sense/query-dsl/column-expressions). The name of column being selected. ```python Python DSL theme={null} import vitalx.aggregation as va va.select(va.Sleep.col("efficiency")) ``` ```python JSON DSL theme={null} { "select": [ { "sleep": "efficiency" } ] } ``` ## Perform an analysis and select the output value Perform data analysis with in-built algorithms using value macro expressions: * [Sleep Analysis](/sense/query-dsl/sleep-analysis) value macro expressions The name of the value macro, e.g., `sleep_score` for the Chronotype value macro. ```python Python DSL theme={null} import vitalx.aggregation as va va.select(va.Sleep.chronotype()) ``` ```python JSON DSL theme={null} { "select": [ { "value_macro": "chronotype" } ] } ``` ## Aggregate a table column Aggregate a [Table Column expression](/sense/query-dsl/column-expressions) with respect to the `group_by` clause. The specified aggregate function is applied to each and every group created by the `group_by` clause. The result set is N aggregated values where N is the number of groups. | Aggregate function | Python DSL | JSON DSL | | ------------------ | ---------------- | ------------------------------------ | | Minimum | `$EXPR.min()` | `{ "func": "min", "arg": $EXPR }` | | Maximum | `$EXPR.max()` | `{ "func": "max", "arg": $EXPR }` | | Mean | `$EXPR.mean()` | `{ "func": "mean", "arg": $EXPR }` | | Median | `$EXPR.median()` | `{ "func": "median", "arg": $EXPR }` | | Standard Deviation | `$EXPR.stddev()` | `{ "func": "stddev", "arg": $EXPR }` | | Count | `$EXPR.count()` | `{ "func": "count", "arg": $EXPR }` | | Oldest Value | `$EXPR.oldest()` | `{ "func": "oldest", "arg": $EXPR }` | | Newest Value | `$EXPR.newest()` | `{ "func": "newest", "arg": $EXPR }` | | Aggregate function | Python DSL | JSON DSL | | ------------------ | ------------- | --------------------------------- | | Sum | `$EXPR.sum()` | `{ "func": "sum", "arg": $EXPR }` | * the aggregation function name, e.g., `sum`; or * if the Split by Source mode is enabled β€” `$FUNCTION_NAME.$SOURCE_COLUMN_NAME`, e.g., `mean.efficiency`. ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.Sleep.col("stage_asleep_second").newest() ) ``` ```python JSON DSL theme={null} { "select": [ { "func": "newest", "arg": { "sleep": "stage_asleep_second" } } ] } ``` ## Select the Index Column Select the primary datetime index of the table using the [Index Column expression](/sense/query-dsl/column-expressions#index-column-expression). `timestamp` (constant). ```python Python DSL theme={null} import vitalx.aggregation as va va.select(va.Sleep.col("efficiency")) ``` ```python JSON DSL theme={null} { "select": [ { "sleep": "efficiency" } ] } ``` ## Select the Group Key Columns Select the [Group Key Columns](/sense/query-dsl/column-expressions#group-key-column-expression) associated with the `group_by` clause. You can select one specific group key column by offset, or select all group key columns with a `*` wildcard. `group_key.$OFFSET`, where `$OFFSET` corresponds to the N-th expression of the `group_by` clause. ```python Python DSL theme={null} import vitalx.aggregation as va # Select all group key columns va.select(va.group_key("*")) # Select the 2nd group key column va.select(va.group_key(2)) ``` ```python JSON DSL theme={null} # Select all group key columns { "select": [ { "group_key": "*" } ] } # Select the 2nd group key column { "select": [ { "group_key": 2 } ] } ``` # Sleep Macros Source: https://docs.junction.com/sense/query-dsl/sleep-analysis Value macro expressions for Sleep Analysis Macros are precomputed fields available in the Vital Aggregation DSL. Use them anywhere you would use a regular column expression. Reference a macro with the `value_macro` expression: ```python JSON DSL theme={null} { "select": [ { "value_macro": "chronotype" } ] } ``` ```python Python DSL theme={null} import vitalx.aggregation as va va.select(va.Sleep.chronotype()) ``` ### Available Sleep Macros | Macro Name | Description | Output Column Name | | ----------------------- | ----------------------------------------------------------------------------------- | ------------------------------- | | **Chronotype** | The chronotype of the sleep session, categorized as `lark`, `third`, or `owl`. | `chronotype` | | **Sleep Score** | A score from 0 to 100 computed using the Junction Sense Sleep Score model. | `sleep_score` | | **Asleep At** | Computed time of user falling asleep based on sleep session start time and latency. | `asleep_at` | | **Awake At** | Computed time the user woke up (may differ from session end time). | `awake_at` | You can combine multiple sleep macros in a single query and mix them with standard aggregations or other select expressions. ### Example query ```json theme={null} { "select": [ { "group_key": "*" }, { "func": "mean", "arg": { "value_macro": "sleep_score" } }, { "func": "mean", "arg": { "sleep": "efficiency" } }, { "func": "newest", "arg": { "value_macro": "chronotype" } }, { "func": "newest", "arg": { "value_macro": "asleep_at" } }, { "func": "newest", "arg": { "value_macro": "awake_at" } } ], "group_by": [ { "date_trunc": { "value": 1, "unit": "week" }, "arg": { "index": "sleep" } } ] } ``` ## Chronotype Determine the chronotype of each sleep session. Chronotype categories are based on the sleep midpoint time: * **lark** if midpoint β‰₯ 17:00 or ≀ 03:30 * **third** if midpoint ≀ 06:00 * **owl** otherwise ### Get last chronotype of the week ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.Sleep.chronotype().latest() ).group_by( date_trunc(Sleep.index(), 1, "week") ) ``` ```python JSON DSL theme={null} { "select": [ { "func": "latest", "arg": { "value_macro": "chronotype" } } ], "group_by": [ { "date_trunc": { "unit": "week", "value": 1 } } ] } ``` ## Sleep Score Calculate a Sleep Score (0 to 100) for each sleep session. ### Get mean sleep score of the week ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.Sleep.score().mean() ).group_by( date_trunc(Sleep.index(), 1, "week") ) ``` ```python JSON DSL theme={null} { "select": [ { "func": "mean", "arg": { "value_macro": "sleep_score" } } ], "group_by": [ { "date_trunc": { "unit": "week", "value": 1 } } ] } ``` ## Awake At / Asleep At Pinpoint when a user first fell asleep and when they became fully awake during each sleep session. The `asleep_at` macro returns the bedtime start adjusted by the recorded sleep latency, while `awake_at` returns the bedtime start plus the offset of the final non-awake sleep segment. ### Get mean asleep at and awake at of the week ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.Sleep.asleep_at().mean(), va.Sleep.awake_at().mean() ).group_by( date_trunc(Sleep.index(), 1, "week") ) ``` ```python JSON DSL theme={null} { "select": [ { "func": "mean", "arg": { "value_macro": "asleep_at" } }, { "func": "mean", "arg": { "value_macro": "awake_at" } } ], "group_by": [ { "date_trunc": { "unit": "week", "value": 1 } } ] } ``` # WHERE clause Source: https://docs.junction.com/sense/query-dsl/where-clause ## Overview Use the WHERE clause to filter summary or timeseries rows before Junction Sense evaluates aggregations or grouping. When a GROUP BY clause is present, the filter runs first so only matching rows contribute to each group. The WHERE clause evaluates SQL-style predicates against the same [column expressions](/sense/query-dsl/column-expressions) that are available in the `select` and `group_by` clauses. ## Supported operators WHERE predicates support these operators: * Comparison: `>`, `>=`, `<`, `<=`, `=`, `!=` * Logical: `AND`, `OR`, `NOT` * Parentheses for explicit precedence * String literals enclosed in single quotes (for example, `'long_sleep'`) Comparison operands can reference table columns (for example, `stage_rem_second`) or Source columns (for example, `source_type`). Column identifiers follow the Continuous Query column names. See [Column Mappings](/sense/query-dsl/column-expressions#column-mappings) for a full list. ## Filter rows before grouping ```python Python DSL theme={null} import vitalx.aggregation as va va.select( va.group_key("*"), va.Sleep.score().mean(), va.Sleep.col("stage_asleep_second").sum(), ).group_by( va.date_trunc(va.Sleep.index(), 1, "day"), va.Sleep.col("state"), ).where( "type = 'long_sleep'" ) ``` ```python JSON DSL theme={null} { "select": [ { "group_key": "*" }, { "func": "mean", "arg": { "value_macro": "sleep_score" } }, { "func": "sum", "arg": { "sleep": "stage_asleep_second" } } ], "group_by": [ { "date_trunc": { "value": 1, "unit": "day" }, "arg": { "index": "sleep" } }, { "sleep": "state" } ], "where": "type = 'long_sleep'" } ``` ## Combine multiple device sources Filter for specific provider and source type combinations with parentheses to manage precedence. ```python theme={null} va.select( va.Sleep.score().mean(), ).where( "(source_type = 'watch' AND source_provider = 'apple_health_kit') OR (source_type = 'ring' AND source_provider = 'oura')" ) ``` ## Filter without grouping You can apply a WHERE clause to ungrouped queries to trim the dataset before computing aggregates. ```python theme={null} va.select( va.Sleep.score().mean() ).where("source_type = 'watch' AND source_provider = 'apple_health_kit'") ``` In the JSON DSL, supply the same predicate string via the top-level `where` property. ```json theme={null} { "select": [ { "func": "mean", "arg": { "value_macro": "sleep_score" } } ], "where": "source_type = 'watch' AND source_provider = 'apple_health_kit'" } ``` ## Evaluation order 1. Apply the WHERE predicate to the incoming rows. 2. Evaluate the GROUP BY clause (if provided) on the filtered rows. 3. Calculate the aggregates in the SELECT clause. This order ensures filters never exclude rows after grouping has started, keeping results consistent with SQL semantics. # Using Continuous Query Source: https://docs.junction.com/sense/using-continuous-query Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. You can create a **Continuous Query** to have your Junction Sense [Query](/sense/overview) automatically evaluated by Junction as needed, and receive any query result changes through [your Webhook or ETL Pipeline destinations](/webhooks/introduction). Continuous Query runs automatically on all existing and new Users in your Team; Continuous Query is scheduled intelligently in response to any new data points or updates discovered through device data connections made through [Junction Link](/wearables/connecting-providers/introduction) and [Junction Mobile SDKs](/wearables/sdks/vital-health). Junction pushes any Continuous Query result table changes to your Webhook or ETL Pipeline destinations. The latest result table can be queried through [Junction API](/api-reference/sense/continuous-query/get-result-table) at any time. A high-level illustration of the continuous query structure With Continuous Query, you can focus on adding value to your product with the aggregated data insights: * you need not build your own scalable data pipeline from scratch to ingest, store and aggregate standardized device data. * you need not poll the Query API to receive query results. ## Continuous Query requirements While Continuous Query uses the same [Query DSL](/sense/query-dsl/) as the Query API, some restrictions are in place: * The query must [group by a truncated datetime](/sense/query-dsl/group-by-clause#group-by-a-truncated-datetime). * The query must [select all group key columns](/sense/query-dsl/select-clause#select-the-group-key-columns). Do reach out to us if your use case is hampered by these restrictions. ## Subscribing to query result changes When a Continuous Query is re-evaluated and the result set has changed, Junction sends a [Result Table Changed](/event-catalog/continuous_query.result_table.changed) data event to your [your Webhook or ETL Pipeline destinations](/webhooks/introduction). This data event includes only the delta (new rows or changed rows with respect to the [group keys](]\(/sense/query-dsl/select-clause#select-the-group-key-columns\))). To re-fetch the whole result set, use the [Get Result Table](/api-reference/sense/continuous-query/get-result-table) endpoint. ```json theme={null} { "event_type": "continuous_query.result_table.changed", "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2", "client_user_id": "01HW3FSNVCHC3B2QB5N0ZAAAVG", "team_id": "6b74423d-0504-4470-9afb-477252ccf67a", "data": [ { "data": { "group_key.0": [ "2025-02-17T00:00:00" ], "mean.efficiency": [ 98.22 ], "mean.sleep_score": [ 76.75 ], "min.sleep_score": [ 61 ], "newest.chronotype": [ "third" ] }, "query_id": "b59425f8-eb5a-4c60-ae95-eda70179da04", "query_slug": "sleep-analysis-v1" } ] } ``` ## Managing your Continuous Queries You can manage your Continuous Query using our [Org Management API](/api-reference/org-management): * [Create Continuous Query Endpoint](/api-reference/sense/continuous-query/create) * [List Continuous Queries Endpoint](/api-reference/sense/continuous-query/list) You can also manage them through the *Continuous Query* section of the Junction Dashboard: Screenshot of the query creator # Using Query API Source: https://docs.junction.com/sense/using-query-api Junction Sense is in **closed beta**. Interested in the Junction Sense? Get in touch with your Customer Success Manager. You can make use of the Query API through these mediums: | Channel | Remarks | | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | [Junction Sense Python SDK](/sense/python-sdk/getting-started) | The `vitalx-aggregation` package provides an expressive Python Query DSL with [Polars DataFrame](https://pola.rs) integrations. | | [Junction Sense Query API endpoint](/api-reference/sense/query-one) | JSON API calls. | | [Typed bindings of Vital API](/home/libraries) | Call the Aggregation API in Go, Java, Node and Python. | # Provider Types Source: https://docs.junction.com/wearables/connecting-providers/auth_types Providers have different authentication flows. Some require a username and password, others require an email address and password. Junction supports the following provider types: * `OAuth` * `Email` * `Email + Password` The majority of providers are `OAuth` providers. These providers require a user to be redirected to a third party website to authenticate. Once authenticated, the user is redirected back to the link widget. The `Email` and `Email + Password` providers require a user to enter their email address and password into the link widget. This is then sent to Junction and used to connect to the provider. The list of providers and their oauth types are as follows: ### 'OAuth' Current OAuth providers are: | Provider | Description | | ------------ | ------------------------------------------- | | `Fitbit` | Activity Trackers (all devices) | | `Garmin` | Fitness watches (all devices) | | `Oura` | Smart Sleep tracking ring | | `Strava` | Running & Cycling Social Network | | `Wahoo` | Cycling Equipment | | `Withings` | Fitness scales, watches and health monitors | | `Google Fit` | Activity Trackers (all devices) | | `Polar` | Finnish sports tech pioneer | | `Cronometer` | Nutrition data | | `Omron` | Blood Pressure data | | `WHOOP` | Smart Activity Watches | | `Dexcom` | Glucose monitors | To connect an oauth provider: ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { LinkTokenExchange, LinkGenerateOauthLinkRequest, OAuthProviders } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: LinkTokenExchange = { userId: "", } const tokenResponse = await client.link.token(request) const linkRequest: LinkGenerateOauthLinkRequest = { vitalLinkToken: tokenResponse.linkToken, } const auth = await client.link.generateOauthLink( OAuthProviders., linkRequest ) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.o_auth_providers import OAuthProviders client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) token_response = client.link.token(user_id="") oauth = client.link.generate_oauth_link( oauth_provider=OAuthProviders., vital_link_token=token_response.link_token, ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.LinkGenerateOauthLinkRequest; import com.vital.api.resources.link.requests.LinkTokenExchange; import com.vital.api.types.OAuthProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); LinkTokenExchange request = LinkTokenExchange .builder() .userId("") .build(); var token_response = vital.link().token(request); LinkGenerateOauthLinkRequest link_request = LinkGenerateOauthLinkRequest .builder() .vitalLinkToken(token_response.getLinkToken()) .build(); var oauth = vital.link().generateOauthLink(OAuthProviders.OURA, link_request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) provider := vital.ProvidersOura request := &vital.LinkTokenExchange{ UserId: "", Provider: &provider, } response, err := client.Link.Token(context.TODO(), request) if err != nil { return err } generateLinkRequest := &vital.LinkGenerateOauthLinkRequest{ VitalLinkToken: response.LinkToken, } oauth, err := client.Link.GenerateOauthLink(context.TODO(), vital.OAuthProvidersOura, generateLinkRequest) if err != nil { return err } fmt.Printf("Received data %s\n", oauth) ``` For `OAuth` Providers we return an `oauth_url` that can be used to redirect users to. In a web view, on redirection, you should check the user has connected to the provider successfully. In the case of mobile, the user should receive a message to return to the app. The possible error codes that are returned are as follows: * `401 INVALID_REQUEST` Link Token is Invalid * `400 MISSING_LINK_TOKEN` Missing link token * `400 INVALID_PROVIDER` Provider is not supported * `400 INVALID_USER_ID` User id is incorrect * `400 INVALID_CREDENTIALS` Credentials for provider are incorrect ### 'Email' Current email providers are: | Provider | Description | | ----------- | -------------------------- | | `Freestyle` | Abbott CGM Glucose monitor | ### 'Email + Password' Current email and password providers are: | Provider | Description | | ------------------- | ---------------------------------- | | `Zwift` | Virtual cycling and running | | `Peloton` | Popular Indoor Exercise bike | | `Eight Sleep` | Smart Mattress | | `Beurer` | Blood pressure and glucose devices | | `Hammerhead` | Cycling Computer | | `Dexcom G6 & Older` | CGM Glucose Monitor | | `MyFitnessPal` | Meal Tracking Application | | `Kardia` | EKG Application | To connect a email and password provider: ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { IndividualProviderData, PasswordProviders } from '@tryvital/vital-node/api'; const request: IndividualProviderData = { username: '', password: '', } const data = await client.link.connectPasswordProvider(PasswordProviders., request) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.password_providers import PasswordProviders client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.link.connect_password_provider( PasswordProviders., username="", password="" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.IndividualProviderData; import com.vital.api.types.PasswordProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); IndividualProviderData request = IndividualProviderData .builder() .username("") .password("") .build(); var data = vital.link().connectPasswordProvider(PasswordProviders., request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.IndividualProviderData{ Username: "", Password: "", } response, err := client.Link.ConnectPasswordProvider( context.TODO(), vital.PasswordProviders, request ) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` # Bring Your Own OAuth Source: https://docs.junction.com/wearables/connecting-providers/bring-your-own-oauth/overview Bring Your Own OAuth (BYOO) is available for [the Grow and Scale plans](https://tryvital.io/pricing). WHOOP BYOO is available for [the Launch, Grow and Scale plans](https://tryvital.io/pricing). See [our WHOOP Guide](/wearables/guides/whoop) for more information. Junction supports bringing your own OAuth application credentials, so that your user would see the OAuth consent flow in your own brand when they connect to cloud-based OAuth providers. This provides a more cohesive experience for your users as it gives a truly whitelabelled experience for your application/mobile application. Typically when you don't use custom credentials your oauth screen will look like this and used our shared credentials: Default Credentials With custom credentials you can use your own team name and setup your own logo for the provider. ## OAuth Providers supporting BYOO Some BYOO providers do not have a developer program with open sign-up. | Provider | Slug | Remarks | | --------------------------------------------------- | ------------------- | --------------------------------------- | | [Fitbit](https://www.fitbit.com/global/uk/home) | `fitbit` | - | | [Garmin](https://www.garmin.com) | `garmin` | - | | [Google Fit](https://www.google.com/fit/) | `google_fit` | - | | [Oura](https://ouraring.com) | `oura` | - | | [Strava](https://www.strava.com) | `strava` | - | | [Wahoo](https://wahoofitness.com) | `wahoo` | - | | [Withings](https://www.withings.com) | `withings` | - | | [Dexcom](https://www.dexcom.com) | `dexcom_v3` | [↗️ Guide](/wearables/guides/dexcom_v3) | | [WHOOP](https://www.whoop.com) | `whoop_v2` | [↗️ Guide](/wearables/guides/whoop) | | [Polar](https://www.polar.com/accesslink-api/) | `polar` | - | | [Cronometer](https://www.cronometer.com) | `cronometer` | - | | [Omron](https://www.omron-healthcare.com) | `omron` | - | | [MyFitnessPal API](https://www.myfitnesspalapi.com) | `my_fitness_pal_v2` | - | | [MapMyFitness](https://developer.mapmyfitness.com) | `map_my_fitness` | - | | [Ultrahuman](https://www.ultrahuman.com) | `ultrahuman` | - | ### Provider Rate Limits This section documents the *starting* rate limit of a brand new OAuth application, should you choose to BYOO. | Provider | Starting Rate Limit | | ----------------------------------------------- | ----------------------------------------------------------------------------------- | | [Fitbit](https://www.fitbit.com/global/uk/home) | 150 requests per hour per user | | [Garmin](https://www.garmin.com) | 10,000 days of data per minute | | [Google Fit](https://www.google.com/fit/) | 600 requests per minute | | [Oura](https://ouraring.com) | 5,000 requests per 5 minutes | | [Strava](https://www.strava.com) | 100 requests per 15 minutes
1,000 requests per day | | [Wahoo](https://wahoofitness.com) | 200 requests per 5 minutes
1,000 requests per hour
5,000 requests per day | | [Withings](https://www.withings.com) | 120 requests per minute | | [Dexcom](https://www.dexcom.com) | 60,000 requests per hour | | [WHOOP](https://www.whoop.com) | 100 requests per minute
10,000 requests per day | | [Polar](https://www.polar.com/accesslink-api/) | 20 requests per 15 minutes per user
100 requests per day per user | | [Cronometer](https://www.cronometer.com) | N/A | | [Omron](https://www.omron-healthcare.com) | N/A | ## Setting up your OAuth credentials ### Through the Org Management API Use the [Prepare Team Custom Credentials](/api-reference/org-management/team-custom-credentials/prepare-team-custom-credentials) endpoint to obtain the instructions to setup your OAuth application. Use the [Set Team Custom Credentials](/api-reference/org-management/team-custom-credentials/upsert-team-custom-credentials) endpoint to apply your OAuth application credentials to your Junction Team. BYOO credentials for the following providers are immediately active, once set through the [Set Team Custom Credentials](/api-reference/org-management/team-custom-credentials/upsert-team-custom-credentials): | Provider | Slug | | --------------------------------------------------- | ------------------- | | [Garmin](https://www.garmin.com) | `garmin` | | [Google Fit](https://www.google.com/fit/) | `google_fit` | | [Oura](https://ouraring.com) | `oura` | | [Strava](https://www.strava.com) | `strava` | | [Wahoo](https://wahoofitness.com) | `wahoo` | | [Withings](https://www.withings.com) | `withings` | | [Dexcom](https://www.dexcom.com) | `dexcom_v3` | | [WHOOP](https://www.whoop.com) | `whoop_v2` | | [Polar](https://www.polar.com/accesslink-api/) | `polar` | | [Cronometer](https://www.cronometer.com) | `cronometer` | | [Omron](https://www.omron-healthcare.com) | `omron` | | [Ultrahuman](https://www.ultrahuman.com) | `ultrahuman` | | [MyFitnessPal API](https://www.myfitnesspalapi.com) | `my_fitness_pal_v2` | | [MapMyFitness](https://developer.mapmyfitness.com) | `map_my_fitness` | The following providers are not immediately active. You need to take a couple of extra steps to activate them: [Fitbit](https://www.fitbit.com/global/uk/home) (`fitbit`) 1. You must press the "Verify" button on all the Webhook Subscribers in the Fitbit Developer Portal. 2. Once the buttons are gone and replaced by (usually zeroed) delivery statistics, it implies that Fitbit has completed the verification challenge with Junction. ### Through the Dashboard To setup custom OAuth application credentials via the Dashboard, you need to do the following: 1. Create a developer account with the cloud-based OAuth providers. 2. Generate a OAuth application credentials from your developer account. You can do this by going to the providers developer accounts page and creating a new application. 3. Navigate to **Custom Credentials** under **Config** on the left sidebar in the Junction Dashboard. 4. Find the provider you want to configure and click the **Setup** button. 5. In the input form that appears, enter your **OAuth Client ID** and **OAuth Client Secret**. Some OAuth providers are unavailable unless you can Bring Your Own OAuth (BYOO) β€” you will have to apply directly to the provider for an OAuth application, and provide Junction your assigned credentials once the application is accepted. # Sharing Credentials Source: https://docs.junction.com/wearables/connecting-providers/bring-your-own-oauth/sharing-credentials ## Audience This guide is dedicated to Junction customers looking to share the usage of their OAuth Client Credentials between Junction [Bring Your Own OAuth](/wearables/connecting-providers/bring-your-own-oauth/overview) and their existing production systems. This guide is not applicable if you: * do not have existing OAuth Client Credentials; or * can configure your OAuth Client Credentials to point exclusively at Junction. ## Background Many providers use OAuth 2.0, in which each partner entity is registered as a confidential application. Each application is issued one set of OAuth client credentials (client ID + secret). As a Cross-Site Request Forgery (CSRF) mitigation, they also typically require every application to pre-register a Redirect URI. The `/authorize` flow would then validate inbound requests against the Redirect URI on record. ## Challenge Most providers allow **only one** Redirect URI registration per OAuth application. This means your OAuth application cannot simutaneously support both your own existing OAuth callback endpoint, as well as [Junction Link](/wearables/connecting-providers/introduction) OAuth callback endpoint. ## Recommended Apporach To support this credential sharing use case, Junction recommends you to: 1. Continue to point your OAuth application at your existing OAuth callback endpoint; 2. Extend your endpoint to [detect and redirect](#details) callbacks from Junction Link originiated OAuth requests to the [Junction Link OAuth callback endpoint](#vital-oauth-callback-endpoint-url). ```mermaid theme={null} sequenceDiagram participant Junction Link participant Your API participant BYOO Provider actor User Agent autonumber Junction Link-->>User Agent: OAuth 2.0 Authorization Request URL User Agent->>BYOO Provider: Navigate to URL (1) BYOO Provider-->>User Agent: Prompt User Agent-->>BYOO Provider: Approve or Deny BYOO Provider->>Your API: Redirect Your API->>Junction Link: Redirect ``` ### Details The Redirect URI in your OAuth application settings should continue to point at your existing OAuth callback endpoint. Your OAuth callback endpoints can rely on the `state` query parameter to differentiate the request origin, i.e., whether it is from Junction or from your own production systems. OAuth requests originated from Junction would have the **Junction Link Token** as the `state` query parameter. Junction Link Token is a JSON Web Token (JWT), so you can use the *unverified claims* of the JWT as a discriminator. When you detect a valid Junction Link Token, perform a `307 Temporary Redirect` to the [Junction OAuth callback endpoint](#vital-oauth-callback-endpoint-url) and passing on *all* the URL query parameters. The exact JWT structure of the Junction Link Token is as follows: A JSON Web Token (JWT). You need not verify the signature of this JWT. To prevent Cross-Site Request Forgery attacks, you must still check that the `aud` claim matches the expected [Junction Link API Base URL](#vital-link-api-base-url). | Key | Value | | --------- | -------------------------- | | `aud` | Junction Link API Base URL | | `sub` | Junction Link Session ID | | `team_id` | Junction Team ID | | `user_id` | Junction User ID | | Environment | Base URL | | ------------- | -------------------------------------------- | | Production US | `https://api.tryvital.io/v2/link` | | Production EU | `https://api.eu.tryvital.io/v2/link` | | Sandbox US | `https://api.sandbox.tryvital.io/v2/link` | | Sandbox EU | `https://api.sandbox.eu.tryvital.io/v2/link` | | Environment | Base URL | | ------------- | --------------------------------------------------------------- | | Production US | `https://api.tryvital.io/v2/link/connect/{PROVIDER}` | | Production EU | `https://api.eu.tryvital.io/v2/link/connect/{PROVIDER}` | | Sandbox US | `https://api.sandbox.tryvital.io/v2/link/connect/{PROVIDER}` | | Sandbox EU | `https://api.sandbox.eu.tryvital.io/v2/link/connect/{PROVIDER}` | ```python Python (FastAPI) theme={null} import fastapi from urllib.parse import urlencode VITAL_LINK_BASE_URL = "https://api.tryvital.io/v2/link" @router.get("/oauth_callback/fitbit") def handle_oauth_callback( request: fastapi.Request, state: Annotated[str, fastapi.Query()], ..., ) -> fastapi.Response: state_string = base64.b64decode(state) # Test if this is a JWT. # If so, try to extract the unverified claim payload, and see if this is # a Junction Link JWT. if ( state_string.startswith('{"') and (state_parts := state_string.split(".", maxsplit=3)) and len(state_parts) == 3 and (unverified_claims := json.loads(state_parts[1])) and unverified_claims.get("aud") == VITAL_LINK_BASE_URL ): query = urlencode(request.query_params) return fastapi.RedirectResponse(f"{VITAL_LINK_BASE_URL}/connect/fitbit?{query}") # Not a Junction Link JWT. Fallback to existing logic return process_oauth_callback(...) ``` When you set your OAuth Client Credential through the [Set Team Custom Credentials](/api-reference/org-management/team-custom-credentials/upsert-team-custom-credentials) endpoint, you must specify a Redirect URI override that points at your your OAuth callback endpoint. When a Redirect URI override is present, Junction uses the override value you provided to initiate the OAuth authorization flow. # Building a Custom Widget Source: https://docs.junction.com/wearables/connecting-providers/custom_widget Junction Link API covers **only** [cloud-based providers](/wearables/providers/introduction#cloud-based-providers). Your mobile app has to manage connections with SDK-based providers β€” such as Apple HealthKit and Android Health Connect β€” through the [Junction Mobile Health SDK](/wearables/sdks/vital-health), separately from Junction Link. You may want to have your own custom Link widget to match your app's UI. This is useful for customers who don't want to use Junction link but still want to use the Junction API. It's worthwhile ensuring that you have read the [Auth Types](/docs/auth-types) documentation before proceeding. A full code example in React Native can be seen [here](https://github.com/tryVital/vital-connect-rn). An overview of the process is as follows: 1. Get a list of all available providers ["/v2/providers"](/api-reference/providers) 2. Build your UI using this list of providers 3. When selecting a provider, generate a link token for that provider ["/v2/link/token"](/api-reference/link/generate-link-token) 4. Use the link token to generate an oauth link, or authorize an email, or email and password. ## 1. List of all available providers The first step to creating a custom widget is to make a call to the [providers endpoint](/api-reference/providers). This will return a list of all available providers. You can then use this list to build your UI. ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from "@tryvital/vital-node"; import { LinkTokenExchange, LinkGenerateOauthLinkRequest, OAuthProviders, } from "@tryvital/vital-node/api"; const client = new VitalClient({ apiKey: "", environment: VitalEnvironment.Sandbox, }); const allProviders = await client.providers.getAll(); // allProviders // [ // { // "name": "Oura", // "slug": "oura", // "logo": "https://logo_url.com", // "auth_type": "oauth", // "supported_resources": ["workouts", "sleep"] // } // ] ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.o_auth_providers import OAuthProviders client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) all_providers = client.providers.get_all() # allProviders # [ # { # "name": "Oura", # "slug": "oura", # "logo": "https://logo_url.com", # "auth_type": "oauth", # "supported_resources": ["workouts", "sleep"] # } # ] ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.LinkGenerateOauthLinkRequest; import com.vital.api.resources.link.requests.LinkTokenExchange; import com.vital.api.types.OAuthProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); var all_providers = vital.providers().getAll() ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) response, err := client.Providers.getAll(context.TODO()) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ## 2. Build your UI in your app Once you have a list of all available providers, you can build your UI. You can use the `logo` field to display the provider's logo. You can use the `name` field to display the provider's name. You can use the `supported_resources` field to display the resources that the provider supports. Custom UI ## 3. Generate a link token Once a user has selected a provider, you can generate a link token. This link token is used to generate an oauth link, or authorize an email, or email and password. You can read more about the link token [here](/api-reference/link/generate-link-token). ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from "@tryvital/vital-node"; import { LinkTokenExchange, LinkGenerateOauthLinkRequest, OAuthProviders, } from "@tryvital/vital-node/api"; const client = new VitalClient({ apiKey: "", environment: VitalEnvironment.Sandbox, }); const request: LinkTokenExchange = { userId: "", }; const tokenResponse = await client.link.token(request); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.o_auth_providers import OAuthProviders client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) token_response = client.link.token(user_id="") ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.LinkGenerateOauthLinkRequest; import com.vital.api.resources.link.requests.LinkTokenExchange; import com.vital.api.types.OAuthProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); LinkTokenExchange request = LinkTokenExchange .builder() .userId("") .build(); var token_response = vital.link().token(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) provider := vital.ProvidersOura request := &vital.LinkTokenExchange{ UserId: "", Provider: &provider, } response, err := client.Link.Token(context.TODO(), request) fmt.Printf("Received data %s\n", response) ``` ## 4. Link the user to the provider Once you have a link token, you can use it to generate an oauth link, or authorize an email, or email and password. ### Connect an oauth provider ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { LinkTokenExchange, LinkGenerateOauthLinkRequest, OAuthProviders } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: LinkTokenExchange = { userId: "", } const tokenResponse = await client.link.token(request) const linkRequest: LinkGenerateOauthLinkRequest = { vitalLinkToken: tokenResponse.linkToken, } const auth = await client.link.generateOauthLink( OAuthProviders., linkRequest ) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.o_auth_providers import OAuthProviders client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) token_response = client.link.token(user_id="") oauth = client.link.generate_oauth_link( oauth_provider=OAuthProviders., vital_link_token=token_response.link_token, ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.LinkGenerateOauthLinkRequest; import com.vital.api.resources.link.requests.LinkTokenExchange; import com.vital.api.types.OAuthProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); LinkTokenExchange request = LinkTokenExchange .builder() .userId("") .build(); var token_response = vital.link().token(request); LinkGenerateOauthLinkRequest link_request = LinkGenerateOauthLinkRequest .builder() .vitalLinkToken(token_response.getLinkToken()) .build(); var oauth = vital.link().generateOauthLink(OAuthProviders.OURA, link_request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) provider := vital.ProvidersOura request := &vital.LinkTokenExchange{ UserId: "", Provider: &provider, } response, err := client.Link.Token(context.TODO(), request) if err != nil { return err } generateLinkRequest := &vital.LinkGenerateOauthLinkRequest{ VitalLinkToken: response.LinkToken, } oauth, err := client.Link.GenerateOauthLink(context.TODO(), vital.OAuthProvidersOura, generateLinkRequest) if err != nil { return err } fmt.Printf("Received data %s\n", oauth) ``` ### Connact an Email/Password provider ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { IndividualProviderData, PasswordProviders } from '@tryvital/vital-node/api'; const request: IndividualProviderData = { username: '', password: '', } const data = await client.link.connectPasswordProvider(PasswordProviders., request) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.password_providers import PasswordProviders client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.link.connect_password_provider( PasswordProviders., username="", password="" ) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.IndividualProviderData; import com.vital.api.types.PasswordProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); IndividualProviderData request = IndividualProviderData .builder() .username("") .password("") .build(); var data = vital.link().connectPasswordProvider(PasswordProviders., request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.IndividualProviderData{ Username: "", Password: "", } response, err := client.Link.ConnectPasswordProvider( context.TODO(), vital.PasswordProviders, request ) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ### Bringing this all together Below is an example of how you can bring all of this together to build your own custom widget. This example is in React Native, but the same principles apply to any language. ```javascript React Native theme={null} import React, {useEffect, useState} from 'react'; import { SafeAreaView, StatusBar, Linking, View, useColorScheme, Platform, } from 'react-native'; import {VitalHealth, VitalResource} from '@tryvital/vital-health-react-native'; const handleOAuth = async (userId: string, item: Provider) => { const linkToken = await Client.Link.getLinkToken( userId, `${AppConfig.slug}://link`, ); const link = await Client.Providers.getOauthUrl(item.slug, linkToken.link_token); Linking.openURL(link.oauth_url); }; const ListItem = ({ userId, item, navigation, }: { userId: string; item: Provider; navigation: any; }) => { const {colors} = useTheme(); const isDarkMode = useColorScheme() === 'dark'; const [isLoading, setLoading] = useState(false); const handleNativeHealthKit = async () => { const providerSlug = Platform.OS == 'ios' ? ManualProviderSlug.AppleHealthKit : ManualProviderSlug.HealthConnect; setLoading(true); const isHealthSDKAvailable = await VitalHealth.isAvailable(); if (!isHealthSDKAvailable) { console.warn('Health Connect is not available on this device.'); navigation.navigate('ConnectionCallback', { state: 'failed', provider: providerSlug, }); return; } try { await VitalHealth.configure({ logsEnabled: true, numberOfDaysToBackFill: 30, androidConfig: {syncOnAppStart: true}, iOSConfig: { dataPushMode: 'automatic', backgroundDeliveryEnabled: true, }, }); } catch (e) { setLoading(false); console.warn(`Failed to configure ${providerSlug}`, e); navigation.navigate('ConnectionCallback', { state: 'failed', provider: providerSlug, }); } await VitalHealth.setUserId(userId); try { await VitalHealth.askForResources([ VitalResource.Steps, VitalResource.Activity, VitalResource.HeartRate, VitalResource.Sleep, VitalResource.Workout, VitalResource.BloodPressure, VitalResource.Glucose, VitalResource.Body, VitalResource.Profile, VitalResource.ActiveEnergyBurned, VitalResource.BasalEnergyBurned, ]); await VitalCore.createConnectedSourceIfNotExist(providerSlug); setLoading(false); navigation.navigate('ConnectionCallback', { state: 'success', provider: providerSlug, }); } catch (e) { setLoading(false); navigation.navigate('ConnectionCallback', { state: 'failed', provider: providerSlug, }); } }; const onPress = async () => { if (item.auth_type === 'oauth') { await handleOAuth(userId, item); } else if ( item.slug === 'apple_health_kit' || item.slug === 'health_connect' ) { await handleNativeHealthKit(); } }; return ( onPress()}> {({isHovered, isFocused, isPressed}) => { return ( {item.name} {item.description} ); }} ); }; export const LinkDeviceScreen = ({navigation}) => { const isDarkMode = useColorScheme() === 'dark'; const {colors} = useTheme(); const styles = makeStyles(colors); const [providers, setProviders] = React.useState([]); const [devices, setDevices] = React.useState([]); const [loading, setLoading] = React.useState(false); const [searchText, setSearchText] = React.useState(''); const [error, setError] = React.useState(null); const [userId, setUserId] = React.useState(''); const handleSearch = (text: string) => { setSearchText(text); if (text === '' && text.length <= 2) { setDevices(providers); } else { const filtered = providers.filter(item => item.name.toLowerCase().includes(text.toLowerCase()), ); setDevices(filtered); } }; useEffect(() => { const getDevices = async () => { setLoading(true); setError(null); const user_id = await getData('user_id'); if (user_id) { const providers = await Client.Providers.getProviders(); setLoading(false); setUserId(user_id); setProviders(providers); } else { setUserId(''); setLoading(false); console.warn('Failed to get all supported providers'); setError('Failed to get devices'); } }; }, [navigation]); return (

Connect a Device

{!loading && error ? ( Failed to get supported Providers ) : ( ( )} keyExtractor={item => item.slug} /> )}
); }; ``` # Link Errors Source: https://docs.junction.com/wearables/connecting-providers/errors ## Error delivery ### URL Query Junction performs a 307 Temporary Redirect to the `redirect_url` specified in your Link Token in the following scenarios: * All connections via [the Junction Link widget](/wearables/connecting-providers/introduction) * [Connect OAuth Provider](/api-reference/link/link-oauth-provider) endpoint used by your [Custom Widget](/wearables/connecting-providers/custom_widget) Error parameters would be appended to the `redirect_url` before the redirection. For example, given a redirect\_url of `https://example.com/vital/callback`: ``` https://example.com/vital/callback ?state=error &error_type=provider_credential_error &error=Invalid%20username%20or%20password ``` ### JSON Response Junction responds 200 OK with a JSON response in the following scenarios: * [Connect Password Provider](/api-reference/link/link-password-provider) endpoint used by your [Custom Widget](/wearables/connecting-providers/custom_widget) * [Connect Email Provider](/api-reference/link/link-email-provider) endpoint used by your [Custom Widget](/wearables/connecting-providers/custom_widget) Error parameters would be embedded into the JSON response. For example: ```json theme={null} { "state": "error", "error_type": "provider_credential_error", "error": "Invalid username or password" } ``` ## Error Parameters Constant: `error` One of the [Link Error Types](#error-types). A human-readable error explanation for troubleshooting purposes. Your application logic should not rely on this message. ## Error Types This list is **non-exhaustive**. Your application logic must gracefully handle any unrecognized error type. | Error Type | Description | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `invalid_token` | The Link Token is invalid or malformed. | | `token_expired` | The Link Token has expired. | | `token_consumed` | A successful connection establishment had consumed this Link Token. | | `provider_credential_error` |
  • OAuth: An error occurred during the provider authentication flow.
  • Password & Email: The supplied user credential is invalid.
| | `provider_password_expired` | The supplied password has expired due to the password rotation policy of the provider. The user must rotate their password directly with the provider before trying to connect again. Applicable only to the patient-based `abbott_libreview` integration. | | `provider_api_error` | An unexpected error occurred when Junction Link attempts to connect to the provider API. | | `unsupported_region` | The region you specified is not supported. Applicable only the practice-based `freestyle_libre` integration. | | `duplicate_connection` | The provider credential has an existing connection to a different user in your team. Applicable only if you have opted in to the `reject_duplicate_connection` team setting. | | `required_scopes_not_granted` | User did not grant **all** required scopes, so Junction cannot establish the connection. | | `incorrect_mfa_code` | The MFA code entered is incorrect. | # Introduction to Link Source: https://docs.junction.com/wearables/connecting-providers/introduction Junction Link enables your users to connect their accounts with [supported cloud-based providers](https://docs.tryvital.io/wearables/providers/introduction) to Junction. Junction Link is offered in two forms: * [Junction Link Widget](/wearables/connecting-providers/link_flow) is a web flow hosted by Junction, providing a complete provider connection wizard UX that is ready to integrate. * [Junction Link API](/wearables/connecting-providers/custom_widget) allows you to build your own Link Widget like user experience. Junction Link API covers **only** [cloud-based providers](/wearables/providers/introduction#cloud-based-providers). Your mobile app has to manage connections with SDK-based providers β€” such as Apple HealthKit and Android Health Connect β€” through the [Junction Mobile Health SDK](/wearables/sdks/vital-health), separately from Junction Link. ![link-gif](https://storage.googleapis.com/vital-assets/vitalLink.gif) # Launching Link Source: https://docs.junction.com/wearables/connecting-providers/launching_link Junction Link API covers **only** [cloud-based providers](/wearables/providers/introduction#cloud-based-providers). Your mobile app has to manage connections with SDK-based providers β€” such as Apple HealthKit and Android Health Connect β€” through the [Junction Mobile Health SDK](/wearables/sdks/vital-health), separately from Junction Link. Create a Junction Link Token for a user using the [Create Link Token](/api-reference/link/generate-link-token) endpoint. Junction Link Token expires 60 minutes after its creation.
```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { LinkTokenExchange } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: LinkTokenExchange = { userId: "", provider: "oura", } const data = await client.link.token(request) ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment from vital.types.providers import Providers client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.link.token(user_id="", provider=Providers.OURA) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.LinkTokenExchange; import com.vital.api.types.Providers; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); LinkTokenExchange request = LinkTokenExchange .builder() .userId("") .provider(Providers.OURA) .build(); var data = vital.link().token(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) provider := vital.ProvidersOura request := &vital.LinkTokenExchange{ UserId: "", Provider: &provider, } response, err := client.Link.Token(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` ```bash cURL theme={null} curl --request POST \ --url {{BASE_URL}}/v2/link/token \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' --header 'x-vital-api-key: ' ```
Use the Link Token to [launch the Link Widget](/wearables/vital-link/link_flow) on your frontend. You can launch the Junction Link Widget using the Link Web URL (`link_web_url`) as provided by the [Generate a Link Token endpoint](/api-reference/link/generate-link-token): * If you have created the Link Token for a specific OAuth provider, Junction Link will dispatch the user automatically to the provider's sign-in page. * Otherwise, your user will be presented with a list of providers to connect to. You can customize the list via the `filter_on_providers` parameter when creating the Link Token. When the Link flow completes, the flow will redirect to the `redirect_url` you specified earlier when creating the Link Token. We will also append query parameters to the redirection target based on the Link flow outcome: | Flow state | Query parameters | | ---------- | ---------------------------------------------------------------------------------- | | Success | `state=success` | | Error | See [Link Errors](https://docs.tryvital.io/wearables/connecting-providers/errors). | ```js Launching Junction-Link theme={null} import 'react-app-polyfill/ie11'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { useCallback } from 'react'; import { useVitalLink } from '@tryvital/vital-link'; import { useState } from 'react'; const API_URL = process.env.API_URL ? process.env.API_URL : "http://localhost:3001" const getTokenFromBackend = async (userKey: string, env: string) => { const resp = await fetch(); return await resp.json(); }; const App = props => { const userKey = '560596b9-924b-4af9-a670-cf21870fdac5'; const [isLoading, setLoading] = useState(false); const onSuccess = useCallback(metadata => { // Device is now connected. }, []); const onExit = useCallback(metadata => { // User has quit the link flow. }, []); const onError = useCallback(metadata => { // Error encountered in connecting device. }, []); const config = { onSuccess, onExit, onError, env: "sandbox" }; const { open, ready, error } = useVitalLink(config); const handleVitalOpen = async () => { setLoading(true); const token = await getTokenFromBackend(userKey, "sandbox"); open(token); setLoading(false); }; return ( ); }; ReactDOM.render(, document.getElementById('root')); ```
# Link Flow Source: https://docs.junction.com/wearables/connecting-providers/link_flow Junction Link API covers **only** [cloud-based providers](/wearables/providers/introduction#cloud-based-providers). Your mobile app has to manage connections with SDK-based providers β€” such as Apple HealthKit and Android Health Connect β€” through the [Junction Mobile Health SDK](/wearables/sdks/vital-health), separately from Junction Link. The Junction Link flow starts when a user wants to connect a wearable device with Junction. An overview of the process can be found below. Make sure that: 1. You have your Junction Team API Key [dashboard](https://app.junction.com) ready to use. 2. You have created a Junction User that corresponds to a user in your domain. [See](/home/quickstart#3-creating-your-first-user) for an example. Call the [Generate a Link Token endpoint](/api-reference/link/generate-link-token) with the target User ID using your Junction Team API Key. The API response provides a short-lived Link Token (`link_token`), as well as a Web URL (`link_web_url`). You can use the Web URL to launch the Junction Link Widget directly. Alternatively, you can use the Link Token in concert with the Junction Link API to [build your custom Link Widget experience](/wearables/connecting-providers/custom_widget). # Abbott LibreView Source: https://docs.junction.com/wearables/guides/abbott-libreview For Abbott CGMs, e.g. Freestyle Libre. ## Overview Junction supports two ways to connect to LibreView for Abbott CGMs: | Provider Slug | Remarks | | ------------------ | --------------------------------------------------------------------------------- | | `abbott_libreview` | Connect via LibreView patient account in any region. | | `freestyle_libre` | Connect via regional LibreView practices (Junction-managed, or custom practices). | The abbott\_libreview provider is no longer supported for new connections. This change applies to organizations that do not already have at least one active abbott\_libreview connection. Existing abbott\_libreview connections will continue to sync as normal. ### `freestyle_libre` for practice-based connections With the `freestyle_libre` integration, users have to share/connect their LibreView patient account with either Junction's LibreView practice, or a custom practice of yours. Junction accesses the patient CGM data as a member of the LibreView practice. ### `abbott_libreview` for patient-based connections With the `abbott_libreview` integration, users can connect their Abbott CGM devices simply by signing in with their LibreView patient account. Junction accesses the patient CGM data as their personal account. * You would not need to maintain any LibreView practices. * You would not need to instruct your users to connect their account to a LibreView practice, before they can attempt to connect via Junction Link. #### ⚠️ Prepare for regular disconnection with `abbott_libreview` Patient-based connections through `abbott_libreview` may **disconnect** at least once a year due to password rotation enforcement. You can observe the error state through: 1. the [Provider Connection Error](/event-catalog/provider.connection.error) event; and 2. the [Get User Connections endpoint](/api-reference/user/get-users-connected-providers). The following error types are relevant in this password rotation situation: | Error Type | Remarks | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `provider_credential_error` | The patient has changed their password. They need to re-connect with the new password via Junction Link. | | `provider_password_expired` | Login is blocked due to password expiration. Once the patient has changed their password, they need to re-connect with the new password via Junction Link. | When you detect a disconnection, guide your user to: 1. Sign into the LibreLink app or the LibreView web portal. 2. Update their account passwords. 3. Use the Junction Link Widget or your custom widget to re-establish the `abbott_libreview` connection. ## LibreView data use floating time basis All LibreView (Freestyle Libre) data are in **floating time basis**. Please go through the [Timestamps and Time Zones](/wearables/providers/timestamps-and-time-zones) guide to understand the implications, as well as how to properly handle floating time data. ## Libre Notes Timeseries LibreView, in addition to standard CGM data, provides insights manually added by the user. This data is available in the following types: * `note`: A general note added by the user, often providing context for other entries. * `carbohydrates`: Grams of carbohydrates logged by the user. * `insulin_injection`: Units of insulin injected by the user, categorized as either `rapid_acting` or `long_acting`. Timestamps for these events have hourly granularity and are marked with the beginning of the hour. ## Connecting a LibreView patient account (`abbott_libreview`) ### Junction Link Widget Abbott LibreView is automatically available as an option in the [Junction Link Widget](/wearables/connecting-providers/launching_link). ### Junction Link API (custom widget) If you intend to build a [custom widget on top of the Junction Link API](/wearables/connecting-providers/custom_widget), Abbott LibreView is exposed as a **password provider**: Start the linking process by [generating a short-lived Link Token](/api-reference/link/generate-link-token). Submit the username and password of the LibreView patient account to the [Link Password Provider](/api-reference/link/link-password-provider) endpoint. You need not specify any region in your request. Junction will match the correct LibreView region transparently. **Important:** Abbott LibreView linking can take longer than other providers. We recommend configuring an extended client-side timeout (up to 60 seconds) when making this Link request. If your timeout is shorter, linking may fail even when credentials are valid. The [Link Password Provider](/api-reference/link/link-password-provider) endpoint responds with `state: "pending_provider_mfa"`, alongside the MFA method being used, as well as a MFA hint. This means the user must complete multi-factor authentication before Junction can establish the connection. You should prompt the user to input the verification code they received out-of-band. Submit the MFA code to the [Complete Password Provider MFA](/api-reference/link/complete-password-provider-mfa) endpoint. You must use the same Link Token you generated in Step 1. The linking process is completed. Historical pulls are scheduled to start as soon as possible. ## Setting up practice-based connections (`freestyle_libre`) ### Adding users to a Practice When integrating with Junction, you have two options: use Junction's practice, or yours. We call the latter a custom practice. Depending on the approach, you need to instruct your user's to connect their Libre account to a practice. This allows Junction to start pulling data from their account. This is done through the Libre app: ### Supported Regions #### πŸ‡ΊπŸ‡Έ Junction US region | Region Code | Region | | ----------- | ------------- | | `ca` | Canada | | `hk` | Hong Kong | | `in` | India | | `kr` | South Korea | | `ph` | Philippines | | `sg` | Singapore | | `tw` | Taiwan | | `us`\* | United States | *(\*) Default if region is unspecified.* #### πŸ‡ͺπŸ‡Ί Junction EU region | Region Code | Region | | ----------- | -------------- | | `ar` | Argentina | | `at` | Austria | | `au` | Australia | | `be` | Belgium | | `bh` | Bahrain | | `br` | Brazil | | `ch` | Switzerland | | `cl` | Chile | | `co` | Colombia | | `cz` | Czechia | | `de` | Germany | | `dk` | Denmark | | `eg` | Egypt | | `es` | Spain | | `fi` | Finland | | `fr` | France | | `gb`\* | United Kingdom | | `gr` | Greece | | `hk` | Hong Kong | | `hr` | Croatia | | `ie` | Ireland | | `il` | Israel | | `in` | India | | `it` | Italy | | `jo` | Jordan | | `kr` | South Korea | | `kw` | Kuwait | | `lb` | Lebanon | | `lu` | Luxembourg | | `mx` | Mexico | | `nl` | Netherlands | | `no` | Norway | | `nz` | New Zealand | | `om` | Oman | | `ph` | Philippines | | `pl` | Poland | | `pt` | Portugal | | `qa` | Qatar | | `sa` | Saudi Arabia | | `se` | Sweden | | `sg` | Singapore | | `si` | Slovenia | | `sk` | Slovakia | | `tr` | TΓΌrkiye | | `tw` | Taiwan | | `za` | South Africa | *(\*) Default if region is unspecified.* ### Bring Your Own Practice `freestyle_libre` custom practice connections is available for [the Scale plan](https://tryvital.io/pricing). Get in touch with [Junction Support](/home/getting-support) to request the region-specific Junction practice email address. Invite the Junction practice email address to join your LibreView practice as a **Care Team Admin**. Inform Junction support of the invitation, and wait for the support acknowledgement of successful practice connection. Once the connection is established, Junction Link would recognize all patient emails having connected to your LibreView practice. These patients do not need to connect separately to the [Junction practices](#using-vital-practices). ### Using Junction Practices You can use a Junction practice to test the integration with Freestyle Libre. This modality is available to all subscriptions. Junction has two LibreView Practices that are available in all supported regions: | Environment | Practice Name | | ----------- | ---------------- | | Sandbox | tryVital-sandbox | | Production | tryVital | You need to ask your user to add one of the above practices in their Libre App. Once that's done, data will start flowing. ### Junction Link Widget To connect your user to Junction via the Link Widget you can follow the guide [here](/wearables/connecting-providers/launching_link). ### Junction Link API (custom widget) 1. To connect to Freestyle Libre, you must create a Junction Link token. ```python Python theme={null} token_response = client.link.token(user_id="") ``` ```javascript Node theme={null} const request: LinkTokenExchange = { userId: " } const data = await client.link.token(request) ``` ```java Java theme={null} LinkTokenExchange request = LinkTokenExchange .builder() .userId("") .build(); var tokenResponse = vital.link().token(request); ``` ```go Go theme={null} request := &vital.LinkTokenExchange{ UserId: " } tokenResponse, err := client.Link.Token(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n response) ``` 2. Libre is an [email](/api-reference/link/link-email-provider) provider. This means that when connecting a user we have to use the connect email provider method, passing in the email of the user's Libre account and region in short form. ```python Python theme={null} linkResponse = client.link.connect_email_auth_provider( provider="freestyle_libre email=" vital_link_token=token_response.link_token ) ``` ```javascript Node theme={null} const linkRequest: EmailProviderAuthLink = { vitalLinkToken: tokenResponse.linkToken, email: "" } const connectEmailResponse = await client.link.connectEmailAuthProvider( "freestyle_libre linkRequest ) ``` ```java Java theme={null} EmailProviderAuthLink request = EmailProviderAuthLink .builder() .vitalLinkToken(tokenResponse.getLinkToken()) .email("") .build(); var data = vital.link().connectEmailAuthProvider(" } emailAuth, err := client.Link.ConnectEmailAuthProvider(context.TODO(), "freestyle_libre emailAuthRequest) if err != nil { return err } fmt.Printf("Received data %s\n emailAuth) ``` Once this is complete we will start syncing data from their [Libre account](/api-details/data_flow). # Android Health Connect Source: https://docs.junction.com/wearables/guides/android-health-connect Android Health Connect is an SDK-based provider. Your Android consumer app would embed the Junction Mobile SDKs on a supported stack. Data are then pulled from the Health Connect data store on the user's Android device. Refer to the [Mobile SDK Installation](/wearables/sdks/installation) and [Junction Health SDK](/wearables/sdks/vital-health) guides for SDK installation instructions and general API usage. This guide contains information on the the behaviour and required configuration specific to the Android Health Connect integration. Learn about the minimum runtime and build-time requirements of Junction Mobile SDKs, and how to add them into your project through your package dependency manager. Learn about the initial setup required by Junction Health SDK, and the API exposed by Junction Health SDK for managing and inspecting health data permissions and automatic data sync. ## Getting Started To enable this integration, you would need to integrate [Junction Core SDK](/wearables/sdks/vital-core) and [Junction Health SDK](/wearables/sdks/vital-core) into your Native iOS, React Native or Flutter mobile app. Review the [installation requirements and package installation instructions](/wearables/sdks/installation). Follow the [SDK Authentication](/wearables/sdks/authentication) guidance to integrate your app with the authentication mechanism of Junction Core SDK. Follow the [Junction Health SDK](/wearables/sdks/vital-health) starting guidance to [configure the Health SDK](/wearables/sdks/vital-health#sdk-configuration) and to [ask for health data read permissions](/wearables/sdks/vital-health#ask-user-for-health-data-permissions) from your user. Health data sync is activated only on `VitalResource`s which you have asked permissions from your user for. If you did not ask for permissions, data sync would not occur. Follow the [Running as Foreground Service](#running-as-foreground-service) guidance below to customize the Foreground Service notification which may be shown by the Android OS during any prolonged health data sync attempts. Follow the [Background Sync](#background-sync) guidance below to configure regular background sync. ## Review the Google Play Store policy Apps using Android Health Connect **require prior Google approval** before they can be released on Google Play Store. Check out the official [Google Play Store policy on Android Health Connect](https://support.google.com/googleplay/android-developer/answer/12991134?hl=en-GB). Note that the application form is located under the "How do I access data through Health Connect?" section. ## Configure your AndroidManifest.xml ### Health Connect privacy dialogue Missing manifest declarations may result in Health Connect ignoring your app. Check [the Health Connect Getting Started guide](https://developer.android.com/health-and-fitness/guides/health-connect/develop/get-started#show-privacy-policy) for the official requirements. Here is a minimum declaration example: ```xml theme={null} ``` ### Health Connect permissions Your app manifest (`AndroidManifest.xml`) must declare all the read or write permissions for all the data types you intend to sync. For example, if you intend to sync Blood Pressure records and Blood Glucose reocrds, your app manifest must contain the following `` declarations: ```xml theme={null} ``` | SDK `VitalResource` type | Read permissions required | | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Profile` | `android.permission.health.READ_HEIGHT` | | `Body` | `android.permission.health.READ_BODY_FAT`
`android.permission.health.READ_WEIGHT` | | `Workout` | `android.permission.health.READ_EXERCISE`
`android.permission.health.READ_HEART_RATE`
`android.permission.health.READ_RESPIRATORY_RATE`
`android.permission.health.READ_DISTANCE`
`android.permission.health.READ_ACTIVE_CALORIES_BURNED`
`android.permission.health.READ_ELEVATION_GAINED`
`android.permission.health.READ_POWER`
`android.permission.health.READ_SPEED` | | `Activity` | `android.permission.health.READ_ACTIVE_CALORIES_BURNED`
`android.permission.health.READ_BASAL_METABOLIC_RATE`
`android.permission.health.READ_TOTAL_CALORIES_BURNED`
`android.permission.health.READ_DISTANCE`
`android.permission.health.READ_STEPS`
`android.permission.health.READ_FLOORS_CLIMBED`
`android.permission.health.READ_DISTANCE`
`android.permission.health.READ_VO2_MAX` | | `Sleep` | `android.permission.health.READ_SLEEP`
`android.permission.health.READ_HEART_RATE`
`android.permission.health.READ_RESPIRATORY_RATE`
`android.permission.health.READ_HEART_RATE_VARIABILITY`
`android.permission.health.READ_OXYGEN_SATURATION`
`android.permission.health.READ_RESTING_HEART_RATE` | | `Glucose` | `android.permission.health.READ_BLOOD_GLUCOSE` | | `BloodPressure` | `android.permission.health.READ_BLOOD_PRESSURE` | | `HeartRate` | `android.permission.health.READ_HEART_RATE` | | `Steps` | `android.permission.health.READ_STEPS` | | `ActiveEnergyBurned` | `android.permission.health.READ_ACTIVE_CALORIES_BURNED` | | `BasalEnergyBurned` | `android.permission.health.READ_ACTIVE_CALORIES_BURNED`
`android.permission.health.READ_BASAL_METABOLIC_RATE`
`android.permission.health.READ_TOTAL_CALORIES_BURNED` | | `Water` | `android.permission.health.READ_HYDRATION` | ### Foreground Service permissions Junction Health SDK runs all its data sync workers inside a short-service Foreground Services. At build time, Junction Health SDK injects all the required `shortService` Foreground Services declarations into your AndroidManifest.xml. You need not modify your app's Manifest to enable this. Junction Health SDK can run work inside the [AndroidX WorkManager](https://developer.android.com/develop/background-work/background-tasks/persistent/how-to/long-running#declare-foreground-service-types-manifest) foregorund service, as well as our own `SyncOnExactAlarmService`. The merged AndroidManifest.xml of your app would include both of them. ## Prepare your app architecture Incompliance to the guidelines below may result in Health Connect permission flow failing to launch on specific Android OS versions. When requesting permission using the `ActivityResultContract` created by Junction Health `createPermissionRequestContract()`, you must launch the contract using AndroidX Activity Result API. Attempts to launch the contract manually via the legacy `Activity.startActivityForResult` API would result in an `android.content.ActivityNotFoundException` exception on Android 14 and later. The `MainActivity` of your Android host app project must be augmented with either of the two options below: 1. Subclassing `VitalHealthReactActivity` provided by the Junction Health SDK `com.vitalhealthreactnative` package, instead of the default `ReactActivity`. 2. Overriding `onRequestPermissionsResult(...)` so as to manually propagate permission request results to the Junction Health SDK. ```kotlin theme={null} // MainActivity in your host app project open class MainActivity: ReactActivity() { override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) // Propagate permission flow results to Junction Health SDK. VitalHealthReactNativeModule.onRequestPermissionsResult( this.reactInstanceManager, requestCode, permissions, grantResults ) } } ``` The argumentation above is a workaround to React Native internals AndroidX-incompatible handling of permission request result callbacks, which breaks the AndroidX Activity Result API required by Android Health Connect. The `MainActivity` of your Android host app project must subclass `FlutterFragmentActivity` instead of `FlutterActivity`. This is because `FlutterFragmentActivity` is AndroidX compatible and therefore supports the Activity Result API as required by Android Health Connect, whilst `FlutterActivity` is not. ## Synchronization ### Sync On App Launch When your app resumes from background (i.e., having no Activity), Junction Health SDK triggers a data sync on all the resources to which the user has granted read permission. The SDK imposes a **2-minute** throttle on automatic data sync. This is to prevent rapid app switching from causing an excessive amount of data sync work being scheduled. Junction Health SDK relies on the [AndroidX ProcessLifecycleOwner](https://developer.android.com/reference/androidx/lifecycle/ProcessLifecycleOwner) to get notified of your app's resumption. ### Running as Foreground Service You cannot opt-out of this behaviour. Junction Health SDK runs all its data sync workers inside a Foreground Service. Running as a foreground service helps in two ways: 1. It ensures ongoing data sync can run to completion even if the user switches away from your app; and 2. data sync workers have to be **in foreground** to read data from Health Connect. Health Connect prohibits reading data from background. Android requires every Foreground Service to [be associated with a user-visible notification](https://developer.android.com/develop/background-work/services/foreground-services#user-dismiss-notification). The OS typically grants a grace period of about 10 seconds before making the user aware of this notification. In other words, if the data sync worker completes within the grace period, no notification would be posted. Junction Health SDK installs these notification copies by default: | Item | Copy | | -------------------- | ---------------------------------------------------------------- | | Notification Title | Health Data Sync | | Notification Content | `{APP_NAME}` is synchronizing with Health Connect... | | Channel Title | Health Data Sync | | Channel Description | Notifies when `{APP_NAME}` is synchronizing with Health Connect. | You can customize it through two ways: Register your custom `SyncNotificationBuilder` through `VitalHealthConnectManager.syncNotificationBuilder`. You should register it as soon as your app process is created. One way to ensure this is through the AndroidX Startup library. You can define an `Initializer` of your own that depends on the SDK `VitalHealthConnectInitializer`. #### An example App Startup Initializer implementation ```kotlin theme={null} package com.example.app import android.content.Context import androidx.startup.Initializer import io.tryvital.vitalhealthconnect.VitalHealthConnectManager import io.tryvital.vitalhealthconnect.VitalHealthConnectInitializer class ExampleSyncNotificationBuilderInitializer: Initializer { override fun create(context: Context) { val manager = VitalHealthConnectManager.getOrCreate(context) manager.syncNotificationBuilder = ExampleSyncNotificationBuilder } override fun dependencies(): MutableList>> = mutableListOf( VitalHealthConnectInitializer::class.java, ) } ``` #### An example `SyncNotificationBuilder` implementation ```kotlin theme={null} package io.tryvital.sample import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.content.Context.NOTIFICATION_SERVICE import androidx.core.app.NotificationCompat import io.tryvital.vitalhealthconnect.SyncNotificationBuilder import io.tryvital.vitalhealthconnect.model.VitalResource object ExampleSyncNotificationBuilder: SyncNotificationBuilder { override fun build(context: Context, resources: Set): Notification { return NotificationCompat.Builder(context, createChannel(context)) .setContentTitle("Example Sync") .setContentText("Syncing your data") .setOngoing(true) .setSmallIcon(android.R.drawable.ic_popup_sync) .build() } fun createChannel(context: Context): String { val importance = NotificationManager.IMPORTANCE_MIN val mChannel = NotificationChannel("ExampleSyncNotification", "Example Sync", importance) mChannel.description = "Notifies when Example is syncing your data" val notificationManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(mChannel) return mChannel.id } } ``` Register your custom copies through `VitalHealth.setSyncNotificationContent` as early as possible in your React Native root component. ```typescript theme={null} import { VitalHealth } from '@tryvital/vital-health-react-native'; await VitalHealth.setSyncNotificationContent({ "notificationTitle": "Example Sync", "notificationContent": "Syncing your data", "channelName": "Example Sync", "channelDescription": "Notifies when Example is syncing your data" }); ``` Register your custom copies through `vital_health.setSyncNotificationContent` as early as possible in your Flutter app startup logic. ```typescript theme={null} import 'package:vital_health/vital_health.dart' as vital_health; vital_health.setSyncNotificationContent(vital_health.SyncNotificationContent( "Example Sync", "Syncing your data", "Example Sync", "Notifies when Example is syncing your data")); ``` ## Background Sync Junction Health SDK supports an **opt-in** Android Background Sync feature. It provides a continuous monitoring experience with Android Health Connect, siimlar to [Background Delivery for Apple HealthKit](/wearables/guides/apple-healthkit#background-delivery) on iOS. ### Sync Frequency The [Sync On App Launch](#sync-on-app-launch) behaviour is always-on. When Background Sync is enabled, the SDK additionally schedules hourly sync using the Android [Exact Alarms](https://developer.android.com/develop/background-work/services/alarms/schedule) mechanism. The OS has full discretion on whether to honour or defer the time as scheduled by the Junction Health SDK. This includes the policies of the base Android OS, as well as any vendor-specific OS augmentation. For example, the device may enter [Doze mode](https://developer.android.com/training/monitoring-device-state/doze-standby) during device inactivity. Doze mode would batch and defer most Exact Alarms and other background work to run at a lower frequency. Not to be confused with Alarm Clock apps, **Exact Alarm** is the technical name of an Android framework for scheduling app wake-ups in background at certain wall clock time. It is the Junction Health SDK being silently "alarmed" in background. Your user **will not be alarmed hourly** as a result of enabling Background Sync. However, if the data sync took longer than 10 seconds, the OS might nudge them of this occurrence with a user-visible notification. The notification content is configurable by you β€” see the ["Running as Foreground Service"](#running-as-foreground-service) section for more details. ### Runtime Permission Request Since Android 12 (API Level 31), scheduling Exact Alarm (`SCHEDULE_EXACT_ALARM`) requires a runtime permission request from the user. More specifically: 1. The user must go through an interactive flow to grant the *"Alarms & Reminders"* permission. * This flow is also reachable through the *"Alarms & Reminders"* section in Android Settings. 2. The user may refuse to grant the *"Alarms & Reminders"* permission. 3. The user may revoke the *"Alarms & Reminders"* permission through Android Settings at any time. Since Android 13 (API Level 33), a new mutually exclusive `USE_EXACT_ALARM` permission was introduced. This permits your app to schedule Exact Alarms without interactive permission requests. `USE_EXACT_ALARM` attracts scrutiny during Google Play Store review. Per the Google Play [Exact alarm permission policy](https://support.google.com/googleplay/android-developer/answer/9888170?hl=en-GB): > USE\_EXACT\_ALARM is a restricted permission and apps must only declare this permission if their core functionality supports > the need for an exact alarm. Apps that request this restricted permission are subject to review, and those that do not meet > the acceptable use case criteria will be disallowed from publishing on Google Play. > > *(excerpted on 20 March 2024)* If you choose to incorporate `USE_EXACT_ALARM`, you should prepare to justify to Google Play Store: 1. Why hourly background sync is quintessential to your product experience; and 2. Why the interactive permission request required by `SCHEDULE_EXACT_ALARM` is non-optimal to your product experience. Here is a matrix summarizing the Exact Alarm permission request requirements: | Android OS | API Level | Requirements | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android 11 or below | \<=30 | No interactive permission request. | | Android 12 | 31, 32 | `SCHEDULE_EXACT_ALARM`: Requires a runtime permission request. Revokable. | | Android 13 and above | >=33 | App must declare either:
  • `SCHEDULE_EXACT_ALARM`: Requires runtime permission request. Revokable.
  • `USE_EXACT_ALARM`: No interactive permission request.
| ### Updating your AndroidManifest.xml Your app's AndroidManifest.xml must include the following `uses-permission` claims: Make sure you read and understand [Runtime User Permission](#runtime-user-permission-android-12) before moving forward. This option is mutually exclusive with `USE_EXACT_ALARM` in the second tab. ```xml theme={null} ``` Make sure you read and understand [Runtime User Permission](#runtime-user-permission-android-12) before moving forward, especially the Google Play Store review policy surrounding the `USE_EXACT_ALARM` permission. This option is mutually exclusive with `SCHEDULE_EXACT_ALARM` in the first tab. ```xml theme={null} ``` ### Enabling Background Sync You can enable Background Sync through `enableBackgroundSyncContract()` (an AndroidX `ActivityResultContract`). If the runtime [requires an interactive permission request](#runtime-user-permission-android-12), this contract will launch an Android Intent to request the *"Alarms & Reminders"* permission. The contract result is a boolean, indicating whether or not the Background Sync has been enabled successfully. For example, if the user did not grant the permission during the said Android Intent, the contract returns `false`. Otherwise, the contract returns `true` synchronously when no user interaction is required. You must launch this `ActivityResultContract` either through the in-built Android Compose support, or through the AndroidX Activity Result API if you do not use Android Compose. You can also inspect if Background Sync has been enabled through the `isBackgroundSyncEnabled` property. ```kotlin Android Compose theme={null} import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.material3.* import io.tryvital.vitalhealthconnect.VitalHealthConnectManager val manager: VitalHealthConnectManager = ... val permissionsLauncher = rememberLauncherForActivityResult( manager.enableBackgroundSyncContract() ) { success -> Log.i("VitalBackgroundSync", "Enabled? $success") val enabled = manager.isBackgroundSyncEnabled Log.i("VitalBackgroundSync", "Enabled? $enabled") } Button(onClick = { permissionsLauncher.launch(Unit) }) { Text("Enable Background Sync") } ``` ```kotlin AndroidX Activity Result API theme={null} import androidx.activity.ComponentActivity import io.tryvital.vitalhealthconnect.VitalHealthConnectManager val activity: ComponentActivity = ... val manager: VitalHealthConnectManager = ... activity.registerForActivityResult(contract) { success -> Log.i("VitalBackgroundSync", "Enabled? $success") val enabled = manager.isBackgroundSyncEnabled Log.i("VitalBackgroundSync", "Enabled? $enabled") } ``` You can enable Background Sync through `enableBackgroundSync()`. If the runtime [requires an interactive permission request](#runtime-user-permission-android-12), `enableBackgroundSync()` will launch an Android Intent to request the *"Alarms & Reminders"* permission. The promise result is a boolean, indicating whether or not the Background Sync has been enabled successfully. For example, if the user did not grant the permission during the said Android Intent, the contract returns `false`. Otherwise, `enableBackgroundSync()` returns `true` immediately when no user interaction is required. You can also inspect if Background Sync has been enabled through the `isBackgroundSyncEnabled` property. When running on iOS, both `enableBackgroundSync()` and `isBackgroundSyncEnabled` always return `true` immediately. ```typescript theme={null} import { VitalHealth } from "@tryvital/vital-health-react-native"; const success = await VitalHealth.enableBackgroundSync(); console.log("background sync enabled?", success); const enabled = await VitalHealth.isBackgroundSyncEnabled; console.log("background sync enabled?", enabled); ``` You can enable Background Sync through `enableBackgroundSync()`. If the runtime [requires an interactive permission request](#runtime-user-permission-android-12), `enableBackgroundSync()` will launch an Android Intent to request the *"Alarms & Reminders"* permission. The promise result is a boolean, indicating whether or not the Background Sync has been enabled successfully. For example, if the user did not grant the permission during the said Android Intent, the contract returns `false`. Otherwise, `enableBackgroundSync()` returns `true` immediately when no user interaction is required. You can also inspect if Background Sync has been enabled through the `isBackgroundSyncEnabled` property. When running on iOS, both `enableBackgroundSync()` and `isBackgroundSyncEnabled` always return `true` immediately. ```typescript theme={null} import 'package:vital_health/vital_health.dart' as vital_health; final success = await vital_health.enableBackgroundSync(); log("background sync enabled? ${success}") final enabled = await vital_health.isBackgroundSyncEnabled; log("background sync enabled? ${enabled}") ``` ### Disabling Background Sync You can disable Background Sync through the `disableBackgroundSync()` method. You can also inspect if Background Sync has been disabled through the `isBackgroundSyncEnabled` property. ```kotlin theme={null} import io.tryvital.vitalhealthconnect.VitalHealthConnectManager val manager: VitalHealthConnectManager = ... manager.disableBackgroundSync() val enabled = manager.isBackgroundSyncEnabled Log.i("VitalBackgroundSync", "Enabled? $enabled") ``` You can disable Background Sync through the `disableBackgroundSync()` method. You can also inspect if Background Sync has been disabled through the `isBackgroundSyncEnabled` property. When running on iOS, this API **is a no-op**. HealthKit Background Delivery is always-on on iOS. ```typescript theme={null} import { VitalHealth } from "@tryvital/vital-health-react-native"; await VitalHealth.disableBackgroundSync(); const enabled = await VitalHealth.isBackgroundSyncEnabled; console.log("background sync enabled?", enabled); ``` You can disable Background Sync through the `disableBackgroundSync()` method. You can also inspect if Background Sync has been disabled through the `isBackgroundSyncEnabled` property. When running on iOS, this API **is a no-op**. HealthKit Background Delivery is always-on on iOS. ```dart theme={null} import 'package:vital_health/vital_health.dart' as vital_health; await vital_health.disableBackgroundSync(); final enabled = await vital_health.isBackgroundSyncEnabled; log("background sync enabled? ${enabled}") ``` ### Miscellaneous When you `signOut()` a user, Junction SDK will automatically disable Background Sync. # Apple HealthKit Source: https://docs.junction.com/wearables/guides/apple-healthkit Apple HealthKit is an SDK-based provider. Your iOS consumer app would embed the Junction Mobile SDKs on a supported stack. Data are then pulled from the Apple HealthKit data store on the user's iOS device. Refer to the [Mobile SDK Installation](/wearables/sdks/installation) and [Junction Health SDK](/wearables/sdks/vital-health) guides for SDK installation instructions and general API usage. This guide contains information on the the behaviour and required configuration specific to the Apple HealthKit integration. Learn about the minimum runtime and build-time requirements of Junction Mobile SDKs, and how to add them into your project through your package dependency manager. Learn about the initial setup required by Junction Health SDK, and the API exposed by Junction Health SDK for managing and inspecting health data permissions and automatic data sync. ## Sync Frequency | App state | Behaviour | | ---------- | ------------------------------------------------------------------------- | | Foreground | Apple HealthKit delivers any buffered and new changes immediately. | | Background | Hourly batch delivery of changes, subject to operating system throttling. | While the SDK schedules hourly data sync with Apple HealthKit, iOS has full discretion to defer the scheduled time based on factors like CPU usage, battery usage, connectivity, and Low Power Mode. In other words, the Junction SDK β€” or any third-party HealthKit apps β€” cannot guarantee the sync to happen on a strict schedule. The hourly schedule is advisory and non-binding from the persective of iOS. The actual delivery frequency can therefore fluctuate from hourly, to once a day, or when certain opportunity arises (e.g., when Sleep Mode ends, or when the phone starts charging). ## Getting Started To enable this integration, you would need to integrate [Junction Core SDK](/wearables/sdks/vital-core) and [Junction Health SDK](/wearables/sdks/vital-core) into your Native iOS, React Native or Flutter mobile app. Review the [installation requirements and package installation instructions](/wearables/sdks/installation). Follow the [SDK Authentication](/wearables/sdks/authentication) guidance to integrate your app with the authentication mechanism of Junction Core SDK. Follow the [Junction Health SDK](/wearables/sdks/vital-health) guidance to [configure your App Delegate](/wearables/sdks/vital-health#configure-your-app-delegate-ios-only), [configure the Health SDK](/wearables/sdks/vital-health#sdk-configuration) and to [ask for health data read permissions](/wearables/sdks/vital-health#ask-user-for-health-data-permissions) from your user. Health data sync is activated only on `VitalResource`s which you have asked permissions from your user for. If you did not ask for permissions, data sync would not occur. Follow the [Apple HealthKit Background Delivery guidance](#setting-up-apple-healthkit-background-delivery) below to setup your iOS app target as an eligible target for Apple HealthKit Background Delivery. ## Setting up Apple HealthKit Background Delivery You **MUST** [configure your App Delegate](/wearables/sdks/vital-health#configure-your-app-delegate-ios-only) for Apple HealthKit Background Delivery to function as expected. Junction Health SDK can subscribe to Apple HealthKit Background Delivery, so that data sync can happen automatically in background. The subscription persists even when: 1. Your app user does not open your app regularly; 2. Your app user forces quit your app; or 3. The iPhone has been restarted for any reason. Enabling Apple HealthKit will attract additional scrutiny in the App Store Review process. You should be prepared to explain and demonstrate your usage of these health and fitness data. ### 1. Setup app entitlements Enable the "HealthKit > Background Delivery" entitlement: Enable the "Background Modes > Background Processing entitlement: ### 2. Update your Info.plist In your "Info > Custom iOS Target Properties" section β€” also known as the `Info.plist` file β€” these entries should be configured: | Key | Value | | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Privacy - Health Share Usage Description
(`NSHealthShareUsageDescription`) | An explanation of your usage of the user's HealthKit data. | | Permitted background task scheduler identifiers
(`BGTaskSchedulerPermittedIdentifiers`) | Include `io.tryvital.VitalHealthKit.ProcessingTask` in the array, alongside any other BGTask identifiers of yours. | If you request write permissions, you must also specify: | Key | Value | | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | Privacy - Health Update Usage Description
(`NSHealthUpdateUsageDescription`) | An explanation of what user health data your app is writing to Apple HealthKit. | ### 3. Epilogue You are all set! Note that there is no need to call `syncData()` manually at all. Once you have asked the user for permission on resources, sync would automatically starts every app launch, as well as whenever your app is woken up by Apple HealthKit to respond to newly available data. As per the [documentation](https://developer.apple.com/documentation/healthkit/hkhealthstore/1614175-enablebackgrounddelivery): > HealthKit wakes your app whenever a process saves or deletes samples of the specified type. The system wakes your app at most once per time period defined by the specified frequency. Some sample types have a maximum frequency of `HKUpdateFrequency.hourly`. The system enforces this frequency transparently. > > For example, on iOS, `stepCount` samples have an hourly maximum frequency. This means that although we have background delivery's frequency set to `.hourly`, we cannot guarantee hourly syncing on the dot. # Dexcom (G6 and older) Source: https://docs.junction.com/wearables/guides/dexcom `dexcom` is the provider slug for Dexcom (G6 and older).
For the OAuth-based `dexcom_v3` integration, see [Dexcom (OAuth based)](./dexcom_v3).
This provider is being phased out in favour of the new [OAuth-based Dexcom](./dexcom_v3) integration. Consider migrating to the new API at your earliest convinence. ## Enabling Dexcom Share Your user needs to enable [Dexcom Share](https://www.dexcom.com/faqs/how-do-i-share-my-dexcom-g6-glucose-data-followers) for this connection to work: They **must** use the Dexcom G6 mobile app ([iOS](https://apps.apple.com/us/app/dexcom-g6/id1209262925), [Android](https://play.google.com/store/apps/details?id=com.dexcom.g6)). Note that the Dexcom Clarity app would not work for this integration. Please refer to the [official help article on how to enable Dexcom Share](https://www.dexcom.com/faqs/how-do-i-share-my-dexcom-g6-glucose-data-followers). They **must** have at least one follower\[1] on Dexcom Share to keep the sharing activated. Otherwise, the API would stop reporting data to Junction. \[1] You can setup your own Dexcom account for your user to add as a follower in the Dexcom G6 mobile app. Alternatively, your user can invite `support@junction.com` as a follower. They **must** connect to Junction using their *own* Dexcom account β€” the one which their Dexcom G6 app is signed into. This should **not** be the accounts of the followers. ## Junction Widget To connect your device with the *widget*, follow the link wizard ensuring and enter your username and password. ## Junction Link You can also connect a Dexcom user to vital using vital client. For additional information please consult the API reference. ```python Python theme={null} link_token = client.Link.create(user_id) client.Link.password_provider( link_token, "dexcom", username, password ) ``` ```javascript JavaScript theme={null} const { link_token } = await client.Link.create(user_id); await client.Link.password_provider( link_token, "dexcom", username, password ); ``` ## Webhooks On successful connection, you will receive a [Connection create](/webhooks/data_flow#connection-created) wehbook. [Daily webhooks](/webhooks/data_flow#daily-data-flow) `data.daily.glucose.created` and `data.daily.glucose.updated` will also be issued to your team as new or more up-to-date data becomes available during the day. For this provider however, Junction will begin to aggregate the data for the last 24 hours and will not go beyond that. As a result, there will be no [Historical webhooks](/webhooks/data_flow#historical-data-flow) issued. If you would like to pull the data immediately after connection, you can use the `user/refresh` endpoint. # Dexcom (OAuth based) Source: https://docs.junction.com/wearables/guides/dexcom_v3 `dexcom_v3` is the provider slug for Dexcom (OAuth based).
For the password-based `dexcom` integration, see [Dexcom (G6 and older)](./dexcom).
Bring Your Own OAuth (BYOO) is available for [the Grow and Scale plans](https://tryvital.io/pricing). WHOOP BYOO is available for [the Launch, Grow and Scale plans](https://tryvital.io/pricing). See [our WHOOP Guide](/wearables/guides/whoop) for more information. ## Connecting Dexcom to Junction You can connect your dexcom device to vital through Junction Link Widget or programatically like any other [OAuth](/api-reference/link/link-oauth-provider) provider. We recommend you obtain custom OAuth credentials from [Dexcom](https://developer.dexcom.com) with production access and follow the guide for [setting up custom credentials](/wearables/connecting-providers/bring_your_own_oauth) to get started. ## Webhooks On successful connection, you will receive a [Connection create](/event-catalog/provider.connection.created) webhook, and subsequently, [Daily](/event-catalog/daily.data.glucose.created) and [Historical](/event-catalog/historical.data.glucose.created) Glucose webhooks. The histrical webhook informs you that the last **30 days** of data prior to connection is available and the daily webhooks follow the usual semantics for all vital daily webhooks. # Fitbit Source: https://docs.junction.com/wearables/guides/fitbit ## Webhooks There is one caveat with Fitbit's webhooks. Namely, Fitbit computes the basal metabolic rate (BMR) server-side. This is a fairly static value because it depends on data such as height, weight, gender, age, etc. The fact this is computed value server-side is important to keep in mind, because it means you will always receive `daily.data.activity.updated` webhooks even if you are not wearing the device. This can be confusing at times, as someone who is not aware of this might wonder why are they getting only this one webhook and not the others. If you are only getting `daily.data.activity.updated`, the most likely case is that the user is neither wearing the device, or they did not sync their device with the Fitbit app. If you are sure this is not the case, contact us through our support channels. ## Respiratory Rate timeseries Fitbit does not provide detailed breakdown beyond average breaths-per-minute summary on a per sleep stage basis, i.e., REM average, Deep average, Light average and the whole session average. Given this constraint, Junction constructs a respiratory rate timeseries for Fitbit as follows: * Junction creates a respiratory rate data point for each hypnogram segment reported. * The data point value will be one of the four average values mentioned, based on the stage represented by the segment. * The data point will be timestamped to the **midnpoint** of the segment. ## Time Zone resolution Fitbit stores all their data in floating time. Moreover, They do not tag most of their data points or summaries with time zone information. Since Junction uses [UTC time basis](/wearables/providers/timestamps-and-time-zones), Junction has to convert the floating time basis to the UTC time basis, before we can ingest the data and forward it onwards to you. To facilitate this conversion, we must make an educated guess on the user's time zone. Junction makes the best time zone guess in the following precedences: If the user has granted the Profile scope, Junction uses the timezone offset as provided by their Fitbit Profile. If the user has granted the Activity scope, Junction uses the timezone offset from the most recently recorded activity (if any). If neither Fitbit data sources are available, Junction would use [User Fallback Time Zone](/wearables/providers/timestamps-and-time-zones#user-fallback-time-zone) if it has been set. If none of above is available, Junction assumes naively that everything happens in UTC (+00:00). This is usually incorrect, except for users living an all-year UTC (+00:00) region. When your user refuses to grant both the activity and the profile scope, Fitbit data are more likely be timestamped incorrectly in UTC time basis due to inaccurate or unavailable time zone information. # Garmin Source: https://docs.junction.com/wearables/guides/garmin ## Webhooks & Events Garmin connections have a non-standard [webhook lifecycle](/webhooks/introduction): You will receive a [Provider Connection Created event](/webhooks/introduction#connection-created-stage) normally. You would receive a [Historical Pull Completion event](/webhooks/introduction#historical-data-backfill-stage) **immediately** after the connection creation. However, if you query Junction API immediately, you would observe **no historical data**. The Historical Pull Completion event for Garmin connections does not indicate availability of historical data through the Junction API. All **historical and new data** are delivered incrementally as [Data Events](/webhooks/introduction#incremental-data-stage) (`daily.data.*.*`). This behaviour is exclusive to Garmin, as a downstream effect of Garmin's unique historical data access mechanism for third-party integrations. ## Re-running the historical data backfill stage Normally, when a user with an existing connection re-authenticates with the provider through [Junction Link](https://docs.tryvital.io/wearables/connecting-providers/introduction), Junction re-runs the [Historical Data Backfill stage](/webhooks/introduction#historical-data-backfill-stage) automatically. However, this **does not apply to Garmin** connections β€” you must first [deregister](/api-reference/user/deregister-a-provider) said users' existing Garmin connection for the re-run to take effect. This behaviour is exclusive to Garmin, as a downstream effect of Garmin's unique historical data access mechanism for third-party integrations. ## Junction Widget To connect your device with the *widget*, follow the link wizard ensuring and enter your username and password.
# Google Fit Source: https://docs.junction.com/wearables/guides/google_fit ## Delays in Syncing Data ### Intro Google Fit via Cloud can have significant delay in data availability, more specifically between Google's Google Fit apps & Google's cloud data store - neither of which Junction has any control over. This is unlike using the Google Fit Android SDK directly, which can serve the latest local data immediately from the Google Fit data store, regardless of whether said local data have been uploaded to the Cloud already. Junction isn't certain how/when Google Fit decides to upload new local data to cloud, and also whether or not there is additional delay caused by Google's internal data ingestion pipelines in the cloud. But one thing for sure is that it cannot be forced, and it seems to be managed by Android's standard WorkManager framework. This means connectivity, battery usage, Doze mode and App Standby will all dynamically affect the actual upload frequency. At worst case, this can mean uploads only happen when one is sleeping (the typical favourable time for the OS to run background work β€” the phone is on charger, has been idle extensively, and probably on WiFi). ### Why not use Google Fit Android SDK directly? One way to avoid data delay is to use the Google Fit native SDK directly. However, Google Fit Android API has been deprecated by Google since May 2022 and support is set to end by end of 2024. For this reason we advise on using its successor: [HealthConnect](https://developer.android.com/guide/health-and-fitness/health-connect). You can find our docs [here](/wearables/sdks/android). Keep in mind that HealthConnect has its own set of quirks. The most notable one is that Google decided to ban any data access when apps are in background. So it is unlikely to have the same smooth background monitoring experience like Apple HealthKit out of the box. # Omron Source: https://docs.junction.com/wearables/guides/omron ## Permissions with Omron Omron's API does not provide information on what permissions users granted when connecting. Additionally it gives the same error for unauthorized resources and when a user uses one account in multiple environments, so we can't tell these apart. Because of this, we only accept Omron connections with all permissions granted. # Polar Source: https://docs.junction.com/wearables/guides/polar ## Historical backfill with Polar Polar unlike most other providers doesn't offer historical data for most of their resources. The only resources that do have it are Sleep and Sleep Stream - 28 days each. # WHOOP Source: https://docs.junction.com/wearables/guides/whoop WHOOP [Bring Your Own OAuth](/wearables/connecting-providers/bring_your_own_oauth) is available for [the Launch, Grow and Scale plans](https://tryvital.io/pricing). ## Connecting WHOOP to Junction WHOOP is a [BYOO](/wearables/connecting-providers/bring_your_own_oauth) only provider, so you will need your own app in [WHOOP's developer dashboard](https://developer-dashboard.WHOOP.com/). Then, you can configure the WHOOP integration in the Junction Dashboard by navigating to the Config > Custom Credentials page and clicking "Setup" next to WHOOP V2. WHOOP V2 config ## Limitations WHOOP sets forth the following limitation: > Apps can be used for development immediately with a limit of 10 WHOOP members. To launch your app to all WHOOP members, you must first submit your app for approval. Learn more about app approval in the [WHOOP developer documentation](https://developer.whoop.com/docs/developing/app-approval/). WHOOP reviews apps on a monthly cadence. # Data Attributions Source: https://docs.junction.com/wearables/providers/data-attributions ## Source Type For summary data types, you can identify the Source Type through `source.type`. This is available in both summary data events and API responses. For timeseries data types: * You can identify the Source Type through `$.data.source.type` in timeseries data events you received. * You can request timeseries data grouped by their Source Type through [the Grouped Timeseries Data API](/api-reference/timeseries-grouped/steps). ### Common | Source Type | Description | | ------------------ | ------------------------------------------------------------------------------ | | `unknown` | The default value. Junction does not know how the provider collects this data. | | `app` | The user manually enters this data through an app. | | `multiple_sources` | This data is derived from multiple sources. | ### Wearables | Source Type | Description | | ------------- | ----------------------------------------------------- | | `watch` | A smart watch collects this data automatically. | | `phone` | A smart phone collects this data automatically. | | `ring` | A smart ring collects this data automatically. | | `chest_strap` | A smart chest strap collects this data automatically. | ### Health devices | Source Type | Description | | ------------- | ------------------------------------------------------------------ | | `manual_scan` | A biosensor that needs to be manually scanned. | | `automatic` | A biosensor that uploads data continuously in background. | | `fingerprick` | A glucose testing device which analyzes fingerprick blood samples. | | `cuff` | A blood pressure cuff. | ### Supported providers The following provider integrations would tag data with Source Type: | Provider | Source Types | | --------------- | ----------------------------------------------------------- | | Apple HealthKit | `phone`, `watch`, `app`, `multiple_sources`, `unknown` \[1] | | Fitbit | `watch`, `scale`, `app`, `multiple_sources` | | Oura | `ring`, `app`, `multiple_sources` | | Garmin | `watch`, `scale`, `cuff`, `app`, `multiple_sources` | | Freestyle Libre | `automatic`, `manual_scan` | \[1] Only data from Apple Watch, iPhone and Apple Health app are tagged. Data from third-party apps (like the Oura iOS app) are tagged as `unknown` currently. ## Device ID Device ID indicates the specific device that collected the data, when this information is available with certainty. For both summary and timeseries data types, it is available at `source.device_id`. When the originating device cannot be determined, this field is `null`. ### Device Attribution Coverage Device attribution is available when Junction can confidently identify the originating device: | Provider | Coverage | Notes | | --------------------------------- | --------------------------------------------------- | -------------------------------------------------------------------- | | Withings | All resources except Body, Body Fat and Body Weight | Regardless of the number of reported devices | | Polar | Workout and Sleep resources | Regardless of the number of reported devices | | All other providers and resources | All resources | When exactly one device is reported for the user-provider connection | For connections with multiple devices where the provider doesn't embed device metadata, `source.device_id` remains `null` to avoid incorrect attribution. ### Supported providers The following provider integrations can provide Device ID attribution: | Provider | | ---------------------------------- | | Withings | | Polar | | Fitbit | | Oura | | Garmin | | Renpho | | Omron | | Peloton | | Dexcom v3 | | EightSleep | | Freestyle Libre / Abbott Libreview | | Apple HealthKit | | Android Health Connect | ## App ID App ID indicates the origin application of the data. For summary data types, it is available at `source.app_id`. Note that App ID is unavailable on timeseries data at this time. ### Supported providers The following provider integrations would tag data with App ID: | Provider | Remarks | | ---------------------- | ----------------------------------------------------------------------------------------- | | Apple HealthKit | App Store Bundle ID, or Unique Device ID (`com.apple.health.{UUID}`) for first-party data | | Android Health Connect | Android Application Package Name | ## Workout ID If a Workout ID is present, the data are associated with the referenced Junction [Workout](/api-reference/data/workouts/get-summary) summary. Workout ID is only available in the following Workout Stream timeseries resources (`workout_*`): | Resource | Remarks | | ------------------------- | ------------------------------------------------------ | | `workout_distance` | All data must have an associated Workout ID and Sport. | | `workout_swimming_stroke` | All data must have an associated Workout ID and Sport. | ### Supported providers The following provider integrations would tag data with Workout ID: | Provider | | --------------- | | Apple HealthKit | ## Sport If a sport slug is present, the data are known to be measuring the referenced Sport type. Sport slug is only available in the following timeseries resources: | Resource | Remarks | | ------------------------- | ------------------------------------------------------------------------------------------------------------ | | `workout_distance` | All data must have an associated Workout ID and Sport. | | `workout_swimming_stroke` | All data must have an associated Workout ID and Sport. | | `distance` | All-day wheelchair distance data are tagged with `sport = wheelchair_push`. Otherwise, sport is unspecified. | ### Supported providers The following provider integrations would tag data with Sport: | Provider | | --------------- | | Apple HealthKit | # Data Ingestion Bounds Source: https://docs.junction.com/wearables/providers/data-ingestion-bounds ## Overview By default, Junction: * pulls historical data as far back as indicated by the [pull preferences](/wearables/providers/introduction#customizing-historical-data-pull-range) you specified (if any), or the [default historical pull range](/wearables/providers/introduction#historical-data-pull-range). * accepts **all** new and changed data discovered through polling, incoming provider webhooks and Junction Mobile SDK integrations. If you need to impose a date bounds on the data being collected, you can set **data ingestion bounds** on a per-user basis: * The data ingestion bounds restricts the historical pull range of any new connections of the user. * The data ingestion bounds restricts data ingestion from any connections of the user β€” out of bound data are dropped. * When the user ingestion end date is reached, a 7-day late delivery catchment period starts. Once the catchment period expires, all connections of the user are automatically de-registered and marked as *paused*. Because *dates* are in floating time, you should expect an error margin of Β±1 days on ingestion bound enforcement. If you cannot tolerate an error margin, offset the user data ingestion bounds you set accordingly. ## Setting Ingestion Start and End Dates You can specify the user data ingestion bounds (`ingestion_start` and `ingestion_end`) when you: * [Create a new user](/api-reference/user/create-user); or * [Update an existing user](/api-reference/user/patch-user) ```json Example theme={null} { "user_id": "409d9870-21fb-443a-8daa-c5222659f40e", "team_id": "3aac677c-557f-40b7-9251-0315c1f48e77", "client_user_id": "d734e32e-dd43-4b77-ab56-692524279531", /** snipped **/ "ingestion_start": "2024-01-01", "ingestion_end": "2024-12-15", } ``` ## Changing Ingestion Bounds You can change the ingestion bounds at any time. However, do note that it has no retrospective effect on data already ingested β€” Junction does not delete collected data in response to ingestion bound changes. As long as the connection has not been marked as *paused*, you can change the ingestion end date to extend the ingestion bounds at any time, even during the (post ingestion end) 7-day late delivery catchment period. Otherwise, the ingestion end date has no effect on any *paused* cloud-based connections β€” since these connections have been deregistered with the data provider, the end user needs to go through the Junction Link flow to re-connect. # Primary Key Source: https://docs.junction.com/wearables/providers/data-primary-key ## Summary types Each summary is uniquely identified by its ID (`$.id`). Given the same ID, the latest version of a summary you received **replaces** all its previous versions. ## Timeseries types Each timeseries sample is uniquely identified by a compound key of: * the timeseries resource type; * the source provider; * the [Source Type](/wearables/providers/data-attributions#source-type); and * the sample timestamp. Given the same compound key, the latest value of a sample you received **replaces** all the previous values. # Heart Rate Zones Source: https://docs.junction.com/wearables/providers/heart-rate-zones ## Heart Rate Zones Calculation Heart rate zones are segments in a workout session. They are based on the percentage brackets of the user's maximum heart rate. \[under 50%, 50-60%, 60-70%, 70-80%, 80-90%, above 90%] The maximum heart rate for a person is calculated with a straightforward formula: `220 - age`. ## Fallback Birth Date Some providers may not share the user's birth date, making it impossible for Junction to know the user's age. To solve this, users can provide a Fallback Birth Date as an alternative option. You can specify the Fallback Birth Date when: * [Creating a new user](/api-reference/user/create-user); or * [Patching an existing user](/api-reference/user/patch-user) You will also get information about the source (by slug) and the last updated time of the Fallback Birth Date [when getting an existing user](/api-reference/user/get-user). Fallback Birth Date supplied via the REST API would always have a source slug of `manual`. If no birth date information is available, the assumed age of 30 is used in the calculations. ```json Example theme={null} { "user_id": "409d9870-21fb-443a-8daa-c5222659f40e", "team_id": "3aac677c-557f-40b7-9251-0315c1f48e77", "client_user_id": "d734e32e-dd43-4b77-ab56-692524279531", /** snipped **/ "fallback_birth_date": { "value": "1980-09-12", "source_slug": "manual", "updated_at": "2022-09-11T13:45:56+00:00" } } ``` # Historical Data Pull Range Source: https://docs.junction.com/wearables/providers/historical-data-pull-range Junction fetches the historical data immediately when the connection is established. The pull range can be estimated by `[now - days_to_pull, now]`. | Provider | Default | Configurable | Remarks | | ---------------------------------------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Abbott LibreView](https://www.libreview.com) | 90 days | ❌ | - | | [Fitbit](https://www.fitbit.com/global/uk/home) | 90 days | ⚠️ | Activity and heartrate timeseries data are fixed to 14 days. | | [Garmin](https://www.garmin.com) | 90 days | βœ… | - | | [Google Fit](https://www.google.com/fit/) | 90 days | βœ… | - | | [Oura](https://ouraring.com) | 180 days | βœ… | - | | [Peloton](https://www.onepeloton.com) | 180 days | βœ… | - | | [Strava](https://www.strava.com) | 14 days | βœ… | - | | [Wahoo](https://wahoofitness.com) | 180 days | βœ… | - | | [WHOOP](https://www.whoop.com) | 180 days | βœ… | - | | [Zwift](https://zwift.com) | 270 days | βœ… | - | | [Withings](https://www.withings.com) | 90 days | βœ… | - | | [8Sleep](https://www.eightsleep.com) | 90 days | βœ… | - | | [Apple HealthKit](https://www.apple.com/uk/ios/health/) (SDK) | 30 days | βœ… | - | | [Android Health Connect](https://developer.android.com/health-connect) (SDK) | 30 days | ❌ | Google restricts access to historical data to [30 days before the first successful permission request](https://developer.android.com/health-and-fitness/guides/health-connect/develop/frequently-asked-questions#time-range). | | [Hammerhead](https://www.hammerhead.io) | 30 days | βœ… | - | | [Dexcom](https://www.dexcom.com) | 30 days | βœ… | - | | [Dexcom (G6 And Older)](https://www.dexcom.com) | 1 day | ❌ | - | | [MyFitnessPal](https://www.myfitnesspal.com) | 14 days | βœ… | - | | [Polar](https://www.polar.com/accesslink-api/#polar-accesslink-api) | 28 days | ❌ | Polar only supports historical backfill for Sleep and Sleep Stream resources. | | [Cronometer](https://www.cronometer.com) | 28 days | βœ… | - | The configurable maximum is 365 days at this time. ### Customizing historical data pull range Org Management API is available for [the Scale plan](https://tryvital.io/pricing). You can configure the historical pull range using the [Team Data Pull Preferences](/api-reference/org-management/team-data-pull-preferences/upsert-team-data-pull-preferences) feature of the Org Management API. You can specify a number of days to backfill that uniformly applies to all resources of a provider, as well as overriding the the number of days to backfill for specific resources. The Team Data Pull Preferences you specified is *advisory*. There are scenarios in which Junction systems may not adhere strictly to your stated preferences. # Supported Providers Source: https://docs.junction.com/wearables/providers/introduction We currently Support over 300+ devices, a full list can be seen below. ## Cloud Based Providers ### OAuth Providers Bring Your Own OAuth (BYOO) is available for [the Grow and Scale plans](https://tryvital.io/pricing). WHOOP BYOO is available for [the Launch, Grow and Scale plans](https://tryvital.io/pricing). See [our WHOOP Guide](/wearables/guides/whoop) for more information. These data provider connections would use **Junction-branded** OAuth application by default. Optionally, you can *Bring Your Own OAuth* β€” existing or newly applied API applications β€” to your Junction Team: 1. The data provider OAuth authentication flow would use your branding; and 2. Connections of your own OAuth application would be rate limited by the data provider independently from Junction's default application. | Provider (Slug) | Junction default app | Developer Program for BYOO | Remarks | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Fitbit](https://www.fitbit.com/global/uk/home)
`fitbit`
Activity Trackers (all devices)

[↗️ Fitbit Guide](/wearables/guides/fitbit) | 🟒
Enabled | 🟒
Self Sign-Up | You can [create your own Fitbit API Application](https://dev.fitbit.com/build/reference/web-api/developer-guide/getting-started/). Note that for access to Intraday (timeseries) data, you must [submit an application to Fitbit](https://dev.fitbit.com/build/reference/web-api/intraday/). | | [Garmin](https://www.garmin.com)
`garmin`
Fitness watches (all devices)

[↗️ Garmin Guide](/wearables/guides/garmin) | 🟒
Enabled | ⚠️
Application required | You must [apply for Garmin Developer Program](https://developer.garmin.com/gc-developer-program/program-faq/). | | [Strava](https://www.strava.com)
`strava`
Running & Cycling Social Network | 🟑
Congested | ⚠️
Application required | You can self-create a Strava API Application for testing. To connect more than 1 athelete, you must [submit your app to Strava for review](https://developers.strava.com/docs/getting-started/). | | [Oura](https://ouraring.com)
`oura`
Smart Sleep tracking ring | 🟒
Enabled | ⚠️
Application required | You can self-create an Oura API Application for testing. To connect more than 10 Oura users, you must [submit your app to Oura for review](https://cloud.ouraring.com/docs/). | | [Wahoo](https://wahoofitness.com)
`wahoo`
Cycling Equipment | 🟒
Enabled | ⚠️
Application required | You must [apply for Wahoo Cloud API access](https://developers.wahooligan.com/cloud). | | [Withings](https://www.withings.com)
`withings`
Fitness scales, watches and health monitors | 🟑
Congested | 🟒
Self Sign-Up | You can [create your own Withings API Application](https://developer.withings.com/developer-guide/v3/withings-solutions/withings-api-plans). | | [Polar](https://www.polar.com/accesslink-api/)
`polar`
Finnish sports tech pioneer

[↗️ Polar Guide](/wearables/guides/polar) | 🟒
Enabled | 🟒
Self Sign-Up | You can [create your own Polar API Application](https://www.polar.com/accesslink-api/#introduction). | | [Cronometer](https://www.cronometer.com)
`cronometer`
Nutrition data | 🟒
Enabled | ⚠️
Application required | You must [apply to be a Cronometer API partner](https://cronometer.com/about/). | | [Ultrahuman](https://www.ultrahuman.com)
`ultrahuman`
Real-time nutrition and fitness tracking | 🟒
Enabled | ⚠️
Application required | You must [apply to be a Ultrahuman API partner](https://blog.ultrahuman.com/blog/accessing-the-ultrahuman-partnership-api/). | | [Dexcom](https://www.dexcom.com)
`dexcom_v3`
Dexcom CGM Glucose monitors

[↗️ Dexcom Guide](/wearables/guides/dexcom_v3) | βž–
Unavailable | ⚠️
Application required | You can self-create a Dexcom Sandbox API Application for testing. To connect Dexcom production users, you must [apply for the Dexcom Digital Health Partner status](https://developer.dexcom.com/docs/dexcom/scopes-access). | | [WHOOP](https://www.whoop.com)
`whoop_v2`
Your Personal Digital Fitness and Health Coach

[↗️ WHOOP Guide](/wearables/guides/whoop) | βž–
Unavailable | ⚠️
Application required | You can self-create a Whoop API Application for testing. To connect more than 10 WHOOP members, you must [submit your app to WHOOP for review](https://developer.whoop.com/docs/developing/app-approval). | | [MyFitnessPal API](https://www.myfitnesspalapi.com)
`my_fitness_pal_v2`
Nutrition data | βž–
Unavailable | βž–
Application closed | N/A | | [MapMyFitness](https://developer.mapmyfitness.com)
`map_my_fitness`
Technology for all atheletes | βž–
Unavailable | βž–
Application closed | N/A | ### Non-OAuth Providers | Provider (Slug) | Description | Remarks | | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | -------------------------- | | [Beurer](https://www.beurer.com/web/gb/)
`beurer_api` | Beurer Blood Pressure monitors | Password Auth | | [Dexcom (G6 And Older)](https://www.dexcom.com)
`dexcom`

[↗️ Dexcom Guide](/wearables/guides/dexcom) | Dexcom CGM Glucose monitors | Password Auth | | [8Sleep](https://www.eightsleep.com)
`eight_sleep` | Smart Mattress | Password Auth | | [Abbott LibreView](https://www.libreview.com/)
`abbott_libreview`

[↗️ LibreView Guide](/wearables/guides/abbott-libreview) | Abbott CGM Glucose monitor | Password Auth | | [Freestyle Libre](https://www.libreview.com/)
`freestyle_libre`

[↗️ LibreView Guide](/wearables/guides/abbott-libreview) | Abbott CGM Glucose monitor | Practice-based Connections | | [Hammerhead](https://www.hammerhead.io)
`hammerhead` | Cycling computers | Password Auth | | [Peloton](https://www.onepeloton.com)
`peloton` | Popular Indoor Exercise bike | Password Auth | | [Zwift](https://zwift.com)
`zwift` | Virtual cycling and running | Password Auth | ### Deprecated providers | Provider (Slug) | Status | Availability | Remarks | | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------ | | [Google Fit](https://www.google.com/fit/)
`google_fit` | Google will shut down Google Fit sometime in 2026. Android Health Connect is the successor. | All Junction Teams | OAuth
[↗️ Guide](/wearables/guides/google_fit) | | [MyFitnessPal](https://www.myfitnesspal.com)
`my_fitness_pal` | Deprecated | All Junction Teams | MyFitnessPal Diary Share Key
[↗️ Guide](/wearables/guides/my_fitness_pal) | ## SDK Based Providers | Provider (Slug) | Description | Guide | | ------------------------------------------------------------------------------------------------ | ------------------------------------------------- | ---------------------------------------------------- | | [Accu-Chek](https://www.accu-chek.com) (Bluetooth)
`accuchek_ble` | Glucose Strips/MySugr App | | | [Apple HealthKit](https://www.apple.com/uk/ios/health/)
`apple_health_kit` | Health and fitness data on iPhone and Apple Watch | [↗️ Guide](/wearables/guides/apple-healthkit) | | [Beurer](https://www.beurer.com/web/gb/) (Bluetooth)
`beurer_ble` | Beurer Blood Pressure monitors | | | [Contour](https://www.diabetes.ascensia.com) (Bluetooth)
`contour_ble` | Glucometers | | | [Freestyle Libre BLE](https://www.freestylelibre.co.uk/libre/) (NFC)
`freestyle_libre_ble` | Abbott CGM Glucose monitor readings via SDK | | | [Omron](https://www.omron-healthcare.com) (Bluetooth)
`omron_ble` | Blood Pressure monitors and scales | | | [Android Health Connect](https://developer.android.com/health-connect)
`health_connect` | Health and fitness data on Android devices | [↗️ Guide](/wearables/guides/android-health-connect) | ## Providers under beta **Beta**: Providers under beta, these are providers that have been recently added. All providers here are available in sandbox, any feedback you have is greatly appreciated! | Provider | Slug | Description | | ------------------------------------------------- | -------- | ---------------------------------- | | [Omron](https://www.omron-healthcare.com) (Cloud) | `omron` | Blood Pressure monitors and scales | | [Kardia](https://kardia.com/) | `kardia` | Electrocardiogram readings | **Planned**: On our roadmap | Provider | Slug | Description | Stage | | ----------------------------------------------- | ---------- | ------------------------- | ------- | | [Xiaomi](https://www.xiaomi.com) | `xiaomi` | All data | Enquire | | [Suunto](https://www.suunto.com) | `suunto` | Fitness Watch | Planned | | [iGlucose](https://smartmeterrpm.com/iglucose/) | `iglucose` | Glucose Strips | Planned | | [KetoMojo](https://keto-mojo.com) | TBD | Glucose, Ketones and more | Planned | ## Full Device Support List # Resources Source: https://docs.junction.com/wearables/providers/resources ## Overview There are two broad resource categories provided by Junction: * A **Summary** type is a summarization of a specific calendar day or a session. * A **Timeseries** type is a time-ordered collection of data samples. There is no relation in resource availability among these two categories. Some providers do offer both, while some providers offer only one category. For example: * Fitbit provides daily activity summaries, as well as **some** high-frequency activity timeseries data. * Oura provides only daily activity summary, without any complementary high-frequency activity timeseries data. There is also no strict relation between a field in a *Summary* type and a *Timeseries* type. For example: * The existence of a `sleep_efficiency` summary field does not imply that there is a corresponding `sleep_efficiency` timeseries. * The existence of a Glucose Timeseries type does not imply that there is a corresponding `glucose` summary field. * A *Timeseries* type could derive multiple summary fields, e.g., heart rate statistics and HR zones. ### Summary types | Granularity | Resources | | ----------- | ------------------------------------------------------------------------------------------------------------ | | Daily | `activity` | | Session | `sleep`, `sleep_cycle`, `workouts`, `body`, `meal`, `menstrual_cycle`, `electrocardiogram`, `workout_stream` | | Single | `profile` | ### Discrete Timeseries types Each value is associated with a single point in time. | Category | Resources | | ----------------- | -------------------------------------------------------------------------------------------- | | Cardiorespiratory | `respiratory_rate` | | Vitals | `blood_oxygen`, `blood_pressure`, `glucose`, `heartrate`, `hrv`, `electrocardiogram_voltage` | | Wellness | `stress_level` | | Body | `fat`, `weight` | ### Interval Timeseries types Each value is associated with a half-open time interval (`[start, end[`). | Category | Resources | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Activity | `calories_active`, `calories_basal`, `distance`, `floors_climbed`, `steps`, `workout_duration`, `fall`, `wheelchair_push`, `stand_duration`, `stand_hour` | | Body | `body_temperature`, `body_temperature_delta`, `insulin_injection`, `waist_circumference`, `body_mass_index`, `lean_body_mass`, `basal_body_temperature` | | Vitals | `afib_burden`, `heart_rate_alert`, `forced_expiratory_volume_1`, `forced_vital_capacity`, `peak_expiratory_flow_rate`, `inhaler_usage`, `heart_rate_recovery_one_minute` | | Cardiorespiratory | `vo2_max` | | Nutrition | `carbohydrates`, `caffeine`, `water` | | Wellness | `mindfulness_minutes`, `sleep_apnea_alert`, `sleep_breathing_disturbance`, `uv_exposure`, `daylight_exposure`, `handwashing` | | Workout Stream | `workout_distance`, `workout_swimming_stroke` | | Diary | `note` | ## Timeseries Data Sampling Rates Junction records and forwards timeseries data from the data provider, without any upsampling or downsampling applied. Even within the same data provider, these data may come from different devices or data sources with varying sampling rates. Because of this, you should not make assumptions of any particular timeseries resource having a fixed sampling rate. For technical design and capacity planning purposes, you may model after the following three categories of timeseries resources: ### Very High Frequency Timeseries Second-level peak sampling rate; typically 1000s to low 10000s samples per day. | Peak rate | Example Scenario | | -------------------------- | -------------------- | | 1 sample every 3 seconds | Apple Watch workouts | | 1 sample every 15 seconds | Garmin | | 1 sample every 1-15 minute | Apple Watch, Fitbit | Resources that fall into this category: | Resource | Events | Notable Providers | | ----------------- | ------------------------------ | --------------------- | | `heartrate` | `daily.data.heartrate.*` | Apple, Fitbit, Garmin | | `calories_active` | `daily.data.calories_active.*` | Apple, Fitbit, Garmin | ### High frequency Timeseries Minute-level peak sampling rate; typically 100s samples per day. | Peak rate | Example Scenario | | ------------------------- | -------------------------------------------------- | | 1 sample every 1 minute | Fitbit Intraday timeseries data | | 1 sample every 15 minutes | Garmin all-day activity timeseries data ("epochs") | Resources that fall into this category: | Resource | Events | Notable Providers | | ------------------- | -------------------------------- | --------------------- | | `steps` | `daily.data.steps.*` | Apple, Fitbit, Garmin | | `calories_basal` | `daily.data.calories_basal.*` | Apple | | `distance` | `daily.data.distance.*` | Apple, Fitbit, Garmin | | `glucose` | `daily.data.glucose.*` | LibreView, Dexcom | | `stress_level` | `daily.data.stress_level.*` | Garmin | | `hrv` | `daily.data.hrv.*` | Garmin, Fitbit | | `respiratory_rate` | `daily.data.respiratory_rate.*` | Garmin, Fitbit | | `blood_oxygen` | `daily.data.blood_oxygen.*` | Fitbit | | `daylight_exposure` | `daily.data.daylight_exposure.*` | Apple | | `stand_duration` | `daily.data.stand_duration.*` | Apple | ### Sparse Timeseries All timeseries resources not specified as (Very) High Frequency fall into this category. Day-level peak sampling rate; typically no more than 5 per day. | Peak rate | Example Scenario | | ------------------------- | -------------------------------------------------- | | 1 sample every day | Apple Watch wrist temperature delta | | 1 sample every 15 minutes | Garmin all-day activity timeseries data ("epochs") | | Unpredictable / Never | Heart Rate Alerts, Sleep Apnea Alerts | ## Summary types by providers ## Timeseries types by providers ## Fetching via the API Summary resources can be fetched from our `/summary//` API, while everything else via `/timeseries//`. For example, `Workouts` is made up of fields specific to an workout, like calories, distance and duration. When you make a request to `/summary/workouts/` you get the following fields: ```json theme={null} { "workouts": [ { "user_id": "a6fea8eb-402f-4578-96ba-189df1a8ca75", "user_key": "a6fea8eb-402f-4578-96ba-189df1a8ca75", "id": "12398c74-ea41-4d9c-a1b7-8c529ee67e46", "title": null, "timezone_offset": 3600, "average_hr": 147, "max_hr": 178, "distance": 31557.85, "time_start": "2022-07-29T12:05:49+00:00", "time_end": "2022-07-29T13:26:08+00:00", "calories": 729.0, "sport": { "id": 108, "name": "Road Biking", "slug": "road_biking" }, "hr_zones": [ 5, 76, 288, 1467, 2148, 835 ], "moving_time": 4819, "total_elevation_gain": 374.0, "elev_high": null, "elev_low": null, "average_speed": 6.548, "max_speed": 17.448, "average_watts": null, "device_watts": null, "max_watts": null, "weighted_average_watts": null, "map": null, "provider_id": "9297103021", "source": { "name": "Garmin", "slug": "garmin", "logo": "https://storage.googleapis.com/vital-assets/garmin.png" } } } ``` `Heartrate` on the other hand, is a timeseries resources. You can fetch it via `timeseries//heartrate` and looks like this: ```json theme={null} [ { "timestamp": "2022-04-29T08:35:57+00:00", "timezone_offset": 3600, "value": 174.0, "type": null, "unit": "bpm" }, { "timestamp": "2022-04-30T11:22:38+00:00", "timezone_offset": 3600, "value": 79.0, "type": null, "unit": "bpm" }, { "timestamp": "2022-04-30T11:22:39+00:00", "timezone_offset": 3600, "value": 80.0, "type": null, "unit": "bpm" }, { "timestamp": "2022-04-30T11:22:40+00:00", "timezone_offset": 3600, "value": 78.0, "type": null, "unit": "bpm" }, { "timestamp": "2022-04-30T11:22:41+00:00", "timezone_offset": 3600, "value": 78.0, "type": null, "unit": "bpm" } ] ``` Different providers (e.g. Garmin), have different resources available. As an example, Garmin doesn't have `Glucose` as a resource. ## Fields by Provider Resources alone are not the whole story. Depending on the provider, a field might not be available. For example both Garmin and Strava provide `Workouts`, yet the former doesn't have `ele_high` nor `ele_low`. Providers mark with `*` means that Junction calculates the field's value. In some cases, they might came as `null`, since we weren't able to make the calculation. Even if a field is available for a provider, it doesn't mean it's available. For example, Fitbit is capable of generating HRV data. However HRV data is only available for specific high-end devices. Another common scenario is Apple HealthKit data. If the user doesn't give permission to a particular field, it won't be available. **Be mindful of these situations and similar ones**. ### Activity ### Body ### Sleep ### Workout # Synthetic Data (Sandbox) Source: https://docs.junction.com/wearables/providers/test_data Using demo users to build and test your Junction integration This feature is currently available for the following providers: * Apple Health Kit * Fitbit * Oura * Freestyle Libre The others will be coming soon. If there is a particular provider you would like us to add please do not hesitate to contact us at [support@junction.com](mailto:support@junction.com)! When integrating with Junction, you may not own a physical device for each provider you're planning to support. For this, we provide demo users with test data so you can build and test the whole integration without touching a real device. A demo user gives you the same experience as using a real one, the only difference is that you won't go through the [Junction Link](/wearables/vital-link/introduction) flow. Demo users have certain limitations to keep in mind: * Only available in sandbox. * A demo user cannot have connections to a real device. If you already have a user with real data, you need to create a new one to connect a demo account. * There is no limitation on the number of demo users you can create, but they do count towards the overall users limit in sandbox. Up to date limits can be found [on this page](/home/environments). * Demo users expire after 7 days, meaning the user and its data will be deleted. ## Usage First, you need a Junction user. You can create one either through the [dashboard](https://app.junction.com) or through the [API](/api-reference/user/create-user). After creating the user, the `Users` page of your dashboard will look like this: Now you can create a demo connection for the Junction user you just created, in this case `150db84c-537c-4cad-a6e9-24dc589d7fa2`. You can directly hit the [API endpoint](/api-reference/link/link-demo-provider) or using one of our client libraries, as shown below: ```bash Creating a demo connection theme={null} curl --request POST \ --url https://api.sandbox.tryvital.io/v2/link/connect/demo \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --header 'x-vital-api-key: ' \ --data '{"user_id": "", "provider": ""}' ``` ```javascript Node theme={null} import { VitalClient, VitalEnvironment } from '@tryvital/vital-node'; import { DemoConnectionCreationPayload, DemoProviders } from '@tryvital/vital-node/api'; const client = new VitalClient({ apiKey: '', environment: VitalEnvironment.Sandbox, }); const request: DemoConnectionCreationPayload = { userId: "", provider: DemoProviders., } const data = await client.link.connectDemoProvider(request); ``` ```python Python theme={null} from vital.client import Vital from vital.environment import VitalEnvironment client = Vital( api_key="YOUR_API_KEY", environment=VitalEnvironment.SANDBOX ) data = client.link.connect_demo_provider(user_id="", provider=DemoProviders.) ``` ```java Java theme={null} import com.vital.api.Vital; import com.vital.api.core.Environment; import com.vital.api.resources.link.requests.DemoConnectionCreationPayload; import com.vital.api.types.DemoProviders; Vital vital = Vital.builder() .apiKey("YOUR_API_KEY") .environment(Environment.SANDBOX) .build(); DemoConnectionCreationPayload request = DemoConnectionCreationPayload .builder() .userId("") .provider(DemoProviders.) .build(); var data = vital.link().connectDemoProvider(request); ``` ```go Go theme={null} import ( "context" vital "github.com/tryVital/vital-go" vitalclient "github.com/tryVital/vital-go/client" "github.com/tryVital/vital-go/option" ) client := vitalclient.NewClient( option.WithApiKey(""), option.WithBaseURL(vital.Environments.Sandbox), ) request := &vital.DemoConnectionCreationPayload{ UserId: "", Provider: vital.DemoProviders, } response, err := client.Link.ConnectDemoProvider(context.TODO(), request) if err != nil { return err } fmt.Printf("Received data %s\n", response) ``` After the demo connection is created, a Fitbit logo will appear beside your user. From here, everything works exactly the same as if you connected a real device. This means you will receive the following webhook updates: * [Connection created](/webhooks/data_flow#connection-created), as soon as the connection is created. * [Historical webhooks](/webhooks/data_flow#historical-data-flow). We simulate historical data for the demo device and send the corresponding webhook updates, as in the real-world scenario. * [Daily webhooks](/webhooks/data_flow#daily-data-flow). We also simulate updates to the device data every couple of hours so you can test receiving the data when a user recorded a workout or other activity. * [Refresh user data](/api-reference/user/refresh-user-data). You can also instantaneously refresh a user's data through this endpoint. ## Backfill Data For demo connections there will be 30 days of historic data backfilled. This is in contrast with data backfill for non demo providers, where the number of days data backfilled varies according to the provider. You can find out more [here](/wearables/providers/introduction#historical-days-retrieved) ## Data Format When connecting to the live Junction client, there are three data response structures available for data. Raw, Stream and Summary When connecting to the demo Junction client data is only available in the Summary structure. Summary restructures provider data fields, to have a consistent response structure across all providers. This is the easiest way to use provider data across an application which connects to multiple providers. # Timestamps and Time Zones Source: https://docs.junction.com/wearables/providers/timestamps-and-time-zones ## Timestamps All Junction data timestamps are ISO 8601 formatted as follows: | Providers | Time Basis | Pattern | | --------------------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------- | | `abbott_libreview`
`freestyle_libre` \[1] | Floating Time \[2] | `YYYY-mm-ddTHH:mm:ss+00:00`
The `+00:00` TZ specifier should be **ignored** \[3]. | | *Everyone else* | UTC | `YYYY-mm-ddTHH:mm:ss+00:00`
Always specified as `+00:00` (UTC). | \[1] `freestyle_libre` specific: Some older teams may see floating time data (Freestyle Libre) without the `+00:00` TZ specifier, as part of an earlier iteration of the feature. Contact Junction support if you wish to disable this behaviour, or if you intend to adopt the Junction backend SDKs. \[2] [Floating time](https://www.w3.org/International/wiki/FloatingTime) is not affixed to any time zone. This is not the same as time relative to a local time zone, i.e., a known UTC offset or geographical location. \[3] Junction sends timestamps in floating time with `+00:00` for OpenAPI 3.x interoperability. OAS 3 requires all timestamps to be [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339), which in turn requires TZ specifier to be mandatory. When processing data from Freestyle Libre, you should [**reinterpret literally**](#data-point-in-floating-time-freestyle-libre) the date and time components as local date-time in the desired time zone. Do not convert from UTC/Zulu. ## Time Zones Junction formats time zones in data as a *nullable* integer offset to UTC: * A positive offset indicates east of UTC. * A negative offset indicates west of UTC. * `null` indicates the time zone information is absent. Each provider has their own affinity of time zone information: | Affinity | Time Basis | Time Zone | | ------------------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | βœ… | UTC | The data comes with time zone information. | | User Fallback, or UTC | UTC | We assume the data were captured in the [User Fallback Time Zone](#user-fallback-time-zone). We fallback to UTC if the former is not set. | | User Fallback, or Absent | UTC | We assume the data were captured in the [User Fallback Time Zone](#user-fallback-time-zone). Mark as no time zone (`null`) if the former is not set. | | Inferred, or Absent | UTC | We infer the time zone from the data. Mark as no time zone (`null`) if the inferrence fails. | | Absent | UTC | We do not know the exact time zone the recording took place in. Mark as no time zone (`null`). | | Floating Time | Floating Time | We do not know the exact time zone the recording took place in. Mark as no time zone (`null`). | ### Cloud-based Providers | Provider | Activity Summary (Daily) | Session Summary (e.g. Sleep) | Timeseries Sample | Remarks | | ---------------------------------------------------------------------------- | ------------------------ | ---------------------------- | ------------------- | -------------------------------------------------------------- | | [Freestyle Libre](https://www.freestylelibre.co.uk/libre/) `freestyle_libre` | *N/A* | *N/A* | Floating Time | | | [Abbott LibreView](https://www.libreview.com/) `abbott_libreview` | *N/A* | *N/A* | Floating Time | | | [Fitbit](https://www.fitbit.com/global/uk/home) `fitbit` | βœ… | βœ… | βœ… | ⚠️ [Time Zones](/wearables/guides/fitbit#time-zone-resolution) | | [Garmin](https://www.garmin.com) `garmin` | βœ… | βœ… | βœ… | | | [Google Fit](https://www.google.com/fit/) `google_fit` | User Fallback, or UTC | User Fallback, or Absent | Absent | | | [Oura](https://ouraring.com) `oura` | βœ… | βœ… | βœ… | | | [Peloton](https://www.onepeloton.com) `peloton` | *N/A* | βœ… | *N/A* | | | [Strava](https://www.strava.com) `strava` | *N/A* | βœ… | βœ… | | | [Wahoo](https://wahoofitness.com) `wahoo` | *N/A* | Absent | *N/A* | | | [WHOOP](https://www.whoop.com) `whoop_v2` | βœ… | βœ… | *N/A* | | | [Zwift](https://zwift.com) `zwift` | *N/A* | User Fallback, or Absent | *N/A* | | | [Withings](https://www.withings.com) `withings` | βœ… | βœ… | βœ… | | | [8Sleep](https://www.eightsleep.com) `eight_sleep` | *N/A* | βœ… | βœ… | | | [Hammerhead](https://www.hammerhead.io) `hammerhead` | *N/A* | Inferred, or Absent | Inferred, or Absent | | | [Dexcom](https://www.dexcom.com) `dexcom_v3` | *N/A* | *N/A* | βœ… | | | [Polar](https://www.polar.com) `polar` | βœ… | βœ… | βœ… | | | [Kardia](https://kardia.com/) `kardia` | *N/A* | *N/A* | βœ… | | | [Omron](https://www.omron-healthcare.com) `omron` | βœ… | *N/A* | βœ… | | ### SDK-based Providers On-device data often do not capture the time zone at recording time. Junction SDK uses the device's current time zone as the fallback of closest approximation. | Provider | Activity Summary (Daily) | Session Summary | Timeseries Samples | | --------------------------------------------------------------------------------------------------- | ------------------------ | --------------- | ------------------ | | [Apple HealthKit](https://www.apple.com/uk/ios/health/) `apple_health_kit` | βœ… | βœ… | βœ… | | [Android Health Connect](https://developer.android.com/health-connect) `health_connect` | βœ… | βœ… | βœ… | | [Omron](https://www.omron-healthcare.com) `omron_ble` | *N/A* | N/A | βœ… | | [Contour](https://www.diabetes.ascensia.com) `contour_ble` | *N/A* | N/A | βœ… | | [Accu-Chek](https://www.accu-chek.com) `accuchek_ble` | *N/A* | N/A | βœ… | | [Freestyle Libre BLE](https://www.freestylelibre.co.uk/libre/) `freestyle_libre_ble` | N/A | N/A | βœ… | ## Examples ### Data point with time zone offset The data point was recorded on July 7, 2023 at 1:00 PM in UTC-07:00. ```json theme={null} { "source": { "provider": "apple_health_kit", }, "data": [ { "timestamp": "2023-07-07T20:00:00+00:00", "timezone_offset": -25200 } ] } ``` ### Data point with no time zone offset (`null`) The data point was timestamped to Aug 13, 2023 at 8:12 AM in UTC+00:00 (Zulu). We do not know the exact time zone the recording took place in. ```json theme={null} { "source": { "provider": "apple_health_kit", }, "data": [ { "timestamp": "2023-08-13T08:12:00+00:00", "timezone_offset": null } ] } ``` ### Data point in floating time (Abbott Libreview & Freestyle Libre) This Freestyle Libre data point was recorded on Sep 27, 2023 at 7:48 AM in floating time (whichever time zone the user was in). We do not know the exact time zone the recording took place in. You can display the date and time components literally to the user, e.g., `2023/09/27 07:48 AM`, as they are relative to their perception of local time. If you need to convert it to UTC for persistence, you need to pick a time zone based on your understanding of the user. Then you should reinterpret the date and time components **literally** in said time zone. For example, we knew that this example user is based in `America/New_York`. So we would interpret this data point to be in `2023/09/27 07:48 AM ET`. We then finally convert this to UTC, resulting in `2023/09/27 11:48 AM UTC`. ```json theme={null} { "source": { "provider": "freestyle_libre", }, "data": [ { "timestamp": "2023-09-27T07:48:00+00:00", "timezone_offset": null } ] } ``` ## User Fallback Time Zone Some providers neither expose nor even capture time zone information at source. So Junction can only request data and interpret them strictly in UTC. If you prefer the data to be contextualize to the geographical location of a user, a Fallback Time Zone (denoted by an [IANA tz database identifier](https://data.iana.org/time-zones/tz-link.html)) can be specified on a per-user basis. Once specified, Junction would use the time zone to pull data and interpret timestamps from any time zone unware providers from that point onwards. You can specify the Fallback Time Zone when: * [Creating a new user](/api-reference/user/create-user); or * [Patching an existing user](/api-reference/user/patch-user) You will also get information about the source (by slug) and the last updated time of the Fallback Time Zone [when getting an existing user](/api-reference/user/get-user). Fallback Time Zone manually supplied via the REST API would always have a source slug of `manual`. ```json Example theme={null} { "user_id": "409d9870-21fb-443a-8daa-c5222659f40e", "team_id": "3aac677c-557f-40b7-9251-0315c1f48e77", "client_user_id": "d734e32e-dd43-4b77-ab56-692524279531", /** snipped **/ "fallback_time_zone": { "id": "Europe/London", "source_slug": "manual", "updated_at": "2022-09-11T13:45:56+00:00" } } ``` # Update Frequency Source: https://docs.junction.com/wearables/providers/update-frequency Whenever Junction detects that new data is available, [data events](/event-catalog) are always sent to your configured endpoints, regardless of the provider being push-based or polling-based. ## Cloud Based Providers | Primary Model | Providers | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Hybrid | [Fitbit](https://www.fitbit.com/global/uk/home), [Withings](https://www.withings.com), [Oura](https://ouraring.com) | | Push | [Garmin](https://www.garmin.com), [Strava](https://www.strava.com), [WHOOP](https://www.whoop.com), [Cronometer](https://www.cronometer.com) | | Polling | [Wahoo](https://wahoofitness.com), [LibreView](https://www.libreview.com/), [Google Fit](https://www.google.com/fit/), [Peloton](https://www.onepeloton.com), [Zwift](https://zwift.com), [EightSleep](https://www.eightsleep.com), [Hammerhead](https://www.hammerhead.io), [Dexcom](https://www.dexcom.com) | Cloud Based Providers can operate in the following three models: In the Push model, the data provider proactively notifies Junction of any resource changes. Junction goes and fetches the notified resources, compare it against what we have already ingested, and send out the differences (new or updated entities) to your Webhook endpoints or ETL Pipeline destinations. While resources using the Push model typically has lower latency than polling, data providers may impose throttling, debouncing or batching semantics on these push notifications to Junction on their end. Junction schedules a recurring job to query the data provider API resources: * For most resources, the schedule is every 15 minutes or so. * Some resources may use a sparser schedule due to data provider rate limits. Junction then compare the reported data against what we have already ingested, and send out the differences (new or updated entities) to your Webhook endpoints or ETL Pipeline destinations. Some data providers support the Push model only on a subset of API resources. Junction prefers to use the Push model whenever available, but otherwise falls back to the Polling model. ## SDK Based Providers SDK Based Providers are all push-based, where data are pushed from the Junction SDK embedded inside your Android or iOS app. | Provider | Description | | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | [Apple HealthKit](https://www.apple.com/uk/ios/health/) |
  • Auto sync on app launch and in foreground
  • Hourly Background Sync, subject to OS throttling
| | [Android Health Connect](https://developer.android.com/health-connect) |
  • Sync On App Launch
  • Opt-in Hourly Background Sync, subject to OS throttling
| | [Omron](https://www.omron-healthcare.com) | Manual Post | | [Contour](https://www.diabetes.ascensia.com) | Manual Post | | [Accu-Chek](https://www.accu-chek.com) | Manual Post | For more information on Apple HealthKit and Android Health Connect, please refer to the specific guides: * [Junction Health SDK: Automatic Data Sync](/wearables/sdks/vital-health#automatic-data-sync) * [Apple HealthKit: Sync Frequency](/wearables/guides/apple-healthkit#sync-frequency) * [Android Health Connect: Sync Frequency](/wearables/guides/android-health-connect#sync-frequency) # Mobile SDK Authentication Source: https://docs.junction.com/wearables/sdks/authentication ## Junction Sign-In Token In the *Vital Sign-In Token* scheme, each mobile app installation signs in with Junction Mobile SDK as an individual user in your Team. The app installation would be granted only Junction API access scoped to that specific user. Instead of hardcoding a [Junction Team API Key](#vital-api-keys), your app would request your own backend to generate a **Junction Sign-In Token** for the user, through your own API authentication and authorization systems. In this model, your Junction Team API Keys are kept strictly as a server-side secret. The signed-in user is persistent across app relaunch and device reboots. Only user-level resources of the signed-in user can be accessed. ### Overview In typical circumstances, within each app installation, you would sign in your authenticated user only once with the Junction Mobile SDK. You would do so as part of your app's user lifecycle. There are some known circumstances where you would have to sign in the user again: * You have signed out your user in response to your app's user lifecycle changes; or * The Junction SDK sign-in state is out of sync with your app's user lifecycle; or * The SDK automatically signed out, because it had detected the deletion of the Junction User. It is unnecessary to request and sign-in with the Junction Sign-In Token every time your app launches. You should sign-in and sign-out of the Junction Mobile SDK as an integral part of your app's user lifecycle: 1. When the user signs in to your app, you would request the Junction Sign-In Token from your backend, and then sign in with the Junction Mobile SDK. 2. When the user signs out from your app, you would also sign out from the Junction Mobile SDK. Junction Mobile SDK persists the signed-in Junction User β€” as well as any settings and sync state β€” across app relaunches and device reboot. We recommend regularly reconciling the state of the Junction Mobile SDK and your app's user lifecycle. This is because: 1. The SDK sign-in process involves remote API calls, which can fail due to Internet connectivity; 2. Unknown edge cases in your integration of Junction Core SDK into your app's user lifecycle may result in the state being out of sync; and 3. If you are integrating Junction Mobile SDK into an existing production app, this reconciliation can serve as a one-off migration for app installations upgrading from an older version of your app. You can achieve this through the [Core SDK](/wearables/sdks/vital-core#core-sdk-status) API in three steps: 1. Query the [Core SDK](/wearables/sdks/vital-core#core-sdk-status) Status and the Current User ID; 2. Compare these against your app's user state; and 3. Only if a discrepancy is detected, perform a Junction SDK sign-in or sign-out in accordance to the discrepancy. You would typically schedule this as an asynchronous task that spawns when your application process launches. Do not sign in and sign out every time your application process launches. **Junction Sign-In Token** is a short-lived bearer token issued for a specific Junction User. Junction Mobile SDK uses it to sign in with Junction's Identity Provider as the Junction User represented by the token. Each app installation can only have one active signed-in Junction User. Internally, the Junction Mobile SDK: 1. **exchanges** the short-lived Junction Sign-In Token for a permanent sign-in, which is in the form of an OAuth 2.0 access token and an OAuth 2.0 refresh token. 2. discards the Junction Sign-In Token upon a successful exchange. 3. stores these token secrets securely in the device persistent storage. 4. calls the Junction API using the OAuth 2.0 access token. 5. transparently manages the OAuth 2.0 refresh token flow to keep the access token fresh and valid. This is why the documentation emphasizes the "sign in once" expectation. The SDK does not rely on the Junction Sign-In Token for day-to-day operation and API calls. It only needs this artifact once upfront to exchange for the token secrets it needs. ### Case Study: A typical Backend - Mobile App flow ```mermaid theme={null} sequenceDiagram participant Your App actor Your Identity Provider autonumber Your App->>Your Identity Provider: User authentication Your Identity Provider-->>Your App: Token Secret for authenticating to your Backend API ``` Your end user signs in with your Identity Provider β€” a separate IdP, or your own API acting as one β€” through password authentication, Social Login, Single Sign-On (SSO) or any other authentication methods. ```mermaid theme={null} sequenceDiagram participant Your App actor Your Backend autonumber Your App->>Your Backend: Request the user profile Your Backend-->>Your App: User profile Your App->>Your Backend: Request a Junction Sign-In Token for the user ``` Once your user has successfully authenticated, your app is issued with token secrets for accessing your backend API. Your app stores these token secrets securely. It then makes some API requests to your backend API, in order to gather all information required to setup your app into an authenticated state. Among these API requests, one would be requesting your backend API to issue a Junction Sign-In Token for your authenticated user. ```mermaid theme={null} sequenceDiagram actor Your Backend actor Junction API autonumber Your Backend->>Junction API: Token Request via API Key Junction API-->>Your Backend: Issued a Junction Sign-In Token ``` Your backend API receives and validates the token issuance request. Once validated, your backend service: * Look up the Junction User associated with this authenticated user; * If one does not yet exist, call the [Create User](/api-reference/user/create-user) endpoint to create a Junction User; and * Call the [Create Sign-In Token](/api-reference/user/create-sign-in-token) endpoint to obtain a Junction Sign-In Token for this Junction user. Only this step would use a [Junction Team API Key](/api-details/authentication). Because this step happens in your backend service, this enables you to keep the Junction Team API Key strictly as a server-side secret. ```bash theme={null} curl --request POST --url '{{BASE_URL}}/v2/user/{{USER_ID}}/sign_in_token' --header 'X-Vital-API-Key: ' ``` Your backend API then responds to your app with the obtained Junction Sign-In Token: ```json theme={null} { "user_id": "e209947b-323e-4108-8e18-1518b80da0bd", "sign_in_token": "XYZ==" } ``` ```mermaid theme={null} sequenceDiagram participant Your App participant Junction Mobile SDK actor Your Backend autonumber Your Backend-->>Your App: The Junction Sign-In Token Your App->>Junction Mobile SDK: Sign in with Junction Sign-In Token ``` Your app receives a response from your backend API, which includes the Junction Sign-In Token for the authenticated user. Your app then calls the Junction Mobile SDK `signIn` method with the Junction Sign-In Token: ```swift Native iOS theme={null} import VitalCore let response: MyAPIResponse = // .... do { try await VitalClient.signIn(withRawToken: response.signInToken) print("Signed in with Junction successfully") } catch let error { print("Error signing in with Junction", error) } ``` ```kotlin Native Android theme={null} import io.tryvital.client.VitalClient import android.content.Context import android.util.Log val response: MyAPIResponse val applicationContext: Context try { VitalClient.signIn(applicationContext, response.signInToken) Log.i("vital_sdk", "Signed in with Junction successfully") } catch (e: Throwable) { Log.e("vital_sdk", "Error signing in with Junction", e) } ``` ```typescript React Native theme={null} import { VitalCore } from "@tryvital/vital-core-react-native"; const response: MyAPIResponse do { await VitalCore.signIn(response.signInToken) console.log("Signed in with Junction successfully") } catch let error { console.error("Error signing in with Junction", error) } ``` ```dart Flutter theme={null} import 'package:vital_core/vital_core.dart' as vital_core; MyAPIResponse response; try { await vital_core.signIn(response.signInToken) Fimble.i("Signed in with Junction successfully") } catch (error) { Fimble.e("Error signing in with Junction", error) } ```
`signIn()` throws an error when you have already signed in with the Junction SDK. You can [inspect the Core SDK status](/wearables/sdks/vital-core#core-sdk-status) at any time for troubleshooting. You can also rely on the Core SDK status to reconcile differences between your own user session state and Junction SDK sign-in.
The Junction Mobile SDK is now signed-in with the Junction User associated with your authenticated user. You can now proceed to use all the Junction SDK features.
## Junction Team API Keys API Key is **discouraged** for production mobile apps, since it would be distributed as cleartext. API Key support is intended only for customer early evaluation in Sandbox. Use [Junction Sign-In Token](#vital-sign-in-token) whenever possible. Junction Mobile SDK can be configured to authenticate using API Key alongside a target user ID. ```swift Native iOS theme={null} import VitalCore VitalClient.configure( apiKey: "sk_us_...", environment: .sandbox(.us) ) VitalClient.setUserId(UUID(uuidString: "ccd6f98d-3a2a-433b-a114-8fe064f301ed")!) ``` ```kotlin Native Android theme={null} import io.tryvital.client.VitalClient import android.content.Context val applicationContext: Context VitalClient.configure( applicationContext, Region.US, Environment.Sandbox, "sk_us_..." ) VitalClient.setUserId(applicationContext, "ccd6f98d-3a2a-433b-a114-8fe064f301ed") ``` ```typescript React Native theme={null} import { VitalCore } from "@tryvital/vital-core-react-native"; await VitalCore.configure( "sk_us_...", "sandbox", "us", true, ); await VitalCore.setUserId("ccd6f98d-3a2a-433b-a114-8fe064f301ed"); ``` ```dart Flutter theme={null} import 'package:vital_core/vital_core.dart' as vital_core; await vital_core.configure( "sk_us_...", vital_core.Environment.sandbox, vital_core.Region.us ); await vital_core.setUserId("ccd6f98d-3a2a-433b-a114-8fe064f301ed"); ```
When running on iOS, the SDK must be **explicitly** configured before other SDK methods can be invoked. Alternatively, follow the [Apple HealthKit guide](https://docs.tryvital.io/wearables/guides/apple-healthkit#2-integrate-with-ios-system-callbacks) to enable SDK Automatic Configuration during app launch. This auto-configures the SDK using the last known configuration you supplied β€” including but not limited to the API Key, the environment and the target User ID. Not only is this a prerequisite to enable HealthKit Background Delivery, this allows you also to only call `configure(...)` and `setUserId(...)` once to "sign-in" the user persistently when using the Junction Team API Key scheme. ## Sign Out Regardless of the authentication scheme you used, you can sign out with the Junction SDK using `signOut()`. This erases any persistent user session and configuration stored by the Junction Core and Health SDKs. ```swift Native iOS theme={null} import VitalCore await VitalClient.shared.signOut() ``` ```kotlin Native Android theme={null} import io.tryvital.client.VitalClient import android.content.Context val applicationContext: Context VitalClient.getOrCreate(applicationContext).signOut() ``` ```typescript React Native theme={null} import { VitalCore } from "@tryvital/vital-core-react-native"; await VitalCore.signOut(); ``` ```dart Flutter theme={null} import 'package:vital_core/vital_core.dart' as vital_core; await vital_core.signOut(); ``` ## Migrate from Junction Team API Keys to Junction Sign-In Tokens Always use [Junction Sign-In Token](#vital-sign-in-tokens) for your production mobile apps. An existing app installation signed-in with Junction Team API Key + User ID can be seamlessly migrated to use Junction Sign-In Tokens. It is as simple as performing a one-off migration logic during app launch: Check whether the Junction SDK status includes `useApiKey` (i.e., the user is signed in using Junction Team API Key). Similar to the new user sign-in flow, your app needs to obtain a Junction Sign-In Token through your backend service. Your app can simply sign-in with the Junction Sign-In Token. Note that it is **unnecessary to reset** the SDK beforehand β€” the SDK `signIn` method would automatically migrate, as long as the supplied Sign-In Token is compatible with the existing API Key sign-in (i.e., having the same Junction user ID, same Junction environment, and same Junction region). ```swift Native iOS theme={null} import VitalCore if VitalClient.status.contains(.useApiKey) { do { let response = await callBackend(...) try await VitalClient.signIn(withToken: response.signInToken) } print("Signed in with Junction successfully") } catch let error { print("Error signing in with Junction", error) } } ``` ```kotlin Native Android theme={null} import io.tryvital.client.VitalClient import android.content.Context import android.util.Log val applicationContext: Context VitalClient.getOrCreate(applicationContext) if (VitalClient.Status.useApiKey in VitalClient.status) { try { val response = callBackend(...) VitalClient.signIn(applicationContext, response.signInToken) Log.i("vital_sdk", "Signed in with Junction successfully") } catch (e: Throwable) { Log.e("vital_sdk", "Error signing in with Junction", e) } } ``` ```typescript React Native theme={null} import { VitalCore } from "@tryvital/vital-core-react-native"; const status = await VitalCore.status(); if (status.includes("useApiKey")) { do { const response = await callBackend(...); await VitalCore.signIn(response.signInToken); console.log("Signed in with Junction successfully"); } catch let error { console.error("Error signing in with Junction", error); } } ``` ```dart Flutter theme={null} import 'package:vital_core/vital_core.dart' as vital_core; Set status = await vital_core.clientStatus(); if (status.contains(vital_core.ClientStatus.useApiKey)) { try { MyAPIResponse response = await callBackend(...); await vital_core.signIn(response.signInToken) Fimble.i("Signed in with Junction successfully") } catch (error) { Fimble.e("Error signing in with Junction", error) } } ``` # Health SDK Connection Policies Source: https://docs.junction.com/wearables/sdks/health/connection-policies ## Auto Connect mode (default) A connection is automatically created β€” or re-instated β€” when: * You [ask for health data permission](/wearables/sdks/health/overview#ask-user-for-health-data-permissions) through the Health SDK; or * the Health SDK begins a data sync attempt for any resource. In Auto Connect mode, any de-registration attempt through the [Deregister Connection endpoint](/api-reference/user/deregister-a-provider) has no effect. The Auto Connect mode will eventually re-instate the connection, typically at the next data sync attempt. Calling the Health SDK `connect()` or `disconnect()` methods in Auto Connect mode will throw an error. ## Explicit Connect mode Explicit Connect mode is available on iOS SDK 1.8.0+, Android SDK 4.2.0+, React Native SDK 5.4.0+ and Flutter SDK 4.6.0+. A connection is created only when: * You call the Health SDK `connect()` method. A connection is destroyed only when: * You call the Health SDK `disconnect()` method; or * You use the [Deregister Connection endpoint](/api-reference/user/deregister-a-provider) to de-register the HealthKit or Health Connect connection remotely from your backend servers. `connect()` and `disconnect()` in Explicit Connect mode requires a call to Junction API over the Internet. If the Health SDK fails to confirm connection or disconnection with the Junction API, an error would be thrown. In the Explicit Connect mode: 1. The Health SDK does not automatically create or re-instate a connection: * Not when you [ask for health data permission](/wearables/sdks/health/overview#ask-user-for-health-data-permissions) through the Health SDK; * Not when it detects either any iOS resource which your app has asked permission for, or any Android resource which your app has asked permission for; and * Not at the beginning of any sync attempt. 2. Your application logic has full control on when to connect and disconnect. 3. You can stop data sync by de-registering HealthKit and Health Connect connection via the [Deregister Connection endpoint](/api-reference/user/deregister-a-provider). ## Pausing synchronization temporarily The Health SDK supports a device-side [Pause Synchronization](/wearables/sdks/vital-health#pausing-data-synchronization) toggle, which can suspend and resume data synchronization temporarily without disconnection or signing out. This toggle works in both the Auto Connect mode and the Explicit Connect mode. # Junction Health SDK Source: https://docs.junction.com/wearables/sdks/health/overview ## Overview Make sure you have successfully integrated with a Junction Mobile SDK authentication scheme, before you proceed to try out Junction Health SDK. Learn how to authenticate your mobile app user with Junction Mobile SDK. This Junction Health SDK guidance page focuses on the inital setup and API usage of the Health SDK that is common to both the Apple HealthKit and Android Health Connect integration. Please consult the following guides on requirements and restrictions of Apple HealthKit and Android Health Connect: Learn how to integrate with Apple HealthKit through Junction Mobile SDKs, available in Native iOS, React Native and Flutter. Learn how to integrate with Android Health Connect through Junction Mobile SDKs, available in Native Android, React Native and Flutter. ## Initial Setup ### Review the Connection Policy options Junction Health SDK has two Connection Policy options: * The default [Auto Connect](/wearables/sdks/health/connection-policies#auto-connect-mode-default) mode; and * The opt-in [Explicit Connect](/wearables/sdks/health/connection-policies#explicit-connect-mode) mode. If your device connection management UX is built upon an explicit notion of connecting and disconnecting Apple HealthKit and Health Connect connections β€” as if they are like their cloud-based counterparts β€” you might find the [Explicit Connect](/wearables/sdks/health/connection-policies#explicit-connect-mode) mode more appealling. ### iOS apps: Configure your App Delegate This section applies to: * πŸ”˜ Native iOS apps * πŸ”˜ React Native iOS apps This section does not apply to: * ❌ Fluter iOS apps (using Junction Flutter SDK 4.3.8 or later). Junction **Flutter** SDK (4.3.8 and later) automatically integrates with your App Delegate through the Flutter Plugin machinery. You need not manual edit your AppDelegate in **Flutter** iOS projects. You must configure your App Delegate as instructed if you use Junction Health SDK on iOS. Not doing so may result in missing background delivery, or app process termination by the operating system. In your AppDelegate's `application(_:didFinishLaunchingWithOptions:)` implementation, it **must** call Junction Health SDK `automaticConfiguration()` synchronously before it returns. Junction needs to register various handlers with the system frameworks like Apple HealthKit and BackgroundTasks. These frameworks require said registrations to be completed **before** the "app finished launching" moment [\[1\]](https://developer.apple.com/documentation/healthkit/hkhealthstore/1614175-enablebackgrounddelivery#3801028) [\[2\]](https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler/register\(fortaskwithidentifier:using:launchhandler:\)#Discussion). For React Native apps, the AppDelegate is part of the template Swift or Objective-C code in your generated Xcode project. ```swift Swift theme={null} import VitalHealthKit class AppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil ) -> Bool { VitalHealthKitClient.automaticConfiguration() // ... return true } } ``` ```objc Objective-C / React Native theme={null} #import "AppDelegate.h" #import "VitalHealthKitConfiguration.h" - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [VitalHealthKitConfiguration automaticConfiguration]; // ... return YES; } ``` ### Configure the Junction Health SDK The Health SDK must be configured before use. The configuration is persistent, so you typically only need to do this a single time β€” immediately after [a successful authentication with the Core SDK](/wearables/sdks/authentication). ```swift Native iOS theme={null} import VitalHealthKit VitalHealthKitClient.configure( VitalHealthKitClient.Configuration( backgroundDeliveryEnabled: true, // Using Auto Connect mode (default) connectionPolicy: .autoConnect // Using Explicit Connect mode connectionPolicy: .explicit ) ) ``` ```kotlin Native Android theme={null} import io.tryvital.vitalhealthconnect.VitalHealthConnectManager import io.tryvital.vitalhealthconnect.model.ConnectionPolicy val manager = VitalHealthConnectManager.configure(context) manager.configureHealthConnectClient( syncNotificationBuilder = null, // Using Auto Connect mode (default) connectionPolicy = ConnectionPolicy.AutoConnect // Using Explicit Connect mode connectionPolicy = ConnectionPolicy.Explicit ) ``` ```typescript React Native theme={null} import { VitalHealth, HealthConfig } from "@tryvital/vital-health-react-native"; let config = new HealthConfig(); config.iOSConfig.backgroundDeliveryEnabled = true; // Using Auto Connect mode (default) config.connectionPolicy = "autoConnect"; // Using Explicit Connect mode config.connectionPolicy = "explicit"; await VitalHealth.configure(config); ``` ```dart Flutter theme={null} import 'package:vital_health/vital_health.dart' as vital_health; await vital_health.configure( config: const vital_health.HealthConfig( iosConfig: vital_health.IosHealthConfig( backgroundDeliveryEnabled: true, ), // Using Auto Connect mode (default) connectionPolicy: vital_health.ConnectionPolicy.autoConnect, // Using Explicit Connect mode connectionPolicy: vital_health.ConnectionPolicy.explicit, ), ); ``` ## Health Data Permissions ### Ask user for health data permissions Before the SDK can read any data, you need to ask the end user to grant health data permissions. ```swift Native iOS theme={null} import VitalHealthKit await VitalHealthKitClient.shared.ask( readPermissions: [.activity, .workout, .sleep], writePermissions: [] ) ``` ```kotlin Native Android theme={null} // // Android Compose // import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.material3.* import io.tryvital.vitalhealthconnect.VitalHealthConnectManager val manager: VitalHealthConnectManager = ... val contract = manager.createPermissionRequestContract( readResources = set( VitalResource.Activity, VitalResource.Workout, VitalResource.Sleep, ), writeResources = emptySet(), ) val coroutineScope = rememberCoroutineScope() val permissionsLauncher = rememberLauncherForActivityResult(contract) { outcomeAsync -> coroutineScope.launch { val outcome = outcomeAsync.await() Log.i("VitalPermissionOutcome", outcome.toString()) } } Button(onClick = { permissionsLauncher.launch(Unit) }) { Text("Ask for permission") } // // AndroidX Activity Result API // import androidx.activity.ComponentActivity import io.tryvital.vitalhealthconnect.VitalHealthConnectManager val activity: ComponentActivity = ... val manager: VitalHealthConnectManager = ... activity.registerForActivityResult(contract) { outcomeAsync -> CoroutineScope(Dispatchers.Main).launch { val outcome = outcomeAsync.await() Log.i("VitalPermissionOutcome", outcome.toString()) } } ``` ```typescript React Native theme={null} import { VitalHealth, VitalResource } from "@tryvital/vital-health-react-native"; await VitalHealth.ask( [VitalResource.Activity, VitalResource.Workout, VitalResource.Sleep], [] ) ``` ```dart Flutter theme={null} import 'package:vital_health/vital_health.dart' as vital_health; final List readResources = [ vital_health.HealthResource.activity, vital_health.HealthResource.sleep, vital_health.HealthResource.workout, ]; await vital_health.askForPermission(readResources, []); ``` Regardless of whether the user has granted or denied the permissions, any subsequent re-request would be non-interactive and returns instantly. This is because the operating system only prompts the end user once for each resource type. If you wish to provide a way for users to review their permission grants or denials, you can [inform them of the system app location where they can do so](#check-if-a-read-permission-was-granted-or-denied-spoiler-you-cant). The health data read permission prompted is managed by the operating system. Junction cannot customize or alter its behaviour. ### Check if your app has asked for permissions before You can check if your app has already asked the user *at least once* before, for permissions on a specific `VitalResource`. Because [Ask for Permission](#ask-user-for-health-data-permissions) are gracefully ignored by the operating system beyond the first time, checking before asking can help you skip the parts of your UX journey that only makes sense to users going through [Ask for Permission](#ask-user-for-health-data-permissions) for the first time. The only exception to this behaviour is when a `VitalResource` pulls a new data type introduced in a new OS release. In this scenario, calling [Ask for Permission](#ask-user-for-health-data-permissions) would result in a OS permission prompt that asks for permissions on *specifically* those new data types. If you wish to provide a way for users to review their permission grants or denials, you can [inform them of the system app location where they can do so](#check-if-a-read-permission-was-granted-or-denied-spoiler-you-cant). ```swift Native iOS theme={null} import VitalHealthKit VitalHealthKitClient.shared.hasAskedForPermission(resource: .activity) ``` ```kotlin Native Android theme={null} import io.tryvital.vitalhealthconnect.VitalHealthConnectManager val manager = VitalHealthConnectManager.configure(context) manager.hasAskedForPermission(VitalResource.Activity) ``` ```typescript React Native theme={null} import { VitalHealth, VitalResource } from "@tryvital/vital-health-react-native"; await VitalHealth.hasAskedForPermission(VitalResource.Activity); ``` ```dart Flutter theme={null} import 'package:vital_health/vital_health.dart' as vital_health; await vital_health.hasAskedForPermission(vital_health.HealthResource.Activity); ``` ### Check if a read permission was granted or denied (⚠️ Spoiler: You can't) Junction Health SDK allows you to [ask for permission](#ask-user-for-health-data-permissions) and [check if your app has asked for permission before](#check-if-your-app-has-asked-for-permissions-before). However, Junction Health SDK **cannot** tell you: 1. Whether or not a user has granted or denied a read permission after the [Ask for Permission](#ask-user-for-health-data-permissions) has concluded. 2. The state of a read permission at any exact moment β€” granted or denied. Apple made a deliberate choice to conceal the state of HealthKit data type read permissions from third-party apps. Quoting [Apple's own words](https://developer.apple.com/documentation/healthkit/hkhealthstore/1614154-authorizationstatus) (as at 27 September 2024): > To help prevent possible leaks of sensitive health information, your app cannot determine whether or not a user has granted permission to read data. > > If you are not given permission, it simply appears as if there is no data of the requested type in the HealthKit store. > > \[...] In other words, all third-party apps β€” including anyone using the Junction Health SDK β€” would: 1. not be able to determine if a particular read permission has been granted or denied by the user; 2. not be able to manage or review read permissions inside their app. The best course of action out of this is to inform your users of the location where they can review and manage their health data read permissions: | Operating System | System App | Location | | -------------------- | ------------------ | ------------------------------------- | | iOS 8.0 and above | Health app | Sharing > Apps and Services | | iOS 15.0 and above | Settings app | Privacy & Security > Health | | Android 13 or below | Health Connect app | Apps and Permissions | | Android 14 and above | Settings app | Health Connect > Apps and Permissions | When designing your user experience, **assume** you would have zero knowledge of permission grants and denials after the user has gone through the [Ask for Permission](#ask-user-for-health-data-permissions) flow. ## Automatic Data Sync Junction Health SDK automates data sync for you on both Android and iOS. Please first consult the provider-specific guides for the requirements and setup instructions: * [Apple HealthKit guide](/wearables/guides/apple-healthkit) * [Android Health Connect guide](/wearables/guides/android-health-connect) ### iOS | Sync is automatically... | | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Activated on | All resource types you have [asked permission for](#ask-user-for-health-data-permissions). | | Triggered by | Two behaviours:
  • Foreground: Apple HealthKit immediately delivers buffered and new changes.
  • Background: Hourly batch delivery of changes, subject to OS throttling.
| HealthKit change notification is an always-on behaviour. If you did not configure [Apple HealthKit Background Delivery](/wearables/guides/apple-healthkit) as instructed, your app will not receive any HealthKit change notification while it is in background. In turn, background sync would not occur. ### Android | Sync is automatically... | | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Activated on | All resource types you have [asked permission for](#ask-user-for-health-data-permissions). | | Triggered by | Two mechanisms:
  • [Sync On App Launch](/wearables/guides/android-health-connect#sync-on-app-launch)
  • [Background Sync](/wearables/guides/android-health-connect#background-sync) if it is enabled.
| Sync On App Launch is an always-on behaviour. Background Sync is an **opt-in** behaviour. Please refer to the [Android Health Connect guide](/wearables/guides/android-health-connect) for the full context and required configuration of these two behaviours. ### Background Sync Frequency Junction Health SDK schedules hourly background sync with both Apple HealthKit and Android Health Connect, provided that you have: 1. Configured [Apple HealthKit Background Delivery](/wearables/guides/apple-healthkit) as per instruction; and 2. Configured [Health Connect Background Sync](/wearables/guides/android-health-connect#background-sync) and request user permissions (when necessary) as per instruction. However, this schedule is only advisory. The OS has full discretion to defer the scheduled time based on runtime constraints like battery power, as well as platform background execution policies. For more detailed explanation, please refer to: * [Apple HealthKit: Sync Frequency](/wearables/guides/apple-healthkit#sync-frequency) * [Android Health Connect: Sync Frequency](/wearables/guides/android-health-connect#sync-frequency) In other words, your product experience should not assume near real-time availability of wearable data, since the norm is a variable delay ranging from an hour to a day. Longer delay is also possible due to the lack of Internet connectivity, e.g., a long-haul flight, or an off-the-grid trip. The only possible exception is when your product experience does involve **active usage** of your consumer app with an active Internet connection. For example, iOS 17+ supports third-party apps initiating an Apple Watch workout session that would be live synced with your iPhone app. ### Pausing Data Synchronization You can pause and unpause data sync at any time, without having to sign-out the user from the Junction Mobile SDK. Note that pausing data sync does not reset the incremental sync progress and does not trigger a historical stage upon unpause. It only "freezes" the sync progress. ```swift Native iOS theme={null} import VitalHealthKit // Pause Synchronization VitalHealthKitClient.shared.pauseSynchronization = true // Unpause Synchronization VitalHealthKitClient.shared.pauseSynchronization = false // Check if synchronization is paused print("Paused? \(VitalHealthKitClient.shared.pauseSynchronization)") ``` ```kotlin Native Android theme={null} import io.tryvital.vitalhealthconnect.VitalHealthConnectManager val manager = VitalHealthConnectManager.getOrCreate(context) // Pause Synchronization manager.pauseSynchronization = true // Unpause Synchronization manager.pauseSynchronization = false // Check if synchronization is paused Log.i("sync", "Paused? ${manager.pauseSynchronization}") ``` ```typescript React Native theme={null} import { VitalHealth } from "@tryvital/vital-health-react-native"; // Pause Synchronization await VitalHealth.setPauseSynchronization(true); // Unpause Synchronization await VitalHealth.setPauseSynchronization(false); // Check if synchronization is paused const isPaused = await VitalHealth.pauseSynchronization; ``` ```dart Flutter theme={null} import 'package:vital_health/vital_health.dart' as vital_health; // Pause Synchronization await vital_health.setPauseSynchronization(true); // Unpause Synchronization await vital_health.setPauseSynchronization(false); // Check if synchronization is paused final isPaused = await vital_health.pauseSynchronization; ``` ## Manually start data sync You can force data sync to start with `syncData()`. ```swift Native iOS theme={null} import VitalHealthKit VitalHealthKitClient.shared.syncData() ``` ```kotlin Native Android theme={null} import io.tryvital.vitalhealthconnect.VitalHealthConnectManager val manager = VitalHealthConnectManager.getOrCreate(context) manager.syncData() ``` ```typescript React Native theme={null} import { VitalHealth } from "@tryvital/vital-health-react-native"; await VitalHealth.syncData(); ``` ```dart Flutter theme={null} import 'package:vital_health/vital_health.dart' as vital_health; await vital_health.syncData(); ``` ## Writing data You can also write data back to Apple HealthKit and Android Health Connect: ```swift Native iOS theme={null} import VitalHealthKit try await VitalHealthKitClient.shared.write( input: .water(milliliters: 1000), startDate: Date(), endDate: Date() ) ``` ```kotlin Native Android theme={null} import io.tryvital.vitalhealthconnect.VitalHealthConnectManager import io.tryvital.vitalhealthconnect.model.WritableVitalResource val manager = VitalHealthConnectManager.getOrCreate(context) manager.writeRecord( WritableVitalResource.Water, Instant.now(), Instant.now(), 100.0 ) ``` ```typescript React Native theme={null} await VitalHealth.writeHealthData( VitalWriteResource.Water, 100.0, new Date(), new Date(), ) ``` ```dart Flutter theme={null} import 'package:vital_health/vital_health.dart' as vital_health; await vital_health.writeHealthData(vital_health.HealthResourceWrite.water, startDate, endDate, 1000); ``` Not all resources are writable in both iOS and Android. Please refer to the `health_resource.dart` file. ## Sync Status You can observe the `syncStatus` stream for continuous updates on the health data sync status. ```swift Native iOS theme={null} import VitalHealthKit VitalHealthKitClient.shared.status.sink { status in print("Sync Status: \(status)") } ``` ```kotlin Native Android theme={null} import io.tryvital.vitalhealthconnect.VitalHealthConnectManager import kotlinx.coroutines.* import kotlinx.coroutines.flow.* val coroutineScope: CoroutineScope val manager = VitalHealthConnectManager.getOrCreate(context) manager.status .onEach { status -> Log.i("HealthDataSync", "$status") } .launchIn(coroutineScope) ``` ```typescript React Native theme={null} import { VitalHealthEvents, VitalHealthReactNativeModule } from "@tryvital/vital-health-react-native"; const healthEventEmitter = new NativeEventEmitter(VitalHealthReactNativeModule); healthEventEmitter.addListener(VitalHealthEvents.statusEvent, (event: any) => { console.log(VitalHealthEvents.statusEvent, event); }); ``` ```dart Flutter theme={null} import 'package:vital_health/vital_health.dart' as vital_health; Stream status = vital_health.syncStatus; ``` ## Sync Progress Debugging UI (iOS only) Junction Health SDK for iOS includes a `ForEachVitalResource` β€” a pre-baked SwiftUI View providing a live-updated sync progress feed of all the `VitalResource`s whose permissions have been asked for. You can use `ForEachVitalResource()` in any position which you would use `ForEach`. For example, you can use it inside a `Section` of a `List`: ```swift Native iOS theme={null} import VitalHealthKit struct SyncProgressView: View { var view: some View { List { Section(header: Text("Sync Progress")) { ForEachVitalResource() } } } } ``` If you are using UIKit, you can still access it through `UIHostingController`: ```swift Native iOS theme={null} let viewController: UIViewController let hostingController = UIHostingController(rootView: SyncProgressView()) viewController.present(hostingController, animated: true, completion: nil) ``` You can open the Apple HealthKit Sync Progress Inspector UI through a simple call: ```dart Flutter theme={null} import 'package:vital_health/vital_health.dart' as vital_health; unawaited(vital_health.openSyncProgressView()); ``` ```typescript React Native theme={null} import { VitalHealth } from "@tryvital/vital-health-react-native"; await VitalHealth.openSyncProgressView(); ``` This Sync Progress Inspector UI provides a live-updated sync progress feed of all the VitalResources whose permissions have been asked for. Note that it is currently a no-op on Android. # Installation Source: https://docs.junction.com/wearables/sdks/installation ## Project requirements ### iOS Applies also to Flutter and React Native projects. | Parameter | Requirement | | ------------------------- | ----------- | | Minimum deployment target | iOS 15.1 | If you are integrating on React Native or Flutter, or integrating on Native iOS via CocoaPods, make sure you have updated the minimum iOS deployment target in: * your `Podfile`; and * your iOS App Target. ```diff Podfile theme={null} # e.g., React Native default -platform :ios, min_ios_version_supported # Set minimum deployment target to be iOS 14.0. +platform :ios, '15.1' ``` ### Android Applies also to Flutter, React Native and Expo projects. | Parameter | Requirement | | ----------------------------- | ---------------------------------------- | | Minimum SDK Level | 26 (Health Connect), 29 (Samsung Health) | | Compile SDK Level | 36 | | Kotlin compiler version | 2.1.20 or above | | Android Gradle Plugin version | 8.9.1 or above | | Gradle version | 8.11.1 or above | ### Flutter | Parameter | Requirement | | ----------- | ------------- | | Flutter SDK | 3.13 or above | ### React Native #### Expo | Parameter | Requirement | | --------- | ----------- | | Expo | 53 or above | #### Bare React Native projects | Parameter | Requirement | | ----------------- | --------------- | | React Native Core | 0.72.0 or above | | Node.js | 20.0 or above | ## Setup package dependencies ## iOS Junction Mobile SDKs are available through both Swift Package Manager and CocoaPods. ### Swift Package Manager Add the vital-ios package (`https://github.com/tryVital/vital-ios`) as a dependency of your project. Link `VitalHealthKit` and `VitalDevices` as appropriate. Note that `VitalCore` is mandatory. ### CocoaPods Add the following declarations to your app target in your Podfile: ```ruby theme={null} pod 'VitalCore' pod 'VitalDevices' pod 'VitalHealthKit' ``` ## Android Make sure Maven Central is included in the list of repositories in your `build.gradle`. ```groovy theme={null} repositories { mavenCentral() } ``` Then include our Android SDK artifacts as dependencies of your modules as needed: ```groovy theme={null} def vital_version = '3.2.1' implementation 'io.tryvital:vital-client:$vital_version' implementation 'io.tryvital:vital-health-connect:$vital_version' implementation 'io.tryvital:vital-devices:$vital_version' ``` ## React Native ### Expo projects #### Installation Only [Expo Prebuild](https://docs.expo.dev/guides/adopting-prebuild/) is supported. Expo Go is incompatible, since it supports neither native library binaries, nor [Expo Config Plugin](https://docs.expo.io/guides/config-plugins/). First install the package with [`npx expo install`](https://docs.expo.dev/more/expo-cli/#installation). ```sh theme={null} npx expo install @tryvital/vital-core-react-native npx expo install @tryvital/vital-health-react-native ``` After installing the Junction React Native SDK packages, add the bundled Junction Expo Config Plugins to the [`plugins`](https://docs.expo.io/versions/latest/config/app/#plugins) array of your Expo app configuration file (`app.json` or `app.config.js`): ```json app.json/app.config.js theme={null} { "expo": { "plugins": [ "@tryvital/vital-core-react-native", "@tryvital/vital-health-react-native" ] } } ``` #### Android specific configuration In your Expo app configuration file (`app.json` or `app.config.js`), use the `expo-build-properties` config plugin to customize the minimum, target and compile Android SDK version: ```json app.json/app.config.js theme={null} { "expo": { // ... "plugins": [ [ "expo-build-properties", { "android": { "compileSdkVersion": 34, "targetSdkVersion": 34, "minSdkVersion": 26 } } ], // ... ] } } ``` Refer to [Project Requirements > Android](#android) for the constraints on these SDK version levers. Next, rebuild your app as described in the ["Adding custom native code"](https://docs.expo.dev/workflow/customizing/) guide. #### Customizations You can customize the integration by passing additional properties to the Expo config plugin: | Key | Value | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `healthSharePermission` (*string*) | Sets the iOS `NSHealthShareUsageDescription` permission message to the `Info.plist`. Defaults to `Allow $(PRODUCT_NAME) to check health info` | ```json app.json/app.config.js theme={null} { "expo": { "plugins": [ "@tryvital/vital-core-react-native", [ "@tryvital/vital-health-react-native", { "healthSharePermission": "Custom health share permission" } ] ] } } ``` After making changes to your Config Plugin settings, you must re-run Expo Prebuild to re-generate your iOS and Android projects. ### Bare React Native projects Install Junction SDK packages through NPM or Yarn: ```commandline theme={null} npm install @tryvital/vital-core-react-native npm install @tryvital/vital-health-react-native npm install @tryvital/vital-devices-react-native ``` Pull all dependencies for your React Native iOS project: ```commandline theme={null} cd ios pod install ``` `pod install` on your iOS project might error due to version lock conflicts on the Junction iOS libraries. This usually happens after you have bumped the React Native SDK package versions. Use `pod update` to resolve the conflict: ```commandline theme={null} pod update VitalCore VitalDevices VitalHealthKit VitalLogging --repo-update ``` ## Flutter Add Junction SDK packages to your `pubspec.yaml`: ```yaml theme={null} dependencies: vital_core: ^3.1.1 vital_health: ^3.1.1 vital_devices: ^3.1.1 ``` `pod install` on your iOS project might error due to version lock conflicts on the Junction iOS libraries. This usually happens after you have bumped the Flutter SDK package versions. Use `pod update` to resolve the conflict: ```commandline theme={null} pod update VitalCore VitalDevices VitalHealthKit --repo-update ``` # Junction Core SDK Source: https://docs.junction.com/wearables/sdks/vital-core ## Overview Check out the [SDK Authentication guide](/wearables/sdks/authentication) on the available SDK authentication schemes, and how to authenticate with the Junction Mobile SDK. Learn how to authenticate your mobile app user with Junction Mobile SDK. ## Accessing Junction API You can access Junction API through `VitalClient` with typed request methods and response models. ```swift Native iOS theme={null} // e.g. Link Service let linkService = VitalClient.shared.link ``` ```kotlin Native Android theme={null} // e.g. Link Service val linkService = VitalClient.getOrCreate(context).linkService ``` ```dart Flutter theme={null} import 'package:vital_core/vital_core.dart' as vital_core; vital_core.VitalClient client; // For customers using Junction Team API Keys for early evaluation. client = vital_core.VitalClient() ..init(region: Region.eu, environment: Environment.sandbox, apiKey: 'sk_eu_...'); // For customers using the Junction Sign-In Tokens scheme client = vital_core.VitalClient.forSignedInUser(environment: Environment.sandbox, region: Region.eu); // e.g. Link Service final linkService = client.linkService; ```
Typed API access is not yet available in React Native. ## Generating Junction Link URL You can obtain an URL to the Junction Link Widget through `VitalClient.linkWidgetUrl`. Note that the URL contains a short-lived token with 10-minute expiry. For the `redirectUrl`, you should set it to a URL with a custom URL scheme which your app would register to handle. ```swift Native iOS theme={null} let widgetUrl = await VitalClient.shared.link.createProviderLink( redirectURL: "x-vital-app://" ) ``` ```dart Flutter theme={null} Uri widgetUrl = await client.linkWidgetUrl(redirectUrl: "x-vital-app://"); ``` ## Core SDK Status You can inspect the status of the Core SDK for troubleshooting, as well as for your own business logic. For example, you may reconcile your user session and the Junction SDK sign-in based on the Core SDK status. The SDK normally reports one of the following status combinations: | Combinations | Auth Scheme | Remarks | | ------------------------------------------- | ---------------------- | -------------------------------------------------------------------------------- | | *Empty* | N/A | The SDK has neither been configured nor an active sign-in. | | `Configured` | API Key | The SDK has been configured, but does not have an active sign-in. | | `Configured`, `SignedIn` & `UseApiKey` | API Key | The active sign-in was done by setting an API Key and a target Junction User ID. | | `Configured`, `SignedIn` & `UseSignInToken` | Junction Sign-In Token | The active sign-in was done by consuming a Junction Sign-In Token. | ### Get the current status ```swift Native iOS theme={null} import VitalCore let status: VitalClient.Status = VitalClient.status ``` ```kotlin Native Android theme={null} import io.tryvital.client.VitalClient import android.content.Context val applicationContext: Context VitalClient.getOrCreate(applicationContext) val status = VitalClient.status ``` ```typescript React Native theme={null} import { VitalCore } from "@tryvital/vital-core-react-native"; await VitalCore.status(); ``` ```dart Flutter theme={null} import 'package:vital_core/vital_core.dart' as vital_core; await vital_core.clientStatus(); ``` ### Observe status changes over time ```swift Native iOS theme={null} import VitalCore // Combine Publisher let cancellable = VitalClient.statusDidChange.sink { _ in } // AsyncStream Task { for status in VitalClient.statuses { // ... } } ``` ```kotlin Native Android theme={null} import io.tryvital.client.VitalClient import android.content.Context val applicationContext: Context val statuses: Flow> statuses = VitalClient.statuses(applicationContext) val job = statuses .onEach { /** ... **/ } .launchIn(...) ``` ```typescript React Native theme={null} import { VitalCore } from "@tryvital/vital-core-react-native"; VitalCore.observeStatusChange((statuses) => { // ... }); ``` ```dart Flutter theme={null} import 'package:vital_core/vital_core.dart' as vital_core; // NOTE: This stream does not yield the current statuses on listen. Stream> statuses; statuses = vital_core.clientStatusStream; ``` ## Get Current Junction User ID Aside from the Core SDK Status, you can also query the Junction User ID of the signed-in user: ```swift Native iOS theme={null} import VitalCore let userId: String? = VitalClient.currentUserId ``` ```kotlin Native Android theme={null} import io.tryvital.client.VitalClient import android.content.Context val applicationContext: Context VitalClient.getOrCreate(applicationContext) val userId = VitalClient.currentUserId ``` ```typescript React Native theme={null} import { VitalCore } from "@tryvital/vital-core-react-native"; const userId = await VitalCore.currentUserId(); ``` ```dart Flutter theme={null} import 'package:vital_core/vital_core.dart' as vital_core; final userId = await vital_core.currentUserId(); ``` ## Reset the SDK (Sign Out) Refer to [the Sign Out section in the SDK Authentication guide](/wearables/sdks/authentication#sign-out). ## Verbose Logging to Console/Logcat You can enable verbose logging to have a deeper look into how Junction Mobile SDK is reacting to your calls, as well as all the automatic behaviours that the SDK is performing in background. To avoid any missing log entry, you should enable verbose logging as early as possible β€” preferably before any usage of any Junction Mobile SDKs. On Android, verbose logging is enabled by default when running as a debuggable build (i.e., with `ApplicationInfo.FLAG_DEBUGGABLE`). ```swift Native iOS (Swift) theme={null} import VitalCore class AppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil ) -> Bool { VitalLogger.stdOutEnabled = true // ... return true } } ``` ```objc Native iOS (Objective-C) theme={null} #import "AppDelegate.h" @import VitalCore - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { VitalLoggerObjC.stdOutEnabled = YES; // ... return YES; } ``` ```kotlin Native Android theme={null} import io.tryvital.client.utils.VitalLogger VitalLogger.getOrCreate().enabled = true ``` # Junction Devices SDK Source: https://docs.junction.com/wearables/sdks/vital-devices ## Prerequisites Your app project must declare the required permission usage. This applies equally to both native, Flutter and React Native app projects. Add the following to your `Info.plist` file: ```xml theme={null} NSBluetoothAlwaysUsageDescription Our app uses bluetooth to find, connect and transfer data between different devices ``` Bluetooth permission on iOS is automatically requested on the first scanning attempt. Add the following to your `AndroidManifest.xml`: ```xml theme={null} ``` You will have to request the appropriate permissions at in your app before you can call the Junction Devices SDK. ## Bluetooth devices ### Choose Device Model The SDK provides a list of supported Bluetooth devices. Select the matching instance to prepare for scanning. ```swift Native iOS theme={null} import VitalDevices let devicesManager = DevicesManager() /// Get the brands let brands = DevicesManager.brands() /// Each brand has devices let devices = DevicesManager.devices(for: brands[0]) ``` ```kotlin Native Android theme={null} import io.tryvital.vitaldevices.* val deviceManager = VitalDeviceManager.create(context) val deviceModels = devices(Brand.AccuChek) val deviceModel = deviceModels[0] ``` ```typescript React Native theme={null} import { VitalDevicesManager } from "@tryvital/vital-devices-react-native"; const devicesManager = new VitalDevicesManager(); let deviceModels = VitalDevicesManager.supportedDevices; let deviceModel = deviceModels[0]; ``` ```dart Flutter theme={null} import 'package:vital_devices/vital_devices.dart'; final deviceManager = DeviceManager(); final deviceModels = await deviceManager.getDevices(Brand.accuChek); final deviceModel = deviceModels[0]; ``` ### Scanning for devices First you have to scan for one of the supported devices. ```swift Native iOS theme={null} let cancellable = devicesManager .search(for: state.device) .sink { scannedDevice in print("Discovered: \(scannedDevice)") } // Stop scanning cancellable.cancel() ``` ```kotlin Native Android theme={null} val job = deviceManager .search(deviceModel) .catch { Log.i("Devices", "Error scanning ${it.message}", it.cause) } .onEach { scannedDevice -> Log.i("Devices", "Discovered ${scannedDevice}") } .launchIn(Dispatchers.Main) // Stop scanning job.cancel() ``` ```typescript React Native theme={null} let scanner = deviceManager.scanForDevice(deviceModel, { onDiscovered: (device) => { console.log("Discovered: " + device) }, onError: (error) => console.log(error) }) // Stop scanning scanner.cancel() ``` ```dart Flutter theme={null} deviceManager.scanForDevices(deviceModel).listen((newDevice) { Fimber.i("Discovered: $newDevice"); }); // Stop scanning await deviceManager.stopScan(); ``` Depending on the type of device you are connecting to, you will have to call different methods to connect to it. ### Blood Pressure Monitor ```swift Native iOS theme={null} let scannedDevice: ScannedDevice let reader = deviceManager.bloodPressureReader(for: scannedDevice) reader.read(device: scannedDevice).sink { samples in print("Read samples: \(samples)") } ``` ```kotlin Native Android theme={null} val scannedDevice: ScannedDevice val samples = deviceManager.bloodPressure(context, scannedDevice).read() ``` ```typescript React Native theme={null} let scannedDevice: ScannedDevice let samples = await devicesManager.readBloodPressure(scannedDevice.id) ``` ```dart Flutter theme={null} List bloodPressureSamples; bloodPressureSamples = await deviceManager.readBloodPressureData(scannedDevice); ``` ### Glucose Meter ```swift Native iOS theme={null} let scannedDevice: ScannedDevice let reader = deviceManager.glucoseMeter(for: scannedDevice) reader.read(device: scannedDevice).sink { samples in print("Read samples: \(samples)") } ``` ```kotlin Native Android theme={null} val scannedDevice: ScannedDevice val samples = deviceManager.glucoseMeter(context, scannedDevice).read() ``` ```typescript React Native theme={null} let scannedDevice: ScannedDevice let samples = await devicesManager.readGlucoseMeter(scannedDevice.id) ``` ```dart Flutter theme={null} List glucoseSamples; glucoseSamples = await deviceManager.readGlucoseMeterData(scannedDevice); ``` After you have received samples depending on the type of device you might need to star scanning again to receive the next set of samples. ## Freestyle Libre 1 Readings taken with the SDK are not guaranteed to match the official Freestyle Libre app. This mismatch happens due to the algorithm difference used by us, compared to the official Freestyle Libre. We currently support Libre 1 sensors via NFC readings. Please make sure you add NFC capabilities in your app: Also add the key `NFCReaderUsageDescription` in your info.plist. This key should explain why your app needs to use NFC. To use the reader: ```swift Native iOS theme={null} let reader = Libre1Reader( readingMessage: "Ready for reading", errorMessage: "Failed reading from sensor", completionMessage: "Completed successfully!", queue: mainQueue ) let reading = try await reader.read() ``` ```kotlin Native Android theme={null} import android.app.Activity import io.tryvital.vitaldevices.devices.Libre1Reader let currentActivity: Activity val reading = Libre1Reader.create(currentActivity).read() ``` ```typescript React Native theme={null} import { VitalDevicesManager } from "@tryvital/vital-devices-react-native"; let devicesManager: VitalDeviceManager let result = await devicesManager.readLibre1("reading", "errored", "completed") ``` `read()` returns a result object with two fields: 1. the sensor information; and 2. a list of glucose samples. ## Upload device samples to Junction This automatic behaviour is available since Junction iOS 1.0.0, Junction Android 3.0.0, Junction Flutter SDK 4.0.0 and Junction React Native 4.0.0. Junction Devices SDK automatically uploads any blood pressure and glucose samples discovered during the read operation, provided that the Junction Core SDK [has a signed-in user](/wearables/sdks/vital-core#core-sdk-status). The SDK does not currently buffer samples that fail to upload. You might want to ensure that the device has Internet connectivity before initiating the read operation. # Introduction Source: https://docs.junction.com/wearables/vital-app/introduction Junction Connect iOS app is available for [the Grow and Scale plans](https://tryvital.io/pricing). The app is available on the App Store, and you can download it [here](https://apps.apple.com/us/app/vital-connect/6444167196). The Junction iOS app allows your customers to quickly sync their Apple HealthKit data with you. This is quite handy if you don't have an iOS app, but you still want to analyse the data stored in the Apple Health app.
## How does it work? The flow is quite straighforward: 1. Your user, is already part of our system (e.g. they have a `userId`). If they aren't, you can create one by using our [Create User](/api-reference/user/create-user) endpoint. 2. You create a code, against a `userId`. You can use our [Create Code](/api-reference/link/create-code) endpoint for this. 3. You provide this code to your user. They will then go to the Settings and input this code. Once succesfully, their data will be synced. A Junction code can only be used once. If your user is facing issues, please ask them if they tried the code multiple times. ## Stop sharing data After the user enters the code successfully they will have the option to: 1. Stop sharing data. 2. Add other providers (e.g. Garmin, Oura). If the user stops sharing data, the app will clean the user's credentials stored locally. It will also reset the SDK. However it won't delete the connection between that user and Apple HealthKit on our servers. This means that even if the user stops sharing data, the already collected data won't be deleted. This is true for both Apple HealthKit but also for any other provider the user might be connected to (e.g. Garmin, Oura). If you want to disconnect your user from a provider, you need to call the [deregister provider endpoint](/api-reference/user/deregister-a-provider). # Debugging Source: https://docs.junction.com/webhooks/debugging You can debug webhook events in one of two ways: the Dashboard or using the Svix API. ### [Junction Dashboard](https://app.junction.com) The Junction [Dashboard](https://app.junction.com) has a Webhooks section that allows you to test and observe webhook events. # Azure Event Hubs Source: https://docs.junction.com/webhooks/etl-pipelines/azure-event-hubs ETL Pipelines integration is available for [the Scale plan](https://tryvital.io/pricing). ## Event Structure Each event is published with these [Azure EventData Properties](https://learn.microsoft.com/en-us/dotnet/api/azure.messaging.eventhubs.eventdata.properties?view=azure-dotnet#azure-messaging-eventhubs-eventdata-properties): | Type | Key | Value | | ------------- | ---------------- | ---------------------------------------------------------------------------------------------------- | | Property | event-type | Junction event type, e.g., `daily.data.activity.created` | | Property | user-id | Junction User ID of the event subject | | Property | team-id | Junction Team ID of the event subject | | Property | content-encoding | `gzip`: The blob is compressed by gzip. absent: The blob is uncompressed. | | Property | idempotency-key | The event delivery ID; only unique amongst all events from the same user and of the same event type. | | Partition Key | - | Junction User ID | | Data | - | The JSON blob. This may or may not be compressed β€” see `content-encoding`. | By default, payload smaller than 1KiB would not be compressed. You must check `content-encoding` to identify if decompression of the event payload blob is necessary. To force Junction to always compress the payload, set `compression: always` on your [Team ETL Pipeline](https://docs.tryvital.io/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines) configuration. At this time, Junction publishes exclusively JSON blobs (`blob_type == json`). Having said that, you are strongly encouraged to check for and drop any events with unrecognized `Content-Type` and `Content-Encoding`. ## Broker authentication Junction connects to your Azure Event Hub namespace through a connection URL you supplied in your [Team ETL Pipeline](https://docs.tryvital.io/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines) configuration: * It must embed a Event Hub Shared Access Signature (SAS); and * It must **not** specify an Event Hub β€” the default Event Hub should be declared separately as part of your [Team ETL Pipeline](https://docs.tryvital.io/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines) configuration. ## Configuration Org Management API is available for [the Scale plan](https://tryvital.io/pricing). You can manage your Azure Event Hub destination through the Org Management API [Team ETL Pipeline](https://docs.tryvital.io/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines) endpoints. A basic configuration would look as such: ```json theme={null} { "team_id": "TEAM_ID", "preferences": { "enabled": ["azure_amqp"], "preferred": "azure_amqp", }, "azure_amqp": { "connection_string": "Endpoint://...;...", "default_event_hub": "vital_event_ingestion" }, "push_historical_data": true } ``` ### Multiple Event Hubs You can [configure your Azure Event Hub destination](https://docs.tryvital.io/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines) to route certain events to designated Event Hubs that are *not* the default Event Hub. You can do so by declaring a list of Event Hub Matchers (JSON Path: `$.azure_amqp.event_hub_matchers`) . For every outbound event, Junction sends it to the first ever Event Hub which has a matching Event Type prefix, or the default Event Hub if none matches. ```json Example theme={null} "azure_amqp": { ..., "event_hub_matchers": [ { "event_type_prefix": "labtest.", "event_hub": "labtest-events" }, { "event_type_prefix": "daily.", "event_hub": "wearable-events" }, { "event_type_prefix": "historical.", "event_hub": "wearable-events" }, { "event_type_prefix": "provider.", "event_hub": "wearable-events" } ] } ``` ### Azure Blob Storage You can [configure your Azure Event Hub destination](https://docs.tryvital.io/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines) to route certain events to an Azure Blob Storage container. This is useful if you have enabled the Provider Raw Data firehose, which may publish raw data events beyond the 1 MiB size limit of Azure Event Hub. Junction writes each matched event as a Block Blob. The blob name follows the standard format of: ``` # uncompressed {team-id}/{user-id}/{timestamp_rfc3339}#{event-type}#{idempotency-key}.json # gzip compressed {team-id}/{user-id}/{timestamp_rfc3339}#{event-type}#{idempotency-key}.json.gz ``` You can do so by declaring an Event Hub Matcher that targets a Blob Storage container instead of an Event Hub: ```json Example theme={null} "azure_amqp": { ..., "event_hub_matchers": [ { "event_type_prefix": "raw.garmin.", "blob_storage": { "connection_string": "Endpoint://...;...", "container": "garmin-raw-data" } }, { "event_type_prefix": "raw.", "blob_storage": { "connection_string": "Endpoint://...;...", "container": "provider-raw-data" } }, { "event_type_prefix": "daily.", "event_hub": "wearable-events" }, ... ] } ``` Some polling-based providers can write 96 writes per day per user on certain event types, e.g., like `daily.data.activity.*` and `raw.fitbit.daily_activity_summary`. Azure Blob Storage typically charges based on volume and classes of operations, in addition to the retention cost. # Google Cloud Pub/Sub Source: https://docs.junction.com/webhooks/etl-pipelines/google-cloud-pubsub ETL Pipelines integration is available for [the Scale plan](https://tryvital.io/pricing). # Event Structure Each event is published with these Cloud Pub/Sub message attributes: | Type | Key | Value | | ------------ | ----------------- | ---------------------------------------------------------------------------------------------------- | | Attribute | `event-type` | Junction event type, e.g., `daily.data.activity.created` | | Attribute | `blob-type` | `json` : UTF8 JSON document | | Attribute | `blob-codec` | `gzip`: The blob is compressed by gzip. `null` or absent: The blob is uncompressed. | | Attribute | `idempotency-key` | The event delivery ID; only unique amongst all events from the same user and of the same event type. | | Ordering Key | - | Junction User ID (If you have enabled Event Ordering) | Payload smaller than 1KiB would not be compressed. You must check `blob_codec` to identify if decompression of the message payload blob is necessary. At this time, Junction publishes exclusively JSON blobs (`blob-type == json`). Having said that, you are strongly encouraged to check for and drop any events with unrecognized `blob-type` and `blob-codec`. ## Pub/Sub event ordering If you wish to receive event for each Junction user in their publication order, Junction supports Pub/Sub Event Ordering, and uses the Junction User ID as the event ordering key. Junction publishes all events through the `us-central1` and `europe-west1` PubSub regional endpoints. Pub/Sub Exactly Once Delivery is **discouraged** to be used together with Event Ordering. Our own trial suggested that the combination would struggle to move high volume of events. ## IAM Permission Grant the relevant IAM Principals on your topic with these pre-defined roles: * Pub/Sub Publisher (`roles/pubsub.publisher`) * Pub/Sub Viewer (`roles/pubsub.viewer`) Or more specifically these permissions if you wish to create a custom IAM role with a minimized grant: * `pubsub.topics.get` * `pubsub.topics.publish` | Environment | Region | Service Account | | ----------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- | | Sandbox | US | [customer-topics-us@vital-sandbox.iam.gserviceaccount.com](mailto:customer-topics-us@vital-sandbox.iam.gserviceaccount.com) | | Sandbox | Europe | [customer-topics-eu@vital-sandbox.iam.gserviceaccount.com](mailto:customer-topics-eu@vital-sandbox.iam.gserviceaccount.com) | | Production | US | [customer-topics-us@vital-prod-307415.iam.gserviceaccount.com](mailto:customer-topics-us@vital-prod-307415.iam.gserviceaccount.com) | | Production | Europe | [customer-topics-eu@vital-prod-307415.iam.gserviceaccount.com](mailto:customer-topics-eu@vital-prod-307415.iam.gserviceaccount.com) | ## Configuration Org Management API is available for [the Scale plan](https://tryvital.io/pricing). You can manage your RabbitMQ destination through the Org Management API [Team ETL Pipeline](https://docs.tryvital.io/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines) endpoints. A basic configuration would look as such: ```json theme={null} { "team_id": "TEAM_ID", "preferences": { "enabled": ["cloud_pubsub"], "preferred": "cloud_pubsub", }, "cloud_pubsub": { "project": "my-gcp-project-123456", "topic": "event-ingestion", "message_ordering": true }, "push_historical_data": true } ``` # ETL Pipelines Source: https://docs.junction.com/webhooks/etl-pipelines/overview ETL Pipelines integration is available for [the Scale plan](https://tryvital.io/pricing). With ETL Pipelines, Junction streams all [Junction events](/webhooks/event-structure) continuously to a supported destination over direct authenticated TLS connections. Compared to [Webhooks](/webhooks/introduction), this simplifies your operational complexity by removing the need to operate public unauthenticated HTTPS endpoints for receiving incoming Webhooks. Offload the pressure on your public HTTP services. Events are published through authenticated channels over TLS. Data events larger than 1 KiB are compressed before publication. Publication order can be preserved for select destination types. ## Destinations The following destinations are supported: ## Event Schema Webhooks, ETL Pipelines and our API endpoints all share the same JSON object schema. Webhooks and ETL Pipelines use identical JSON event payload structure. Check out our [Event Catalog](/event-catalog). ## Features ### Data compression Junction does not compress payload blobs that are smaller than 1 KiB. Please refer to the destination-specific documentation below on how to detect a compressed blob versus an uncompressed blob. ### Static outbound IPs Junction establish connections to your ETL Pipeline destination through a static pool of IPs. Contact Junction support to obtain the up-to-date static IP list. ### Double writing for migration To ensure smooth switchover from Webhooks to your new ETL Pipelines destination, Junction supports double writing events to the combination of: * all your Webhook endpoints; and * your ETL Pipelines destination. This is not a proper fan-out feature, and is only intended to aid customers evaluating and one-off migrating between destinations. There can only be one *preferred* destination, and the secondary destination(s) do not enjoy the same reliability guarantee as the *preferred* one. ### Pushed Historical Data When you use ETL Pipelines, Junction can *push* all the historical data as `daily.data.*` data events to your ETL Pipelines destination. The data-less `historical.data.*.created` event would still be emitted to denote the completion of historical pull. ## Configuring ETL Pipelines You can change the ETL Pipelines configuration on your Teams at any time through the [Set Team ETL Pipelines](https://docs.tryvital.io/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines) endpoint of the Org Management API. Your new configuration is typically active as soon as the endpoint has acknowledged your request. Refer to the destination-specific documentation for configuration examples. Feel free to reach out Junction support if you need assisted setup, or if you need a Junction Management Key to access the Org Management API. # RabbitMQ Source: https://docs.junction.com/webhooks/etl-pipelines/rabbitmq ETL Pipelines integration is available for [the Scale plan](https://tryvital.io/pricing). ## Event Structure Each event is published with these RabbitMQ message custom headers: | Key | Value | | ------------------ | ---------------------------------------------------------------------------- | | Routing Key | Junction event type, e.g., `daily.data.activity.created` | | `Content-Type` | application/json : UTF8 JSON document | | `Content-Encoding` | gzip: The blob is compressed by gzip. null/absent: The blob is uncompressed. | | Data | Encoded JSON blob. This may or may not be compressed β€” see blob\_codec. | Payload smaller than 1KiB would not be compressed. You must check `Content-Encoding` to identify if decompression of the message payload blob is necessary. At this time, Junction publishes exclusively JSON blobs (`blob_type == json`). Having said that, you are strongly encouraged to check for and drop any events with unrecognized `Content-Type` and `Content-Encoding`. ## Broker authentication Junction only supports password authentication at this time. Junction publishes events with the following settings: * Publisher Confirm mode is required * Events are published with the Mandatory flag but without the Immediate flag. * Events are published with Event Type as the Routing Key. Please get in touch if these need to be configured differently for your RabbitMQ exchange. ## Configuration Org Management API is available for [the Scale plan](https://tryvital.io/pricing). You can manage your RabbitMQ destination through the Org Management API [Team ETL Pipeline](https://docs.tryvital.io/api-reference/org-management/team-etl-pipeline/upsert-team-etl-pipelines) endpoints. A basic configuration would look as such: ```json theme={null} { "team_id": "TEAM_ID", "preferences": { "enabled": ["rabbitmq"], "preferred": "rabbitmq", }, "rabbitmq": { "uri": "amqps://...", "exchange": "default" }, "push_historical_data": true } ``` # Event Structure Source: https://docs.junction.com/webhooks/event-structure All our webhook events have the following standard structure: * A top-level `event_type` field, specifying the payload type contained in the `data` field. * A top-level `data` field containing the event-specific payload. * A set of top-level fields describing the event subject: * `team_id`: Your Junction Team ID. * `user_id` and `client_user_id`: The unique Junction User ID. * `client_user_id`: Your application user reference you declared for [the Junction User](/api-reference/user/create-user). Schemas and examples are available through [the Event Catalog tab in the Webhooks section of our Web app](https://app.junction.com). ```json Basic event structure theme={null} { "data": { # ... event specific data }, "event_type": "daily.data.glucose.created", "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2", "client_user_id": "01HW3FSNVCHC3B2QB5N0ZAAAVG", "team_id": "6b74423d-0504-4470-9afb-477252ccf67a" } ``` ```json Example: Provider connection created theme={null} { "data": { "source": { "logo": "https://storage.googleapis.com/vital-assets/freestyle.png", "name": "Freestyle Libre BLE", "slug": "freestyle_libre_ble" }, "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2" }, "event_type": "provider.connection.created", "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2", "client_user_id": "01HW3FSNVCHC3B2QB5N0ZAAAVG", "team_id": "6b74423d-0504-4470-9afb-477252ccf67a" } ``` ## Wearable data events Wearable data events have the following extended standard structure: * An `event_type` field for differentiating the payload contained in the data field. * A `data` field, which comprises of: * The UUID of the Junction user for which this event is intended β€” note that this is not the `client_user_id` of the Junction user. (JSON path: `$.data.user_id`) * The metadata about the data source of this event (JSON path: `$.data.source`) * `name`: The human-readable name of the source * `slug`: The stable identifier Junction assigned to the source β€” see [Supported providers](/wearables/providers/introduction) for the complete list. * `logo`: The URL to an icon representing the source. * Any number of event-specific fields. ```json Wearable data event structure theme={null} { "data": { # ... event specific fields # Common wearable data event fields "source": { "logo": "https://path/to/example", "name": "Fitbit", "slug": "fitbit" }, "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2" }, "event_type": "daily.data.glucose.created", "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2", "client_user_id": "01HW3FSNVCHC3B2QB5N0ZAAAVG", "team_id": "6b74423d-0504-4470-9afb-477252ccf67a" } ``` ### Summary types Each time a [summary object](/wearables/providers/resources) is created or updated, Junction emits a data event for the occurrence. The object is made available directly at the top-level `data` field (JSON path: `$.data`), alongside the standard fields listed above. Each summary is uniquely identified by its ID (`$.id`). Given the same ID, the latest version of a summary you received **replaces** all its previous versions. ```json Summary data event structure theme={null} { "data": { # ... All summary object fields # Common wearable data event fields "source": { "logo": "https://path/to/example", "name": "Fitbit", "slug": "fitbit" }, "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2" }, "event_type": "daily.data.activity.created", "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2", "client_user_id": "01HW3FSNVCHC3B2QB5N0ZAAAVG", "team_id": "6b74423d-0504-4470-9afb-477252ccf67a" } ``` ```json Example: Activity updated theme={null} { "data": { "calendar_date": "2023-05-17", "calories_active": 134, "calories_total": 1006, "daily_movement": 1162, "date": "2023-05-17T00:00:00+00:00", "distance": 1162, "floors_climbed": 2, "high": null, "id": "715dd6a5-0c67-471c-b5fe-9681a980a2c2", "low": null, "medium": null, "steps": 1496, "timezone_offset": 3600, # Common wearable data event fields "source": { "logo": "https://storage.googleapis.com/vital-assets/apple_health.png", "name": "Apple HealthKit", "slug": "apple_health_kit" }, "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2" }, "event_type": "daily.data.activity.updated", "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2", "client_user_id": "01HW3FSNVCHC3B2QB5N0ZAAAVG", "team_id": "6b74423d-0504-4470-9afb-477252ccf67a" } ``` ### Timeseries types (except for Workout Streams) Each time a batch of [timeseries samples](/wearables/providers/resources) has been created or updated, Junction emits a data event for the batch of samples. The samples are made available under an inner `data` field under the top-level `data` field (JSON path: `$.data.data`). Each timeseries sample is uniquely identified by a compound key of: * the timeseries resource type; * the source provider; * the [Source Type](/wearables/providers/data-attributions#source-type); and * the sample timestamp. Given the same compound key, the latest value of a sample you received **replaces** all the previous values. While most timeseries data are immutable, there have been exceptions of some sources aggregating some data types using time bucketing, and some may even send updates ASAP on time buckets that have incomplete data. For example, activity timeseries data from Apple HealthKit (integration in production) and Garmin (integration planned) do exhibit such behaviour.

These exceptions are the rationale behind the recommended deduplication semantic as stated above.
```json Timeseries data event structure theme={null} { "data": { "data": [ { "timestamp": "2023-05-16T10:00:00+00:00", "timezone_offset": 3600, "type": null, "unit": "count", "value": 237 }, { "timestamp": "2023-05-16T11:00:00+00:00", "timezone_offset": 3600, "type": null, "unit": "count", "value": 451 }, # ... other timeseries samples in the batch ], "source_id": 16, # Common wearable data event fields "source": { "logo": "https://storage.googleapis.com/vital-assets/apple_health.png", "name": "Apple HealthKit", "slug": "apple_health_kit" }, "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2" }, "event_type": "daily.data.steps.created", "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2", "client_user_id": "01HW3FSNVCHC3B2QB5N0ZAAAVG", "team_id": "6b74423d-0504-4470-9afb-477252ccf67a" } ``` ```json Example: Calories Active created theme={null} { "data": { "data": [ { "timestamp": "2023-05-17T07:00:00+00:00", "start": "2023-05-17T07:00:00+00:00", "end": "2023-05-17T08:00:00+00:00", "timezone_offset": 3600, "type": "", "unit": "kcal", "value": 39.518054167148996 }, { "timestamp": "2023-05-17T08:00:00+00:00", "start": "2023-05-17T08:00:00+00:00", "end": "2023-05-17T09:00:00+00:00", "timezone_offset": 3600, "type": "", "unit": "kcal", "value": 31.270257990822234 }, { "timestamp": "2023-05-17T09:00:00+00:00", "start": "2023-05-17T09:00:00+00:00", "end": "2023-05-17T09:01:00+00:00", "timezone_offset": 3600, "type": "", "unit": "kcal", "value": 5.733999999999999 } ], "source_id": 16, # Common wearable data event fields "source": { "logo": "https://storage.googleapis.com/vital-assets/apple_health.png", "name": "Apple HealthKit", "slug": "apple_health_kit" }, "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2" }, "event_type": "daily.data.calories_active.created", "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2", "client_user_id": "01HW3FSNVCHC3B2QB5N0ZAAAVG", "team_id": "6b74423d-0504-4470-9afb-477252ccf67a" } ``` ### Workout Streams While Workout Streams are classified as a timeseries type, it contains a huge amount of timeseries data which cannot be delivered by our webhook message transport. So events for Webhook Streams are shallow, with only some minimal metadata about the workout. Successful reception of the event indicates that the data are available to be read via our REST API, e.g., [the Workout Stream endpoint](/api-reference/workouts/get-stream) `GET /v2/timeseries/workouts/{workout_id}/stream`. ```json Workout Stream created theme={null} { "data": { "message": "Due to payload size limits, to access the workout stream, please use the /workouts/edc80dd0-8cc8-4ec8-8b80-931fd9a3309a/stream endpoint.", "provider_id": "51278201336", "sport": { "id": 57, "name": "Other", "slug": "other" }, "workout_id": "fdc70dd0-8cc8-4ec8-8b80-121fd9a3302a" # Common wearable data event fields "source": { "logo": "https://storage.googleapis.com/vital-assets/fitbit.png", "name": "Fitbit", "slug": "fitbit" }, "user_id": "bf6222fb-3b81-4201-9630-118bfee01e03", }, "event_type": "daily.data.workout_stream.created", "user_id": "4a29dbc7-6db3-4c83-bfac-70a20a4be1b2", "client_user_id": "01HW3FSNVCHC3B2QB5N0ZAAAVG", "team_id": "6b74423d-0504-4470-9afb-477252ccf67a" } ``` # Introduction Source: https://docs.junction.com/webhooks/introduction With Webhooks and [ETL Pipelines](/webhooks/etl-pipelines/overview), Junction pushes any new data or updates to your endpoint or your ETL destination as soon as they are discovered. ## Connection Created stage When a user successfully connects a provider through [Junction Link](/wearables/connecting-providers/introduction), Junction sends a Provider Connection Created event to acknowledge it: `provider.connection.created` Check out the [Provider Connection Created event](/event-catalog/provider.connection.created) in the Event Catalog. ```json Webhook Event theme={null} { "event_type": "provider.connection.created", "data": { "user_id": "4eca10f9-...", "provider": { "name": "Strava", "slug": "strava", "logo": "https://storage.googleapis.com/vital-assets/strava.png" }, "resource_availability": { ... } } } ``` ## Historical Data Backfill stage Junction also schedules jobs to backfill *historical* data upon connection establishment. Historical data are data that exist prior to the connection establishment. Because this can [amount to months of data](/wearables/providers/introduction#historical-data-pull-range) β€” and might potentially run into temporary provider-side API rate limits β€” these backfill jobs may take minutes to hours to complete. You can inspect the status of historical data backfill jobs of each individual user through [Junction Dashboard](https://app.junction.com) or [Junction API](/api-reference/data/introspection/historical-pulls). Once the historical data backfill job of a specific [resource](/wearables/providers/resources) has **fully** completed, Junction sends a **Historical Pull Completion** event for the resource: `historical.data.{RESOURCE}.created` This event is a data-less notification. Use the [Junction Data Access API](/api-reference/data/sleep/get-summary) to fetch the resource you are interested in. Constrain your API call using the information in the Historical Pull Completion event. For example, the event includes the [provider](/wearables/providers/introduction) slug as well as full datetime range which Junction has looked through. Check out the [Historical Workout Data Pull Completion event](/event-catalog/historical.data.workouts.created) in the Event Catalog. ```json theme={null} { "event_type": "historical.data.workouts.created", "data": { "user_id": "e9e072e8-...", "start_date": "2020-06-21T08:23:01+00:00", "end_date": "1996-11-02T14:39:28+00:00", "provider": "zwift" } } ``` [ETL Pipelines](/webhooks/etl-pipelines/overview) can opt-in to receive historical data as *Vital Data Events* (as outlined below in the [Incremental Data stage](#incremental-data-stage) section). Note that Junction would still send the Historical Pull Completion event to signal the completion of the historical data backfill stage. ## Incremental Data stage Once the historical data backfill stage completes, Junction [monitors for new data and changes](/wearables/providers/introduction#data-frequency). Whenever new data or changes are discovered, Junction sends them out as **Data Events** as soon as possible: You can inspect the state of incremental data updates of each individual user through [Junction Dashboard](https://app.junction.com) or [Junction API](/api-reference/data/introspection/user-resources). * Initial discovery β€” `daily.data.{RESOURCE}.created` * Subsequent updates β€” `daily.data.{RESOURCE}.updated` Treat the `daily.data` prefix as a misnomer for *Incremental Data*. It **does not** imply a daily delivery schedule. Junction sends out *Data Events* as soon as we discover new or updated entries as reported by a provider. This also implies: 1. Junction does not prioritize or aggregate Data Events across providers: * You may receive multiple events of the same type from a single provider, because the provider supports multiple sources, devices or apps (e.g., iPhone sleeps and Apple Watch sleeps from Apple HealthKit); * You may receive multiple events of the same type from multiple providers, because the user has connected to multiple providers. 2. There is no standard Data Event delivery frequency or schedule across providers *and* resources: * Some may be sent only once per day; * Some may be sent multiple times at irregular times; * Some may appear to be sent on a regular schedule throughout the day. Your system typically would ingest relevant Junction Data Events into a database. You can query the database with any business rules and query constraints suitable to your product experience. This may include your own data prioritization rules based on, e.g., [Source Type](/wearables/providers/data-attributions#source-type). Junction also offers the [Junction Sense Aggregation API](/sense/overview) for your data aggregation and consolidation needs. Check out the [Workout Created event](/event-catalog/daily.data.workouts.created) in the Event Catalog. The event structure and deduplication semantic of [summary types](/wearables/providers/resources) and [timeseries types](/wearables/providers/resources) have a few key differences. Please refer to [Event Structure](/webhooks/event-structure) for a more in-depth discussion. ```json theme={null} { "event_type": "daily.data.workouts.created", "data": { "id": "c90bd6fb-...", "average_hr": 198, "distance": 8544, "time_start": "2012-11-24T22:57:01+00:00", "time_end": "2017-04-22T04:57:31+00:00", "calories": 4470, "hr_zones": [8279], "user_id": "ab3247dc-...", ..., "provider_id": "dolor ipsum reiciendis Lorem veniam elit. esse", "source": { "provider": "zwift", "type": "unknown" } } } ``` ## Deep Dive into Junction Webhooks