How to Integrate the Polar API (2026)
Last verified August 12, 2026 · 8 min read
Covered here:Polar
Polar's cloud API is called Open AccessLink, and the fastest way to learn it is to read the code Polar publishes. This page is built from Polar's official accesslink-example-python repository on GitHub — README, OAuth wrapper, endpoint modules — fetched and read directly. Every host, path, and payload field below appears in that source.
Sourcing note. Polar's hosted reference at polar.com/accesslink-api is unreachable from our fetch environment (blocked at the egress layer), so this page is limited to what the official example repository contains. Anything it does not cover — webhooks, rate limits, token lifetimes, retention windows — is marked not documented in the sources we could reach. Verify those against Polar's live docs.
Still choosing a provider? That belongs to our wearable data APIs roundup; this page assumes the decision is made.
What you'll need#
- A Polar Flow account — the same consumer account your users have. You sign into the developer portal with it.
- An API client created at the AccessLink admin portal, which issues a client ID and a client secret.
- At least one registered authorization redirect URL. The README warns that a wrong callback URL is the most common reason the examples fail, and notes you can add or edit redirect URLs later as long as the right one is the default.
- A server-side token store. The example writes tokens to a local YAML file; in production, a database row keyed to your internal user.
There is no partner-approval gate in the documented flow — you create the client yourself and start immediately.
Three hosts, two data families#
AccessLink v3 has three constants, taken straight from the example's client class:
AUTHORIZATION_URL = "https://flow.polar.com/oauth2/authorization"
ACCESS_TOKEN_URL = "https://polarremote.com/v2/oauth2/token"
ACCESSLINK_URL = "https://www.polaraccesslink.com/v3"
Three hosts for three jobs: the Flow domain authorizes, a separate host mints tokens, the AccessLink host serves data. Teams assuming one base URL trip on this immediately.
There are then two families of data endpoint, and the split drives your whole ingestion design. The README states it plainly: transactional endpoints "discard the data after it has been fetched," while non-transactional endpoints "do not discard the data after it has been fetched." Exercises appear in both. Sleep and Nightly Recharge are documented in the example only as non-transactional; activity summaries and physical information only as transactional.
Step 1: Create an API client#
Sign in at the AccessLink admin portal with your Polar Flow credentials and create a client. Set your authorization redirect URL — the example uses a localhost callback on port 5000 at the path /oauth2_callback, and stresses that the value must match exactly. Copy the client ID and secret into server-side config; the secret must never ship to a client.
Step 2: Send the user through Polar Flow#
The authorization request is an ordinary authorization-code redirect. Per the README it is as short as this:
GET https://flow.polar.com/oauth2/authorization
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.example.com/oauth2_callback
Those three parameters are all the example's URL builder sends. Two absences matter:
- No scope parameter anywhere in the example. Unlike the eight toggleable scopes on an Oura integration, there is nothing here to narrow. Whether AccessLink supports scoping at all is not documented in the sources we could reach — verify.
- No
stateparameter either. The official sample omits it. That is a sample-app simplification, not a recommendation: generate a random state per request and check it on the callback, as you would in any OAuth flow for health data.
If the user is not signed in, Polar Flow shows a login page; on consent the browser returns to your callback with a code.
Step 3: Exchange the code for a token, fast#
Two details catch people out.
First, the code is short-lived. A comment in both sample apps states that "the authorization code is only valid for 10 minutes, so the access token should be fetched immediately after the authorization step." Do not queue the exchange behind a background job.
Second, the token request authenticates with HTTP Basic, not with credentials in the form body. The wrapper attaches Basic auth from the client ID and secret whenever no user token is present:
curl -s -X POST "https://polarremote.com/v2/oauth2/token" \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Accept: application/json;charset=UTF-8" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://yourapp.example.com/oauth2_callback"
The response carries two fields the example consumes: access_token and x_user_id, Polar's identifier for that user, needed in nearly every later path. Store both against your internal user ID.
Note what the example does not do: it never reads an expiry, never stores a refresh token, and has no refresh logic. That is consistent with Polar's long-lived tokens, but the actual lifetime and whether a refresh grant exists are not documented in the sources we could reach — verify, and handle a 401 by re-running authorization.
Step 4: Register the user (the step everyone forgets)#
Authorization alone does not give you data. The example's users resource is explicit: "Once partner has been authorized by user, partner must register user before being able to access her data." POST to the users collection with the user's bearer token, carrying your own identifier for them:
curl -s -X POST "https://www.polaraccesslink.com/v3/users" \
-H "Authorization: Bearer USER_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"member-id": "your-internal-user-id"}'
The field name is hyphenated — member-id. Both sample apps wrap this call with one specific carve-out: HTTP 409 Conflict means the user is already registered for this client and can be ignored. Treat registration as idempotent, swallow the 409, and treat anything else as real.
De-registration is a DELETE on the user path; its docstring notes it "will revoke the access token authorized by user," so it doubles as your disconnect action. Reading a user's basic profile is a GET on that same path.
Step 5: Ask what's available, then open a transaction#
For transactional data you do not fetch a date range. You ask whether anything is waiting, then open a transaction that snapshots it.
The availability check is client-level: the example passes no user token, so it falls through to Basic auth and returns pending data across your users.
curl -s "https://www.polaraccesslink.com/v3/notifications" \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET"
The console app iterates the response's available-user-data array and branches on each item's data-type, handling three values:
| Data type | Open a transaction with |
|---|---|
EXERCISE | POST to the user's exercise-transactions path |
ACTIVITY_SUMMARY | POST to the user's activity-transactions path |
PHYSICAL_INFORMATION | POST to the user's physical-information-transactions path |
curl -s -X POST \
"https://www.polaraccesslink.com/v3/users/USER_ID/exercise-transactions" \
-H "Authorization: Bearer USER_ACCESS_TOKEN"
A successful open returns a resource-uri — the transaction's own URL, used for everything that follows. An empty response means nothing is new: the example's HTTP layer turns a 204 into an empty object and the transaction factory returns nothing, which the console app prints as "No new exercises available." Code that as a normal, frequent outcome, not an error.
Step 6: Read the resources, then commit#
GET the transaction URL for a list of resource URLs. The list key differs per type: exercises come back under exercises, daily activity under activity-log, physical information under physical-informations.
Then GET each resource URL for its summary, plus any sub-resources. An exercise documents four: /gpx (Accept header application/gpx+xml), /tcx (Accept application/vnd.garmin.tcx+xml — that Garmin-flavoured media type is not a typo), /heart-rate-zones, and /samples, which returns one URL per sample type to fetch individually. Daily activity exposes /step-samples and /zone-samples the same way.
Finally, PUT the transaction URL to commit. The docstring says this "should be done after retrieving data from the transaction." Commit closes the loop — and because transactional endpoints discard data once fetched, it is the point of no return.
The non-transactional shortcut#
Three endpoints need no transaction, take only a bearer token, and leave the data in place:
curl -s -H "Authorization: Bearer USER_ACCESS_TOKEN" \
"https://www.polaraccesslink.com/v3/exercises"
curl -s -H "Authorization: Bearer USER_ACCESS_TOKEN" \
"https://www.polaraccesslink.com/v3/users/sleep/"
curl -s -H "Authorization: Bearer USER_ACCESS_TOKEN" \
"https://www.polaraccesslink.com/v3/users/nightly-recharge/"
The example web app uses only these three plus the user profile, which is why the README calls it "the easiest and fastest way to get started." If sleep and Nightly Recharge are what you need, you may never open a transaction. Whether these endpoints accept date parameters is not documented in the sources we could reach — verify.
Gotchas and production notes#
- Commit destroys. Committing discards that data on Polar's side. Persist everything you pulled, and confirm the write succeeded, before you PUT. Nothing in the example re-opens a committed transaction.
- Only what arrives after linking. The README frames the data flow as beginning "once user has linked their user account to client application and synchronizes data from Polar device to Polar Flow." A long-standing open issue on the same repository, filed by an outside developer and unresolved in the thread, reports that "currently it is only possible to retrieve data that has been synced after the authorization date." Sync regularly or lose history — and set expectations that connecting Polar does not backfill a user's past.
- No webhooks in the documented example. Polar's sample is pull-only: notifications, transaction, commit. Whether AccessLink offers a push callback is not documented in the sources we could reach — verify. Until then plan a scheduler, not a listener; what are webhooks covers the shape you would migrate to.
- No published rate limits in reach. The example has no retry, backoff, or 429 handling. Poll notifications on a conservative interval, not per-user in a tight loop.
- Two auth modes, not one. Client-level calls (token exchange, notifications) use HTTP Basic with your client credentials; user-level calls use a bearer token. Mixing them up produces failures that look like bad tokens.
- AccessLink is not Polar's only surface. Polar also publishes an official Bluetooth LE SDK for Android and iOS that streams real-time heart rate, ECG, accelerometer, and PPG from its sensors and watches — a different, on-device model for live signal during a session rather than summaries after a sync.
The rest of the cluster is at how to integrate a fitness or health API.
Frequently asked questions
- Is the Polar AccessLink API self-serve or does it need partner approval?
- Self-serve. Polar's official example repository documents creating an API client yourself at the AccessLink admin portal by signing in with a Polar Flow account, which issues a client ID and secret straight away. There is no application or partner-approval step in the documented flow, which makes Polar one of the lowest-friction wearable integrations to start. You do need to register at least one authorization redirect URL on the client, and the example warns that a mismatched callback URL is the most common reason integrations fail.
- What are the Polar AccessLink OAuth and API base URLs?
- Three different hosts do three different jobs, per Polar's own example client. Users authorize at flow.polar.com/oauth2/authorization, the authorization code is exchanged for a token at polarremote.com/v2/oauth2/token, and data is served from www.polaraccesslink.com/v3. The token exchange authenticates with HTTP Basic using your client ID and secret rather than putting them in the form body, and it must happen quickly because the example states the authorization code is only valid for 10 minutes.
- What is a transaction in the Polar AccessLink API?
- A transaction is a snapshot of newly available data that you open, read, and then commit. You POST to a per-user transaction endpoint for exercises, activity summaries, or physical information; a successful open returns a resource URI, and an empty response means nothing new is waiting. You then GET the transaction for a list of resource URLs, fetch each one, and finally PUT the transaction URL to commit. Committing matters because Polar's transactional endpoints discard the data once it has been fetched, so persist everything before you commit.
- Why can I only see recent Polar data and not a user's full history?
- Because AccessLink exposes only recent data rather than a user's archive. Polar's example README describes the data flow as beginning once the user has linked their account and synchronized their device to Polar Flow, and a long-standing open issue on that same repository reports that only data synced after the authorization date can be retrieved. Plan for no backfill: sync on a regular schedule, store everything you pull, and tell users that connecting Polar starts their history rather than importing it.
- Does Polar AccessLink support webhooks or publish rate limits?
- Neither is documented in the sources we could reach. Polar's official example integration is pull-only, built around a notifications endpoint, transactions, and commits, and it contains no webhook registration, no retry logic, and no 429 handling. Polar's hosted reference site was unreachable from our fetch environment, so treat push callbacks, rate limits, and token lifetimes as open questions to verify against the live documentation. Until you confirm otherwise, design for scheduled polling on a conservative interval.
Keep reading
Elsewhere on the site
Pages that share this one’s concepts and sources, from other sections.
Next steps
Was this page useful?
Independent comparison, last reviewed August 12, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.
← All integration guides · by AIFitnessAPI