# Fitbit API

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 3-4 hours
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to the Fitbit API by registering your app at dev.fitbit.com (no partner approval required), deploying a Firebase Cloud Function to handle the OAuth 2.0 token exchange using your client secret, and building a FlutterFlow API Group that sends requests to api.fitbit.com with the Bearer access token. The client secret must stay in the Cloud Function — never in Flutter client code or FlutterFlow API Call headers.

## Why Fitbit Is the Easiest Wearable API to Connect — and the One Security Rule You Cannot Skip

Fitbit stands out from other wearable APIs (Garmin requires partner approval; HealthKit is iOS-only and has no cloud API) because the developer portal at dev.fitbit.com is self-serve and open to any registered developer. You can have API credentials in minutes, no application process required. The Fitbit Web API at `api.fitbit.com` covers the essential fitness data types: daily activity summaries (steps, distance, calories, active minutes), sleep stages (REM, deep, light, awake), heart rate (resting, zones, intraday), and body measurements. All data is cloud-synced, so it works on iOS, Android, and web equally well from FlutterFlow.

The one non-negotiable rule: the OAuth 2.0 client secret must never appear in your Flutter app. When users authorize your app through Fitbit's OAuth flow, your app receives an authorization code. Exchanging that code for an access token requires sending the client secret to Fitbit's token endpoint. If you do this in Dart, the secret is compiled into the app binary and can be extracted by decompiling the APK or IPA. Anyone with your client secret can impersonate your application and access any user's data who has authorized it. A Firebase Cloud Function holds the secret, performs the exchange, and returns only the access token to the app. This is not optional — it is a Fitbit Terms of Service requirement and basic security hygiene.

Fitbit's rate limit is approximately 150 requests per hour per user (verify current limits at dev.fitbit.com). For a typical daily dashboard that reads steps, sleep, and heart rate on app open, this is generous. However, if you refresh frequently or read intraday data (minute-by-minute heart rate), you will hit the limit quickly. Cache responses in Firestore — read once, store in Firestore, serve from Firestore until data is stale.

## Before you start

- Fitbit developer account at dev.fitbit.com (free, no approval required)
- Firebase project with Cloud Functions enabled (Blaze pay-as-you-go plan for outbound HTTP in Cloud Functions)
- FlutterFlow project on a paid plan with API Calls and Custom Code access
- Node.js knowledge for writing the Firebase Cloud Function token exchange and refresh logic
- Understanding of OAuth 2.0 authorization-code flow (the user grants permission, your app gets a code, you exchange it for tokens)

## Step-by-step guide

### 1. Register your app at dev.fitbit.com

Go to dev.fitbit.com and sign in with your Fitbit account (or create one — it is free). Click 'Register an App' to start the app registration form. Fill in the Application Name (your app's name as it will appear on the Fitbit consent screen), Description, Application Website (your app's website or a placeholder), Organization (your name or company), Organization Website, and Terms of Service / Privacy Policy URLs. Under OAuth 2.0 Application Type, select 'Personal' for a single-user app or 'Client' for a multi-user app (choose 'Client' for any app where different users connect their own Fitbit accounts). In the Redirect URI field, enter the URL that Fitbit will redirect to after the user authorizes your app — this should be your Cloud Function's callback URL (you will get this URL after deploying the function in step 2; for now you can use a placeholder and update it later). Select the OAuth 2.0 Flow as 'Authorization Code Flow' — do not use the Implicit flow, which is deprecated. Under Default Access Type, select 'Read-Only' unless you need to log data back to Fitbit. Under Scopes, tick the data types you need: activity (steps, calories, active minutes), heartrate, sleep, profile (user info), weight if needed. After submitting, Fitbit shows your OAuth 2.0 credentials: the Client ID (public, can be in your app) and the Client Secret (never in client code — goes into your Cloud Function). Copy both and store the client secret securely.

**Expected result:** Your Fitbit app is registered and you have the Client ID and Client Secret. The Client ID is safe to use publicly; the Client Secret is stored securely and never enters any code file.

### 2. Deploy a Cloud Function for OAuth token exchange and refresh

