# GymFit API

Exercises with the muscles they target, muscles, and fitness calculators (BMR, TDEE, BMI, IBW).

For agents and code generators: the OpenAPI specification is at [/openapi.json](/openapi.json), and this reference as Markdown at [/docs.md](/docs.md). AI assistants can call the API as an MCP server: see *AI assistants (MCP)* below.

## Quickstart

1. [Create a free account](https://www.gymfit-api.com/signup) and create a key in your dashboard: 1,000 requests a month, no credit card.
2. Send the key in the `X-API-Key` header:

```bash
curl "https://api.gymfit-api.com/v2/exercises/search?query=bench%20press&limit=3" \
  -H "X-API-Key: YOUR_API_KEY"
```

3. Each result has an `id` and a `slug`. Read the whole exercise (muscles, steps, tips, variants, alternatives) with `GET /v2/exercises/{id}`, and draw its muscles with `GET /v2/exercises/{id}/heatmap`.

Keep the key on your server in production: a key shipped in a mobile app or a web page can be read by anyone who opens it. The code samples of the routes call the API directly, as for a prototype.

## Authentication

Send your API key in the `X-API-Key` header. No key yet? [Sign up](https://www.gymfit-api.com/signup) for the free plan: 1,000 requests a month, no credit card. Create and manage your keys from your [dashboard](https://www.gymfit-api.com/dashboard): a key is only shown once, when it is created.

Subscribed through RapidAPI? Keep calling the API through RapidAPI, with your RapidAPI key.

## Search and filters

`GET /v2/exercises/search` reads what people type: typos (`bech press`), abbreviations (`rdl`, `ohp`) and muscles (`quads`, `lower back`).

- **Filters combine**: `?muscle=Pecs&equipment=Dumbbell,Cable&difficulty=Beginner`. The values come from `GET /v2/equipment`, `/v2/categories`, `/v2/families` and `/v2/muscle-groups`, each with how many exercises have it.
- **Autocomplete**: `GET /v2/exercises/suggest?q=bench` while the person types. It is free and does not count in the quota.
- **Filters with counts**: `facets=true` adds how many matches fall under each category, difficulty, tier, equipment and muscle group.
- **Did you mean**: `suggest=true` adds `suggestion` when a word of the query looks misspelled.
- **Pages**: `limit` (up to 100) and `offset`; `total` is the number of matches.
- **One line per movement**: `group=true` shows each movement once, with its variants counted under it.

## Muscle heatmap

`GET /v2/exercises/{id}/heatmap` answers an SVG of the body, with the target muscles of the exercise and its secondary ones in color.

- `view`: `front`, `back` or `both` (default).
- Colors in hex, the `#` optional: `target`, `secondary`, `base` (the muscles the exercise does not work), `body` and `outline`. For example `?target=D4F53C&secondary=7A8C2E`.
- The heatmap of an exercise does not change: it comes with `Cache-Control: private, max-age=86400`, keep it on your side instead of asking for it at each display.
- It needs the key like every route: load it from your server, or fetch it with the key and show it (see the code samples of the route).

## AI assistants (MCP)

GymFit is also an [MCP](https://modelcontextprotocol.io) server: Claude, ChatGPT, Claude Code, Cursor, VS Code or any MCP client searches the exercises, reads them and runs the calculators for you, on your plan.

**Claude, ChatGPT and the apps that sign in with OAuth**: add a custom connector with the URL `https://mcp.gymfit-api.com/mcp`, then sign in to GymFit and allow it. No key: the assistant uses your account, and you disconnect it from your [dashboard](https://www.gymfit-api.com/dashboard/ai).

**Claude Code, Cursor and the clients that take a key**:

- URL: `https://api.gymfit-api.com/mcp` (Streamable HTTP)
- Key: in the `X-API-Key` header, or as `Authorization: Bearer YOUR_API_KEY` for clients that only take a token. A RapidAPI key does not work here: the MCP server takes a GymFit key.

Claude Code:

```bash
claude mcp add --transport http gymfit https://api.gymfit-api.com/mcp --header "X-API-Key: YOUR_API_KEY"
```

Cursor (`.cursor/mcp.json`), and the same entry in most clients (VS Code names the list `servers` and adds `"type": "http"`):

```json
{
  "mcpServers": {
    "gymfit": {
      "url": "https://api.gymfit-api.com/mcp",
      "headers": { "X-API-Key": "YOUR_API_KEY" }
    }
  }
}
```

Keep the key out of shared files: Cursor reads `${env:GYMFIT_API_KEY}` and Claude Code `${GYMFIT_API_KEY}` in a `.mcp.json`.

The tools are read-only: `search_exercises`, `get_exercise`, `find_alternatives`, `get_exercise_heatmap`, `search_muscles`, `get_muscle`, `list_filter_values`, `calculate_bmr`, `calculate_tdee`, `calculate_bmi`, `calculate_ideal_weight` and `get_usage`. Each tool call counts as one request of your quota, like a call to the API; `get_usage` is free. A limit reached is answered to the assistant as an error it can read, with the time to wait or the way to upgrade.

## Plans, quotas and rate limits

| Plan | Price / month | Requests / month | Beyond the quota | Requests / minute |
|---|---|---|---|---|
| Free | $0 | 1,000 | Refused | 30 |
| Starter | $9 | 20,000 | $0.002 per request | 60 |
| Pro | $29 | 100,000 | $0.001 per request | 300 |

Only successful (2xx) responses count in the quota. Every response carries:

- `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds until the minute window resets)
- `X-Quota-Limit`, `X-Quota-Used` and `X-Quota-Remaining` for the current billing period
- `X-Request-Id`: mention it when you contact the support

When a limit is reached, the API answers `429` with a `Retry-After` header: wait that many seconds before sending again, and space out the retries rather than sending them at once. Your consumption is also available from `GET /v2/usage`, which is free and does not count in your quota.

## Errors

Errors follow [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) (`application/problem+json`):

```json
{
  "type": "https://api.gymfit-api.com/docs#error-invalid_api_key",
  "title": "Unauthorized",
  "status": 401,
  "code": "invalid_api_key",
  "detail": "Missing or invalid API key. Get a free key at https://www.gymfit-api.com/signup",
  "instance": "/v2/exercises/search",
  "requestId": "9f3c2d1e-5b7a-4c8e-9d6f-1a2b3c4d5e6f"
}
```

Branch on `code`, which is stable: `invalid_api_key`, `validation_failed` (with an `errors` list), `not_found`, `rate_limited`, `quota_exceeded`, `internal_error`...

## Versions

This reference documents v2, under `/v2`. v1 stays available under `/v1` for existing integrations: the same routes, with the legacy `image` and `images` fields, and without the muscle heatmap.

Base URL: `https://api.gymfit-api.com`. OpenAPI specification: https://api.gymfit-api.com/openapi.json

## Exercises

Search the 5,800 exercises, read one with its muscles, steps and variants, find its alternatives and draw its muscle heatmap.

### Search exercises

`GET /v2/exercises/search`

Search the exercise catalog by what the user types, with filters and pagination.

**What it understands:**
- Words in any order, plural forms and spellings (`pushup`, `push-ups`, `curl biceps`)
- Typos (`bech press`)
- Abbreviations (`rdl`, `db curl`, `ohp`)
- Muscles (`quads`, `lower back`, `abs no equipment`)

**Ranking:** exact name first, then the words in the name, then in the rest of the exercise, weighed by how often the exercise is done and its level.

**Default results:** the exercises people do. `include=all` adds the rare ones and the exercises of rehab, plyometrics, aerobics and warm-up; typing the name of a rare exercise finds it anyway.

**Filters:** body part, equipment (several values, comma separated), muscle (by id, name or common name, with `muscleRole`), muscle group, family, tier, type, category, difficulty, purpose, force, tracking type, laterality. `ids` fetches up to 50 exercises by id or slug. `group=true` shows each movement once, with its variants counted under it.

**Sort:** by relevance (default), `popularity`, `name` or `difficulty`.

**Options:** `suggest=true` adds `suggestion`, the query put right when a word looks misspelled. `facets=true` adds `facets`, the count of the matches per category, difficulty, tier, equipment and muscle group. Both are off by default.

Without a query, the most popular exercises come first.

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `query` | query | string | Search query to filter exercises by name (case-insensitive). |
| `bodyPart` | query | string | Filter by target body part. Values: `Legs`, `Back`, `Chest`, `Shoulders`, `Arms`, `Core`, `Neck`. |
| `type` | query | string | Filter by exercise type (compound or isolation). Values: `Compound`, `Isolation`. |
| `limit` | query | number | Number of results to return. Default `20`. |
| `offset` | query | number | Number of results to skip for pagination. Default `0`. |
| `equipment` | query | array of string | Equipment the exercise uses. Several values, separated by commas, give the exercises that use any of them. Values: `Barbell`, `Dumbbell`, `Machine`, `Bodyweight`, `Kettlebell`, `ResistanceBand`, `BattleRope`, `MedicineBall`, `BosuBall`, `PowerSled`, `SmithMachine`, `StabilityBall`, `TrapBar`, `Stepper`, `WheelRoller`, `Towel`, `Landmine`, `Cable`, `Bench`, `Box`, `EzBar`, `ParallelBars`, `PullUpBar`, `Rings`, `Rope`, `Sledgehammer`, `Stick`, `SuspensionTrainer`, `Weighted`, `Chair`, `FoamRoller`, `PreacherBench`, `Hurdle`, `Block`. |
| `muscle` | query | string | A muscle: its id, its name (`Pectoralis major`) or its common name (`Pecs`). |
| `muscleRole` | query | string | With `muscle`: `target` for the exercises it is the main mover of (default), `synergist` for those it helps, `any` for both. Default `target`. Values: `target`, `synergist`, `any`. |
| `family` | query | string | Family of movements (`GET /v2/families`): `squat`, `bench-press`... |
| `tier` | query | string | How common the exercise is. Values: `Standard`, `Variant`, `Niche`. |
| `trackingType` | query | string | How a set is logged: weight and reps, time, distance... Values: `WeightReps`, `Reps`, `WeightedBodyweightReps`, `AssistedReps`, `Duration`, `WeightDuration`, `DistanceDuration`, `WeightDistance`. |
| `laterality` | query | string | How the sides work: both, one at a time, or taking turns. Values: `Bilateral`, `Unilateral`, `Alternating`. |
| `ids` | query | string | Exercises to fetch at once: up to 50 ids or slugs, separated by commas (`bench-press-barbell,squat-barbell`). The rare exercises are included. |
| `sort` | query | string | Order of the results. `relevance` is for a text search, the others sort the matches. Default `relevance`. Values: `relevance`, `popularity`, `name`, `difficulty`. |
| `category` | query | string | Kind of exercise. Values: `Strength`, `Weightlifting`, `Plyometrics`, `Cardio`, `Stretching`, `Mobility`, `Yoga`. |
| `difficulty` | query | string | How hard the exercise is. Values: `Beginner`, `Intermediate`, `Advanced`. |
| `muscleGroup` | query | string | Muscle group the exercise works the most (`GET /v2/muscle-groups`), e.g. `Quadriceps`. |
| `purpose` | query | string | Purpose of the exercise: rehab, yoga, plyometrics... Values: `Strength`, `Calisthenics`, `Weightlifting`, `Strongman`, `Plyometrics`, `Cardio`, `Aerobics`, `Stretching`, `Mobility`, `Yoga`, `Pilates`, `Rehab`, `WarmUp`. |
| `force` | query | string | Push, pull or static. Values: `Push`, `Pull`, `Static`. |
| `include` | query | string | `default` shows the exercises people do; `all` adds the rare ones and the exercises of rehab, plyometrics, aerobics and warm-up. Default `default`. Values: `default`, `all`. |
| `group` | query | boolean | Shows each movement once, with its variants counted under it. Default `false`. |
| `suggest` | query | boolean | Adds `suggestion` to the answer when a word of the query looks misspelled: the query put right, for a "Did you mean" line. Default `false`. |
| `facets` | query | boolean | Adds `facets` to the answer: how many of the matches fall under each category, difficulty, tier, equipment and muscle group, to build filters. Default `false`. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/exercises/search?query=bench%20press&limit=20&offset=0" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: Exercises matching the search criteria
- `400`: Invalid search parameters

**Example response**

```json
{
  "results": [
    {
      "id": "c2b6fccf-2c2c-43e1-aca3-a3cb73caa78b",
      "name": "Bench Press (Barbell)",
      "bodyPart": "Chest",
      "slug": "bench-press-barbell",
      "equipment": "Barbell",
      "category": "Strength",
      "difficulty": "Intermediate",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 100,
      "variantCount": 26
    },
    {
      "id": "3f05cb6b-dfe4-4aaa-9e45-2cbb79ffbf20",
      "name": "Bench Press (Dumbbell)",
      "bodyPart": "Chest",
      "slug": "bench-press-dumbbell",
      "equipment": "Dumbbell",
      "category": "Strength",
      "difficulty": "Intermediate",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 90,
      "variantCount": 22
    },
    {
      "id": "9f14802d-441a-42b4-bd23-79b1e2c89d0e",
      "name": "Bench Press (Smith Machine)",
      "bodyPart": "Chest",
      "slug": "bench-press-smith-machine",
      "equipment": "SmithMachine",
      "category": "Strength",
      "difficulty": "Beginner",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 62,
      "variantCount": 7
    },
    {
      "id": "d2204c30-e837-4fe8-88ed-843d424fb036",
      "name": "Bench Press (Band)",
      "bodyPart": "Chest",
      "slug": "bench-press-band",
      "equipment": "ResistanceBand",
      "category": "Strength",
      "difficulty": "Beginner",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Variant",
      "popularity": 30,
      "variantCount": 2
    },
    {
      "id": "b0884130-ccad-466f-b591-8ab041e07098",
      "name": "Seated Shoulder Press (Dumbbell)",
      "bodyPart": "Shoulders",
      "slug": "seated-shoulder-press-dumbbell",
      "equipment": "Dumbbell",
      "category": "Strength",
      "difficulty": "Intermediate",
      "primaryGroups": [
        "Shoulders"
      ],
      "tier": "Standard",
      "popularity": 88,
      "variantCount": 7
    }
  ],
  "total": 20,
  "count": 20,
  "facets": {
    "category": [
      {
        "value": "Strength",
        "count": 102
      }
    ],
    "difficulty": [
      {
        "value": "Intermediate",
        "count": 72
      },
      {
        "value": "Beginner",
        "count": 27
      },
      {
        "value": "Advanced",
        "count": 3
      }
    ],
    "tier": [
      {
        "value": "Niche",
        "count": 52
      },
      {
        "value": "Variant",
        "count": 35
      },
      {
        "value": "Standard",
        "count": 15
      }
    ],
    "equipment": [
      {
        "value": "Bench",
        "count": 88
      },
      {
        "value": "Barbell",
        "count": 32
      },
      {
        "value": "Dumbbell",
        "count": 32
      },
      {
        "value": "Kettlebell",
        "count": 9
      },
      {
        "value": "SmithMachine",
        "count": 9
      }
    ],
    "muscleGroup": [
      {
        "value": "Chest",
        "count": 84
      },
      {
        "value": "Shoulders",
        "count": 28
      },
      {
        "value": "Triceps",
        "count": 27
      },
      {
        "value": "Calves",
        "count": 1
      },
      {
        "value": "Trapezius",
        "count": 1
      }
    ]
  }
}
```

A real answer, its lists cut to 5 items.

**TypeScript**

```typescript
type ExerciseSummary = {
  id: string
  slug: string | null
  name: string
  equipment?: string
  difficulty?: string | null
  primaryGroups?: string[]
}

type SearchResponse = {
  total: number
  count: number
  results: ExerciseSummary[]
}

export async function searchExercises(query: string, limit = 20): Promise<SearchResponse> {
  const url = new URL('https://api.gymfit-api.com/v2/exercises/search')
  url.search = new URLSearchParams({ query, limit: String(limit) }).toString()

  const response = await fetch(url, {
    headers: { 'X-API-Key': process.env.GYMFIT_API_KEY ?? '' }
  })

  if (!response.ok) {
    const problem = await response.json()
    throw new Error(`${problem.code}: ${problem.detail}`)
  }

  return response.json()
}
```

**Swift**

```swift
import Foundation

struct ExerciseSummary: Decodable {
    let id: String
    let slug: String?
    let name: String
    let equipment: String?
    let difficulty: String?
    let primaryGroups: [String]?
}

struct SearchResponse: Decodable {
    let total: Int
    let count: Int
    let results: [ExerciseSummary]
}

let apiKey = "YOUR_API_KEY"

func searchExercises(_ query: String, limit: Int = 20) async throws -> SearchResponse {
    var components = URLComponents(string: "https://api.gymfit-api.com/v2/exercises/search")!
    components.queryItems = [
        URLQueryItem(name: "query", value: query),
        URLQueryItem(name: "limit", value: String(limit))
    ]
    var request = URLRequest(url: components.url!)
    request.setValue(apiKey, forHTTPHeaderField: "X-API-Key")

    let (data, response) = try await URLSession.shared.data(for: request)
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    return try JSONDecoder().decode(SearchResponse.self, from: data)
}
```

**Kotlin**

```kotlin
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request

@Serializable
data class ExerciseSummary(
    val id: String,
    val slug: String? = null,
    val name: String,
    val equipment: String? = null,
    val difficulty: String? = null,
    val primaryGroups: List<String> = emptyList()
)

@Serializable
data class SearchResponse(val total: Int, val count: Int, val results: List<ExerciseSummary>)

private val client = OkHttpClient()
private val json = Json { ignoreUnknownKeys = true }

// Blocking: call it from a coroutine on Dispatchers.IO
fun searchExercises(query: String, limit: Int = 20): SearchResponse {
    val url = "https://api.gymfit-api.com/v2/exercises/search".toHttpUrl().newBuilder()
        .addQueryParameter("query", query)
        .addQueryParameter("limit", limit.toString())
        .build()
    val request = Request.Builder()
        .url(url)
        .header("X-API-Key", BuildConfig.GYMFIT_API_KEY)
        .build()

    client.newCall(request).execute().use { response ->
        check(response.isSuccessful) { "GymFit API: ${response.code}" }
        return json.decodeFromString(response.body!!.string())
    }
}
```

**Dart (Flutter)**

```dart
import 'dart:convert';

import 'package:http/http.dart' as http;

const apiKey = String.fromEnvironment('GYMFIT_API_KEY');

class ExerciseSummary {
  ExerciseSummary({
    required this.id,
    required this.name,
    this.slug,
    this.equipment,
    this.primaryGroups = const [],
  });

  factory ExerciseSummary.fromJson(Map<String, dynamic> json) => ExerciseSummary(
        id: json['id'] as String,
        name: json['name'] as String,
        slug: json['slug'] as String?,
        equipment: json['equipment'] as String?,
        primaryGroups: (json['primaryGroups'] as List<dynamic>? ?? []).cast<String>(),
      );

  final String id;
  final String name;
  final String? slug;
  final String? equipment;
  final List<String> primaryGroups;
}

Future<List<ExerciseSummary>> searchExercises(String query, {int limit = 20}) async {
  final uri = Uri.https('api.gymfit-api.com', '/v2/exercises/search', {
    'query': query,
    'limit': '$limit',
  });
  final response = await http.get(uri, headers: {'X-API-Key': apiKey});

  if (response.statusCode != 200) {
    throw Exception('GymFit API: ${response.statusCode} ${response.body}');
  }

  final body = jsonDecode(response.body) as Map<String, dynamic>;
  return (body['results'] as List<dynamic>)
      .map((item) => ExerciseSummary.fromJson(item as Map<String, dynamic>))
      .toList();
}
```

### Suggest exercises while typing

`GET /v2/exercises/suggest`

Exercises to offer under a search box while a person types: what is typed so far (two characters at least) gives the most popular exercises whose name or text starts or holds it.

**Not billable:** a search box calls this on every key. It counts in the rate limit but never in the monthly quota; a click on a suggestion is a billable `GET /v2/exercises/{id}`.

For the full result page, use `GET /v2/exercises/search`.

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `q` | query | string | The beginning of what is typed, two characters at least. Required. |
| `limit` | query | number | Number of suggestions. Default `8`. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/exercises/suggest?q=bench%20pr" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: Suggestions, the most popular first
- `400`: Less than two characters, or invalid parameters

**Example response**

```json
{
  "results": [
    {
      "id": "c2b6fccf-2c2c-43e1-aca3-a3cb73caa78b",
      "slug": "bench-press-barbell",
      "name": "Bench Press (Barbell)",
      "equipment": "Barbell",
      "category": "Strength"
    },
    {
      "id": "3f05cb6b-dfe4-4aaa-9e45-2cbb79ffbf20",
      "slug": "bench-press-dumbbell",
      "name": "Bench Press (Dumbbell)",
      "equipment": "Dumbbell",
      "category": "Strength"
    },
    {
      "id": "a1281637-139d-4864-903b-67d62037c16e",
      "slug": "incline-bench-press-barbell",
      "name": "Incline Bench Press (Barbell)",
      "equipment": "Barbell",
      "category": "Strength"
    },
    {
      "id": "e48f6911-2a0d-45b5-b854-61aca0570173",
      "slug": "incline-bench-press-dumbbell",
      "name": "Incline Bench Press (Dumbbell)",
      "equipment": "Dumbbell",
      "category": "Strength"
    },
    {
      "id": "8a764a28-b2d8-496e-9d08-fa352280b178",
      "slug": "close-grip-bench-press-barbell",
      "name": "Close Grip Bench Press (Barbell)",
      "equipment": "Barbell",
      "category": "Strength"
    }
  ],
  "count": 8
}
```

A real answer, its lists cut to 5 items.

### Get exercise details

`GET /v2/exercises/{id}`

Retrieve comprehensive information about a specific exercise.

**Includes:**
- Complete exercise metadata (name, equipment, target body part)
- Muscle engagement details (target and synergist muscles)
- Step-by-step instructions
- Exercise variations

The exercise is found by its id or its slug (`bench-press-barbell`). The muscles it works can be drawn with `GET /v2/exercises/{id}/heatmap`.

**Use cases:**
- Display exercise details in workout builder
- Show proper form and technique
- Suggest exercise variations

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `id` | path | string | Id (UUID) or slug of the exercise. Required. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/exercises/bench-press-barbell" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: Detailed exercise information
- `400`: Invalid id or slug
- `404`: Exercise not found

**Example response**

```json
{
  "id": "c2b6fccf-2c2c-43e1-aca3-a3cb73caa78b",
  "name": "Bench Press (Barbell)",
  "slug": "bench-press-barbell",
  "bodyPart": "Chest",
  "equipment": "Barbell",
  "type": "Compound",
  "description": "The classic flat-bench press with a barbell, a staple for pressing strength. It works the chest, the front delts and the triceps.",
  "difficulty": "Intermediate",
  "category": "Strength",
  "force": "Push",
  "trackingType": "WeightReps",
  "laterality": "Bilateral",
  "tier": "Standard",
  "popularity": 100,
  "purposes": [
    "Strength"
  ],
  "tips": [
    "Keep your shoulder blades pulled together and your feet planted throughout.",
    "Press the bar back in a slight arc so it finishes over your shoulders."
  ],
  "mistakes": [
    "Bouncing the bar off the chest.",
    "Lifting the hips off the bench to push the bar up.",
    "Flaring the elbows straight out to the sides."
  ],
  "aliases": [
    "Weighted Bench Press (Barbell)"
  ],
  "primaryGroups": [
    "Chest"
  ],
  "secondaryGroups": [
    "Shoulders",
    "Triceps"
  ],
  "family": {
    "id": "bench-press",
    "label": "Bench press"
  },
  "equipments": [
    {
      "equipment": "Barbell",
      "main": true
    },
    {
      "equipment": "Bench",
      "main": false
    }
  ],
  "targetMuscles": [
    {
      "id": "ffd76ee8-7633-4062-9c91-9092d637567a",
      "name": "Pectoralis major",
      "commonName": "Pecs",
      "bodyPart": "Chest",
      "group": "Chest"
    }
  ],
  "secondaryMuscles": [
    {
      "id": "d0f075f6-831c-43b9-9749-15e1b74987be",
      "name": "Deltoid",
      "commonName": "Delts",
      "bodyPart": "Shoulders",
      "group": "Shoulders"
    },
    {
      "id": "dec5954e-80b5-493b-8911-a73ef54d96ee",
      "name": "Triceps brachii",
      "commonName": "Triceps",
      "bodyPart": "Arms",
      "group": "Triceps"
    }
  ],
  "instructions": [
    {
      "order": 1,
      "description": "Lie on a flat bench with your eyes under the bar, feet flat on the floor and shoulder blades pulled together."
    },
    {
      "order": 2,
      "description": "Grip the bar a little wider than shoulder width, palms facing away, and unrack it over your chest with straight arms."
    },
    {
      "order": 3,
      "description": "Inhale and lower the bar under control to your mid-chest, elbows at about 45 degrees from your body."
    },
    {
      "order": 4,
      "description": "Pause briefly with the bar just touching your chest."
    },
    {
      "order": 5,
      "description": "Exhale and press the bar back up until your arms are straight."
    }
  ],
  "base": null,
  "variations": [
    {
      "id": "a1281637-139d-4864-903b-67d62037c16e",
      "name": "Incline Bench Press (Barbell)",
      "bodyPart": "Chest",
      "slug": "incline-bench-press-barbell",
      "equipment": "Barbell",
      "difficulty": "Intermediate",
      "tier": "Standard",
      "popularity": 88
    },
    {
      "id": "8a764a28-b2d8-496e-9d08-fa352280b178",
      "name": "Close Grip Bench Press (Barbell)",
      "bodyPart": "Arms",
      "slug": "close-grip-bench-press-barbell",
      "equipment": "Barbell",
      "difficulty": "Intermediate",
      "tier": "Standard",
      "popularity": 72
    },
    {
      "id": "c3eece11-94f5-49fc-9789-a03eb217e34b",
      "name": "Decline Bench Press (Barbell)",
      "bodyPart": "Chest",
      "slug": "decline-bench-press-barbell",
      "equipment": "Barbell",
      "difficulty": "Intermediate",
      "tier": "Standard",
      "popularity": 65
    },
    {
      "id": "864c3482-fe80-4740-85c7-1c3fdfeba898",
      "name": "Pause Bench Press (Barbell)",
      "bodyPart": "Chest",
      "slug": "pause-bench-press-barbell",
      "equipment": "Barbell",
      "difficulty": "Intermediate",
      "tier": "Variant",
      "popularity": 45
    },
    {
      "id": "859bb806-b4d8-4200-aea1-f9758e412ed1",
      "name": "Floor Chest Press (Barbell)",
      "bodyPart": "Chest",
      "slug": "floor-chest-press-barbell",
      "equipment": "Barbell",
      "difficulty": "Intermediate",
      "tier": "Variant",
      "popularity": 40
    }
  ],
  "alternatives": [
    {
      "id": "3f05cb6b-dfe4-4aaa-9e45-2cbb79ffbf20",
      "name": "Bench Press (Dumbbell)",
      "bodyPart": "Chest",
      "slug": "bench-press-dumbbell",
      "equipment": "Dumbbell",
      "category": "Strength",
      "difficulty": "Intermediate",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 90,
      "score": 0.96,
      "reasons": [
        "same-target-muscles",
        "same-movement",
        "other-equipment"
      ]
    },
    {
      "id": "af3d6a16-23a2-42be-a486-fc2c00e66098",
      "name": "Chest Press (Machine)",
      "bodyPart": "Chest",
      "slug": "chest-press-machine",
      "equipment": "Machine",
      "category": "Strength",
      "difficulty": "Beginner",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 85,
      "score": 0.93,
      "reasons": [
        "same-target-muscles",
        "same-movement",
        "other-equipment",
        "easier"
      ]
    },
    {
      "id": "dda1be2c-e910-4115-a864-0864b119c179",
      "name": "Push-up",
      "bodyPart": "Chest",
      "slug": "push-up",
      "equipment": "Bodyweight",
      "category": "Strength",
      "difficulty": "Beginner",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 97,
      "score": 0.93,
      "reasons": [
        "same-target-muscles",
        "no-equipment",
        "easier"
      ]
    },
    {
      "id": "84177edb-a08d-4756-8be7-2bf6515c91e4",
      "name": "Chest Dip",
      "bodyPart": "Chest",
      "slug": "chest-dip",
      "equipment": "Bodyweight",
      "category": "Strength",
      "difficulty": "Intermediate",
      "primaryGroups": [
        "Chest",
        "Triceps"
      ],
      "tier": "Standard",
      "popularity": 85,
      "score": 0.86,
      "reasons": [
        "similar-muscles",
        "no-equipment"
      ]
    },
    {
      "id": "918ceabc-3449-4184-b4f9-c5bcf85e0a14",
      "name": "Chest Press (Suspension)",
      "bodyPart": "Chest",
      "slug": "chest-press-suspension",
      "equipment": "SuspensionTrainer",
      "category": "Strength",
      "difficulty": "Intermediate",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 55,
      "score": 0.75,
      "reasons": [
        "same-target-muscles",
        "other-equipment"
      ]
    }
  ],
  "easier": [
    {
      "id": "af3d6a16-23a2-42be-a486-fc2c00e66098",
      "name": "Chest Press (Machine)",
      "bodyPart": "Chest",
      "slug": "chest-press-machine",
      "equipment": "Machine",
      "category": "Strength",
      "difficulty": "Beginner",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 85,
      "step": 10
    },
    {
      "id": "77714948-0022-410c-a18a-53cd85224fa7",
      "name": "Incline Chest Press (Machine)",
      "bodyPart": "Chest",
      "slug": "incline-chest-press-machine",
      "equipment": "Machine",
      "category": "Strength",
      "difficulty": "Beginner",
      "primaryGroups": [
        "Chest",
        "Shoulders"
      ],
      "tier": "Standard",
      "popularity": 65,
      "step": 10
    },
    {
      "id": "ca6c6d78-58ec-4984-b02d-68d784acc8a5",
      "name": "Incline Bench Press (Smith Machine)",
      "bodyPart": "Chest",
      "slug": "incline-bench-press-smith-machine",
      "equipment": "SmithMachine",
      "category": "Strength",
      "difficulty": "Beginner",
      "primaryGroups": [
        "Chest",
        "Shoulders"
      ],
      "tier": "Standard",
      "popularity": 65,
      "step": 10
    }
  ],
  "harder": []
}
```

A real answer, its lists cut to 5 items.

**TypeScript**

```typescript
type Muscle = {
  id: string
  name: string
  commonName: string | null
  group: string | null
}

type Exercise = {
  id: string
  slug: string | null
  name: string
  difficulty?: string | null
  instructions: { order: number; description: string }[]
  targetMuscles: Muscle[]
  secondaryMuscles: Muscle[]
  tips?: string[]
}

/** null when the exercise does not exist */
export async function getExercise(idOrSlug: string): Promise<Exercise | null> {
  const response = await fetch(
    `https://api.gymfit-api.com/v2/exercises/${encodeURIComponent(idOrSlug)}`,
    { headers: { 'X-API-Key': process.env.GYMFIT_API_KEY ?? '' } }
  )

  if (response.status === 404) {
    return null
  }
  if (!response.ok) {
    throw new Error(`GymFit API: ${response.status}`)
  }

  return response.json()
}
```

**Swift**

```swift
import Foundation

struct Muscle: Decodable {
    let id: String
    let name: String
    let commonName: String?
    let group: String?
}

struct Step: Decodable {
    let order: Int
    let description: String
}

struct Exercise: Decodable {
    let id: String
    let slug: String?
    let name: String
    let difficulty: String?
    let instructions: [Step]
    let targetMuscles: [Muscle]
    let secondaryMuscles: [Muscle]
    let tips: [String]?
}

/// nil when the exercise does not exist (apiKey: see the search)
func getExercise(_ idOrSlug: String) async throws -> Exercise? {
    let url = URL(string: "https://api.gymfit-api.com/v2/exercises/\(idOrSlug)")!
    var request = URLRequest(url: url)
    request.setValue(apiKey, forHTTPHeaderField: "X-API-Key")

    let (data, response) = try await URLSession.shared.data(for: request)
    switch (response as? HTTPURLResponse)?.statusCode {
    case 200: return try JSONDecoder().decode(Exercise.self, from: data)
    case 404: return nil
    default: throw URLError(.badServerResponse)
    }
}
```

**Kotlin**

```kotlin
import kotlinx.serialization.Serializable

@Serializable
data class Muscle(
    val id: String,
    val name: String,
    val commonName: String? = null,
    val group: String? = null
)

@Serializable
data class Step(val order: Int, val description: String)

@Serializable
data class Exercise(
    val id: String,
    val slug: String? = null,
    val name: String,
    val difficulty: String? = null,
    val instructions: List<Step>,
    val targetMuscles: List<Muscle>,
    val secondaryMuscles: List<Muscle>,
    val tips: List<String> = emptyList()
)

// null when the exercise does not exist. client and json: see the search.
// Blocking: call it from a coroutine on Dispatchers.IO
fun getExercise(idOrSlug: String): Exercise? {
    val request = Request.Builder()
        .url("https://api.gymfit-api.com/v2/exercises/$idOrSlug")
        .header("X-API-Key", BuildConfig.GYMFIT_API_KEY)
        .build()

    client.newCall(request).execute().use { response ->
        if (response.code == 404) return null
        check(response.isSuccessful) { "GymFit API: ${response.code}" }
        return json.decodeFromString(response.body!!.string())
    }
}
```

**Dart (Flutter)**

```dart
import 'dart:convert';

import 'package:http/http.dart' as http;

class Muscle {
  Muscle({required this.name, this.commonName});

  factory Muscle.fromJson(Map<String, dynamic> json) =>
      Muscle(name: json['name'] as String, commonName: json['commonName'] as String?);

  final String name;
  final String? commonName;
}

class Exercise {
  Exercise({required this.name, required this.steps, required this.targetMuscles});

  factory Exercise.fromJson(Map<String, dynamic> json) => Exercise(
        name: json['name'] as String,
        steps: [
          for (final step in json['instructions'] as List<dynamic>)
            (step as Map<String, dynamic>)['description'] as String,
        ],
        targetMuscles: [
          for (final muscle in json['targetMuscles'] as List<dynamic>)
            Muscle.fromJson(muscle as Map<String, dynamic>),
        ],
      );

  final String name;
  final List<String> steps;
  final List<Muscle> targetMuscles;
}

/// null when the exercise does not exist (apiKey: see the search)
Future<Exercise?> getExercise(String idOrSlug) async {
  final uri = Uri.https('api.gymfit-api.com', '/v2/exercises/$idOrSlug');
  final response = await http.get(uri, headers: {'X-API-Key': apiKey});

  if (response.statusCode == 404) return null;
  if (response.statusCode != 200) {
    throw Exception('GymFit API: ${response.statusCode}');
  }
  return Exercise.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
}
```

### Get alternative exercises

`GET /v2/exercises/{id}/alternatives`

Find alternative exercises based on similarity scoring.

**Similarity Algorithm:**
- Muscle engagement similarity (70% weight): Uses Jaccard index to compare target and synergist muscles
- Name similarity (30% weight): Fuzzy string matching
- Minimum similarity threshold: 0.7 (70%)

**Results are sorted by similarity score (highest first)**

**Use cases:**
- Suggest alternatives when equipment is unavailable
- Provide exercise variety for progressive overload
- Help with injury adaptations

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `id` | path | string | Id (UUID) or slug of the source exercise. Required. |
| `limit` | query | number | Maximum number of alternatives to return. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/exercises/bench-press-barbell/alternatives?limit=10" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: List of alternative exercises
- `404`: Source exercise not found

**Example response**

```json
[
  {
    "id": "3f05cb6b-dfe4-4aaa-9e45-2cbb79ffbf20",
    "name": "Bench Press (Dumbbell)",
    "bodyPart": "Chest",
    "slug": "bench-press-dumbbell",
    "equipment": "Dumbbell",
    "category": "Strength",
    "difficulty": "Intermediate",
    "primaryGroups": [
      "Chest"
    ],
    "tier": "Standard",
    "popularity": 90,
    "score": 0.96,
    "reasons": [
      "same-target-muscles",
      "same-movement",
      "other-equipment"
    ]
  },
  {
    "id": "af3d6a16-23a2-42be-a486-fc2c00e66098",
    "name": "Chest Press (Machine)",
    "bodyPart": "Chest",
    "slug": "chest-press-machine",
    "equipment": "Machine",
    "category": "Strength",
    "difficulty": "Beginner",
    "primaryGroups": [
      "Chest"
    ],
    "tier": "Standard",
    "popularity": 85,
    "score": 0.93,
    "reasons": [
      "same-target-muscles",
      "same-movement",
      "other-equipment",
      "easier"
    ]
  },
  {
    "id": "dda1be2c-e910-4115-a864-0864b119c179",
    "name": "Push-up",
    "bodyPart": "Chest",
    "slug": "push-up",
    "equipment": "Bodyweight",
    "category": "Strength",
    "difficulty": "Beginner",
    "primaryGroups": [
      "Chest"
    ],
    "tier": "Standard",
    "popularity": 97,
    "score": 0.93,
    "reasons": [
      "same-target-muscles",
      "no-equipment",
      "easier"
    ]
  },
  {
    "id": "84177edb-a08d-4756-8be7-2bf6515c91e4",
    "name": "Chest Dip",
    "bodyPart": "Chest",
    "slug": "chest-dip",
    "equipment": "Bodyweight",
    "category": "Strength",
    "difficulty": "Intermediate",
    "primaryGroups": [
      "Chest",
      "Triceps"
    ],
    "tier": "Standard",
    "popularity": 85,
    "score": 0.86,
    "reasons": [
      "similar-muscles",
      "no-equipment"
    ]
  },
  {
    "id": "918ceabc-3449-4184-b4f9-c5bcf85e0a14",
    "name": "Chest Press (Suspension)",
    "bodyPart": "Chest",
    "slug": "chest-press-suspension",
    "equipment": "SuspensionTrainer",
    "category": "Strength",
    "difficulty": "Intermediate",
    "primaryGroups": [
      "Chest"
    ],
    "tier": "Standard",
    "popularity": 55,
    "score": 0.75,
    "reasons": [
      "same-target-muscles",
      "other-equipment"
    ]
  }
]
```

A real answer, its lists cut to 5 items.

### Get the muscle heatmap of an exercise

`GET /v2/exercises/{id}/heatmap`

SVG image of the body, front and back, with the target and secondary muscles of the exercise highlighted. Colors can be set to match your app.

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `id` | path | string | Id (UUID) or slug of the exercise. Required. |
| `view` | query | string | Side of the body to draw. Default `both`. Values: `front`, `back`, `both`. |
| `target` | query | string | Color of the target muscles (hex, the # is optional). |
| `secondary` | query | string | Color of the secondary muscles (hex, the # is optional). |
| `base` | query | string | Color of the muscles the exercise does not work (hex, the # is optional). Defaults to the colors of the drawing. |
| `body` | query | string | Color of the body and of the lines between the muscles (hex, the # is optional). Defaults to the colors of the drawing. |
| `outline` | query | string | Color of the contour of the body (hex, the # is optional). Defaults to the colors of the drawing. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/exercises/bench-press-barbell/heatmap?target=E53935&secondary=F6A26B&base=D9D9D9&body=2B2B30&outline=45454D" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: The heatmap, as an SVG image
- `400`: Invalid id, slug, view or color
- `404`: Exercise not found

The response is an SVG image (`image/svg+xml`).

**TypeScript**

```typescript
// The heatmap is an SVG that needs the key: fetch it, then show it as an image
export async function heatmapUrl(idOrSlug: string, view = 'both'): Promise<string> {
  const response = await fetch(
    `https://api.gymfit-api.com/v2/exercises/${encodeURIComponent(idOrSlug)}/heatmap?view=${view}`,
    { headers: { 'X-API-Key': process.env.GYMFIT_API_KEY ?? '' } }
  )

  if (!response.ok) {
    throw new Error(`GymFit API: ${response.status}`)
  }

  return URL.createObjectURL(await response.blob())
}

// <img src={await heatmapUrl('bench-press-barbell')} alt="Muscles worked by the bench press" />
```

**Swift**

```swift
import WebKit

// UIImage does not read SVG: a WKWebView draws it (apiKey: see the search)
@MainActor
func showHeatmap(of idOrSlug: String, in webView: WKWebView) async throws {
    let url = URL(string: "https://api.gymfit-api.com/v2/exercises/\(idOrSlug)/heatmap")!
    var request = URLRequest(url: url)
    request.setValue(apiKey, forHTTPHeaderField: "X-API-Key")

    let (data, _) = try await URLSession.shared.data(for: request)
    let svg = String(decoding: data, as: UTF8.self)
    webView.isOpaque = false
    webView.loadHTMLString(
        "<style>body{margin:0}svg{width:100%;height:auto}</style>\(svg)",
        baseURL: nil
    )
}
```

**Kotlin (Compose)**

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import coil.compose.AsyncImage
import coil.decode.SvgDecoder
import coil.request.ImageRequest

// Coil 2 with its SVG decoder: io.coil-kt:coil-compose and io.coil-kt:coil-svg
@Composable
fun Heatmap(idOrSlug: String) {
    val request = ImageRequest.Builder(LocalContext.current)
        .data("https://api.gymfit-api.com/v2/exercises/$idOrSlug/heatmap")
        .addHeader("X-API-Key", BuildConfig.GYMFIT_API_KEY)
        .decoderFactory(SvgDecoder.Factory())
        .build()

    AsyncImage(model = request, contentDescription = "Muscles worked")
}
```

**Dart (Flutter)**

```dart
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';

// flutter_svg draws the SVG and sends the key (apiKey: see the search)
Widget heatmap(String idOrSlug) => SvgPicture.network(
      'https://api.gymfit-api.com/v2/exercises/$idOrSlug/heatmap',
      headers: const {'X-API-Key': apiKey},
      semanticsLabel: 'Muscles worked',
    );
```

## Muscles

The muscles with their common names and groups, and the exercises that work them.

### Search muscles

`GET /v2/muscles/search`

Search the muscles of the catalog.

**Features:**
- Text search on the anatomical name, the common name (`Pecs`, `Quads`) and the aliases (`six pack`, `lower back`)
- Filter by body part and by group (`GET /v2/muscle-groups`)
- Paginated results, ordered by name

**Use cases:**
- Find a muscle by what people call it
- List the muscles of a group
- Fill a muscle picker

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `query` | query | string | Search query to filter muscles by name (case-insensitive). |
| `bodyPart` | query | string | Filter by body part. Values: `Legs`, `Back`, `Chest`, `Shoulders`, `Arms`, `Core`, `Neck`. |
| `limit` | query | number | Number of results to return. Default `50`. |
| `offset` | query | number | Number of results to skip. Default `0`. |
| `group` | query | string | Group of muscles (`GET /v2/muscle-groups`), e.g. `Hamstrings`. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/muscles/search?query=pectoralis" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: Muscles matching criteria

**Example response**

```json
{
  "results": [
    {
      "id": "ffd76ee8-7633-4062-9c91-9092d637567a",
      "name": "Pectoralis major",
      "commonName": "Pecs",
      "bodyPart": "Chest",
      "group": "Chest"
    },
    {
      "id": "6be0d058-5813-49b8-aa58-634171adbee0",
      "name": "Pectoralis minor",
      "commonName": "Pectoralis minor",
      "bodyPart": "Chest",
      "group": "Chest"
    }
  ],
  "total": 2,
  "count": 2
}
```

### Get muscle details

`GET /v2/muscles/{id}`

Retrieve a muscle with everything the catalog knows of it.

**Includes:**
- Anatomical name, common name and aliases
- Body part and group
- Heads of the muscle

The exercises that work it are listed by `GET /v2/muscles/{id}/exercises`.

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `id` | path | string | Muscle UUID. Required. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/muscles/ffd76ee8-7633-4062-9c91-9092d637567a" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: Detailed muscle information
- `404`: Muscle not found

**Example response**

```json
{
  "id": "ffd76ee8-7633-4062-9c91-9092d637567a",
  "name": "Pectoralis major",
  "commonName": "Pecs",
  "bodyPart": "Chest",
  "group": "Chest",
  "aliases": [
    "chest",
    "pectorals"
  ],
  "heads": [
    "Clavicular (Upper)",
    "Sternal (Lower)"
  ]
}
```

### List the muscle groups

`GET /v2/muscle-groups`

The muscle groups (`Chest`, `Lats`, `Hamstrings`...), each with its muscles and their heads, in one call.

A group name is what `muscleGroup` takes on `GET /v2/exercises/search`. The result is the same for every request: keep it.

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/muscle-groups" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: The groups with their muscles

**Example response**

```json
[
  {
    "id": "56cd4301-0124-4c75-bbe9-6a04ccd338e3",
    "name": "Abs",
    "aliases": [
      "abdominals",
      "six pack",
      "core"
    ],
    "muscles": [
      {
        "id": "7d561eae-b72d-48e8-9e5d-a8c82c1fc753",
        "name": "Diaphragm",
        "commonName": "Diaphragm",
        "heads": []
      },
      {
        "id": "b11a737a-d371-4f43-97b3-c3c4bd47cd09",
        "name": "Rectus abdominis",
        "commonName": "Abs",
        "heads": [
          "Lower",
          "Middle",
          "Superior"
        ]
      },
      {
        "id": "45b8f8b5-cff3-4cec-8d1f-52f19ff9af14",
        "name": "Transverse abdominis",
        "commonName": "Deep abs",
        "heads": []
      }
    ]
  },
  {
    "id": "b4798677-fd22-48d4-9662-94daf36cd7f3",
    "name": "Adductors",
    "aliases": [
      "inner thigh",
      "groin"
    ],
    "muscles": [
      {
        "id": "22456832-adb8-4eef-bdf6-03c03ede89fa",
        "name": "Adductor brevis",
        "commonName": "Adductor brevis",
        "heads": []
      },
      {
        "id": "df03e634-d5d7-4d61-bc23-15b5e2f708bb",
        "name": "Adductor longus",
        "commonName": "Adductor longus",
        "heads": []
      },
      {
        "id": "af4088f9-a989-4f23-9cd9-49558602c761",
        "name": "Adductor magnus",
        "commonName": "Adductor magnus",
        "heads": [
          "Adductor (Anterior)",
          "Hamstring (Posterior)"
        ]
      },
      {
        "id": "771f5070-1d6a-435d-8cff-e8bb562d4441",
        "name": "Gracilis",
        "commonName": "Gracilis",
        "heads": []
      },
      {
        "id": "7d2cf221-96bb-4c38-9471-71f6401df991",
        "name": "Pectineus",
        "commonName": "Pectineus",
        "heads": []
      }
    ]
  },
  {
    "id": "fe03d381-5c6a-4229-8faa-f79bb3e0e1b7",
    "name": "Biceps",
    "aliases": [
      "bis",
      "guns"
    ],
    "muscles": [
      {
        "id": "6ad37840-db50-431c-80df-171a031fbdec",
        "name": "Biceps brachii",
        "commonName": "Biceps",
        "heads": [
          "Long",
          "Short"
        ]
      },
      {
        "id": "98b3cd59-8b5c-4fcf-9724-b9874e4b93c1",
        "name": "Brachialis",
        "commonName": "Brachialis",
        "heads": []
      }
    ]
  },
  {
    "id": "62ccea28-6d01-42ea-a66f-04fa153119ef",
    "name": "Calves",
    "aliases": [
      "calf"
    ],
    "muscles": [
      {
        "id": "ac2bb610-6d0e-47eb-bb46-0f767196de7e",
        "name": "Fibularis",
        "commonName": "Peroneals",
        "heads": []
      },
      {
        "id": "5c8b7816-388c-4ccf-8251-b283fa7dbe77",
        "name": "Gastrocnemius",
        "commonName": "Calf",
        "heads": [
          "Lateral (Outer)",
          "Medial (Inner)"
        ]
      },
      {
        "id": "98180f1c-d623-4982-8955-8c93cff2c7f1",
        "name": "Soleus",
        "commonName": "Soleus",
        "heads": []
      }
    ]
  },
  {
    "id": "7a45a47c-f008-42e3-977b-9529dcdcf6b9",
    "name": "Chest",
    "aliases": [
      "pecs",
      "pectorals"
    ],
    "muscles": [
      {
        "id": "ffd76ee8-7633-4062-9c91-9092d637567a",
        "name": "Pectoralis major",
        "commonName": "Pecs",
        "heads": [
          "Clavicular (Upper)",
          "Sternal (Lower)"
        ]
      },
      {
        "id": "6be0d058-5813-49b8-aa58-634171adbee0",
        "name": "Pectoralis minor",
        "commonName": "Pectoralis minor",
        "heads": []
      }
    ]
  }
]
```

A real answer, its lists cut to 5 items.

### List the exercises of a muscle

`GET /v2/muscles/{id}/exercises`

The exercises that work a muscle, the most popular first.

By default the muscle is the main mover (`muscleRole=target`); `synergist` gives the exercises it only helps, `any` both.

The filters and the sort are those of `GET /v2/exercises/search`.

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `id` | path | string | Muscle UUID. Required. |
| `query` | query | string | Search query to filter exercises by name (case-insensitive). |
| `type` | query | string | Filter by exercise type (compound or isolation). Values: `Compound`, `Isolation`. |
| `limit` | query | number | Number of results to return. Default `20`. |
| `offset` | query | number | Number of results to skip for pagination. Default `0`. |
| `equipment` | query | array of string | Equipment the exercise uses. Several values, separated by commas, give the exercises that use any of them. Values: `Barbell`, `Dumbbell`, `Machine`, `Bodyweight`, `Kettlebell`, `ResistanceBand`, `BattleRope`, `MedicineBall`, `BosuBall`, `PowerSled`, `SmithMachine`, `StabilityBall`, `TrapBar`, `Stepper`, `WheelRoller`, `Towel`, `Landmine`, `Cable`, `Bench`, `Box`, `EzBar`, `ParallelBars`, `PullUpBar`, `Rings`, `Rope`, `Sledgehammer`, `Stick`, `SuspensionTrainer`, `Weighted`, `Chair`, `FoamRoller`, `PreacherBench`, `Hurdle`, `Block`. |
| `muscleRole` | query | string | With `muscle`: `target` for the exercises it is the main mover of (default), `synergist` for those it helps, `any` for both. Default `target`. Values: `target`, `synergist`, `any`. |
| `tier` | query | string | How common the exercise is. Values: `Standard`, `Variant`, `Niche`. |
| `sort` | query | string | Order of the results. `relevance` is for a text search, the others sort the matches. Default `relevance`. Values: `relevance`, `popularity`, `name`, `difficulty`. |
| `category` | query | string | Kind of exercise. Values: `Strength`, `Weightlifting`, `Plyometrics`, `Cardio`, `Stretching`, `Mobility`, `Yoga`. |
| `difficulty` | query | string | How hard the exercise is. Values: `Beginner`, `Intermediate`, `Advanced`. |
| `include` | query | string | `default` shows the exercises people do; `all` adds the rare ones and the exercises of rehab, plyometrics, aerobics and warm-up. Default `default`. Values: `default`, `all`. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/muscles/ffd76ee8-7633-4062-9c91-9092d637567a/exercises?query=bench%20press&limit=20&offset=0" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: Exercises of the muscle
- `404`: Muscle not found

**Example response**

```json
{
  "results": [
    {
      "id": "c2b6fccf-2c2c-43e1-aca3-a3cb73caa78b",
      "name": "Bench Press (Barbell)",
      "bodyPart": "Chest",
      "slug": "bench-press-barbell",
      "equipment": "Barbell",
      "category": "Strength",
      "difficulty": "Intermediate",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 100
    },
    {
      "id": "3f05cb6b-dfe4-4aaa-9e45-2cbb79ffbf20",
      "name": "Bench Press (Dumbbell)",
      "bodyPart": "Chest",
      "slug": "bench-press-dumbbell",
      "equipment": "Dumbbell",
      "category": "Strength",
      "difficulty": "Intermediate",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 90
    },
    {
      "id": "9f14802d-441a-42b4-bd23-79b1e2c89d0e",
      "name": "Bench Press (Smith Machine)",
      "bodyPart": "Chest",
      "slug": "bench-press-smith-machine",
      "equipment": "SmithMachine",
      "category": "Strength",
      "difficulty": "Beginner",
      "primaryGroups": [
        "Chest"
      ],
      "tier": "Standard",
      "popularity": 62
    },
    {
      "id": "a1281637-139d-4864-903b-67d62037c16e",
      "name": "Incline Bench Press (Barbell)",
      "bodyPart": "Chest",
      "slug": "incline-bench-press-barbell",
      "equipment": "Barbell",
      "category": "Strength",
      "difficulty": "Intermediate",
      "primaryGroups": [
        "Chest",
        "Shoulders"
      ],
      "tier": "Standard",
      "popularity": 88
    },
    {
      "id": "e48f6911-2a0d-45b5-b854-61aca0570173",
      "name": "Incline Bench Press (Dumbbell)",
      "bodyPart": "Chest",
      "slug": "incline-bench-press-dumbbell",
      "equipment": "Dumbbell",
      "category": "Strength",
      "difficulty": "Intermediate",
      "primaryGroups": [
        "Chest",
        "Shoulders"
      ],
      "tier": "Standard",
      "popularity": 88
    }
  ],
  "total": 84,
  "count": 20
}
```

A real answer, its lists cut to 5 items.

## Reference

The values the search filters take, with how many exercises have each.

### List the equipment

`GET /v2/equipment`

Every value of `equipment`, with the number of exercises that use it. Lists change only when the catalog does: keep them.

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/equipment" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: 

**Example response**

```json
[
  {
    "value": "Barbell",
    "exercises": 171,
    "total": 367
  },
  {
    "value": "Dumbbell",
    "exercises": 239,
    "total": 644
  },
  {
    "value": "Machine",
    "exercises": 130,
    "total": 261
  },
  {
    "value": "Bodyweight",
    "exercises": 861,
    "total": 2988
  },
  {
    "value": "Kettlebell",
    "exercises": 81,
    "total": 213
  }
]
```

A real answer, its lists cut to 5 items.

### List the categories

`GET /v2/categories`

Every value of `category` (strength, cardio, stretching, mobility, yoga...), with the number of exercises.

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/categories" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: 

**Example response**

```json
[
  {
    "value": "Strength",
    "exercises": 1356,
    "total": 3972
  },
  {
    "value": "Weightlifting",
    "exercises": 27,
    "total": 43
  },
  {
    "value": "Plyometrics",
    "exercises": 22,
    "total": 190
  },
  {
    "value": "Cardio",
    "exercises": 85,
    "total": 607
  },
  {
    "value": "Stretching",
    "exercises": 251,
    "total": 581
  }
]
```

A real answer, its lists cut to 5 items.

### List the families of movements

`GET /v2/families`

The families (`squat`, `bench-press`, `row`...) an exercise belongs to, with the number of exercises. `id` is what `family` takes on the search.

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/families" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: 

**Example response**

```json
[
  {
    "id": "stretch-trunk",
    "label": "Abdominal and side stretch",
    "pattern": "stretch",
    "exercises": 9,
    "total": 44
  },
  {
    "id": "core-activation",
    "label": "Abdominal vacuum and draw-in",
    "pattern": "core activation",
    "exercises": 1,
    "total": 2
  },
  {
    "id": "stretch-adductors",
    "label": "Adductor stretch",
    "pattern": "stretch",
    "exercises": 14,
    "total": 33
  },
  {
    "id": "agility-drill",
    "label": "Agility drill",
    "pattern": "agility",
    "exercises": 11,
    "total": 26
  },
  {
    "id": "ankle-mobility",
    "label": "Ankle and foot mobility",
    "pattern": "ankle mobility",
    "exercises": 7,
    "total": 11
  }
]
```

A real answer, its lists cut to 5 items.

### What the catalog holds

`GET /v2/meta`

The size of the catalog and every value its enumerations take (difficulties, forces, tracking types, laterality, purposes, tiers, body parts, equipment, categories).

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/meta" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: 

**Example response**

```json
{
  "exercises": {
    "default": 1900,
    "total": 5796
  },
  "muscles": 49,
  "muscleGroups": 21,
  "families": 136,
  "values": {
    "equipment": [
      "Barbell",
      "Dumbbell",
      "Machine",
      "Bodyweight",
      "Kettlebell"
    ],
    "categories": [
      "Strength",
      "Weightlifting",
      "Plyometrics",
      "Cardio",
      "Stretching"
    ],
    "difficulties": [
      "Beginner",
      "Intermediate",
      "Advanced"
    ],
    "forces": [
      "Push",
      "Pull",
      "Static"
    ],
    "types": [
      "Compound",
      "Isolation"
    ],
    "trackingTypes": [
      "WeightReps",
      "Reps",
      "WeightedBodyweightReps",
      "AssistedReps",
      "Duration"
    ],
    "lateralities": [
      "Bilateral",
      "Unilateral",
      "Alternating"
    ],
    "purposes": [
      "Strength",
      "Calisthenics",
      "Weightlifting",
      "Strongman",
      "Plyometrics"
    ],
    "tiers": [
      "Standard",
      "Variant",
      "Niche"
    ],
    "bodyParts": [
      "Legs",
      "Back",
      "Chest",
      "Shoulders",
      "Arms"
    ]
  }
}
```

A real answer, its lists cut to 5 items.

## Calculator

BMR, TDEE, BMI and ideal body weight.

### Basal metabolic rate (BMR)

`GET /v2/calculator/bmr`

Calories burned at rest in a day, with the Mifflin-St Jeor equation. Rounded to the unit.

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `height` | query | number | Height in centimeters. Required. |
| `age` | query | number | Age. Required. |
| `weight` | query | number | Body weight in kilograms. Required. |
| `gender` | query | string | Gender, which changes the constant of the equation. Required. Values: `male`, `female`. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/calculator/bmr?height=180&age=25&weight=78&gender=male" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: Successful BMR calculation

**Example response**

```json
{
  "result": 1785
}
```

### Total daily energy expenditure (TDEE)

`GET /v2/calculator/tdee`

Calories burned in a day: the BMR times the factor of the activity level (`sedentary` 1.375, `lightlyActive` 1.56, `active` 1.64, `veryActive` 1.82). Rounded to the unit.

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `height` | query | number | Height in centimeters. Required. |
| `age` | query | number | Age. Required. |
| `weight` | query | number | Body weight in kilograms. Required. |
| `gender` | query | string | Gender, which changes the constant of the equation. Required. Values: `male`, `female`. |
| `activityLevel` | query | string | Level of physical activity. Required. Values: `sedentary`, `lightlyActive`, `active`, `veryActive`. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/calculator/tdee?height=180&age=25&weight=78&gender=male&activityLevel=active" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: Successful TDEE calculation

**Example response**

```json
{
  "result": 2927
}
```

### Body mass index (BMI)

`GET /v2/calculator/bmi`

Weight in kilograms divided by the square of the height in meters. Rounded to one decimal.

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `height` | query | number | Height in centimeters. Required. |
| `weight` | query | number | Body weight in kilograms. Required. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/calculator/bmi?height=180&weight=78" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: Successful BMI calculation

**Example response**

```json
{
  "result": 24.1
}
```

### Ideal body weight (IBW)

`GET /v2/calculator/ibw`

Ideal weight in kilograms for a height, with the Devine formula. Rounded to one decimal.

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `height` | query | number | Height in centimeters. Required. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/calculator/ibw?height=180" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: Successful IBW calculation

**Example response**

```json
{
  "result": 75.1
}
```

## Usage

Your consumption and what is left of your quota. Free, not counted in the quota.

### Get your usage

`GET /v2/usage`

Requests and errors per day, month or endpoint (updated every 5 minutes). Defaults to the current month.

**Parameters**

| Name | In | Type | Description |
|---|---|---|---|
| `from` | query | string | First day (UTC), defaults to the start of the current month. |
| `to` | query | string | Last day (UTC, included), defaults to today. |

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/usage?from=2026-09-01&to=2026-09-30" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: 
- `403`: Only available with a key created in the dashboard

**Example response**

```json
{
  "from": "2026-09-01",
  "to": "2026-09-28",
  "groupBy": {},
  "totals": {
    "key": "2026-09-28",
    "requests": 0,
    "success": 0,
    "errors": {
      "invalidRequests": 0,
      "authErrors": 0,
      "notFound": 0,
      "rateLimited": 0,
      "otherClientErrors": 0,
      "serverErrors": 0
    },
    "averageLatencyMs": 0
  },
  "data": [
    {
      "key": "2026-09-28",
      "requests": 0,
      "success": 0,
      "errors": {
        "invalidRequests": 0,
        "authErrors": 0,
        "notFound": 0,
        "rateLimited": 0,
        "otherClientErrors": 0,
        "serverErrors": 0
      },
      "averageLatencyMs": 0
    }
  ]
}
```

### Get your quota

`GET /v2/usage/current`

Plan and quota of the current billing period, in real time.

**Example request**

```bash
curl "https://api.gymfit-api.com/v2/usage/current" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Responses**

- `200`: 
- `403`: Only available with a key created in the dashboard

**Example response**

```json
{
  "plan": "pro",
  "quota": 0,
  "used": 0,
  "remaining": 0,
  "overage": 0,
  "hardLimit": false,
  "overageCostUsd": 0,
  "periodStart": "2026-10-13T00:00:00.000Z",
  "periodEnd": "2026-10-13T00:00:00.000Z",
  "status": "string",
  "cancelAtPeriodEnd": false
}
```
