Skip to main content
RapidDev - Software Development Agency
lovable-integrationsEdge Function Integration

How to Integrate Lovable with Fitbit API

Integrating the Fitbit Web API with Lovable uses Edge Functions to handle OAuth2 PKCE authentication and proxy requests for activity, sleep, heart rate, and nutrition data. Store your Fitbit OAuth credentials in Cloud Secrets, implement the authorization flow per user, fetch health metrics via Edge Functions, and build personal health dashboards with daily activity summaries and trend charts. Setup takes 45 minutes.

What you'll learn

  • How to register a Fitbit application and configure OAuth2 PKCE credentials
  • How to implement the PKCE authorization flow for Fitbit in a Lovable app
  • How to fetch daily activity summaries, sleep data, and heart rate via Edge Functions
  • How to request intraday data access for minute-level time series
  • How to build a personal health dashboard with step counts, sleep scores, and trend charts
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate13 min read45 minutesHealthMarch 2026RapidDev Engineering Team
TL;DR

Integrating the Fitbit Web API with Lovable uses Edge Functions to handle OAuth2 PKCE authentication and proxy requests for activity, sleep, heart rate, and nutrition data. Store your Fitbit OAuth credentials in Cloud Secrets, implement the authorization flow per user, fetch health metrics via Edge Functions, and build personal health dashboards with daily activity summaries and trend charts. Setup takes 45 minutes.

Why integrate Fitbit API with Lovable?

Fitbit is worn by millions of people daily to track steps, calories, sleep quality, heart rate, and exercise. The Fitbit Web API makes all this data accessible to third-party applications through a well-documented OAuth2 flow, enabling you to build health dashboards, fitness coaching tools, and wellness apps that incorporate real biometric data from users' devices.

Fitbit's API provides several data categories: Activity data (steps, calories, distance, floors, active minutes), Sleep data (sleep stages, efficiency, duration, wake times), Heart rate data (resting heart rate, heart rate zones, cardio fitness score), Nutrition data (food logging, water intake, calorie goals), and Body data (weight, BMI, body fat percentage). Most data is available at the daily summary level; intraday time series (minute-by-minute heart rate, step counts) requires requesting special permission from Fitbit for personal projects or working through their partner program.

From a technical perspective, Fitbit uses OAuth2 with PKCE (Proof Key for Code Exchange) — an enhanced version of the standard authorization code flow that prevents code interception attacks. PKCE requires generating a random code verifier, hashing it to create a code challenge, including the challenge in the authorization request, and sending the original verifier in the token exchange. Lovable's Edge Function environment handles all of this server-side, keeping the implementation secure.

Integration method

Edge Function Integration

Fitbit has no native Lovable connector. Integration requires Supabase Edge Functions to manage the OAuth2 PKCE authorization flow, store and refresh access tokens per user, and proxy all Fitbit API data requests. Fitbit uses PKCE for enhanced security in the authorization flow. All data requests — activity summaries, sleep logs, heart rate, and nutrition — run through Edge Functions with credentials in Cloud Secrets. Users must authorize your app access to their Fitbit data.

Prerequisites

  • A Lovable project with Cloud enabled
  • A Fitbit developer account — register at dev.fitbit.com
  • A Fitbit application registered to obtain OAuth2 Client ID and Client Secret
  • A Fitbit device or access to the Fitbit app's manual food/activity logging for testing
  • Understanding of OAuth2 PKCE flow concepts

Step-by-step guide

1

Register a Fitbit application and configure OAuth2