Create a Firebase Cloud Function project (run `firebase init functions` in your terminal if starting fresh, choosing JavaScript or TypeScript). In your `index.js` or `index.ts` file, implement two HTTPS functions: one for the initial authorization code exchange (`fitbitAuth`), and one for refreshing expired tokens (`fitbitRefresh`). The `fitbitAuth` function receives the authorization code from FlutterFlow, sends it to Fitbit's token endpoint at `https://api.fitbit.com/oauth2/token` using a POST request with Basic authentication (`client_id:client_secret` base64-encoded in the Authorization header), receives the `access_token`, `refresh_token`, and `expires_in` in the response, stores the tokens in Firestore under the user's ID, and returns only the `access_token` to the client. The `fitbitRefresh` function receives a userId, loads the stored `refresh_token` from Firestore, sends it to the same token endpoint with `grant_type=refresh_token`, updates Firestore with the new token pair, and returns the new `access_token`. Store your Fitbit client secret with `firebase functions:config:set fitbit.client_id=YOUR_ID fitbit.client_secret=YOUR_SECRET` and deploy with `firebase deploy --only functions`. Note the deployed HTTPS URL of `fitbitAuth` — this is the Redirect URI you need to update in your Fitbit app registration.

```
// index.js — Fitbit OAuth Cloud Functions
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const axios = require('axios');

admin.initializeApp();

// Exchange auth code for tokens
exports.fitbitAuth = functions.https.onRequest(async (req, res) => {
  const { code, userId } = req.query;
  if (!code || !userId) return res.status(400).json({ error: 'code and userId required' });

  const clientId = functions.config().fitbit.client_id;
  const clientSecret = functions.config().fitbit.client_secret;
  const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');

  try {
    const tokenRes = await axios.post(
      'https://api.fitbit.com/oauth2/token',
      `grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(req.query.redirect_uri || '')}`,
      {
        headers: {
          'Authorization': `Basic ${credentials}`,
          'Content-Type': 'application/x-www-form-urlencoded',
        },
      }
    );

    const { access_token, refresh_token, expires_in } = tokenRes.data;
    const expiresAt = Date.now() + (expires_in * 1000);

    // Store tokens server-side
    await admin.firestore().collection('fitbit_tokens').doc(userId).set({
      accessToken: access_token,
      refreshToken: refresh_token,
      expiresAt,
    });

    // Return only the access token to the client
    res.json({ access_token, expires_in });
  } catch (err) {
    const errData = err.response ? err.response.data : { error: err.message };
    res.status(500).json(errData);
  }
});

// Refresh an expired token
exports.fitbitRefresh = functions.https.onRequest(async (req, res) => {
  const { userId } = req.query;
  const doc = await admin.firestore().collection('fitbit_tokens').doc(userId).get();
  if (!doc.exists) return res.status(404).json({ error: 'No token for user' });

  const { refreshToken } = doc.data();
  const clientId = functions.config().fitbit.client_id;
  const clientSecret = functions.config().fitbit.client_secret;
  const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');

  const tokenRes = await axios.post(
    'https://api.fitbit.com/oauth2/token',
    `grant_type=refresh_token&refresh_token=${refreshToken}`,
    { headers: { 'Authorization': `Basic ${credentials}`, 'Content-Type': 'application/x-www-form-urlencoded' } }
  );

  const { access_token, refresh_token, expires_in } = tokenRes.data;
  await admin.firestore().collection('fitbit_tokens').doc(userId).set({
    accessToken: access_token,
    refreshToken: refresh_token,
    expiresAt: Date.now() + (expires_in * 1000),
  }, { merge: true });

  res.json({ access_token });
});
```

**Expected result:** Both Cloud Functions are deployed and accessible. The fitbitAuth URL is updated in the Fitbit app registration as the Redirect URI. Calling fitbitAuth with a valid code returns an access token and writes both tokens to Firestore.

### 3. Launch the Fitbit OAuth consent screen from FlutterFlow

