Unofficial Basic-Fit API Client
Disclaimer: This is an unofficial Python client for the Basic-Fit app API. It is not affiliated with, endorsed by, or in any way connected to Basic-Fit N.V. Use it with your own account, at your own risk.
Introduction
The Unofficial Basic-Fit API Client is an asynchronous Python library for the backend that powers the Basic-Fit mobile app. It lets you read your membership details, gym visit history, in-club body-composition measurements and achievement badges, and it exposes the public workout, recipe and club-finder content library. The client handles the OAuth2 token lifecycle — including Basic-Fit's rotating refresh tokens — so you can focus on the data.
Features
- Authentication Management: Keeps a valid access token, refreshes automatically when it expires, and correctly persists the rotating refresh token Basic-Fit hands back on every refresh.
- One-time browser login (PKCE): A helper that builds the authorize URL and exchanges the returned code for tokens. The sign-in itself happens in a real browser because the login page is protected by a bot-challenge WAF.
- Membership info: Name, membership type, home club, card/membership number, add-ons and outstanding-debt flag.
- Visit history: Full activity feed with a convenience filter for physical gym check-ins.
- Body measurements: Weight, fat %, muscle, water and more from the in-club scales.
- Badges: Earned achievement badges.
- Content library: Search the workout catalog, the recipe catalog (with macros) and the club finder — all via the app's public Contentful endpoint.
- Typed models: Every response is parsed into a documented dataclass, with the raw payload kept on
.rawfor anything not yet modelled.
Features to be Added
- Workout progress / logging endpoints
- Club busyness time-series helpers
(feel free to PR if you manage to implement any of these features)
Installation
Ensure you have Python 3.11 or higher installed. You can install the package using pip:
pip install basicfit
Or, from a checkout:
pip install -r requirements.txt
Authentication
Basic-Fit uses OAuth2 Authorization Code + PKCE. The login page (login.basic-fit.com) sits behind an Imperva browser challenge, so it can't be automated headlessly — you sign in once in a normal browser. The token endpoint (auth.basic-fit.com) is not challenged, so all refreshes after that first login happen automatically in the background.
Refresh tokens rotate. Every refresh returns a new refresh token and invalidates the old one. Always persist the token set after each call — use the
token_updatedcallback below and you never have to think about it.
One-time browser login
import asyncio
import aiohttp
from basicfit import AuthManager, TokenSet, start_login, parse_redirect
async def login():
challenge = start_login()
print("Open this URL and sign in:\n", challenge.authorize_url)
# Your browser will try to open a
# com.basicfit.trainingapp:/oauthredirect?code=... URL — copy it from the
# address bar and paste it here:
redirect = input("Paste the redirect URL: ").strip()
code = parse_redirect(redirect, expected_state=challenge.state)
async with aiohttp.ClientSession() as session:
tokens = await AuthManager.async_exchange_code(
session, code, challenge.verifier
)
# Persist this — it's all you need next time.
print(tokens.to_dict())
asyncio.run(login())
Reusing a stored token
import json
from basicfit import BasicFitClient, TokenSet
def save(tokens): # called automatically after every rotation
with open("tokens.json", "w") as fh:
json.dump(tokens.to_dict(), fh)
with open("tokens.json") as fh:
tokens = TokenSet.from_dict(json.load(fh))
client = BasicFitClient.create(tokens, token_updated=save)
Or, if you only have the raw refresh token string:
client = BasicFitClient.from_refresh_token("<refresh-token>", token_updated=save)
Usage
The client manages its own aiohttp session (pass your own via session= if you prefer) and works as an async context manager.
import asyncio
from basicfit import BasicFitClient, TokenSet
async def main():
async with BasicFitClient.from_refresh_token("<refresh-token>") as client:
# Membership
member = await client.get_member()
print(member.name, member.membership_type, member.home_club)
# Gym visits (last 30 days by default; accepts date ranges, max 365 days)
visits = await client.get_visits()
print(f"{len(visits)} visits")
for v in visits[:5]:
print(v.date, v.club)
# Body composition (most recent first)
for m in await client.get_body_measurements(limit=3):
print(m.date, m.weight, "kg", m.fat, "% fat")
# Achievement badges
for b in await client.get_badges():
print(b.name, b.earned_at)
asyncio.run(main())
Content library
The workout, recipe and club look-ups hit Basic-Fit's public content endpoint and don't require authentication (they work even before login):
async with BasicFitClient.from_refresh_token("<refresh-token>") as client:
workouts = await client.search_workouts("full body", limit=5)
for w in workouts:
print(w.name, w.duration_min, "min", w.body_parts)
recipes = await client.search_recipes("protein", limit=5)
for r in recipes:
print(r.name, r.kcal, "kcal", r.protein_g, "g protein")
clubs = await client.search_clubs(city="Groningen")
for c in clubs:
print(c.display_name, c.address, "closed" if c.closed else "open")
print(client.club_image_url(c.kp_number))
Quickstart script
examples/quickstart.py runs the whole flow end to end: it does the one-time browser login, stores the token in tokens.json, and prints your membership, recent visits, latest weight and badge count. On later runs it reuses (and silently rotates) the stored token.
python examples/quickstart.py
Home Assistant
A companion Home Assistant integration built on this package lives at HA-Basic-Fit — install it via HACS to get your visits, membership, weight and badges as sensors.
Notes
- Locales: the content library uses Contentful locale codes (
en-US,nl,fr,es,de) — notenl, notnl-NL. The client normalises unknown values toen-US. - Ranges: the activities endpoint accepts spans up to 365 days; longer ranges are clamped.
- Errors: the package raises
BasicFitAuthError(sign in again),BasicFitAPIError(bad response, carriesstatus_code),BasicFitNetworkError(timeout/connection) andBasicFitValidationError(bad arguments) — all subclasses ofBasicFitError.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file basicfit-1.0.0.tar.gz.
File metadata
- Download URL: basicfit-1.0.0.tar.gz
- Upload date:
- Size: 19.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98a29fbc7d7edac4bfe2ff376d3e838ce52203fe352fb8b14d2e22ce797d83c4
|
|
| MD5 |
d4e107b46b427f7a1dd61318b0dbeba7
|
|
| BLAKE2b-256 |
6328dacf906fd9a4191f66b39fe90571b3c4ed2a296f704c6ac4397047cc0acb
|
File details
Details for the file basicfit-1.0.0-py3-none-any.whl.
File metadata
- Download URL: basicfit-1.0.0-py3-none-any.whl
- Upload date:
- Size: 20.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
164b4d8f60f1c00ad29801d18110313e0a021e1a336c1ca3e02e38ece5c22e83
|
|
| MD5 |
ba0b5d9f89089cfe7a21e3dd5c0f46f7
|
|
| BLAKE2b-256 |
15308ddc93fa8818f2c6ff2138fe9d879e58c980571510b62b53d1a7d9e2e3e3
|