# Fitbit API

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 2–3 hours
- Last updated: July 2026

## TL;DR

Fitbit is the easiest wearable API to connect to Bubble: self-serve registration at dev.fitbit.com, standard OAuth 2.0, and direct API Connector calls to api.fitbit.com with a Private Bearer token. The OAuth token exchange and refresh flow run in Bubble Backend Workflows (paid plan required). Tokens expire every 8 hours — implement a refresh mechanism from day one. No partner approval needed.

## Connecting Bubble to Fitbit: The Most Approachable Wearable API

Fitbit stands out in the wearable API category as the most developer-friendly option: a self-serve registration portal at dev.fitbit.com, no partner approval process, well-documented REST endpoints, and standard OAuth 2.0 authorization. If you are building a wellness, fitness tracking, or health dashboard app in Bubble and your users wear Fitbit devices, this is the most straightforward wearable integration available.

The core architecture is: per-user OAuth 2.0 tokens stored on Bubble's User Data Type, passed as a Private Bearer header in the API Connector for outbound calls to api.fitbit.com. Every Fitbit user authorizes your Bubble app independently and you receive a separate access_token and refresh_token per user — different from business-account APIs where one credential accesses all client data.

The most important operational consideration is Fitbit's 8-hour access token expiry. Every access_token expires 8 hours after issuance. A user returning to your app after 8 hours will see failed API calls unless token refresh is implemented. Build the refresh flow from day one — it is not optional for production wellness apps.

Token exchange and refresh run in Backend Workflows (paid Bubble plan required). API Connector calls work on any plan. Build with a paid plan from the start to avoid rebuilding the OAuth flow later.

## Before you start

- A Fitbit account to register your developer application at dev.fitbit.com (any Fitbit account, not necessarily a paid account)
- A Bubble account on a paid plan (Starter $32/month minimum) — Backend Workflows are required for the OAuth 2.0 token exchange and refresh flow
- Basic understanding of OAuth 2.0 authorization-code flow (redirect user to authorization URL, receive callback with code, exchange code for tokens)
- At least one Fitbit device account available for testing that has some real activity, sleep, or heart rate data recorded

## Step-by-step guide

### 1. Register a Fitbit developer application and configure the OAuth 2.0 callback

Navigate to dev.fitbit.com and log in with your Fitbit account (create a free account if you do not have one). Click 'Register an App' to create a new application registration. Fill in the fields:

