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.
Metadata
Release files for basicfit 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| basicfit-1.0.0.tar.gz | 19.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| basicfit-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.5 kB
Release files / basicfit-1.0.0.tar.gz
| Download URL | basicfit-1.0.0.tar.gz |
|---|---|
| Size | 19.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
98a29fbc7d7edac4bfe2ff376d3e838ce52203fe352fb8b14d2e22ce797d83c4
|
|
BLAKE2b-256 checksum How to use checksums |
6328dacf906fd9a4191f66b39fe90571b3c4ed2a296f704c6ac4397047cc0acb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.25
|
Release files / basicfit-1.0.0-py3-none-any.whl
| Download URL | basicfit-1.0.0-py3-none-any.whl |
|---|---|
| Size | 20.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
164b4d8f60f1c00ad29801d18110313e0a021e1a336c1ca3e02e38ece5c22e83
|
|
BLAKE2b-256 checksum How to use checksums |
15308ddc93fa8818f2c6ff2138fe9d879e58c980571510b62b53d1a7d9e2e3e3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.25
|
Security for basicfit 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.