The Google Health API is a comprehensive solution built from the ground up, providing developers with robust access to a wide array of consented user health data and diverse data types. The Google Health API utilizes a new console for registering your apps, Google OAuth 2.0, new data types, new endpoint schema, and a new response format.
This guide is designed to help developers migrate their existing Fitbit Web API apps to the new Google Health API. It features recommendations to ensure a seamless migration while retaining users.
Why should you migrate?
This is not just an update, it's a strategic move to ensure your apps are secure and ready for future advancements in health technology. Some of the benefits of using the Google Health API are:
- Access to Comprehensive Data: Gain robust access to a wide array of consented user health data and diverse data types.
- Enhanced Security: Compliance with Google's security best practices, aligning with Google's security, privacy, and identity standards.
- Consistency: Eliminates legacy inconsistencies in data formats, time zones, measurement units, and error handling for a more intuitive developer experience.
- Scalability & Future-Proofing: Designed to scale to meet future demands and supports modern protocols like gRPC.
Transitioning from the Fitbit Web API to the Google Health API involves more than technical modifications. Because of the switch to a new OAuth library, existing access and refresh tokens cannot be transferred, requiring users to re-consent to your updated integration.
Support both login methods
Since the Fitbit Web API and the Google Health API use different systems to handle user logins, while the Fitbit Web APIs are still active, your app will temporarily need to support both authentication methods across your user base.
Instead of your app asking for data directly, implement a layer that decides whether to talk to the Fitbit Web API or Google Health API for a specific user, so the rest of your app doesn't need to worry about the details.
Update your user database to include a flag (for example, oauth_type)
to identify which login system they are using.
- For new users: Automatically set them up with the new Google Health API
(
oauth_type: google). - For existing users: Keep them on the Fitbit Web API until they update
their consent (
oauth_type: fitbit).
| User Scenario | oauth_type State |
Active API Stream | Required Action |
|---|---|---|---|
| New user onboarding | google |
Google Health API only | Launch Google OAuth in a system browser and map IDs with users.getIdentity. |
| Existing Fitbit user (not yet migrated) | fitbit |
Fitbit Web API only | Continue syncing with Fitbit Web API; display a dismissible in-app prompt to upgrade. |
| Existing Fitbit user completing migration | fitbit → google |
Cut over to Google Health API | Verify granted scopes, call users.getIdentity (legacyUserId → healthUserId), switch oauth_type to google, and immediately revoke legacy Fitbit tokens. |
To avoid disrupting the user experience, we recommend not forcing everyone to log out and log back in. Instead:
- When a user who remains connected to the Fitbit Web APIs engages with your app, show them a friendly notification encouraging them to update their connection.
- When the user accepts the update action, trigger the Google Health login flow right then.
- Once the Google login is successful, save the new Google credentials to the
user's profile, switch their
oauth_typeflag fromfitbittogoogle, and immediately disable legacy syncing by programmatically revoking their legacy Fitbit tokens to prevent duplicate data ingestion and dual-linking.
Ensure data continuity
When transitioning an integration from the legacy Fitbit Web API to the Google Health API, developer applications must account for a change in user identification structures.
The legacy Fitbit Web API identifies accounts using a 6-character alphanumeric
string (such as A1B2C3), whereas the Google Health API utilizes a
healthUserId formatted as a string of up to 63 digits & characters.
To bridge this gap without losing user context, developers can query the
getIdentity endpoint to get the Fitbit and Health user
IDs.
This endpoint returns a payload containing both the legacyUserId and the new
healthUserId, enabling applications to dynamically create a mapping between
existing records and the new account system.
Handle unlinked Google Accounts
A user must sign in to the Google Health mobile app to link Google Health to their Google Account before your app can sync data through the Google Health API. See Link Google Health before OAuth consent. Because Google OAuth 2.0 authenticates any valid Google Account during consent, it cannot block accounts that don't yet have an active Google Health profile.
If a user completes Google OAuth consent before creating a Google Health profile
or before migrating their legacy Fitbit account to their Google Account, calling
users.getIdentity or a data
endpoint returns an HTTP 400 Bad Request error with status
FAILED_PRECONDITION and reason ACCOUNT_NOT_LINKED:
{
"error": {
"code": 400,
"message": "The account is not linked to Google Health.",
"status": "FAILED_PRECONDITION",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "ACCOUNT_NOT_LINKED",
"domain": "health.googleapis.com",
"metadata": {
"redirect_uri": "https://fitbit.google.com/auth/signup"
}
}
]
}
}
To verify account access and handle this error:
- Exchange the OAuth authorization code for tokens and immediately call
users.getIdentity(GET https://health.googleapis.com/v4/users/me/identity) before marking the account as linked in your app UI. - If
users.getIdentityor a background data sync call returns HTTP400with reasonACCOUNT_NOT_LINKED, don't mark the account as connected. If the user is migrating from the legacy Fitbit Web API, keep their existing Fitbit connection active and don't revoke their legacy Fitbit tokens. Display an actionable prompt that addresses both new users who have never created a Google Health or Fitbit account and existing Fitbit users who have not yet migrated to a Google Account:
"Your Google Account isn't linked to Google Health yet. Open the Google Health mobile app and sign in with your Google Account to create a profile, or move your existing Fitbit account to your Google Account, then return here to connect."
Provide a call-to-action button that opens the
redirect_urifrom the error metadata (https://fitbit.google.com/auth/signup) or direct legacy Fitbit users to the Fitbit account migration help article.
Backfill historical data
If a user does not authenticate to the new Google Health API endpoints before the legacy endpoints are turned down, their data will still be available as long as they continue syncing their device to the Google Health app. However, you may have a gap in data for this user.
To backfill their data, once the user re-authenticates to the new endpoints, you can use our Google Health API to backfill their historical data. See Query historical data for guidance.
Sample migration and linking implementation
The following reference implementation demonstrates how to handle the OAuth
callback, validate partial or zero consent, resolve user identity with
users.getIdentity (including HTTP 400 ACCOUNT_NOT_LINKED handling),
prevent dual-linking by revoking legacy Fitbit tokens, and route data
requests using oauth_type:
import requests
GOOGLE_HEALTH_IDENTITY_URL = "https://health.googleapis.com/v4/users/me/identity"
FITBIT_REVOKE_URL = "https://api.fitbit.com/oauth2/revoke"
DEFAULT_SIGNUP_URL = "https://fitbit.google.com/auth/signup"
def complete_google_health_linking(user_record: dict, oauth_token_response: dict, fitbit_client_creds: str) -> dict:
"""Completes Google Health account linking and migrates legacy Fitbit users."""
# 1. Check granted scopes (handle zero consent and partial consent)
granted_scopes = set(oauth_token_response.get("scope", "").split())
health_scopes = {s for s in granted_scopes if s.startswith("https://www.googleapis.com/auth/googlehealth")}
if not health_scopes:
return {
"status": "MISSING_PERMISSIONS",
"ui_action": "ROUTE_TO_MISSING_PERMISSIONS_SCREEN",
"message": "At least one permission is required to connect to Google Health.",
}
access_token = oauth_token_response["access_token"]
# 2. Call users.getIdentity and handle HTTP 400 ACCOUNT_NOT_LINKED
identity_resp = requests.get(
GOOGLE_HEALTH_IDENTITY_URL,
headers={"Authorization": f"Bearer {access_token}", "Accept": "application/json"},
timeout=10,
)
if identity_resp.status_code == 400:
error_payload = identity_resp.json().get("error", {})
for detail in error_payload.get("details", []):
if detail.get("reason") == "ACCOUNT_NOT_LINKED":
redirect_uri = detail.get("metadata", {}).get("redirect_uri", DEFAULT_SIGNUP_URL)
return {
"status": "ACCOUNT_NOT_LINKED",
"ui_action": "PROMPT_LINK_GOOGLE_HEALTH_ACCOUNT",
"redirect_uri": redirect_uri,
"message": (
"Your Google Account isn't linked to Google Health yet. "
"Open the Google Health mobile app and sign in with your Google Account "
"to create a profile, or move your existing Fitbit account to your "
"Google Account, then return here to connect."
),
}
identity_resp.raise_for_status()
identity = identity_resp.json()
# 3. Map legacyUserId -> healthUserId and cut over oauth_type to prevent dual-linking
previous_oauth_type = user_record.get("oauth_type")
legacy_fitbit_refresh_token = user_record.get("fitbit_refresh_token")
user_record.update({
"health_user_id": identity.get("healthUserId"),
"legacy_user_id": identity.get("legacyUserId") or user_record.get("legacy_user_id"),
"google_access_token": access_token,
"google_refresh_token": oauth_token_response.get("refresh_token"),
"granted_scopes": list(health_scopes),
"oauth_type": "google",
"fitbit_access_token": None,
"fitbit_refresh_token": None,
})
# 4. Immediately revoke legacy Fitbit token so both APIs never run simultaneously
if previous_oauth_type == "fitbit" and legacy_fitbit_refresh_token:
requests.post(
FITBIT_REVOKE_URL,
headers={
"Authorization": f"Basic {fitbit_client_creds}",
"Content-Type": "application/x-www-form-urlencoded",
},
data={"token": legacy_fitbit_refresh_token},
timeout=10,
)
return {
"status": "CONNECTED",
"health_user_id": user_record["health_user_id"],
"granted_scopes": user_record["granted_scopes"],
}
Communication and timing
To help your users move from their existing Fitbit OAuth to the new Google OAuth, follow these best practices.
Value-first communication
Don't lead with "We updated our API", lead with the benefits integrating Google Health data into your app but ensure they are aware that they need to re-authenticate if they want their data to sync:
- Clearly explain what features are available in your app that are powered by the integration, and tailor your message to how a user benefits from those features.
- Focus on your features and provide use cases instead of technical implementation details.
- Don't say: "You will not be able to connect to the Fitbit API."
- Do say: "To continue seeing detailed workouts with heart rate data, re-consent to the Google Health APIs."
When to notify users
In all user communications, adhere to the Google Health brand guidelines, and use dismissible banners, cards, or alerts.
- Don't trigger the re-consent screen while a user is in the middle of a workout or manually logging something.
- Only make the re-consent mandatory after several weeks of warnings, coinciding with the official Fitbit Web API deprecation deadlines.
- If a user has not re-consented after the hard cutoff, provide a graceful recovery path. Provide a help message in a banner, card or tooltip that helps them understand why their data is missing, and how to fix it.