- Application Name: your app's name
- Description: brief description of your Bubble app's purpose
- Website URL: your Bubble app URL or company site
- Organization and Organization Website: your company details
- OAuth 2.0 Application Type: select 'Server' for server-side token exchange (not 'Personal', which only accesses the developer's own data)
- Callback URL: this is the URL Fitbit redirects the user to after they authorize your app. In Bubble, this will be your Backend Workflow URL. Set it provisionally to https://YOUR-APP.bubbleapps.io/api/1.1/wf/fitbit_oauth_callback (you will create this Backend Workflow in Step 2). You can update this URL in the Fitbit developer portal later.
- Default Access Type: 'Read Only' unless you need to log exercises or update goals from Bubble

Click Save. Fitbit generates your OAuth 2.0 Client ID and Client Secret. Copy both values immediately — the Client Secret is shown only once on some plans. The Client ID is public (used in the authorization URL), but the Client Secret must be treated as a private key and stored in Bubble's API Connector as a Private header (never in visible fields or page JavaScript).

Note the data types you want to access: activity (steps, distance, calories), sleep (duration, efficiency, stages), and heart rate (resting rate, zones). Each requires a separate API scope that you specify during authorization.

```
// Fitbit OAuth 2.0 Authorization URL (build this in Bubble for the 'Connect Fitbit' button)
// Method: redirect user's browser to this URL

https://www.fitbit.com/oauth2/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://YOUR-APP.bubbleapps.io/api/1.1/wf/fitbit_oauth_callback
  &scope=activity%20sleep%20heartrate%20profile
  &expires_in=604800
  &state=RANDOM_STATE_VALUE
```

**Expected result:** A Fitbit developer application exists with a Client ID and Client Secret. The callback URL is configured to point to your Bubble Backend Workflow URL. You have noted the required OAuth scopes for your app's data needs.

### 2. Create Backend Workflows for OAuth token exchange and token refresh

Backend Workflows enable Bubble to run server-side actions triggered by external HTTP requests. You need two: one to receive the OAuth callback from Fitbit and exchange the authorization code for tokens, and one to refresh expired tokens.

First, enable the Workflow API: Settings (gear icon) → API tab → check 'Enable Workflow API' (paid plan required).

Create Backend Workflow 1: 'fitbit_oauth_callback'. This receives the redirect from Fitbit after user authorization. Fitbit appends ?code=AUTHORIZATION_CODE&state=STATE to your callback URL. Add a Detect request data step to capture the code parameter. Add an API Connector action to POST to Fitbit's token endpoint: https://api.fitbit.com/oauth2/token with body: grant_type=authorization_code, code=request_data's code, client_id=YOUR_CLIENT_ID, redirect_uri=YOUR_CALLBACK_URL. Include the Authorization header for this token exchange call: value = 'Basic ' + base64(client_id:client_secret). Configure this token exchange call in the API Connector (mark the Authorization header as Private). After the token exchange succeeds, add a Modify User step: set the current user's fitbit_access_token to the response's access_token, fitbit_refresh_token to refresh_token, and fitbit_token_expires_at to current datetime + 8 hours.

Create Backend Workflow 2: 'fitbit_refresh_token'. This can be triggered by a Schedule API Workflow action (or by a failed API call handler). It POSTs to https://api.fitbit.com/oauth2/token with grant_type=refresh_token, refresh_token=Current User's fitbit_refresh_token. On success, update fitbit_access_token and fitbit_token_expires_at on the User record.

Add fields to the Bubble User Data Type: fitbit_access_token (text, Private in Privacy rules), fitbit_refresh_token (text, Private), fitbit_token_expires_at (date), fitbit_connected (yes/no).

```
// API Connector call: Fitbit Token Exchange
// (Add this as a separate call within the Fitbit API group, Use as: Action)
{
  "name": "Exchange OAuth Code for Tokens",
  "method": "POST",
  "url": "https://api.fitbit.com/oauth2/token",
  "headers": [
    {
      "key": "Authorization",
      "value": "Basic BASE64_ENCODED_CLIENT_ID_COLON_SECRET",
      "private": true
    },
    {
      "key": "Content-Type",
      "value": "application/x-www-form-urlencoded"
    }
  ],
  "body": "grant_type=authorization_code&code=<dynamic>&client_id=YOUR_CLIENT_ID&redirect_uri=https://YOUR-APP.bubbleapps.io/api/1.1/wf/fitbit_oauth_callback"
}

// API Connector call: Fitbit Token Refresh
{
  "name": "Refresh Access Token",
  "method": "POST",
  "url": "https://api.fitbit.com/oauth2/token",
  "headers": [
    {
      "key": "Authorization",
      "value": "Basic BASE64_ENCODED_CLIENT_ID_COLON_SECRET",
      "private": true
    },
    {
      "key": "Content-Type",
      "value": "application/x-www-form-urlencoded"
    }
  ],
  "body": "grant_type=refresh_token&refresh_token=<dynamic: Current User's fitbit_refresh_token>"
}
```

**Expected result:** Two Backend Workflows exist: fitbit_oauth_callback (receives OAuth redirect, exchanges code for tokens, stores them on User) and fitbit_refresh_token (uses refresh_token to get a new access_token). The User Data Type has fitbit_access_token, fitbit_refresh_token, fitbit_token_expires_at, and fitbit_connected fields.

### 3. Configure the API Connector for Fitbit data calls

In Bubble's Plugins tab, install the API Connector (by Bubble, free) if not already installed. Click 'Add another API' and name it 'Fitbit'. Set the base URL to https://api.fitbit.com/1.

Add a shared header: key = Authorization, value = Bearer <dynamic: Current User's fitbit_access_token>. Check the 'Private' checkbox. This keeps the per-user access token server-side. Because this is a dynamic value (different per logged-in user), Bubble evaluates it at call time from the Current User's stored fitbit_access_token field.

Now add individual data calls. Fitbit uses different URL patterns for different data types — initialize each one separately:

Call 1 — 'Get Today's Activity': GET /user/-/activities/date/today.json. The /- means 'current authorized user'. Use as: Data. Initialize with today's date (this call requires no additional parameters). Response includes summary.steps, summary.activeMinutes, summary.caloriesOut.

Call 2 — 'Get Sleep Log': GET /user/-/sleep/date/today.json. Use as: Data. Response includes summary.totalTimeInBed, summary.totalMinutesAsleep, efficiency.

Call 3 — 'Get Heart Rate': GET /user/-/activities/heart/date/today/1d/1min.json. Use as: Data. Response includes activitiesHeart[0].value.restingHeartRate and heart rate zones.

Call 4 — 'Get Steps Time Series': GET /user/-/activities/steps/date/today/7d.json. Use as: Data. Returns 7 days of step data for the trend chart. Response: activities-steps array with dateTime and value fields.

Click 'Initialize call' for each. Provide a real test user's access_token (from the User record after completing the OAuth flow) during initialization. Bubble records the full response shape for each call.

```
{
  "api_name": "Fitbit",
  "base_url": "https://api.fitbit.com/1",
  "shared_headers": [
    {
      "key": "Authorization",
      "value": "Bearer <dynamic: Current User's fitbit_access_token>",
      "private": true
    }
  ],
  "calls": [
    {
      "name": "Get Today's Activity",
      "method": "GET",
      "path": "/user/-/activities/date/today.json",
      "use_as": "Data"
    },
    {
      "name": "Get Sleep Log",
      "method": "GET",
      "path": "/user/-/sleep/date/today.json",
      "use_as": "Data"
    },
    {
      "name": "Get Heart Rate",
      "method": "GET",
      "path": "/user/-/activities/heart/date/today/1d.json",
      "use_as": "Data"
    },
    {
      "name": "Get Steps Time Series (7d)",
      "method": "GET",
      "path": "/user/-/activities/steps/date/today/7d.json",
      "use_as": "Data"
    }
  ]
}
```

**Expected result:** The Fitbit API group in Bubble's API Connector has four initialized calls: Today's Activity, Sleep Log, Heart Rate, and Steps Time Series. Each is initialized with a real successful response and available as a data source in the editor.

### 4. Build the 'Connect Fitbit' OAuth flow in the Bubble UI

Users must individually authorize your Bubble app to access their Fitbit data before any API calls can succeed. Build this connection flow in Bubble:

Add a 'Connect Fitbit' button on your app's settings or onboarding page. The button workflow: check if Current User's fitbit_connected is 'no' or empty. If so, use Bubble's 'Go to external website' action to redirect the user to the Fitbit authorization URL (constructed as shown in Step 1's code block). Use Bubble's 'Add dynamic data' to insert the Current User's unique ID as the state parameter — this lets the callback workflow identify which Bubble user is completing authorization.