With the Cloud Function deployed, implement the 'Connect Fitbit Account' flow in FlutterFlow. The user taps a button, your app opens the Fitbit authorization URL in a browser, the user logs in and grants permission, Fitbit redirects to your Cloud Function callback URL, the function exchanges the code for tokens and stores them in Firestore. In FlutterFlow, add a 'Connect Fitbit' button to a settings or onboarding page. Use a Custom Action (Dart) with the `url_launcher` package to open the Fitbit authorization URL. The URL format is: `https://www.fitbit.com/oauth2/authorize?response_type=code&client_id={YOUR_CLIENT_ID}&redirect_uri={YOUR_CALLBACK_URL}&scope=activity+heartrate+sleep+profile&state={userId}`. Set the `client_id` from your Fitbit app registration (this is safe to include in the URL — it is the public client ID, not the secret). Set `redirect_uri` to your `fitbitAuth` Cloud Function URL. Pass the logged-in user's Firebase UID as the `state` parameter so the callback can associate the tokens with the user. Add the `url_launcher` package to Pubspec Dependencies (`url_launcher: ^6.1.0`) and write a Custom Action that calls `launchUrl(Uri.parse(authUrl))`. After the user completes the OAuth flow, your Cloud Function stores the tokens in Firestore under the user's UID. Add a Firestore listener on the `fitbit_tokens` document for the current user — when it appears, update a boolean App State variable `fitbitConnected` to true and show the connected state in your UI.

```
// custom_action.dart — Launch Fitbit OAuth consent screen
import 'package:url_launcher/url_launcher.dart';

Future<void> launchFitbitAuth(
  String userId,
) async {
  const clientId = 'YOUR_FITBIT_CLIENT_ID'; // Public — safe in client
  const callbackUrl = 'https://us-central1-YOUR_PROJECT.cloudfunctions.net/fitbitAuth';
  const scopes = 'activity+heartrate+sleep+profile';

  final authUrl = Uri.parse(
    'https://www.fitbit.com/oauth2/authorize'
    '?response_type=code'
    '&client_id=$clientId'
    '&redirect_uri=${Uri.encodeComponent(callbackUrl)}'
    '&scope=$scopes'
    '&state=$userId'
  );

  await launchUrl(authUrl, mode: LaunchMode.externalApplication);
}
```

**Expected result:** Tapping 'Connect Fitbit' opens the Fitbit login/authorization page in the device browser. After the user grants permission, the Firestore fitbit_tokens document is created, and the app's connected state updates to show the account is linked.

### 4. Create the Fitbit API Group with Bearer token authentication

In FlutterFlow, click API Calls in the left navigation panel, then + Add → Create API Group. Name it 'Fitbit' and set the Base URL to `https://api.fitbit.com`. Under the API Group's Headers, add an Authorization header with the value `Bearer {{ accessToken }}` — this references a variable called `accessToken` that you will pass when calling the API. Now add API Calls within the group. Click + Add Call → Create API Call and name it 'GetDailyActivity'. Set the method to GET and the endpoint to `/1/user/-/activities/date/{{ date }}.json`. In the Variables tab, add `date` (String, format YYYY-MM-DD), and add `accessToken` (String) which will carry the Fitbit access token retrieved from Firestore. In the API Call's Response & Test tab, paste a sample Fitbit activity response and click Generate JSON Paths to extract `summary.steps`, `summary.caloriesOut`, `summary.fairlyActiveMinutes`, and `summary.veryActiveMinutes`. Create additional API Calls: GetSleep with endpoint `/1.2/user/-/sleep/date/{{ date }}.json` (parse `summary.totalMinutesAsleep`, `summary.stages`), and GetHeartRate with endpoint `/1/user/-/activities/heart/date/{{ date }}/1d.json` (parse `activities-heart[0].value.restingHeartRate`). For each call, pass the `accessToken` loaded from Firestore and the date. If a call returns 401, trigger the refresh flow via the fitbitRefresh Cloud Function and retry with the new token.

```
// Sample API Call JSON configuration for Fitbit
{
  "name": "GetDailyActivity",
  "method": "GET",
  "base_url": "https://api.fitbit.com",
  "endpoint": "/1/user/-/activities/date/{{ date }}.json",
  "headers": {
    "Authorization": "Bearer {{ accessToken }}"
  },
  "variables": [
    { "name": "date", "type": "String", "description": "Date in YYYY-MM-DD format" },
    { "name": "accessToken", "type": "String", "description": "Fitbit OAuth 2.0 Bearer token" }
  ],
  "json_paths": {
    "steps": "$.summary.steps",
    "calories": "$.summary.caloriesOut",
    "activeMinutes": "$.summary.veryActiveMinutes"
  }
}
```

**Expected result:** The Fitbit API Group appears in FlutterFlow with configured calls for activity, sleep, and heart rate. Test requests with a real accessToken from Firestore return Fitbit data and generate JSON path extractors.