Go to dev.fitbit.com and log in with your Fitbit account (or create one if needed — the developer account is separate from a device account but linked). Click 'Register an App'. Fill in the application name, description, website URL, organization, and organization URL. For Application Type, select 'Personal' if you're building a single-user tool, or 'Server' if you're building a multi-user app where multiple people connect their Fitbit accounts. For the OAuth 2.0 Application Type field, select 'Server' — this enables refresh tokens (which Personal apps don't get). Set the Callback URL to your app's callback path: https://your-app.lovable.app/auth/fitbit/callback. Enable the read scopes you need: activity, heartrate, nutrition, profile, settings, sleep, social, weight. After registration, Fitbit shows you the OAuth 2.0 Client ID (a short alphanumeric string) and Client Secret (a longer string). Save both. Fitbit also shows your app on the dev.fitbit.com/apps page where you can update settings, add callback URLs, and check your app's status. Fitbit access tokens expire after 8 hours and refresh tokens are valid for 30 days. Because users must re-authorize after 30 days of inactivity, implement clear messaging in your app when authorization expires and provide an easy re-connect flow.

Pro tip: For intraday time series data (minute-by-minute heart rate or steps), Fitbit requires personal access (only the device owner can access their own intraday data) or an approved partner application. If you're building a personal health dashboard for yourself, this works automatically. For multi-user apps, intraday data requires Fitbit partner program approval.

Expected result: Your Fitbit application is registered with Client ID and Client Secret saved. Scopes are configured for activity, heartrate, sleep, nutrition, and profile.

2

Store credentials and implement PKCE OAuth flow

Open your Lovable project, click '+', select 'Cloud', and expand Secrets. Add FITBIT_CLIENT_ID, FITBIT_CLIENT_SECRET, and FITBIT_REDIRECT_URI as encrypted secrets. Fitbit's PKCE flow adds two steps to standard OAuth2. Before redirecting the user to Fitbit's authorization page, generate a random code_verifier (a 43-128 character URL-safe string). Compute the code_challenge by Base64URL-encoding the SHA-256 hash of the code_verifier. Include code_challenge and code_challenge_method=S256 in the authorization URL. Store the code_verifier in Supabase against a CSRF state token (not in browser local storage, which doesn't survive server-side auth flows). At the callback, retrieve the code_verifier and send it along with the authorization code to the Fitbit token endpoint. Fitbit verifies the verifier matches the original challenge before issuing tokens. Fitbit's authorization URL is https://www.fitbit.com/oauth2/authorize and the token endpoint is https://api.fitbit.com/oauth2/token. Token requests use HTTP Basic authentication with client_id:client_secret Base64-encoded in the Authorization header (unlike the form-based auth that some OAuth2 providers use).

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/fitbit-oauth/index.ts. Handle: action='get-auth-url' with user_id — generate PKCE code_verifier and code_challenge, store them in a pkce_sessions table with user_id and state, return the Fitbit authorization URL with code_challenge, state, and scopes; action='exchange' with code, state, user_id — retrieve code_verifier from pkce_sessions, POST to Fitbit token endpoint with HTTP Basic auth, store access_token, refresh_token, expires_at in fitbit_tokens table; action='get-token' with user_id — return valid access_token (auto-refresh if near expiry).

Paste this in Lovable chat

supabase/functions/fitbit-oauth/index.ts
1// supabase/functions/fitbit-oauth/index.ts
2import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
3import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
4
5const CLIENT_ID = Deno.env.get("FITBIT_CLIENT_ID") ?? "";
6const CLIENT_SECRET = Deno.env.get("FITBIT_CLIENT_SECRET") ?? "";
7const REDIRECT_URI = Deno.env.get("FITBIT_REDIRECT_URI") ?? "";
8const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization" };
9
10async function generatePKCE() {
11 const verifier = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))).replace(/[+/=]/g, (c) => ({'+':'-','/':'_','=':''})[c] ?? c);
12 const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
13 const challenge = btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/[+/=]/g, (c) => ({'+':'-','/':'_','=':''})[c] ?? c);
14 return { verifier, challenge };
15}
16
17serve(async (req) => {
18 if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
19 const supabase = createClient(Deno.env.get("SUPABASE_URL") ?? "", Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "");
20 const body = await req.json();
21
22 try {
23 if (body.action === "get-auth-url") {
24 const { user_id } = body;
25 const { verifier, challenge } = await generatePKCE();
26 const state = crypto.randomUUID();
27 await supabase.from("pkce_sessions").insert({ user_id, state, code_verifier: verifier, created_at: new Date().toISOString() });
28 const params = new URLSearchParams({ response_type: "code", client_id: CLIENT_ID, scope: "activity heartrate nutrition profile sleep weight", redirect_uri: REDIRECT_URI, code_challenge: challenge, code_challenge_method: "S256", state });
29 return new Response(JSON.stringify({ url: `https://www.fitbit.com/oauth2/authorize?${params}` }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
30 }
31
32 if (body.action === "exchange") {
33 const { code, state, user_id } = body;
34 const { data: session } = await supabase.from("pkce_sessions").select("code_verifier").eq("user_id", user_id).eq("state", state).single();
35 if (!session) throw new Error("Invalid state parameter");
36
37 const credentials = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
38 const res = await fetch("https://api.fitbit.com/oauth2/token", {
39 method: "POST",
40 headers: { "Authorization": `Basic ${credentials}`, "Content-Type": "application/x-www-form-urlencoded" },
41 body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: REDIRECT_URI, code_verifier: session.code_verifier }),
42 });
43 const data = await res.json();
44 if (!data.access_token) throw new Error(data.errors?.[0]?.message ?? "Token exchange failed");
45 const expiresAt = new Date(Date.now() + data.expires_in * 1000).toISOString();
46 await supabase.from("fitbit_tokens").upsert({ user_id, access_token: data.access_token, refresh_token: data.refresh_token, expires_at: expiresAt }, { onConflict: "user_id" });
47 await supabase.from("pkce_sessions").delete().eq("state", state);
48 return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
49 }
50
51 return new Response(JSON.stringify({ error: "Unknown action" }), { status: 400, headers: corsHeaders });
52 } catch (err) {
53 return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } });
54 }
55});