When the user authorizes on Fitbit's website, Fitbit redirects back to your Backend Workflow (fitbit_oauth_callback). The Backend Workflow receives the code, exchanges it for tokens (via the API Connector's token exchange call), stores the tokens on the User record, and sets fitbit_connected = yes. Finally, redirect the user back to the dashboard page using a 'Return data from API' step or by having the callback workflow send a notification (Bubble's notification or a page state change via URL parameter).

Add conditional logic to the dashboard page: show the Fitbit data elements only when Current User's fitbit_connected = yes. When fitbit_connected = no, show the 'Connect Fitbit' button and an explanation of what data will be shared and why.

For token refresh: add a condition on the dashboard page's 'Page is loaded' trigger that checks if Current User's fitbit_token_expires_at < Current date/time. If the token has expired, trigger a workflow that runs the fitbit_refresh_token Backend Workflow before loading any Fitbit data. If the refresh fails (refresh token also expired), show a 'Reconnect Fitbit' prompt. RapidDev's team has built dozens of OAuth-connected wellness apps in Bubble — reach out for a free scoping call at rapidevelopers.com/contact if the token flow logic needs architectural help.

```
// Fitbit Authorization URL construction for the 'Connect Fitbit' button
// In Bubble: Go to external website action with this dynamic URL:

https://www.fitbit.com/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https://YOUR-APP.bubbleapps.io/api/1.1/wf/fitbit_oauth_callback&scope=activity%20sleep%20heartrate%20profile&state=[Current User's unique id]

// After OAuth completes, the callback Backend Workflow receives:
// https://YOUR-APP.bubbleapps.io/api/1.1/wf/fitbit_oauth_callback?code=AUTH_CODE&state=USER_UNIQUE_ID

// The workflow then:
// 1. Searches for User where unique id = request_data's state
// 2. Calls Fitbit token exchange with request_data's code
// 3. Sets that User's fitbit_access_token, fitbit_refresh_token, fitbit_token_expires_at, fitbit_connected=yes
```

**Expected result:** The 'Connect Fitbit' button redirects users to Fitbit's authorization screen. After authorization, the callback Backend Workflow runs, stores tokens on the User record, and sets fitbit_connected = yes. The dashboard conditionally shows Fitbit data elements for connected users and the connect button for unconnected users.

### 5. Build the fitness dashboard with step, sleep, and heart rate data

With the OAuth flow working and API Connector initialized, build the fitness dashboard page. Add a condition on page load: when fitbit_connected = yes, run the API calls and show the dashboard. When fitbit_connected = no, show the connect prompt.

For today's steps: add a Text element. Set its content to 'Get Today's Activity's summary steps' from the API Connector. Add a Repeating Group or a progress bar element bound to (current steps ÷ 10000 × 100) to show goal progress.

For the 7-day step trend chart: install a chart plugin from Plugins tab (search 'ApexCharts by Zeroqode' or 'Bubble Charts'). Bind the chart's data series to 'Get Steps Time Series (7d)'s activities-steps'. Set X axis to the dateTime field and Y axis to the value field (step count). The chart automatically renders 7 bars from the API response.

For sleep: add Text elements showing 'Get Sleep Log's summary totalMinutesAsleep ÷ 60' (formatted as hours and minutes) and 'Get Sleep Log's efficiency' (percentage).

For resting heart rate: add a Text element showing 'Get Heart Rate's activities-heart[0] restingHeartRate'.

Handle the case where an API call returns an error due to expired token: add a 'When Get Today's Activity fails' error handler. If the error is a 401, trigger the fitbit_refresh_token Backend Workflow and then re-run the activity call. If the error persists after refresh, show the 'Reconnect Fitbit' prompt.

Rate limit awareness: Fitbit allows up to 150 API requests per user per hour. For a dashboard that makes 4 calls on page load, loading the page 37 times per hour would hit the limit. Add a 30-second minimum between page refreshes using a Bubble timer to prevent runaway reloads.

**Expected result:** A fitness dashboard page shows today's step count (with goal progress indicator), last night's sleep duration and efficiency, current resting heart rate, and a 7-day step trend chart. Token expiry is handled with automatic refresh and a reconnect prompt. The layout is conditionally shown only for Fitbit-connected users.

## Best practices

- Implement Fitbit token refresh before going live. Access tokens expire every 8 hours — any user who returns to your app after a long gap will see broken API calls without a refresh mechanism. Build and test the refresh flow during development, not as an afterthought.
- Always mark the Authorization Bearer token header as Private in Bubble's API Connector. Fitbit access tokens allow reading the user's personal health data — exposed tokens can be misused to access that user's Fitbit account.
- Store Fitbit tokens (fitbit_access_token and fitbit_refresh_token) on the User Data Type with Privacy rules set so only the user themselves can read their own token fields. No other user should be able to read another user's Fitbit tokens.
- Initialize each Fitbit API call separately — activity, sleep, and heart rate endpoints use different URL patterns. Never assume the URL structure from one endpoint applies to another. Verify paths against Fitbit's current API documentation.
- Respect Fitbit's rate limit (verify current limits at dev.fitbit.com). For a dashboard that fetches 4 data types on load, a user refreshing the page more than 30 times in an hour could approach the per-user hourly limit. Add a minimum refresh interval or cache data in a DailyFitbitData Bubble Data Type to reduce repeat calls.
- Request only the OAuth scopes your app actually uses during authorization. If you only display steps and sleep, request 'activity sleep' — not heartrate, weight, or nutrition. Minimal scope requests increase user authorization rates and reduce privacy risk.
- Add Privacy rules to any Bubble Data Type that stores Fitbit health metrics. Daily step counts, sleep patterns, and heart rate data are personal health information — restrict visibility to the associated user only.
- Show a clear 'Connect Fitbit' onboarding flow for new users with an explanation of what data will be accessed and why. First-time users have no context for the OAuth redirect — a confused user will click 'Deny' on Fitbit's authorization screen.

## Use cases

### Personal fitness tracking dashboard

Build a Bubble app where users connect their Fitbit account via OAuth 2.0 and see a dashboard of their daily step count for the past 7 days, last night's sleep duration, current day's active minutes, and resting heart rate. Data refreshes on page load by calling Fitbit's API with the user's stored Bearer token. Add goal indicators showing percentage toward daily step and sleep targets.

Prompt example:

```
Build a Bubble fitness dashboard page showing: today's step count vs. a 10,000-step goal (as a progress bar), last night's sleep hours, this week's daily step counts as a bar chart (7 bars), and average resting heart rate this week. Data from Fitbit API calls wired to Current User's stored fitbit_access_token. Add a 'Connect Fitbit' button for users who have not yet authorized.
```

### Corporate wellness program step challenge

Build a Bubble app for a corporate step challenge where employees connect their Fitbit accounts. A dashboard shows each participant's weekly step total and ranks them on a leaderboard. The app calls Fitbit's step endpoint weekly to update totals. Participants see their own rank, total steps, and how close they are to the team's collective goal.

Prompt example:

```
Build a Bubble step challenge page with a Repeating Group leaderboard showing participant name, weekly step total, and rank. Add a 'Your Stats' section showing the current user's daily step breakdown for the challenge week as a bar chart. Include a team progress bar toward the collective weekly goal. Data from Fitbit API calls per user, stored daily in StepEntry Data Type.
```

### Sleep quality and recovery tracker for athletes

Build a Bubble app for athletes that combines Fitbit sleep data (total sleep, sleep efficiency, REM minutes, deep sleep minutes) with a self-reported recovery rating entered daily. Display sleep consistency trends over 30 days and correlate sleep quality scores with subsequent workout performance. Alert athletes when sleep scores drop below a threshold for multiple consecutive nights.

Prompt example:

```
Build a Bubble athlete recovery page showing a 30-day sleep trend line chart (sleep efficiency percentage), a sleep stage breakdown pie chart for the last night (REM, deep, light, awake), a self-reported daily recovery slider (1–10), and a correlation chart comparing average sleep score to workout intensity for the past month. Data from Fitbit sleep endpoint and Recovery Data Type.
```

## Troubleshooting

### Fitbit API calls return 401 Unauthorized on the dashboard after working correctly for several hours

Cause: Fitbit access tokens expire every 8 hours. The token stored on the User record is no longer valid, and the API Connector is sending the expired token.

Solution: Implement the token refresh workflow (Step 2). Add a page-load condition that checks if Current User's fitbit_token_expires_at is in the past, and if so, runs the fitbit_refresh_token Backend Workflow before loading any dashboard data. After refreshing, re-run the API Connector calls. If the refresh itself returns 401 (refresh token also expired or revoked), clear the fitbit_connected field and show the 'Reconnect Fitbit' button.

### 'There was an issue setting up your call' when initializing a Fitbit API call in the API Connector

Cause: The test access token used during initialization is expired, invalid, or belongs to a user who has revoked access. Alternatively, the endpoint URL path is incorrect.

Solution: Ensure you have a fresh, valid Fitbit access token for a real test user. Complete the OAuth flow (Steps 1–3) with your own Fitbit account first, retrieve the stored access_token from Bubble's User record in the Data tab, and use that token during initialization. The token must be less than 8 hours old. Double-check the endpoint path against Fitbit's API documentation for the exact URL pattern for each data type.

### The heart rate endpoint returns data for the activity endpoint correctly, but the sleep endpoint fails with 404 or returns empty data

Cause: Fitbit sleep and heart rate endpoints use different URL patterns from the activity endpoint and must be initialized separately. A sleep call with the wrong URL pattern returns 404, and initializing with empty sleep data (e.g., initializing before the test user has synced sleep data today) gives Bubble no field schema to work with.

Solution: Verify the exact sleep endpoint URL from Fitbit's documentation: /user/-/sleep/date/today.json. Initialize the sleep call using a date when the test Fitbit account has actual sleep data recorded — check the Fitbit app on your phone to confirm the device synced sleep for the date you use in initialization. If today has no sleep data, initialize with yesterday's date as a URL parameter.

### Fitbit OAuth callback URL does not match error when completing authorization

Cause: The redirect_uri used in the authorization URL does not exactly match the Callback URL registered in the Fitbit developer portal. Fitbit requires an exact match including protocol (https://), domain, and path.

Solution: Go to dev.fitbit.com → your app registration → Callback URL. Copy the exact URL you registered. Make sure the authorization URL you build in Bubble's 'Go to external website' action uses exactly the same string — including the /api/1.1/wf/ path, the workflow name, and no trailing slash differences. Update the Fitbit developer portal registration if you have changed your Bubble app's URL.

## Frequently asked questions

### Why do Fitbit API calls fail after working for a few hours?

Fitbit access tokens expire every 8 hours. After expiry, API calls return 401 Unauthorized. You need to implement a token refresh flow: when the token expires, POST to Fitbit's /oauth2/token endpoint with grant_type=refresh_token and the stored refresh_token to get a new access_token. Run this refresh via a Bubble Backend Workflow triggered when a 401 is detected or proactively on page load when fitbit_token_expires_at is in the past.

### Does integrating Fitbit with Bubble require a paid Bubble plan?

The API Connector calls to Fitbit work on any Bubble plan. However, the OAuth 2.0 token exchange (receiving the callback after user authorization) and the token refresh workflow require Backend Workflows (API Workflows), which are only available on paid plans (Starter $32/month or higher). Without Backend Workflows, you cannot complete the per-user OAuth flow, so token management is blocked on the free plan.

### Can I access multiple users' Fitbit data from one Bubble app?

Yes. Each Fitbit user independently authorizes your app via OAuth 2.0 and you receive a separate access_token and refresh_token per user. Store these tokens on each User record in Bubble's User Data Type. When calling the Fitbit API, use the Current User's stored token as the Bearer header. This way each user's API calls retrieve only their own Fitbit data — Bubble's per-user context handles the separation automatically.

### Are Fitbit sleep and step data available in the same API call?

No. Fitbit uses separate endpoint URLs for each data type: /user/-/activities/date/today.json for activity (steps, calories, active minutes), /user/-/sleep/date/today.json for sleep data, and /user/-/activities/heart/date/today/1d.json for heart rate. Each must be configured and initialized as a separate call in Bubble's API Connector. On a dashboard page that shows all three, three separate API Connector calls run when the page loads.

### Does Fitbit charge for API access?

Fitbit's Web API is free for self-serve personal and development use. There is no per-call cost. Some Fitbit data types (intraday heart rate, intraday steps at minute-level resolution) require a special Partner level API access that Fitbit grants to specific use cases. For standard daily/hourly summaries, the free tier is sufficient. Check dev.fitbit.com for current access level requirements.

### What happens if a user revokes Fitbit access to my app?

If a user revokes access from their Fitbit account settings, subsequent API calls with their stored access_token will return 401 Unauthorized, and the refresh_token will also be invalid (refresh attempts return 401 as well). Handle this by clearing fitbit_access_token, fitbit_refresh_token, and fitbit_connected from the User record when both the access call and refresh call return 401, and showing the 'Reconnect Fitbit' prompt.

---

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