### 5. Bind Fitbit data to the UI and handle token refresh and rate limits

With the API Group configured, bind data to your FlutterFlow UI. On your dashboard page, use the On Page Load trigger to: first read the user's `accessToken` from Firestore (using a Firestore Document query for the `fitbit_tokens/{userId}` document), then call GetDailyActivity, GetSleep, and GetHeartRate in sequence (or use separate Backend Queries for each), and store the results in App State variables. Bind the step count to a Text widget, the calories to another, and create a Bar Chart widget showing 7 days of step counts. For the 7-day trend, call the Fitbit activities time series endpoint `/1/user/-/activities/steps/date/today/7d.json` in a separate API Call and parse the `activities-steps` array. Handle the 401 token expired case: in the Action Flow Editor, add a Conditional Action after each Fitbit API Call that checks if the response code is 401 — if so, call the fitbitRefresh Cloud Function (via an API Group pointed at your proxy), update the `accessToken` in App State, and retry the original call with the new token. For rate limit errors (429), show the user a friendly message ('Your fitness data is refreshing — please check back in a few minutes') rather than an error state. Add a pull-to-refresh gesture to the dashboard so users can manually sync when they know their Fitbit device has recently synced new data.

**Expected result:** The FlutterFlow dashboard shows real Fitbit data (steps, sleep, heart rate) for the logged-in user. Token expiration triggers an automatic refresh, and rate limit errors show a friendly message rather than an empty or broken UI.

## Best practices

- Never put the Fitbit OAuth client secret in Flutter Dart code, FlutterFlow API Call headers, or App Constants — it must live exclusively in the Firebase Cloud Function environment configuration
- Store Fitbit refresh tokens server-side in Firestore alongside the access token — never pass the refresh token to the FlutterFlow client
- Cache Fitbit API responses in Firestore to avoid hitting the ~150 request/hour per-user rate limit — serve from cache and only refetch when data is stale or the user explicitly refreshes
- Implement proactive token refresh by storing `expiresAt` with the token in Firestore and refreshing before expiry rather than waiting for a 401 error mid-session
- Handle 429 rate limit responses with a friendly UI message rather than an error state — users should know the data will be available shortly, not that something is broken
- For web-published FlutterFlow apps, route Fitbit data calls through your Cloud Function proxy to avoid CORS errors, since api.fitbit.com does not allow direct browser requests
- Scope your Fitbit app registration to only the data types you display in the app — users are more likely to authorize a short, specific permission list than a long one

## Use cases

### Fitbit-connected daily wellness dashboard

Build a wellness app where users log in with their Fitbit account and see a daily dashboard of steps, calories burned, resting heart rate, and sleep score. OAuth connects the account once; the app reads Fitbit data on each open and displays trend charts for the past 7 days. Works identically on iOS, Android, and web since data is cloud-synced.

Prompt example:

```
A wellness dashboard with four metric cards (steps, calories, heart rate, sleep score) pulled from the Fitbit API for today, plus a line chart showing each metric over the last 7 days
```

### Sleep quality analysis app

Create a sleep analytics app that reads Fitbit sleep stage data (time in bed, time asleep, REM minutes, deep sleep minutes, sleep efficiency) for the past 30 nights and presents a personalized sleep quality report. Users connect their Fitbit once via OAuth and the app refreshes data each morning.

Prompt example:

```
A sleep insights page showing the last 30 nights of Fitbit sleep data, a bar chart of sleep duration, and a breakdown of average REM, deep, and light sleep percentages
```

### Heart rate zone fitness tracker

Build a fitness goal app that reads Fitbit heart rate zone data — time spent in fat-burn, cardio, and peak zones — and tracks weekly progress toward a cardio goal. Users set a weekly active minutes target and the app shows their progress from Fitbit data with a donut chart.

Prompt example:

```
A cardio goal tracker showing this week's time in each Fitbit heart rate zone, a progress ring for the weekly active minutes goal, and a day-by-day breakdown in a ListView
```

## Troubleshooting

### 401 Unauthorized from api.fitbit.com — invalid_token or expired_token

Cause: Fitbit access tokens expire after approximately 8 hours. The token stored in Firestore or App State is stale and needs to be refreshed using the refresh token via the Cloud Function.

