# WorkoutDB API — full guide for agents The exercise database API: ~870 exercises with rich media, fuzzy + full-text search, combinable filters, and English/Spanish content. This document is self-contained — everything an agent needs to start calling the API. The machine-readable contract is at [/openapi.json](/openapi.json); a ready-to-import Postman collection is at [/workoutdb.postman_collection.json](/workoutdb.postman_collection.json). ## Base URL Defaults to `http://localhost:3100`. The public production domain (`https://api.work-out-db.com`) is not live yet — point requests at your deployment. All paths below are relative to the base URL. ## Authentication Send your API key on every `/v1/*` request as either header: - `X-API-Key: ` (preferred), or - `Authorization: Bearer ` Create and manage keys in the dashboard (Dashboard → Keys). `GET /v1/healthz` is the only key-free endpoint. ## Response envelope Every response is wrapped: ```json { "data": , "meta": { "lang": "en", "pagination": { "total": 42, "nextCursor": "…" } }, "requestId": "req_…" } ``` `data` is the payload (object or array), `meta.lang` echoes the resolved language, list endpoints add `meta.pagination`, and `requestId` is the `X-Request-Id` for support. ## Errors Non-2xx responses use one envelope: `{ "error": { "code", "message", "details" }, "requestId" }`. The `code` is stable — switch on it, not the message. | `error.code` | HTTP | Meaning | | --- | --- | --- | | `invalid_request` | 400 | Malformed params, bad cursor, or unsupported ?lang. | | `unauthorized` | 401 | Missing, unknown, or revoked API key. | | `forbidden_plan` | 403 | Your plan does not allow this resource. | | `not_found` | 404 | No exercise or taxonomy with that id/slug. | | `rate_limited` | 429 | Per-minute rate limit exceeded — see Retry-After. | | `quota_exceeded` | 429 | Monthly quota exhausted — see Retry-After, or upgrade. | | `internal` | 500 | Unexpected server error — report with your X-Request-Id. | `429` responses carry `Retry-After` (seconds). Authenticated responses include `X-RateLimit-*` and `X-Quota-*` headers, and every response carries `X-Request-Id`. ## Pagination List endpoints accept `limit` (plan-capped page size) with either `cursor` (opaque, preferred) or `offset`. Follow `meta.pagination.nextCursor` until it is null. ## Localization Pass `?lang=en` or `?lang=es` (or send `Accept-Language`). `meta.lang` reports what was served. Search is locale-aware and accent-insensitive. ## Endpoints ### GET /v1/exercises/search Ranked search (trigram similarity + full-text rank, accent-insensitive) | Param | In | Required | Notes | | --- | --- | --- | --- | | `bodyPart` | query | no | Filter by body part slug (see /v1/bodyparts). (string) | | `target` | query | no | Filter by primary target muscle slug. (string) | | `equipment` | query | no | Filter by equipment slug (see /v1/equipment). (string) | | `muscle` | query | no | Filter by any involved muscle slug (see /v1/muscles). (string) | | `category` | query | no | Filter by category. (string; one of: strength, cardio, mobility, stretching, plyometrics) | | `difficulty` | query | no | Filter by difficulty. (string; one of: beginner, intermediate, advanced) | | `force` | query | no | Filter by force type. (string; one of: push, pull, static) | | `mechanic` | query | no | Filter by mechanic. (string; one of: compound, isolation) | | `q` | query | yes | Free-text query — fuzzy + full-text, accent-insensitive. (string; required) | | `sort` | query | no | Sort field. (string; one of: name, difficulty; default name) | | `order` | query | no | Sort direction. (string; one of: asc, desc; default asc) | | `offset` | query | no | Offset-based pagination start. (integer,null; default 0) | | `limit` | query | no | Page size (plan-capped). (integer; default 10) | | `cursor` | query | no | Opaque pagination cursor (preferred over offset). (string) | | `lang` | query | no | Response language. (string; one of: en, es) | ```bash curl -s -H "X-API-Key: $WORKOUTDB_API_KEY" "http://localhost:3100/v1/exercises/search?bodyPart=chest&q=press&lang=en" ``` Example response: ```json { "data": [ { "id": "…", "slug": "bench-press", "name": "Bench Press", /* … */ } ], "meta": { "lang": "en", "pagination": { "total": 42, "nextCursor": "…" } }, "requestId": "req_…" } ``` ### GET /v1/exercises List/filter exercises (combinable query params) | Param | In | Required | Notes | | --- | --- | --- | --- | | `bodyPart` | query | no | Filter by body part slug (see /v1/bodyparts). (string) | | `target` | query | no | Filter by primary target muscle slug. (string) | | `equipment` | query | no | Filter by equipment slug (see /v1/equipment). (string) | | `muscle` | query | no | Filter by any involved muscle slug (see /v1/muscles). (string) | | `category` | query | no | Filter by category. (string; one of: strength, cardio, mobility, stretching, plyometrics) | | `difficulty` | query | no | Filter by difficulty. (string; one of: beginner, intermediate, advanced) | | `force` | query | no | Filter by force type. (string; one of: push, pull, static) | | `mechanic` | query | no | Filter by mechanic. (string; one of: compound, isolation) | | `q` | query | no | Free-text query — fuzzy + full-text, accent-insensitive. (string) | | `sort` | query | no | Sort field. (string; one of: name, difficulty; default name) | | `order` | query | no | Sort direction. (string; one of: asc, desc; default asc) | | `offset` | query | no | Offset-based pagination start. (integer,null; default 0) | | `limit` | query | no | Page size (plan-capped). (integer; default 10) | | `cursor` | query | no | Opaque pagination cursor (preferred over offset). (string) | | `lang` | query | no | Response language. (string; one of: en, es) | ```bash curl -s -H "X-API-Key: $WORKOUTDB_API_KEY" "http://localhost:3100/v1/exercises?bodyPart=chest&q=press&lang=en" ``` Example response: ```json { "data": [ { "id": "…", "slug": "bench-press", "name": "Bench Press", /* … */ } ], "meta": { "lang": "en", "pagination": { "total": 42, "nextCursor": "…" } }, "requestId": "req_…" } ``` ### GET /v1/exercises/{idOrSlug} Get one exercise by cuid or slug | Param | In | Required | Notes | | --- | --- | --- | --- | | `lang` | query | no | Response language. (string; one of: en, es) | | `idOrSlug` | path | yes | Exercise cuid or slug. (string; required) | ```bash curl -s -H "X-API-Key: $WORKOUTDB_API_KEY" "http://localhost:3100/v1/exercises/bench-press?lang=en" ``` Example response: ```json { "data": { "id": "…", "slug": "bench-press", "name": "Bench Press", /* … */ }, "meta": { "lang": "en" }, "requestId": "req_…" } ``` ### GET /v1/bodyparts List body parts (localized) _No parameters._ ```bash curl -s -H "X-API-Key: $WORKOUTDB_API_KEY" "http://localhost:3100/v1/bodyparts" ``` Example response: ```json { "data": [ { "slug": "chest", "name": "Chest" } ], "meta": { "lang": "en" }, "requestId": "req_…" } ``` ### GET /v1/muscles List muscles (localized) _No parameters._ ```bash curl -s -H "X-API-Key: $WORKOUTDB_API_KEY" "http://localhost:3100/v1/muscles" ``` Example response: ```json { "data": [ { "slug": "chest", "name": "Chest" } ], "meta": { "lang": "en" }, "requestId": "req_…" } ``` ### GET /v1/equipment List equipment (localized) _No parameters._ ```bash curl -s -H "X-API-Key: $WORKOUTDB_API_KEY" "http://localhost:3100/v1/equipment" ``` Example response: ```json { "data": [ { "slug": "chest", "name": "Chest" } ], "meta": { "lang": "en" }, "requestId": "req_…" } ``` ### GET /v1/categories List categories (localized) _No parameters._ ```bash curl -s -H "X-API-Key: $WORKOUTDB_API_KEY" "http://localhost:3100/v1/categories" ``` Example response: ```json { "data": [ { "slug": "chest", "name": "Chest" } ], "meta": { "lang": "en" }, "requestId": "req_…" } ``` ### GET /v1/meta Counts + every filterable value in one call (app-bootstrap friendly) _No parameters._ ```bash curl -s -H "X-API-Key: $WORKOUTDB_API_KEY" "http://localhost:3100/v1/meta" ``` Example response: ```json { "data": { "counts": { "exercises": 873 }, "bodyParts": [ /* … */ ], "equipment": [ /* … */ ] }, "meta": { "lang": "en" }, "requestId": "req_…" } ``` ## TypeScript SDK ```bash npm i @workoutdb/client ``` ```ts import { createWorkoutDB } from '@workoutdb/client'; const db = createWorkoutDB({ apiKey: process.env.WORKOUTDB_API_KEY!, baseUrl: 'http://localhost:3100', // point at your deployment }); const { data, error } = await db.GET('/v1/exercises', { params: { query: { bodyPart: 'chest', q: 'press', lang: 'es' } }, }); if (error) throw new Error(JSON.stringify(error)); console.log(data.data.map((e) => e.name)); // note the envelope: data.data ```