Junction Link API covers only 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, separately from Junction Link.
- Get a list of all available providers “/v2/providers”
- Build your UI using this list of providers
- When selecting a provider, generate a link token for that provider “/v2/link/token”
- 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. This will return a list of all available providers. You can then use this list to build your UI.import { JunctionClient, JunctionEnvironment } from "@junction-api/sdk";
const client = new JunctionClient({
apiKey: "YOUR_API_KEY",
environment: JunctionEnvironment.Sandbox,
});
const allProviders = await client.providers.getAll();
// allProviders
// [
// {
// "name": "Oura",
// "slug": "oura",
// "logo": "https://logo_url.com",
// "auth_type": "oauth",
// "supported_resources": ["workouts", "sleep"]
// }
// ]
from junction import Junction
from junction.environment import JunctionEnvironment
client = Junction(
api_key="YOUR_API_KEY",
environment=JunctionEnvironment.SANDBOX,
)
all_providers = client.providers.get_all()
# allProviders
# [
# {
# "name": "Oura",
# "slug": "oura",
# "logo": "https://logo_url.com",
# "auth_type": "oauth",
# "supported_resources": ["workouts", "sleep"]
# }
# ]
import com.junction.api.Junction;
import com.junction.api.core.Environment;
Junction client = Junction.builder()
.apiKey("YOUR_API_KEY")
.environment(Environment.SANDBOX)
.build();
var allProviders = client.providers().getAll();
import (
"context"
junction "github.com/junction-api/junction-go"
"github.com/junction-api/junction-go/client"
"github.com/junction-api/junction-go/option"
)
c := client.NewClient(
option.WithApiKey("YOUR_API_KEY"),
option.WithBaseURL(junction.Environments.Sandbox),
)
response, err := c.Providers.GetAll(context.TODO(), nil)
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 thelogo 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.

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.import { JunctionClient, JunctionEnvironment } from "@junction-api/sdk";
const client = new JunctionClient({
apiKey: "YOUR_API_KEY",
environment: JunctionEnvironment.Sandbox,
});
const tokenResponse = await client.link.token({ userId: "<user_id>" });
from junction import Junction
from junction.environment import JunctionEnvironment
client = Junction(
api_key="YOUR_API_KEY",
environment=JunctionEnvironment.SANDBOX,
)
token_response = client.link.token(user_id="<user_id>")
import com.junction.api.Junction;
import com.junction.api.core.Environment;
import com.junction.api.resources.link.requests.LinkTokenExchange;
Junction client = Junction.builder()
.apiKey("YOUR_API_KEY")
.environment(Environment.SANDBOX)
.build();
var tokenResponse = client.link().token(
LinkTokenExchange.builder()
.userId("<user_id>")
.build()
);
import (
"context"
junction "github.com/junction-api/junction-go"
"github.com/junction-api/junction-go/client"
"github.com/junction-api/junction-go/option"
)
c := client.NewClient(
option.WithApiKey("YOUR_API_KEY"),
option.WithBaseURL(junction.Environments.Sandbox),
)
response, err := c.Link.Token(context.TODO(), &junction.LinkTokenExchange{
UserId: "<user_id>",
})
if err != nil {
return err
}
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
import { JunctionClient, JunctionEnvironment } from "@junction-api/sdk";
const client = new JunctionClient({
apiKey: "YOUR_API_KEY",
environment: JunctionEnvironment.Sandbox,
});
const tokenResponse = await client.link.token({ userId: "<user_id>" });
const auth = await client.link.generateOauthLink({
oauthProvider: "oura",
vitalLinkToken: tokenResponse.linkToken,
});
from junction import Junction, OAuthProviders
from junction.environment import JunctionEnvironment
client = Junction(
api_key="YOUR_API_KEY",
environment=JunctionEnvironment.SANDBOX,
)
token_response = client.link.token(user_id="<user_id>")
oauth = client.link.generate_oauth_link(
OAuthProviders.OURA,
vital_link_token=token_response.link_token,
)
import com.junction.api.Junction;
import com.junction.api.core.Environment;
import com.junction.api.resources.link.requests.GenerateOauthLinkLinkRequest;
import com.junction.api.resources.link.requests.LinkTokenExchange;
import com.junction.api.types.OAuthProviders;
Junction client = Junction.builder()
.apiKey("YOUR_API_KEY")
.environment(Environment.SANDBOX)
.build();
var tokenResponse = client.link().token(
LinkTokenExchange.builder()
.userId("<user_id>")
.build()
);
var oauth = client.link().generateOauthLink(
OAuthProviders.OURA,
GenerateOauthLinkLinkRequest.builder()
.vitalLinkToken(tokenResponse.getLinkToken())
.build()
);
import (
"context"
junction "github.com/junction-api/junction-go"
"github.com/junction-api/junction-go/client"
"github.com/junction-api/junction-go/option"
)
c := client.NewClient(
option.WithApiKey("YOUR_API_KEY"),
option.WithBaseURL(junction.Environments.Sandbox),
)
tokenResponse, err := c.Link.Token(context.TODO(), &junction.LinkTokenExchange{
UserId: "<user_id>",
})
if err != nil {
return err
}
oauth, err := c.Link.GenerateOauthLink(context.TODO(), &junction.GenerateOauthLinkLinkRequest{
OauthProvider: junction.OAuthProvidersOura,
VitalLinkToken: &tokenResponse.LinkToken,
})
if err != nil {
return err
}
fmt.Printf("Received data %s\n", oauth)
Connect an Email/Password provider
import { JunctionClient, JunctionEnvironment } from "@junction-api/sdk";
const client = new JunctionClient({
apiKey: "YOUR_API_KEY",
environment: JunctionEnvironment.Sandbox,
});
const data = await client.link.connectPasswordProvider({
provider: "whoop",
username: "<username>",
password: "<password>",
});
from junction import Junction, PasswordProviders
from junction.environment import JunctionEnvironment
client = Junction(
api_key="YOUR_API_KEY",
environment=JunctionEnvironment.SANDBOX,
)
data = client.link.connect_password_provider(
PasswordProviders.WHOOP,
username="<username>",
password="<password>",
)
import com.junction.api.Junction;
import com.junction.api.core.Environment;
import com.junction.api.resources.link.requests.IndividualProviderData;
import com.junction.api.types.PasswordProviders;
Junction client = Junction.builder()
.apiKey("YOUR_API_KEY")
.environment(Environment.SANDBOX)
.build();
var data = client.link().connectPasswordProvider(
PasswordProviders.WHOOP,
IndividualProviderData.builder()
.username("<username>")
.password("<password>")
.build()
);
import (
"context"
junction "github.com/junction-api/junction-go"
"github.com/junction-api/junction-go/client"
"github.com/junction-api/junction-go/option"
)
c := client.NewClient(
option.WithApiKey("YOUR_API_KEY"),
option.WithBaseURL(junction.Environments.Sandbox),
)
response, err := c.Link.ConnectPasswordProvider(context.TODO(), &junction.IndividualProviderData{
Provider: junction.PasswordProvidersWhoop,
Username: "<username>",
Password: "<password>",
})
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.React Native
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 (
<Pressable onPress={() => onPress()}>
{({isHovered, isFocused, isPressed}) => {
return (
<HStack space={'md'} justifyContent="flex-start">
<VStack>
<Text color={colors.text} fontType="medium">
{item.name}
</Text>
<Text
fontType="regular"
color={colors.secondary}
size="xs"
flexShrink={1}
flexWrap="wrap">
{item.description}
</Text>
</VStack>
</HStack>
);
}}
</Pressable>
);
};
export const LinkDeviceScreen = ({navigation}) => {
const isDarkMode = useColorScheme() === 'dark';
const {colors} = useTheme();
const styles = makeStyles(colors);
const [providers, setProviders] = React.useState<Provider[]>([]);
const [devices, setDevices] = React.useState<Provider[]>([]);
const [loading, setLoading] = React.useState<boolean>(false);
const [searchText, setSearchText] = React.useState('');
const [error, setError] = React.useState<string | null>(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 (
<SafeAreaView style={styles.container}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={styles.container.backgroundColor}
/>
<View
style={{
paddingVertical: 10,
paddingHorizontal: 16,
flex: 1,
width: '100%',
}}>
<VStack pb={10}>
<HStack
justifyContent={'space-between'}
py={'$3'}
alignItems="center">
<H1>Connect a Device</H1>
<Button onPress={() => navigation.goBack()} variant="link">
<Ionicons name="close-outline" size={25} color={colors.text} />
</Button>
</HStack>
<Input
variant="outline"
size="lg"
isDisabled={false}
isInvalid={false}
isReadOnly={false}>
<InputField
color={colors.text}
fontFamily={AppConfig.fonts.regular}
onChangeText={handleSearch}
value={searchText}
placeholder="Search"
/>
</Input>
</VStack>
{!loading && error ? (
<VStack>
<Text fontType="light">Failed to get supported Providers</Text>
</VStack>
) : (
<FlatList
data={devices}
renderItem={({item}) => (
<ListItem userId={userId} item={item} navigation={navigation} />
)}
keyExtractor={item => item.slug}
/>
)}
</View>
</SafeAreaView>
);
};