Pro tip: Clean up expired pkce_sessions records periodically — sessions older than 10 minutes are likely abandoned OAuth flows and can be deleted. Add a created_at timestamp and delete records where created_at is more than 15 minutes ago at the start of each get-auth-url call.

Expected result: The fitbit-oauth Edge Function handles PKCE generation, authorization URL creation, token exchange, and refresh. Clicking 'Connect Fitbit' generates the auth URL, and completing OAuth stores tokens in Supabase.

3

Create the Fitbit health data Edge Function

With authentication working, create the data Edge Function that proxies Fitbit API requests. Fitbit's REST API uses date-based endpoints — most resources follow the pattern /1/user/-/resource/date/today.json (the dash '-' means the authenticated user's data). Key endpoints: GET /1/user/-/activities/date/{date}.json for daily activity summary (steps, calories, floors, active minutes, distances). GET /1/user/-/sleep/date/{date}.json for a single night's sleep summary. GET /1/user/-/sleep/list.json with beforeDate and limit parameters for sleep history. GET /1/user/-/activities/heart/date/{date}/1d.json for daily heart rate data including resting heart rate and heart rate zones. For date parameters, Fitbit accepts 'today' as a special value, or YYYY-MM-DD format strings. All responses use the user's local timezone for date boundaries — a sleep record for 'today' includes the night ending in the morning of today. Create the data Edge Function to accept an action and date parameter, get the user's access token from fitbit-oauth, and call the appropriate Fitbit endpoint. Return cleaned, structured data rather than the raw Fitbit response (which is deeply nested).

Lovable Prompt

Create a Supabase Edge Function at supabase/functions/fitbit-data/index.ts. Get user access_token via fitbit-oauth get-token. Support: action='activity' with date ('today' or YYYY-MM-DD) — fetch /1/user/-/activities/date/{date}.json, return {steps, calories, floors, activeMinutes, stepGoal, calorieGoal}; action='sleep' with date — fetch /1/user/-/sleep/date/{date}.json, return {duration_hours, efficiency, deep_minutes, light_minutes, rem_minutes, awake_minutes}; action='heart-rate' with date — fetch /1/user/-/activities/heart/date/{date}/1d.json, return {resting_heart_rate, zones: [{name, minutes, max, min}]}.

Paste this in Lovable chat

