# Filtering & Parameters

These rules hold on all three list endpoints, so you only learn them once.

**Which filter each catalog accepts**

| Filter | Exercises | Workouts | Plans |
|--------|-----------|----------|-------|
| `search` | Yes | Yes | Yes |
| `body_parts` | Yes | Yes | Yes |
| `categories` | Yes | No | No |
| `category` | No | Yes (single value) | No |
| `difficulty_level` (alias `dif_level`) | Yes | No | No |
| `dif_level` | Yes (alias) | Yes (single value) | No |
| `level` | No | No | Yes (integer) |
| `translation_languages` | Yes | Yes | Yes |
| `remove_inactive` | Yes | No | No |
| `include_exercises` | No | Yes | No |
| `include_weeks` | No | No | Yes |
| `include_shared_library` | Yes | Yes | Yes |
| `lang` | Yes | Yes | Yes |
| `limit` / `offset` | Yes | Yes | Yes |

**Array parameters: three interchangeable forms**

```text
?body_parts=Chest,Triceps                 // comma-separated
?body_parts=Chest&body_parts=Triceps      // repeated
?body_parts[]=Chest&body_parts[]=Triceps  // bracketed
```

Surrounding whitespace is trimmed, so `?body_parts=Chest, Triceps` works. Multi-word values stay intact: `?body_parts=Lower Back,Full Body` is two body parts, not four (URL-encode the space as `%20`).

`body_parts`, `categories`, `difficulty_level`, and `translation_languages` are arrays. `category` and `dif_level` **on the workout list are single values**: a comma in them is matched literally, so `?dif_level=easy,medium` looks for a difficulty named "easy,medium" and returns an empty page. Issue one request per value and merge client-side.

**Combining filters**

- **Within one filter, values are OR.** `categories=Strength,Cardio/Endurance` returns exercises in either category.
- **Across filters it is AND.** `body_parts=Chest&difficulty_level=easy` returns easy chest exercises.
- **There is no "match all" mode.** `body_parts=Chest,Triceps` means chest *or* triceps, never both.
- **Every name filter is case-insensitive.** `easy`, `Easy`, and `EASY` are the same request.

**Values that match nothing**

Name filters compare on string equality, so an unrecognized value is not an error. You get `200` with an empty array and `pagination.total: 0`. The one exception is `?level=` on the plan list, which must be an integer and returns `400` otherwise.

> **Empty value means no filter.** Sending `?body_parts=` or `?categories=` applies *no* filter and returns the full unfiltered page. If your UI builds query strings from optional fields, omit the parameter entirely rather than sending it blank.

**Booleans must be literal**

`include_shared_library`, `remove_inactive`, `include_exercises`, `include_weeks`, and `include_audio` accept `true` / `false` (also `1` / `0`, `t` / `f`). Anything else, such as `yes` or `on`, is read as **false** without an error. `include_shared_library=yes` silently hides the entire shared library.

**Search is matched against English titles**

`search` is a case-insensitive partial match on the **English** title, whatever `lang` you send. `?search=flexiones&lang=es` returns nothing; search for `push` and read the Spanish titles off the result.

**Result ordering**

Newest first by creation date. When `search` is present, results are ranked by match quality first (exact title, then prefix, then partial) and creation date second.

**Allowed values**

Body parts (18), shared by all three catalogs:

```text
Neck            Shoulders   Chest       External Oblique
Abs             Biceps      Triceps     Forearms
Traps           Lats        Lower Back  Glutes
Quads           Hamstrings  Abductors   Adductors
Calves          Full Body
```

Exercise categories (8):

```text
Strength        Muscle Gain          Weight Loss   Cardio/Endurance
General Fitness Wellness/Flexibility Warm Up       Cooldown
```

Exercise difficulty (3): `easy`, `medium`, `hard`. Returned lowercase on exercises, accepted in any casing everywhere.

Workout `category` is free text set per workout rather than a fixed list (values in use include `Fitness`, `Cardio`, `Strength`, `HIIT`). Workout `dif_level` is stored capitalized (`Easy`, `Medium`, `Hard`) and returned as stored, so compare case-insensitively on your side. Read an unfiltered first page to build a picker rather than hard-coding either list.

**The same filters work on the JWT routes.** `/api/exercises`, `/api/workouts`, and `/api/plans` accept every parameter listed here and return the same data plus internal fields. The one difference: they spell the library toggle `include_kinestex_library` instead of `include_shared_library`.

---
Source: https://www.kinestex.com/docs/content-api-v2/content-api-v2-filtering · Index: https://www.kinestex.com/llms.txt
