Cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Google Health API: 403 "Could not mint UberMint from GaiaMint" on all endpoints

Hi everyone,

I'm getting a 403 PERMISSION_DENIED error on all Google Health API (health.googleapis.com/v4) endpoints. Has anyone else encountered this?

Error response:

{ "error": { "code": 403, "message": "com.google.apps.framework.request.ForbiddenException: Could not mint UberMint from GaiaMint", "status": "PERMISSION_DENIED" } }

Example request:

GET https://health.googleapis.com/v4/users/me/dataTypes/steps/dataPoints?filter=steps.interval.start_tim... >= "YYYY-MM-DDT00:00:00Z" Authorization: Bearer {access_token}

What I've verified:

  • Google Health API is enabled in GCP Console
  • Fitbit account has data and is migrated to Google account
  • OAuth client created as Web Application
  • Health API scopes added to OAuth consent screen (Data Access → Sensitive scopes)
  • OAuth in Testing mode with test user added
  • access_token and refresh_token obtained successfully
  • Scopes confirmed in callback (googlehealth.health_metrics_and_measurements.readonly, activity_and_fitness.readonly, sleep.readonly, profile.readonly, settings.readonly)

The same error occurs on every endpoint I've tried (GET dataPoints, POST dailyRollUp, etc.). The error message looks like an internal token issue rather than a configuration problem on my end.

Is anyone else experiencing this, or has anyone successfully made calls to the Google Health API?

Thanks!

Best Answer
18 REPLIES 18

We've done the exact same as you and are getting the same issue.

Best Answer

Hey mate, i had the same 403 issue a while back. What actually helped me was regenerating the access token and making sure the test user was fully added to the OAuth consent screen. I also double-checked that all the sensitive scopes were approved for that user. After that, the App API calls started working without the permission errors.

Best Answer

Hi @dandaso 

Thanks for sharing your questions. 

We are currently investigating the issues. We'll get back to you as soon as we have an update.

Best Answer
0 Votes

The issue mentioned above has been confirmed to be resolved as of April 4.
Since no changes were made to this program, it is assumed that the issue was resolved due to an internal update on Google’s side.

Thanks!

Best Answer
0 Votes

Hi @dandaso 

Thanks for letting us know!

Best Answer
0 Votes

Hi @DorisFitbit,

I'm hitting the same issue and it doesn't appear to be resolved for my account. Here's my setup:
- Google Health API enabled in GCP Console
- Web Application OAuth client (not Desktop)
- All health scopes added to OAuth consent screen (Data Access) and confirmed granted via Google's tokeninfo endpoint
- Test user added to OAuth consent screen
- Fitbit account signed in with Google, Fitbit Charge 6 with continuous sync and plenty of data
- Token has refresh_token (access_type=offline, prompt=consent)

Every endpoint returns the same error:
{
"error": {
"code": 403,
"message": "com.google.apps.framework.request.ForbiddenException: Could not mint UberMint from GaiaMint",
"status": "PERMISSION_DENIED"
}
}

The identity endpoint returns a slightly different error:
{
"error": {
"code": 403,
"message": "The caller does not have permission",
"status": "PERMISSION_DENIED"
}
}

Calendar API works fine with the same token, confirming the token itself is valid.

Is there an additional enrollment or provisioning step required on Google's side for new GCP projects? The April 4 internal fix mentioned by @dandaso doesn't seem to have applied here.

Thanks

Best Answer
0 Votes

Hi @newbiedev 

Thanks for posting your question. 

We need more details from you. I will email you and we can move the discussion to issue tracker. 

Best Answer
0 Votes

Hi @DorisFitbit

I am facing the same error.

Request:

GET https://health.googleapis.com/v4/users/me/dataTypes/exercise/dataPoints?filter=...&pageSize=25
Authorization: Bearer <access_token>

Response:

{
"message": "com.google.apps.framework.request.ForbiddenException: Could not mint UberMint from GaiaMint",
"googleStatus": "PERMISSION_DENIED",
"googleDetails": null
}
Best Answer
0 Votes

Hi @ammarg 

We're happy to provdie supprot. I will email you for futher details. Please check your email. Thank you!

Best Answer
0 Votes