Pro tip: Fitbit rate limits are generous: 150 API calls per hour per user token. For a personal dashboard refreshing multiple metrics, you're unlikely to hit limits. For multi-user apps, each user's token has their own 150 request/hour allowance — rate limits are not shared across users.

Expected result: The fitbit-data Edge Function returns activity, sleep, and heart rate data for the connected Fitbit user. Calling action='activity' with date='today' returns today's step count, calorie burn, and activity goals.

Common use cases

Daily health metrics dashboard

A personal health app shows today's activity summary — steps toward goal, calories burned, active minutes, sleep duration and quality from last night, and current resting heart rate. Edge Functions fetch the daily activity summary and sleep log from Fitbit and display them in a card-based dashboard with progress rings.

Lovable Prompt

Build a daily health dashboard that fetches Fitbit data for the current user. Create Edge Functions to get: 1) today's activity summary (steps, calories, floors, active minutes, step goal), 2) last night's sleep summary (duration, efficiency, sleep stages time in minutes), 3) today's resting heart rate. Display as three dashboard cards with progress indicators. Show step count with a progress ring toward the daily goal. Add a 7-day step trend mini chart.

Copy this prompt to try it in Lovable

Sleep quality tracker with trend analysis

A wellness app focuses on sleep optimization, showing sleep duration and quality trends over time. An Edge Function fetches 30 days of sleep log data from Fitbit and the frontend displays average sleep duration by day of week, sleep stage distribution (deep, light, REM, wake), and sleep efficiency score trends to identify patterns.

Lovable Prompt

Build a sleep tracker page using Fitbit sleep data. Fetch 30 days of sleep logs via Edge Function using the Fitbit /sleep/list endpoint. For each night show: total sleep time, sleep efficiency percentage, time in each sleep stage (deep, light, REM, awake). Display a bar chart of sleep duration per night for the last 30 days with a target line at 8 hours. Show average statistics cards: avg duration, avg efficiency, avg deep sleep percentage.

Copy this prompt to try it in Lovable

Fitness challenge tracker for teams

A team fitness challenge app lets colleagues connect their Fitbit accounts and compete on weekly step counts. Each participant's Edge Function fetches their weekly step total from Fitbit, which is stored in a Supabase leaderboard table. A dashboard shows the team leaderboard updated daily with step counts and progress toward the weekly goal.

Lovable Prompt

Build a team step challenge. Let multiple users connect their Fitbit accounts. Each connected user's weekly step total is fetched from Fitbit via Edge Function and stored in a Supabase leaderboard table with user_id, week_start_date, total_steps. Display a leaderboard sorted by steps with each participant's name, steps this week, and a progress bar toward the 70,000 step weekly goal. Refresh the leaderboard daily via a scheduled Edge Function.

Copy this prompt to try it in Lovable

Troubleshooting

OAuth callback fails with 'invalid_client' error

Cause: The client_id or client_secret in Cloud Secrets doesn't match the Fitbit app registration, or the Authorization header for the token exchange isn't properly Base64-encoded. Fitbit uses HTTP Basic auth (not form-based auth) for the token endpoint.

Solution: Verify FITBIT_CLIENT_ID and FITBIT_CLIENT_SECRET exactly match your dev.fitbit.com app page. Confirm the token request uses the Authorization: Basic {base64(client_id:client_secret)} header format. The Base64 encoding must use the colon separator between client_id and client_secret: btoa(CLIENT_ID + ':' + CLIENT_SECRET).

typescript
1const credentials = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
2headers: { "Authorization": `Basic ${credentials}` }

Fitbit data endpoint returns 401 with 'expired_token'

Cause: Fitbit access tokens expire after 8 hours. If the token stored in fitbit_tokens has expired and the refresh logic isn't working, all data requests will fail with 401.

Solution: Check the expires_at value in the fitbit_tokens table for the user. If expired, manually trigger a token refresh or have the user reconnect. For future prevention, ensure the get-token action in fitbit-oauth refreshes tokens when they're within 30 minutes of expiry (not just 5 minutes, since Fitbit tokens have a shorter 8-hour lifetime than typical OAuth2 tokens).

