# wger > wger (wger Workout Manager) is a self-hosted, AGPL-3.0 fitness, workout, nutrition and body-weight tracker built on Django. Its REST API is served under `/api/v2/` on every wger instance — including the project's own free public instance at wger.de — and describes itself with a drf-spectacular-generated OpenAPI 3.0.3 schema covering 129 paths and 254 operations. Generated by API Evangelist on 2026-08-27 from the wger project's own published surfaces. wger does not publish an llms.txt of its own (https://wger.de/llms.txt returns an HTTP 200 bot-check page, not a document; https://wger.readthedocs.io/llms.txt returns 404). Everything below was fetched or read on that date. ## What it is - **Self-hosted, not a SaaS.** There is no single deployed version. Any instance you point at may be running anything from 2.3 to 2.7. Read `/api/v2/version/`, `/api/v2/min-server-version/` and `/api/v2/min-app-version/` before assuming a capability exists. wger.de reports 2.7.0a2. - **Free.** No plans, no tiers, no paid API. Community reference data (exercises, ingredients, muscles, equipment, units) is readable with **no account and no key**. - **License:** AGPL-3.0-or-later. ## Base URLs - API base: `https://wger.de/api/v2` (or `https:///api/v2`) - Machine-readable schema: `https://wger.de/api/v2/schema` (OpenAPI 3.0.3; add `?format=json` for JSON) - Swagger UI: `https://wger.de/api/v2/schema/ui` - ReDoc: `https://wger.de/api/v2/schema/redoc` - Endpoint index: `https://wger.de/api/v2/` (JSON map of every collection) ## Authentication Credentials are tried in this order: `Authorization: Token `, then `Authorization: Bearer `, then the session cookie. - **Personal API key** — mint at `/en/user/api-key`. Header: `Authorization: Token `. Best for scripts. - **JWT** — short-lived access token (10 min default) + long-lived refresh token (120 days). There is deliberately no credentials-to-JWT endpoint (it would bypass 2FA); obtain a refresh token from `POST /allauth/app/v1/auth/login` or from the API key page. Refresh at `POST /api/v2/token/refresh` (rotates both tokens and blacklists the old refresh token); verify at `POST /api/v2/token/verify`. - **OAuth2 / OIDC** — wger can act as its own provider. Discovery: `https://wger.de/.well-known/openid-configuration`. Scopes: `api:read`, `api:write`, `openid`, `profile`, `email`. PKCE S256 and the device-code grant are supported. Note that the advertised `jwks_uri` currently returns an empty key set. - **Anonymous** — the catalogs listed under "Read without a key" need nothing at all. ## Conventions an agent must know - **Pagination:** `?limit=` / `?offset=`; response carries `count`, `next`, `previous`, `results`. Default page size 20. `/api/v2/ingredient-sync/` is the exception — cursor pagination, no `count`, follow `next`. - **Filtering:** `?field=value`, AND-joined. Booleans must be `True`/`False`, **case-sensitive** — `1`, `0` and `false` are silently ignored, which produces a wrong result set rather than an error. - **Ordering:** `?ordering=field`, comma-separate for several, `-` to reverse. - **Format:** JSON by default; `.json` and `.api` URL suffixes, or an `Accept` header. - **"Info" projections:** prefer `exerciseinfo/`, `ingredientinfo/` and `nutritionplaninfo/` over their lean siblings — they embed the related objects and save you the join. `Exercise` itself carries no name; names live on `ExerciseTranslation`. - **Errors:** NOT RFC 9457. Django REST Framework envelope — `{"detail": "..."}` or `{"": ["..."]}`. Branch on the HTTP status. - **Rate limits:** only six endpoints are throttled (auth 10/min, registration 5/min, ingredient list 120/min, ingredient detail 300/min, ingredient-sync 600/min). Everything else is unthrottled. There are **no** proactive `RateLimit-*` headers — the only signal is `429` + `Retry-After`. - **Idempotency:** none. No `Idempotency-Key`. A retried POST creates a second row. - **Reversibility:** none. No cancel, undo, restore or soft delete anywhere in the contract. Every `*_destroy` is terminal, and deleting a routine, day, slot, session or measurement category cascades to its children. Deleting a workout session also deletes the sets logged in it. **Escalate a delete to a human rather than rehearsing it — there is nothing to roll back to.** ## The data model in one paragraph Training is a five-level tree: `Routine -> Day -> Slot -> SlotEntry -> Config`. Progression is not a field; it is a per-iteration record in one of ten parallel config collections (sets, reps, weight, RiR, rest — each with a `max-` sibling, so a range like "8–12 reps" is two records). Logging is `WorkoutSession -> WorkoutLog`. **A `WorkoutLog` posted without `routine`, `slot_entry` and `iteration` is freestanding and invisible to every routine view and statistic** — this is the most common integration mistake against this API. Nutrition is `NutritionPlan -> Meal -> MealItem`, with diary entries in `nutritiondiary/`; there is no separate Recipe entity, a recipe is a Meal. Catalog entities carry a `uuid` alongside the integer `id` — sync between instances on the uuid, never the id. ## Read without a key `/api/v2/exercise/` · `/api/v2/exerciseinfo/` · `/api/v2/exercisecategory/` · `/api/v2/muscle/` · `/api/v2/equipment/` · `/api/v2/ingredient/` · `/api/v2/ingredientinfo/` · `/api/v2/language/` · `/api/v2/license/` · `/api/v2/public-templates/` · `/api/v2/setting-repetitionunit/` · `/api/v2/setting-weightunit/` Ingredients are addressable by retail barcode: `/api/v2/ingredient/?code=`. Nutri-Score supports range lookups (`nutriscore__gte=` etc.) since 2.6. ## MCP The project ships a **first-party MCP server**: https://github.com/wger-project/mcp-server — `uvx wger-mcp`, published on PyPI as `wger-mcp` (0.2.0, 2026-08-26) and as `ghcr.io/wger-project/mcp-server`. 85 tools across routines, workout logging, sessions, exercises, ingredients, nutrition, body weight, measurements, analytics and Open Food Facts barcode lookup. Narrow the surface with `MCP_TOOLS`. **There is no wger-hosted MCP endpoint.** The server runs over stdio locally (a `WGER_API_KEY` is the only credential) or over Streamable HTTP if *you* deploy it, in which case auth is OIDC token exchange or a static token. Do not expect to point a client at a wger.de URL. ## Official libraries - `wger-api-client` (PyPI, 2.6.1, 2026-08-13) — typed Python client generated from this schema; sync + async. Major.minor tracks the server release it targets. - `wger-mcp` (PyPI, 0.2.0, 2026-08-26) — the MCP server above. - `@wger-project/react-components` (npm, 26.8.12, 2026-08-12) — the React components wger's own web app is built from. - `wger` (PyPI, 2.1, **2022-10-11**) — the server itself. Five releases stale; use Docker or the source checkout instead. ## Links - Docs: https://wger.readthedocs.io/en/latest/ - API guide: https://wger.readthedocs.io/en/latest/api/api.html - Routine API guide: https://wger.readthedocs.io/en/latest/api/routines.html - Source: https://github.com/wger-project/wger - Organization: https://github.com/wger-project - Releases / changelog: https://github.com/wger-project/wger/releases - Security policy: https://github.com/wger-project/wger/blob/master/SECURITY.md - Sign up: https://wger.de/en/user/registration - Terms: https://wger.de/en/software/terms-of-service - Community: https://discord.gg/rPWFv6W · https://github.com/wger-project/wger/discussions ## Caveats recorded honestly - wger.de fronts every HTML page with an Anubis bot-check interstitial that answers **HTTP 200 with a challenge page for any path, including paths that do not exist**. A 200 on an HTML URL of that host proves nothing. The `/api/v2/` JSON surface and `/.well-known/` documents are served directly and were not challenged. - No status page (`status.wger.de` does not resolve), no SLA, no security.txt, no A2A agent card, no AsyncAPI and no webhooks. - The only deprecated operation in the whole surface is `userprofile_update_legacy` (PUT `/api/v2/userprofile/`).