Solution: Implement automatic token refresh: when any Fitbit API Call returns 401, call your fitbitRefresh Cloud Function with the userId, update the accessToken in App State with the returned new token, and retry the original API Call. Store `expiresAt` alongside the token in Firestore and check it before every call to refresh proactively rather than reactively.

### 429 Too Many Requests — Fitbit rate limit exceeded

Cause: Fitbit limits each user to approximately 150 API requests per hour. Frequent polling, intraday data requests, or reading multiple metrics on every screen navigation can exhaust the limit quickly.

Solution: Cache Fitbit responses in Firestore and serve from cache for reads within the same hour. Avoid polling — only refresh data on explicit user action (pull-to-refresh) or once on page load. For intraday heart rate data (minute-by-minute), which counts heavily against the rate limit, consider fetching it only on demand rather than loading it by default.

### XMLHttpRequest error or CORS error when testing in FlutterFlow's web preview

Cause: Direct calls to api.fitbit.com from a browser are blocked by CORS — the Fitbit API does not include the Access-Control-Allow-Origin header that browsers require. This only affects the web preview and web-published builds, not iOS/Android native builds.

Solution: For web builds, route Fitbit API calls through your Cloud Function proxy (which adds CORS headers) rather than calling api.fitbit.com directly from the FlutterFlow API Group. Update the API Group Base URL to the proxy for web-compatible builds, or add a CORS proxy wrapper to the existing Cloud Function that forwards Fitbit data calls.

### OAuth flow completes but no token is written to Firestore

Cause: The Cloud Function callback URL is not correctly set as the Redirect URI in the Fitbit app registration, or the `state` parameter (userId) is not being passed through the redirect correctly.

Solution: Check the Fitbit app registration at dev.fitbit.com to confirm the Redirect URI exactly matches your fitbitAuth Cloud Function URL (including protocol, no trailing slash). Verify the Cloud Function logs in Firebase console for any errors during the token exchange. Test the callback URL directly by simulating a Fitbit redirect with a code and userId parameter.

## Frequently asked questions

### Do I need to apply to a partner program to use the Fitbit API?

No. Fitbit's Web API is self-serve — you register at dev.fitbit.com with any account, configure your OAuth app, and receive credentials immediately. There is no partner approval process, no business account required, and no waiting period. This makes Fitbit the most accessible wearable API for FlutterFlow developers compared to Garmin (which requires partner approval) or Practo (which requires a provider account).

### Why can't I put the Fitbit client secret directly in the FlutterFlow API Call header?

The OAuth client secret is used to prove your application's identity to Fitbit during the token exchange. If it is in a FlutterFlow API Call header, it is compiled into the Flutter app binary and shipped to every user's device. Anyone who decompiles the APK or IPA can extract it and use it to impersonate your application — gaining the ability to request tokens as your app from any Fitbit user who has authorized it. The Firebase Cloud Function keeps the secret on the server where it cannot be extracted by end users.

### How often do Fitbit access tokens expire?

Fitbit access tokens expire after approximately 8 hours. Refresh tokens do not expire by default unless the user revokes access. Your Cloud Function refresh endpoint should check the stored `expiresAt` timestamp before making Fitbit API calls and refresh proactively if the token is close to expiring, rather than waiting for a 401 response mid-session. Store both the access token and a refreshed timestamp in Firestore so any device the user opens the app on can check token validity.

### Does the Fitbit API work for web-published FlutterFlow apps?

Fitbit API calls from native iOS and Android builds work directly against api.fitbit.com (CORS is not enforced on mobile). For web-published builds, browsers enforce CORS and api.fitbit.com does not include the necessary CORS headers for direct browser calls. Route your Fitbit data calls through your Cloud Function proxy (which adds CORS headers) for web compatibility. The OAuth consent flow (opening the Fitbit auth URL) works identically on all platforms.

### How do I handle a user who revokes Fitbit access?

When a user revokes your app's Fitbit authorization in their Fitbit account settings, subsequent token refresh attempts will fail with an `invalid_grant` or `unauthorized_client` error. Detect this in your fitbitRefresh Cloud Function, delete the Firestore token document, and return an error response that FlutterFlow can handle by showing a 'Please reconnect your Fitbit account' message and the Connect Fitbit button.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/fitbit-api
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/fitbit-api