typescript
1// Use 30-minute buffer for Fitbit's 8-hour tokens
2if (new Date(token.expires_at) < new Date(Date.now() + 30 * 60_000)) {
3 // refresh token
4}

Sleep data shows null or empty stages for sleep records

Cause: Fitbit only provides sleep stage data (deep, light, REM) for 'stages' type sleep records — these require Fitbit's newer devices that support sleep stage detection. Older devices and manually logged sleep use a simpler 'classic' sleep type with only asleep/restless/awake metrics.

Solution: Check the type field in the sleep response: type='stages' has mainSleep.levels.summary with deep, light, rem, wake data; type='classic' has mainSleep.levels.summary with asleep, restless, awake counts. Build your Edge Function to handle both cases and return whichever format applies to the user's device.

typescript
1const isStages = sleepLog.type === 'stages';
2return isStages
3 ? { deep: levels.summary.deep?.minutes ?? 0, rem: levels.summary.rem?.minutes ?? 0 }
4 : { asleep: levels.summary.asleep?.minutes ?? 0 };

Best practices

  • Use 'today' as the date parameter instead of the current date string when fetching current-day data — Fitbit interprets 'today' in the user's local timezone, avoiding off-by-one errors from UTC vs. local time differences.
  • Store the user's Fitbit user_id (returned with the token exchange response) in your fitbit_tokens table — this can be useful for constructing user-specific API calls and debugging.
  • Implement token refresh with at least a 30-minute buffer before expiry for Fitbit's 8-hour tokens — a 5-minute buffer may not be sufficient if users have long sessions.
  • Never request Fitbit intraday time series access in a personal development app if you're not sure you qualify — unauthorized requests for protected data result in API errors and may trigger Fitbit's developer policy enforcement.
  • Cache daily activity summaries in Supabase — they don't change after midnight, so re-fetching yesterday's data from Fitbit on every dashboard load is wasteful. Check if a cached record exists for the requested date before calling Fitbit.
  • Handle the case where a user hasn't worn their Fitbit device — many API responses return zero or null values for days without device activity. Display 'No data for this day' rather than showing misleading zeros.
  • Provide clear user-facing messaging when the Fitbit connection expires (after 30 days of refresh inactivity) with a one-click reconnect button that restarts the OAuth flow.

Alternatives

Frequently asked questions

Do users need a Fitbit device to use this integration?

Users need a Fitbit account and the Fitbit app, but not necessarily a hardware device. Fitbit allows manual logging of food, water, exercise, and weight through the app, which is accessible via the API. However, data like heart rate, sleep stages, and automatic step counting require a Fitbit wearable device. If you're building for users with devices, expect full data; for users without devices, expect primarily manually logged data.

What's PKCE and why does Fitbit require it?

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. In a standard OAuth2 flow, an attacker who intercepts the authorization code could exchange it for tokens. PKCE prevents this by requiring the original code verifier to be presented at token exchange — an intercepted code is useless without the verifier that was generated on the client side. Fitbit requires PKCE for all OAuth2 flows as a security enhancement that has become standard practice for public clients.

Can I access Fitbit data for multiple users in my app?

Yes — each user of your Lovable app authorizes separately through their own Fitbit account. After authorization, their access token is stored per user_id in your fitbit_tokens table. Each user can only access their own Fitbit data. Your Edge Function reads the requesting user's token (based on their Supabase Auth session) and calls Fitbit on their behalf. The '-' in Fitbit's API path (/1/user/-/) automatically resolves to whichever user the access token belongs to.

How do I access Fitbit intraday data (minute-by-minute)?

Intraday time series data (detailed heart rate, step counts per minute) is restricted by Fitbit. For personal projects where users access only their own data, this access is automatically available — the authorized user can fetch their own intraday data. For apps where users access each other's data or you're accessing data programmatically without user involvement, Fitbit requires applying to their partner program and demonstrating a legitimate health use case.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.