One cause of this error is the account which consented to share their data is a legacy Fitbit account.  The Google Health API does not support legacy Fitbit accounts.  It only supports Google accounts.  Here are suggestions to try if you get this error message.

  1. Sign out of the Fitbit mobile app through the Fitbit settings.
  2. Sign into the Fitbit mobile app either by pressing the "Continue with Google" or "Sign in with Google" button.  If you receive a message that states "Can't use Fitbit with this Google Account, your email address is still registered as a legacy Fitbit account.  Follow the steps in this help article to migrate your account.
  3. If you successfully sign into the Fitbit mobile app with your Google account, try consenting again using the same email address.

If you have followed these steps, please let us know.

Best Answer
0 Votes

@GordonFitbitThat's not the issue. I am logged in via my Google account.

Best Answer
0 Votes

Hi @DorisFitbit @GordonFitbit,

I'm hitting a persistent 403 PERMISSION_DENIED on every Google Health API endpoint (health.googleapis.com/v4/...) — same error as @dandaso, @stevey.sepiida, @newbiedev, and
@ammarg in this thread. I've followed Gordon's troubleshooting steps from the reply and the official Google Health API docs with no change. Could you please put me on
the issue tracker (same as @newbiedev)?

Error response (consistent across all endpoints):
{
"error": {
"code": 403,
"message": "com.google.apps.framework.request.ForbiddenException: Could not mint UberMint from GaiaMint",
"status": "PERMISSION_DENIED"
}
}

Account details:
- Email: nextmab gmail.com
- Fitbit account: created via "Continue with Google" several months ago (never a legacy email/password account)
- Device: Pixel Watch with continuous sync — data is healthy on fitbit.com (600k lifetime steps, recent sleep/activity logs confirmed today)

GCP project:
- Project ID: chet-bot
- Project Number: 875231707536

OAuth setup:
- Google Health API enabled in the project
- OAuth Client type: Desktop app
- OAuth consent screen: Testing mode; my email is in the Test users list
- Scopes granted on the issued token:
- googlehealth.health_metrics_and_measurements.readonly
- googlehealth.activity_and_fitness.readonly
- googlehealth.sleep.readonly
- googlehealth.profile.readonly
- Other Google APIs (Calendar, Drive, Tasks) work fine with the same project and the same token, confirming token validity.

