Integrate Garmin Connect with your V0-generated Next.js app using the Garmin Health API and OAuth 1.0a authentication. Create a server-side API route that handles the OAuth flow, exchanges tokens, and fetches user activity, sleep, and wellness data. Store your Garmin consumer key and secret in Vercel environment variables to build fitness dashboards displaying GPS activities, heart rate, steps, and sleep scores.
Building a Garmin Fitness Dashboard with V0 and the Garmin Health API
Garmin is the leading brand for GPS sports watches and fitness trackers, with a loyal user base of runners, cyclists, triathletes, and outdoor adventurers. The Garmin Health API gives approved developers access to rich activity data: GPS tracks, pace, heart rate zones, VO2 max estimates, sleep stages, stress scores, and Body Battery energy levels. If you are building a fitness coaching app, training analytics platform, or athlete management system, integrating Garmin Connect gives you access to the most detailed sports tracking data available.
The Garmin Health API uses OAuth 1.0a — an older authentication standard that requires HMAC-SHA1 signed requests. This is more involved than modern OAuth 2.0 but follows a predictable three-step flow: request a temporary token, redirect the user to authorize on Garmin's website, then exchange the verifier for a persistent access token. Once you have an access token, you can fetch data on behalf of that user indefinitely (until they revoke access).
V0 handles the UI: activity cards with map previews, pace/HR charts, weekly mileage summaries, and sleep score panels. The Next.js API routes handle the authentication flow and data fetching. Note that Garmin requires developer program approval before granting API access — this process typically takes a few days and involves agreeing to Garmin's data use terms.
Integration method
Garmin Health API uses OAuth 1.0a for authentication, which is more complex than modern OAuth 2.0. Integration requires a Next.js API route that handles the three-legged OAuth flow: requesting a temporary token, redirecting the user to Garmin for authorization, then exchanging the verifier for an access token. All API calls are server-side using credentials stored in Vercel environment variables.
Prerequisites
- A V0 account at v0.dev with a Next.js project created
- An approved Garmin Developer Program account — apply at developer.garmin.com (approval may take several days)
- Your Garmin Health API consumer key and consumer secret from the Garmin Developer portal
- A Vercel account with your V0 project deployed (OAuth requires a live callback URL)
- A Garmin Connect account with a device and activity history for testing
Step-by-step guide
Generate the Fitness Dashboard UI with V0
Generate the Fitness Dashboard UI with V0
Start in V0 and describe the fitness dashboard layout you want to build. Request components for different data types: an activity feed with cards showing activity type, distance, duration, and date; a metrics summary showing weekly totals; charts for heart rate and pace over time; and a sleep panel with sleep score and stage breakdown. V0 will generate React components with Tailwind styling and appropriate data visualization placeholders. Ask for loading skeleton states for all data-heavy sections since Garmin API calls have meaningful latency. Request TypeScript interfaces for activity and health data so the components are fully typed. A good design pattern for a fitness dashboard is a two-column layout with a summary sidebar and a main content area with the activity feed. Ask V0 to generate chart components using recharts (a popular library V0 knows well) for pace and heart rate visualizations. Make the dashboard responsive so it works on both desktop and mobile — athletes often check their dashboards on phones.
Create a fitness dashboard with a dark sidebar showing total stats (weekly distance, weekly time, average pace this week) and a main content area with an activities feed. Each activity card shows: sport icon (run/bike/swim), activity name, date, distance in km, pace or speed, and heart rate. Add a sleep section with last 7 nights as a horizontal bar chart with color coding (deep=dark blue, light=light blue, REM=purple). Use a dark charcoal theme with teal (#00B8D9) accents.
Paste this in V0 chat
Pro tip: Garmin data uses metric units (meters, seconds) internally. Your V0 component should handle unit conversion for display — miles vs km, and formatted time strings from raw seconds.
Expected result: A fitness dashboard UI renders in V0 preview with activity cards, metrics summaries, and chart placeholder areas.
Implement OAuth 1.0a Authentication Routes
Implement OAuth 1.0a Authentication Routes
Garmin's Health API uses OAuth 1.0a, which requires signing every request with HMAC-SHA1. This is more complex than OAuth 2.0 but the pattern is consistent. You need three API routes: one to initiate the OAuth flow, one as the callback handler, and one utility for signing requests. The OAuth 1.0a flow works as follows: first, your app makes a signed request to Garmin's request token endpoint to get a temporary token. Then, you redirect the user to Garmin's authorization page with that token. After the user grants permission, Garmin redirects back to your callback URL with a verifier code. Your callback route exchanges the request token and verifier for a persistent access token that you store (typically in a database or encrypted cookie) for future API calls. Install the oauth-1.0a npm package (a lightweight library specifically for this) to handle the signature generation, which is complex to implement manually. The oauth-1.0a package handles the nonce generation, timestamp formatting, and HMAC-SHA1 signing required by the protocol.
1// app/api/garmin/auth/route.ts2import { NextResponse } from 'next/server';3import OAuth from 'oauth-1.0a';4import crypto from 'crypto';56const oauth = new OAuth({7 consumer: {8 key: process.env.GARMIN_CONSUMER_KEY!,9 secret: process.env.GARMIN_CONSUMER_SECRET!,10 },11 signature_method: 'HMAC-SHA1',12 hash_function(base_string, key) {13 return crypto.createHmac('sha1', key).update(base_string).digest('base64');14 },15});1617export async function GET() {18 try {19 const requestTokenUrl = 'https://connectapi.garmin.com/oauth-service/oauth/request_token';20 const callbackUrl = `${process.env.NEXT_PUBLIC_APP_URL}/api/garmin/callback`;2122 const requestData = {23 url: requestTokenUrl,24 method: 'POST',25 data: { oauth_callback: callbackUrl },26 };2728 const headers = oauth.toHeader(oauth.authorize(requestData));2930 const response = await fetch(requestTokenUrl, {31 method: 'POST',32 headers: {33 ...headers,34 'Content-Type': 'application/x-www-form-urlencoded',35 },36 body: `oauth_callback=${encodeURIComponent(callbackUrl)}`,37 });3839 const responseText = await response.text();40 const params = new URLSearchParams(responseText);41 const requestToken = params.get('oauth_token');4243 if (!requestToken) {44 return NextResponse.json({ error: 'Failed to get request token' }, { status: 500 });45 }4647 const authUrl = `https://connect.garmin.com/oauthConfirm?oauth_token=${requestToken}`;48 return NextResponse.redirect(authUrl);49 } catch (error) {50 console.error('Garmin OAuth init error:', error);51 return NextResponse.json({ error: 'OAuth initialization failed' }, { status: 500 });52 }53}Pro tip: Install the oauth-1.0a package: run npm install oauth-1.0a in your project. This handles the complex HMAC-SHA1 signing required by OAuth 1.0a without you writing the cryptographic logic manually.
Expected result: Navigating to /api/garmin/auth redirects the user to the Garmin Connect authorization page.
Handle the OAuth Callback and Fetch Activity Data
Handle the OAuth Callback and Fetch Activity Data
Create the OAuth callback route at app/api/garmin/callback/route.ts that receives the oauth_token and oauth_verifier parameters from Garmin after the user authorizes your app. This route exchanges those credentials for a persistent access token by calling Garmin's access token endpoint. Store the returned oauth_token and oauth_token_secret securely — in a real application, save these to your database associated with the user's account. For this tutorial, you can temporarily store them in an encrypted HTTP-only cookie. Once you have the access token, create the activity fetching route at app/api/garmin/activities/route.ts. This route reads the stored access token, signs a request to the Garmin Health API activities endpoint, and returns the activity list to your frontend. The Garmin Health API activity list endpoint is at https://healthapi.garmin.com/wellness-api/rest/activities. Each activity includes activityType, startTimeInSeconds, durationInSeconds, distanceInMeters, averageHeartRateInBeatsPerMinute, and other detailed metrics. Convert the raw values to user-friendly units in your API route before returning them.
1// app/api/garmin/activities/route.ts2import { NextRequest, NextResponse } from 'next/server';3import OAuth from 'oauth-1.0a';4import crypto from 'crypto';56const oauth = new OAuth({7 consumer: {8 key: process.env.GARMIN_CONSUMER_KEY!,9 secret: process.env.GARMIN_CONSUMER_SECRET!,10 },11 signature_method: 'HMAC-SHA1',12 hash_function(base_string, key) {13 return crypto.createHmac('sha1', key).update(base_string).digest('base64');14 },15});1617export async function GET(request: NextRequest) {18 // In a real app, load these from your database for the authenticated user19 const accessToken = request.cookies.get('garmin_access_token')?.value;20 const accessSecret = request.cookies.get('garmin_access_secret')?.value;2122 if (!accessToken || !accessSecret) {23 return NextResponse.json({ error: 'Not authenticated with Garmin' }, { status: 401 });24 }2526 try {27 const uploadStartTimeInSeconds = Math.floor(Date.now() / 1000) - 30 * 24 * 60 * 60; // 30 days ago28 const uploadEndTimeInSeconds = Math.floor(Date.now() / 1000);2930 const activitiesUrl = `https://healthapi.garmin.com/wellness-api/rest/activities?uploadStartTimeInSeconds=${uploadStartTimeInSeconds}&uploadEndTimeInSeconds=${uploadEndTimeInSeconds}`;3132 const requestData = { url: activitiesUrl, method: 'GET' };33 const token = { key: accessToken, secret: accessSecret };34 const headers = oauth.toHeader(oauth.authorize(requestData, token));3536 const response = await fetch(activitiesUrl, { headers });3738 if (!response.ok) {39 return NextResponse.json(40 { error: `Garmin API error: ${response.status}` },41 { status: response.status }42 );43 }4445 const data = await response.json();46 return NextResponse.json({ activities: data });47 } catch (error) {48 console.error('Garmin activities error:', error);49 return NextResponse.json({ error: 'Failed to fetch activities' }, { status: 500 });50 }51}Pro tip: Garmin's Health API requires time range parameters for activity queries. Always specify uploadStartTimeInSeconds and uploadEndTimeInSeconds — queries without time ranges may return errors or empty results.
Expected result: After OAuth authorization, /api/garmin/activities returns a list of recent Garmin activities with distance, duration, and heart rate data.
Add Environment Variables in Vercel
Add Environment Variables in Vercel
In the Vercel Dashboard, go to your project and navigate to Settings → Environment Variables. Add GARMIN_CONSUMER_KEY and GARMIN_CONSUMER_SECRET — both found in the Garmin Developer Portal under your registered application. Also add NEXT_PUBLIC_APP_URL, which is your Vercel deployment URL (e.g., https://your-app.vercel.app) — this one needs the NEXT_PUBLIC_ prefix because it is used to construct the callback URL in the auth route, and that string is not secret. In the Garmin Developer Portal, register your OAuth callback URL: add https://your-app.vercel.app/api/garmin/callback to your application's allowed callback URLs. Garmin requires that callback URLs be registered in advance — if the callback URL in your request does not match a registered URL, authorization will fail. For local development, register http://localhost:3000/api/garmin/callback as a separate allowed callback URL. After adding all environment variables in Vercel, redeploy your project. Garmin Consumer credentials are permanent once granted — they do not expire like some OAuth 2.0 access tokens, but they can be revoked through the Developer Portal.
Pro tip: Test the OAuth flow on your Vercel deployment rather than localhost — Garmin's OAuth server can behave differently on production domains, and your callback URL must match exactly what is registered in the Developer Portal.
Expected result: Environment variables are set in Vercel, the callback URL is registered in the Garmin Developer Portal, and the full OAuth flow completes successfully.
Common use cases
Personal Fitness Dashboard
An amateur triathlete builds a personal training dashboard with V0 that aggregates their Garmin activities, showing weekly training load, recent runs and rides with pace charts, and 7-day sleep score history.
Create a fitness dashboard with a weekly summary card showing total distance, elevation, and time for the week. Below, a recent activities list with activity type icon, name, date, distance, and average pace. Add a sleep quality panel showing last 7 days as a bar chart. Include a Body Battery level indicator. Fetch data from /api/garmin/activities and /api/garmin/sleep.
Copy this prompt to try it in V0
Coaching Platform with Athlete Monitoring
A running coach builds a platform where athletes connect their Garmin accounts. The coach dashboard shows all connected athletes with their weekly training loads, recent race performances, and recovery status.
Build an athlete roster page with athlete cards showing name, profile photo, latest activity type and date, current week's training load, and recovery score badge (high/medium/low). Add a click-through to athlete detail page with 30-day activity calendar and trend charts.
Copy this prompt to try it in V0
Race Results Integration
A running club website uses V0 to display members' race performances fetched directly from their Garmin data, showing times, pace, and heart rate data from officially recorded race events.
Create a race results page with a leaderboard table showing member name, race event, distance, finish time, and average pace. Include filters for race distance and date range. Add an expandable row that shows a pace chart and heart rate zone breakdown for each run. Fetch from /api/garmin/activities?type=running.
Copy this prompt to try it in V0
Troubleshooting
OAuth redirect shows 'Invalid Consumer Key' on Garmin's authorization page
Cause: The GARMIN_CONSUMER_KEY environment variable is incorrect or the Garmin Developer application is not yet approved.
Solution: Verify the consumer key in the Garmin Developer Portal under your application settings. Ensure your Developer Program application has been approved — the approval process can take several business days after applying.
OAuth callback returns 'oauth_problem=permission_denied' in the URL
Cause: The user denied permission on the Garmin authorization page, or the callback URL is not registered in the Garmin Developer Portal.
Solution: Register your exact callback URL (including the path /api/garmin/callback) in the Garmin Developer Portal. Handle the denied case in your callback route by redirecting to an error page with a friendly message.
Health API returns 403 Forbidden after successful OAuth
Cause: Your Garmin application does not have the required Health API permissions, or you are using the wrong base URL (connectapi.garmin.com for OAuth, healthapi.garmin.com for data endpoints).
Solution: Ensure your Garmin Developer application has been granted Health API access in the Developer Portal. Use healthapi.garmin.com as the base URL for activity and wellness data endpoints, and connectapi.garmin.com only for OAuth token endpoints.
oauth-1.0a package fails with 'Module not found' in Next.js
Cause: The oauth-1.0a npm package may need to be added to your project's package.json dependencies.
Solution: Run npm install oauth-1.0a in your project directory. If the package has CommonJS/ESM compatibility issues with Next.js App Router, add it to the transpilePackages array in next.config.js.
1// next.config.js2module.exports = {3 transpilePackages: ['oauth-1.0a'],4};Best practices
- Store Garmin access tokens (after OAuth) in your database associated with user accounts rather than in cookies, for production apps
- Use time range parameters (uploadStartTimeInSeconds and uploadEndTimeInSeconds) on all Garmin API queries to limit response size
- Store GARMIN_CONSUMER_KEY and GARMIN_CONSUMER_SECRET without the NEXT_PUBLIC_ prefix in Vercel environment variables
- Register both production and development callback URLs in the Garmin Developer Portal before starting development
- Convert Garmin's raw metric units (meters, seconds) to user-preferred units (km/miles, formatted time) in your API route before returning to the frontend
- Cache activity responses for at least 5 minutes since Garmin data does not change in real time and API calls are rate-limited
- Handle the case where users have not connected their Garmin account by showing a clear 'Connect Garmin' button that initiates the OAuth flow
Alternatives
Fitbit API uses modern OAuth 2.0 authentication (easier to implement) and focuses more on everyday wellness metrics, while Garmin emphasizes GPS sports and performance data.
MyFitnessPal is the better choice for nutrition and calorie tracking integration, whereas Garmin Connect specializes in GPS activity and sports performance data.
Google Fit uses OAuth 2.0 and aggregates data from multiple sources including Garmin, making it a simpler integration point if you need a unified view across device brands.
Frequently asked questions
Does Garmin require developer program approval to use the Health API?
Yes. The Garmin Health API requires applying to the Garmin Developer Program at developer.garmin.com. Approval can take several business days and involves agreeing to Garmin's data use and privacy requirements. The Consumer API (for accessing data with user consent) has a separate approval track from the Push API (for receiving real-time webhooks).
Why does Garmin use OAuth 1.0a instead of OAuth 2.0?
Garmin's API was built before OAuth 2.0 became standard, and the platform has not yet migrated. OAuth 1.0a works perfectly well — it just requires more code to implement due to the HMAC-SHA1 request signing. The oauth-1.0a npm package handles all the complexity for you.
Can I access historical Garmin data going back years?
The Garmin Health API provides access to data since the user first connected their account to your application (not their entire Garmin history). The time range you can query is limited by the upload timestamp range parameters. For most use cases, querying 30-90 days of recent data is sufficient and performs well.
Does Garmin support real-time activity tracking?
Garmin provides a Push API (webhook-based) that sends data to your endpoint in near-real-time as activities are uploaded from a device. Setting up Push API requires separate developer approval and a publicly accessible endpoint. It is separate from the Pull API covered in this tutorial.
What types of data can I access via the Garmin Health API?
The Garmin Health API provides access to activities (runs, rides, swims, workouts), daily summaries (steps, calories, active minutes), sleep data (sleep stages, sleep score), heart rate data, Body Battery energy levels, stress scores, and SpO2 measurements. The exact data available depends on the user's device model and which sensors it includes.
Can I use V0 to generate charts for Garmin data?
Yes. V0 knows recharts, Chart.js, and Victory chart libraries well. Describe the chart type you need — for example, a line chart of heart rate over a run, a bar chart of weekly mileage, or a pie chart of time in heart rate zones — and V0 will generate the component. Wire up your Garmin API route data to the chart's data prop.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation