SDKs/Python
terminal Official SDK · Python 3.8+ · API v2
● Live

WorkoutX Python SDK

The official Python client for the WorkoutX API. Query API v2's 1,900+ exercises (2,700+ clips as 720p MP4 animations, male and female models, instructions, tips and anatomical muscle names), generate workouts, fetch supplements, and run Body Scans. Perfect for backend services, data science notebooks, and AI coaching tools. Minimal dependencies, type-hinted, works everywhere Python runs.

pip install workoutx-sdk

✓ Live on PyPI — pypi.org/project/workoutx-sdk · supports Python 3.8+.

Quick start

Create one client with your API key and use wx.v2. The SDK sends the key in the X-WorkoutX-Key header, the only place v2 accepts it. The same client also covers the v1 resources and the Body Scan API (user JWT).

import os from workoutx import WorkoutX wx = WorkoutX( api_key=os.environ["WORKOUTX_API_KEY"], # wx_... sent in the X-WorkoutX-Key header scan_token="eyJ...", # Body Scan (optional) ) page = wx.v2.list(body_part="chest", limit=10) for ex in page["results"]: print(ex["name"], ex["model"]) # Barbell Bench Press male if ex["media"]: print(ex["media"]["mp4"]) # signed 720p MP4 link for your app's video player
science

Built for data & AI

Clean dictionaries you can drop straight into pandas, ML pipelines, or an LLM-powered coaching agent. v2's instructions, tips and anatomical muscle names make good context.

autorenew

Media links, handled

fresh_media(id) reuses the signed links from earlier calls until about 5 minutes before they expire, then fetches new ones. You store exercise ids; the SDK keeps the links valid.

shield

Resilient by default

Automatic retries with backoff on 429 and 5xx, honoring Retry-After. A used-up quota is not retried. Structured WorkoutXError with status, code and tips.

Serve the animations to your apps

Python runs on your server, so it doesn't play video itself: return the media links to your web or mobile client and let it play mp4 directly. Links are signed, expire after about 24 hours and contain no API key, so they're safe to hand out. Store exercise ids, never media URLs, and call fresh_media(id) when a client needs links.

from fastapi import FastAPI, HTTPException app = FastAPI() @app.get("/exercises/{exercise_id}/media") def exercise_media(exercise_id: str): media = wx.v2.fresh_media(exercise_id) # cached until about 5 minutes before expiry if media is None: # e.g. Starter's 1,000-exercise limit reached raise HTTPException(status_code=404) return media # {"mp4": ..., "poster": ..., "expiresAt": ..., "watermarked": ...}

On the client, play mp4 muted and looping and show poster while it loads. The MP4 is a 600×720 loop at 30 fps (usually 100–300 KB); on the Free plan both links are 300×360 watermarked previews.

<video src="{mp4}" poster="{poster}" autoplay loop muted playsinline></video>

Don't download, proxy or re-host the files: serving them from your own servers or CDN isn't permitted on any plan. See Media in v2 for players on iOS, Android and Flutter.

Male and female models, seven languages

Male and female versions are separate exercises. counterpartId points at the same movement with the other model, so you can offer a switch, and model filters a list to one of them. lang returns names, instructions, tips and muscles in Turkish (tr), German (de), Spanish (es), French (fr), Simplified Chinese (zh-SG) or Traditional Chinese (zh-HK). English is the default; filter values stay in English.

bench = wx.v2.get("ex_SAWOOH") # Barbell Bench Press, male model if bench["counterpartId"]: female = wx.v2.get(bench["counterpartId"]) # "ex_HYOSAB": the same movement, female model women_chest = wx.v2.list(body_part="chest", model="female") german_chest = wx.v2.list(body_part="chest", lang="de") spanish = wx.v2.get("ex_SAWOOH", lang="es")

Smart endpoints

All plans

Similar exercises, alternatives with other equipment and calorie estimates, on v2 ids. Ranked results are full v2 exercises with media links plus a score, and never include the exercise itself or its other-model clip. All three take lang. Parameters keep the API's camelCase names (weightKg, excludeEquipment), as in the v1 resources.

similar = wx.v2.similar("ex_SAWOOH", model="any", limit=5) swaps = wx.v2.alternatives("ex_SAWOOH", equipment="dumbbell", limit=3) burn = wx.v2.calories("ex_SAWOOH", weightKg=80, minutes=20) swaps["results"][0]["name"] # "Dumbbell Bench Press" burn["totalKcal"] # 139.4

Workouts and programs

Growth · Scale

v2.workout.generate() returns one session from the rule-based workout engine: a warm-up, exercises grouped into blocks (straight sets, supersets such as C1/C2, or one circuit) with sets, reps or a duration, rest, effort (rir) and swap alternatives, then a cool-down. Every exercise in it, warm-up and cool-down items included, is a full v2 exercise with media links, and model picks the male or female clips. avoidJoints (knee, shoulder, lumbar_spine, elbow, wrist, hip, ankle) leaves out movements that load those joints heavily; it is general guidance, not medical advice. format picks straight, superset or circuit (default auto). The response carries estimatedDurationMinutes and the seed it used; pass the seed back to get the same workout. v2.workout.program() returns a schedule, the week-by-week program and an exerciseLibrary that its exercises reference by exerciseId. The generator needs Growth or Scale (legacy Pro, Ultra); multi-week programs need Scale (legacy Ultra).

workout = wx.v2.workout.generate( goal="muscle_gain", # strength, fat_loss, endurance, mobility, general_fitness duration=45, # minutes, 15-120 level="intermediate", equipment=["dumbbell", "bench"], # or a preset: "home", "bodyweight", "gym" avoidJoints=["knee"], # general guidance, not medical advice format="auto", # straight, superset, circuit model="female", ) for item in workout["warmup"]["items"]: print("Warm-up:", item["exercise"]["name"], f'{item["durationSeconds"]}s') for item in workout["exercises"]: # block "C1" + "C2" = a superset amount = item.get("reps") or f'{item["durationSeconds"]}s' media = item["exercise"]["media"] print(item["block"], item["exercise"]["name"], f'{item["sets"]} x {amount}', media and media["mp4"]) program = wx.v2.workout.program(goal="strength", weeks=8, daysPerWeek=4) # Scale library = {e["id"]: e for e in program["exerciseLibrary"]} program["schedule"][0]["name"] # "Upper (horizontal push and pull)" for x in program["program"][0]["days"][0]["exercises"]: # week 1, day 1 media = library[x["exerciseId"]]["media"] print(x["name"], x["sets"], x.get("reps"), media and media["mp4"])

Supplements

Growth · Scale

The evidence-graded supplement database in the v2 page shape, {"total", "limit", "offset", "results", "disclaimer"}. Growth and Scale (legacy Pro, Ultra); stacks need Scale (legacy Ultra). Informational content only, not medical advice. Supplement text is English.

strong = wx.v2.supplements.list(goal="muscle_gain", evidence="strong") filters = wx.v2.supplements.filters() creatine = wx.v2.supplements.get("creatine-monohydrate") for_bench = wx.v2.supplements.for_exercise("ex_SAWOOH", limit=5) stack = wx.v2.supplements.stack(goal="muscle_gain", level="essential") # Scale

Joint & Rehab filters

Growth · Scale

Narrow v2.list() by the joint a movement works through, an intensity band and descriptive tags, for physical therapy, mobility and senior-fitness apps. Growth and Scale (legacy Ultra). These are descriptive fields, not medical recommendations.

gentle_knee = wx.v2.list( joint_focus="knee", # or jointFocus=; shoulder, lumbar_spine, ... intensity_level="gentle", # gentle, moderate, vigorous movement_tag="joint-friendly", # beginner-friendly, low-intensity, minimal-equipment, ... limit=10, )

Migrating from v1

v1 keeps working on the same key, and you'll get at least 30 days' notice before it's retired. The SDK's v1 resources (exercises, gifs, workout, supplements) are unchanged, so you can move one screen at a time. Map each v1 id you've stored once with v2.legacy() and save the v2 id: 1,324 of the 1,327 v1 exercises have a v2 match.

res = wx.v2.legacy("0025") # match "exact": ex_SAWOOH, Barbell Bench Press if res["match"] in ("exact", "equivalent"): save_exercise_id("0025", res["exercise"]["id"]) # the same movement: replace automatically elif res["match"] in ("alternative", "closest"): suggest_to_user("0025", res["exercise"]) # a substitute: let the user confirm # "none": no v2 version yet, keep the v1 exercise v1 = wx.exercises.get("0025") # v1 calls keep working meanwhile

Step by step, with code for each platform: the v2 migration guide.

Errors and plan limits

Every non-2xx response raises a WorkoutXError with status, code, tip and the parsed body. A feature your plan doesn't include answers 403 and names the plan that has it. A 429 is either a rate limit, which the SDK retries with backoff, or a used-up request quota, which it doesn't retry. The Free plan's 500 requests are a one-time allowance.

from workoutx import WorkoutXError try: wx.v2.workout.generate(goal="strength") except WorkoutXError as e: if e.status == 403: # {"error": "Plan Upgrade Required", "feature": "workoutGenerator", "requiredPlan": "growth"} print("Upgrade to", e.body["requiredPlan"]) elif e.is_rate_limited: print(e.code, e) # Monthly Quota Exceeded, "You have used 500/500 requests..."

Frequently asked questions

How do I install the WorkoutX Python SDK?

Run pip install workoutx-sdk. It supports Python 3.8+ and has minimal dependencies (only certifi for reliable TLS). API v2 lives under wx.v2.

How do I show the exercise videos in my app?

Return wx.v2.fresh_media(id) from your backend and play mp4 on the client in a muted, looping video player, with poster as the still frame. Don't download, proxy or re-host the files.

Can I store the media URLs?

No. Media links are signed and expire after about 24 hours. Store exercise ids and call fresh_media(id) when you need links; it reuses them until they're close to expiry.

Can I use it for data science or AI coaching apps?

Yes. Responses are plain Python dicts/lists, so they slot directly into pandas, scikit-learn pipelines, or prompts for an LLM agent. v2 adds instructions, form tips and anatomical muscle names.

Do my v1 calls keep working?

Yes. wx.exercises, wx.gifs, wx.workout and wx.supplements still call v1, which keeps working; you'll get at least 30 days' notice before it's retired. wx.v2.legacy(v1_id) maps the v1 ids you've stored to v2.

How do I get an API key?

Create a free account in the Developer Portal and copy your wx_... key. Pass it as api_key. The Free plan includes 500 requests in total with watermarked previews; paid plans start at $19 a month.

How does authentication work for the Body Scan API?

Body Scan uses a logged-in user's JWT. Pass scan_token=, or scan_email= and scan_password= to let the SDK log in automatically.

Security & API keys

vpn_key

Keep keys out of source

Load your API key from an environment variable or secret manager rather than hard-coding it. Never commit keys to version control. v2 accepts the key only in the X-WorkoutX-Key header, which the SDK sets for you.

movie

Hand out links, not keys

fresh_media() returns signed links that expire after about 24 hours and contain no API key, so your API can pass them to web and mobile clients. Store exercise ids, not links. (v1's gif_url() still puts the key in the URL.)

lock

Credentials stay on the server

The Body Scan scan_email/scan_password auto-login should only run in trusted server environments — not in code shipped to end users.

Start building with Python

Start free with 500 requests and watermarked previews. 1,900+ exercises as 720p video, workouts, and Body Scan in one SDK.

Get your free API key