Steps already tried (none changed the error):
1. Revoked the OAuth grant at myaccount.google.com/permissions and re-minted brand-new access + refresh tokens.
2. Signed out of the Fitbit mobile app and signed back in via "Continue with Google" (per Gordon's troubleshooting).
3. Installed Health Connect and enabled "Sync with Health Connect" inside the Fitbit app.
4. Verified my email is in the Test users list on the OAuth consent screen.

Example request that 403s:
GET https://health.googleapis.com/v4/users/me/dataTypes/sleep/dataPoints
?filter=sleep.interval.civil_end_time >= "20260428" AND sleep.interval.civil_end_time < "20260429"
&pageSize=100
Authorization: Bearer <access_token>

Happy to provide request IDs, raw token info, or anything else off-list.

Thanks!

Best Answer
0 Votes

Ok i managed to solve it, if this helps anyone.

TL;DR

If every call to [health.googleapis.com/v4/](https://health.googleapis.com/v4/)... returns:

HTTP/1.1 403 Forbidden
{
"error": {
"code": 403,
"status": "PERMISSION_DENIED",
"message": "com.google.apps.framework.request.ForbiddenException: Could not mint UberMint from GaiaMint"
}
}

…and the same OAuth token works fine against other Google APIs (Gmail, Calendar, Drive, Tasks), the cause is most likely scope-mix in your OAuth grant: the Google Health backend rejects access tokens whose grant also includes consumer scopes (Gmail/Drive/Calendar/etc.) for the same Google identity.

Fix: mint two separate tokens for the same Google account: one with only the 4 googlehealth.*.readonly scopes, and one with everything else. Use them in parallel from your application. Both grants coexist on the same Google account.

This took me from 100% 403 to a working /v4/users/me/dataPoints:list returning real watch data.

---

Symptom

my setup:
- One Google Cloud project with the Google Health API enabled.
- A single OAuth consent screen (External, Testing) with the test user added.
- A single OAuth client (Desktop app type).
- A single token file containing a refresh token granted 11 scopes in one consent flow:

[https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly](https://www.googleapis.c...)
[https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly](https://www.g...)
[https://www.googleapis.com/auth/googlehealth.sleep.readonly](https://www.googleapis.com/auth/googleh...)
[https://www.googleapis.com/auth/googlehealth.profile.readonly](https://www.googleapis.com/auth/googl...)
[https://www.googleapis.com/auth/spreadsheets](https://www.googleapis.com/auth/spreadsheets)
[https://www.googleapis.com/auth/documents](https://www.googleapis.com/auth/documents)
[https://www.googleapis.com/auth/drive](https://www.googleapis.com/auth/drive)
[https://www.googleapis.com/auth/gmail.modify](https://www.googleapis.com/auth/gmail.modify)
[https://www.googleapis.com/auth/contacts](https://www.googleapis.com/auth/contacts)
[https://www.googleapis.com/auth/calendar](https://www.googleapis.com/auth/calendar)
[https://www.googleapis.com/auth/tasks](https://www.googleapis.com/auth/tasks)

Behavior:
- Gmail / Calendar / Drive / Tasks: 200 OK, returns expected data.
- Anything under [health.googleapis.com/v4/](https://health.googleapis.com/v4/😞 403 PERMISSION_DENIED - Could not mint UberMint from GaiaMint.

The token itself was clearly valid (other APIs accept it). The 403 is not a missing-scope problem - it surfaces from Google's internal identity-link layer ("UberMint" = Health-specific identity, "GaiaMint" = the Google account identity it's being minted from).

---

What did NOT fix it

I exhausted every client-side angle that the official Google Health troubleshooting docs and the Fitbit forum thread mention:

1. Revoking the OAuth grant at [myaccount.google.com/permissions](https://myaccount.google.com/permissions), deleting the local token, and re-running the OAuth flow to mint a brand-new access + refresh token. Same 403.
2. Signing out of the Fitbit mobile app and signing back in via "Continue with Google" (Gordon's official advice for legacy-Fitbit-account cases). The account had been Google-native from the start, so this was a no-op — and the 403 didn't change.
3. Installing Health Connect on the phone and enabling "Sync with Health Connect" inside the Fitbit app. Sync confirmed working (data flowing into Health Connect), 403 unchanged.
4. Verifying the test user is in the OAuth consent screen Test users list. Confirmed.
5. Verifying all 4 googlehealth.*.readonly scopes are saved on the consent screen Data Access page. Confirmed (multiple times - the consent dialog's "Save" button needs to be clicked at the bottom).
6. Confirming the GCP project has the Google Health API enabled in the API library. Confirmed.

I also cross-checked our request shapes against the v4 discovery doc ([https://health.googleapis.com/$discovery/rest?version=v4](https://health.googleapis.com/$discovery/r...)) — URL paths use kebab-case data type IDs (e.g. heart-rate), filter expressions use snake-case (data_type_id="heart_rate"), correct.

So: token valid, scopes granted, project configured, app correct. Yet 403 every time.

---

The root cause: scope-mix in the OAuth grant

OAuth grants on Google work like this: when you complete a consent flow with a set of scopes S, Google records a grant (account, client_id, S). A subsequent flow with a different scope set S' for the same (account, client_id) replaces or merges the grant - the most-recent flow wins for any overlapping scope, and disjoint scope sets coexist.

my hypothesis (which the experiment below confirms): the Google Health backend's identity-link step refuses to mint an UberMint when the inbound access token's grant also contains non-Health consumer scopes for that same Gaia identity. The grant looks "mixed" to it, and it returns 403 instead of issuing the Health identity.

i have not seen Google document this anywhere. But the empirical behavior is clean: pure-Health grant works, mixed grant doesn't, on the same Google account, same OAuth client, same project.

---

The fix: two scope-pure tokens, same account

I split the single 11-scope grant into two separate grants on the same (account, client_id):

- Health token — 4 scopes only:
googlehealth.profile.readonly
googlehealth.sleep.readonly
googlehealth.activity_and_fitness.readonly
googlehealth.health_metrics_and_measurements.readonly

- Main token — the other 7 (Gmail / Drive / Calendar / Tasks / Docs / Sheets / Contacts).

Both are minted by running the same OAuth installed-app flow twice, with different scope lists and writing to different token files. The order matters: mint the main token first, then the health token, because the second flow will revoke any prior grant whose scopes overlap. (Two flows with disjoint scope sets do not overlap, so they coexist.)

The application then loads each token from its own path and uses it for the appropriate API:

- Gmail/Drive/Calendar/Tasks calls -> main token.
- Anything under [health.googleapis.com/v4/](https://health.googleapis.com/v4/) -> health token.

---

Empirical verification

After the split, with both tokens freshly minted:

Gmail with the main token (no Health scopes):
$ curl -H "Authorization: Bearer $MAIN_TOKEN" \
[https://gmail.googleapis.com/gmail/v1/users/me/profile](https://gmail.googleapis.com/gmail/v1/users/...)
{
"emailAddress": "nextmab gmail.com",
"messagesTotal": 10875,
...
}

Google Health with the health-only token (4 scopes):
$ curl -X POST -H "Authorization: Bearer $HEALTH_TOKEN" \
-H "Content-Type: application/json" \
"[https://health.googleapis.com/v4/users/me/dataPoints:list](https://health.googleapis.com/v4/users/me...)" \
-d '{"filter":"data_type_id=\"resting_heart_rate\""}'
{
"dataPoints": [
{
"dataTypeId": "resting_heart_rate",
"restingHeartRate": { "bpm": 62 },
"civilEndTime": { "date": { "year": 2026, "month": 4, "day": 29 }, ... },
"metadata": { "dataOriginType": "DEVICE", ... }
}
]
}

Resting heart rate from a Pixel Watch 4, on the same Google account that had been getting 403'd for days.

Cross-verification - the failing case still fails:

Reissuing the original 11-scope token (Health + consumer scopes in one grant) reproduces the 403 immediately. Splitting them fixes it. The scope set is the only variable.

---

How to apply this fix

If you're stuck on UberMint 403 and your token also has non-Health scopes, follow this exact order. The order matters: you need to mint the Health token first while the consent
screen is restricted to Health scopes only, then add the rest.

1. Revoke any existing grant. Go to https://myaccount.google.com/permissions, find your OAuth client, click Remove access. Delete any local token files. You want to start clean -
no mixed grant lying around.

2. Restrict the consent screen to Health scopes only. In the Google Cloud Console -> APIs & Services -> OAuth consent screen -> Data Access, remove every non-Health scope. The only
scopes saved should be the 4 googlehealth.*.readonly ones:
googlehealth.profile.readonly
googlehealth.sleep.readonly
googlehealth.activity_and_fitness.readonly
googlehealth.health_metrics_and_measurements.readonly
Click update and Save at the bottom of the page.

3. Mint the Health-only token. Run your installed-app OAuth flow requesting only those 4 scopes, and write it to a dedicated path (e.g. google_health_token.json). The consent
dialog should show only the 4 Health scopes - if it shows anything else, the consent screen wasn't fully cleaned up in step 2.

4. Add the rest of your scopes back to the consent screen. Go back to the same Data Access page and add Gmail / Drive / Calendar / Tasks / Docs / Sheets / Contacts (whatever your
app needs). Click Save again.

5. Mint the main (non-Health) token. Run your OAuth flow a second time, this time requesting only the non-Health scopes you just added, and write it to your normal token path
(e.g. google_token.json). The consent dialog this time should show only consumer scopes.

6. Wire your app to use both. Health calls (anything under https://health.googleapis.com/v4/) load credentials from the Health token path. Everything else
(Gmail/Drive/Calendar/Tasks) uses the main token path.

7. Verify with curl. Both tokens should now work for their respective APIs, and https://health.googleapis.com/v4/users/me/dataPoints:list should stop 403'ing.

If you're using google-auth-oauthlib's InstalledAppFlow.from_client_secrets_file(client_secret, scopes), you just call it twice with two different scope lists and write to two
different paths.

Important: never re-run the main OAuth flow with Health scopes added back in. The moment the main flow's grant includes any googlehealth.* scope, it overwrites the scope-pure
Health grant and you're back to 403. Keep your main script's scope list Health-free permanently.

Best Answer
0 Votes

Joining the still-open thread with @newbiedev and @ammarg — please add me to the same internal issue tracker.

Symptom: Persistent HTTP 500 INTERNAL on GET https://health.googleapis.com/v4/users/me/dataTypes/sleep/dataPoints since 20260425, still reproducing on 20260430.

 
 
json
{"error":{"code":500,"message":"Internal error encountered.","status":"INTERNAL"}}

Same OAuth grant: heart-rate, daily-resting-heart-rate, weight, body-fat, total-calories, distance, daily-HRV, daily-SpO2, respiratory-rate, steps, intraday HR, intraday HRV all return 200 OK with data. Only the sleep dataType list action 500s.

Ruled out (extensive matrix):

  • Auth / scope: tokeninfo confirms googlehealth.sleep.readonly granted, fresh access tokens, refresh_token works fine.
  • Response size: pageSize=5 also 500s, so it isn't a truncation/oversize issue.
  • Time-window filtering: startTime/endTime query params return 400 (not accepted on this endpoint).
  • Adjacent endpoints: :filter POST → 404, :reconcile POST → 404 (despite being listed as an "allowed action" in the :dailyRollUp 400 error metadata), :dailyRollUp POST → 400 ("not supported for sleep"), daily-sleep-summary dataType → 400 (invalid).
  • AIP-160 filter query param: supported but no time-based sleep field is in the allowlist (sleep.interval.startTime, sleep.interval.endTime, sleep.startTime, sleep.interval.endTimeMillis etc. all return "Member X is not supported for filtering").
  • @nextmab's scope-mix fix: tried it. Created a brand-new Desktop OAuth client in the same project, ran fresh OAuth via loopback, tokeninfo confirms scope-pure (only the 3 googlehealth.*.readonly scopes — no Calendar/Drive/Cloud). Sleep endpoint still 500s.
  • @GordonFitbit's legacy-account workaround: doesn't apply — account has been Google-native, and sleep was successfully syncing via this exact API through 20260424, then broke 20260425.

Account / setup:

  • Pixel Watch 3, Fitbit app + Health Connect enabled, sleep data confirmed present in the Fitbit app daily.
  • GCP OAuth Client: Desktop type, freshly minted today
  • Same GCP project also hosts an older Web Application client used by Home Assistant; the new Desktop client was created specifically to isolate the test.

Happy to provide request IDs, full response headers, or cURL repros off-list.

Thanks!

Best Answer

@mehtadone 

Hi mehtadone,

Since 2026/04/25, and still as of 2026/04/30, I have been consistently getting HTTP 500 INTERNAL on the following GET request:
GET https://health.googleapis.com/v4/users/me/dataTypes/sleep/dataPoints

I’m seeing exactly the same issue on my side as well.

At this point, this looks very likely to be a regression on Google’s side. It might be faster to raise an issue with Google Health on GCP.

The official release of the integration is apparently planned for the end of May 2026, but in my opinion, it does not yet seem to be at production-quality level.

Thanks,
dandaso

Best Answer
0 Votes

I tried but the support and forums on GCP are so confusing!

Best Answer
0 Votes

Hi, 
skip the OAuth-rebuild rabbit hole - that's the 403 fix, not the 500. The 500 is purely a :list server bug for sleep, i saw that aswell, fixed by routing the same query
through :reconcile.

Try to:

Stop calling :list. Call :reconcile instead. Same dataType, same response shape, doesn't 500.

GET …/sleep/dataPoints → 500 INTERNAL (broken server-side)
POST …/sleep/dataPoints:reconcile → 200 OK with the same dataPoints array

Concrete request

POST https://health.googleapis.com/v4/users/me/dataTypes/sleep/dataPoints:reconcile
Authorization: Bearer <access_token>
Content-Type: application/json

{ "consistencyToken": null }

- First call: consistencyToken: null.
- Subsequent calls: echo back the nextConsistencyToken from the previous response (or pass null again if you don't care about delta semantics you'll just get
the full snapshot every time).

Response shape

{
"dataPoints": [
{
"dataPointId": "...",
"interval": { "startTime": "...", "endTime": "..." },
"value": { "sleep": { ... stages, efficiency, etc. } }
},
...
],
"nextConsistencyToken": "..."
}

Identical to what :list would return, your existing parser keeps working unchanged. Filter to "last night" client-side by interval.endTime (this endpoint doesn't
accept startTime/endTime query params same constraint as :list).
GL

Best Answer
0 Votes
Best Answer
0 Votes