# Trainer Chat Endpoint

The unified conversational endpoint. Your users talk to the trainer in natural language — *"give me a 30-minute dumbbell chest workout"*, *"make it harder"*, *"let's start"* — and the API classifies each message's intent, generates or modifies a workout plan, answers fitness questions, and manages the user's fitness profile.

```
POST https://data.kinestex.com/api/trainer/chat
```

Integration is minimal by design:
- **One required field per message** (`message`). Everything else is optional.
- **The fitness profile is stored server-side.** Send it once and never again.
- **Workout history is stored server-side.** The AI plans around recently worked muscle groups automatically.
- **Workout preferences are extracted from the message.** *"45 minutes, dumbbells, chest and triceps"* needs no separate preferences object.

**Request fields:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `message` | string | **Yes** | The user's natural-language message |
| `session_id` | string | No | Chat session ID. Omit to use the user's default session (created automatically). Pass the value from a previous response to target a specific session. |
| `stage` | string | No | Client-declared intent that skips AI classification when you already know it: `"CREATE_WORKOUT"` (first message of a session) or `"RECOMMEND_NEXT"` (first message after a completed workout). Anything else falls back to the classifier. |
| `profile_data` | object | No | The user's fitness profile (see below). Only needed once — it is saved server-side and reused. Sending it again updates the stored profile. |
| `workout_details` | object | No | Explicit workout preferences (see below). If omitted on a create, preferences are extracted from the message itself. |
| `readiness` | object | No | Free-form JSON describing the user's current condition (sleep, soreness, energy…), passed to the AI as context. |

Advanced AI controls (`model`, `enable_thinking`, `thinking_effort`: `"minimal"` / `"low"` / `"medium"` / `"high"`) are also accepted — available model identifiers are provided by your KinesteX contact.

**Profile object** (persists server-side per user, always bound to the authenticated user):

| Field | Type | Values / Unit |
|-------|------|---------------|
| `age` | int | years |
| `weight` | float | kg |
| `height` | float | cm |
| `gender` | string | `"MALE"`, `"FEMALE"`, `"OTHER"` |
| `fitness_goals` | string[] | `"strength"`, `"muscle_gain"`, `"weight_loss"`, `"cardio_endurance"`, `"general_fitness"`, `"wellness_flexibility"` |
| `fitness_level_pushups` | string | e.g. `"0-5"`, `"6-15"`, `"16+"` |
| `fitness_level_cardio` | string | e.g. `"1 mile"`, `"2 mile"`, `"3+ mile"` |
| `fitness_level_squats` | string | e.g. `"0-10"`, `"10-21"`, `"22+"` |
| `injuries` | object[] | `{ "body_part", "preference": "avoid" \| "include", "severity": "severe" \| "moderate" \| "light" }` — `"avoid"` hard-filters exercises targeting that body part; `"include"` keeps them but picks light/rehabilitative variants |
| `health_conditions` | string[] | free text, e.g. `["Hypertension"]` |
| `other_preferences` | string | free text, passed verbatim to the AI |
| `preferred_duration_minutes` | int | default workout length (also learned from explicit duration requests) |
| `training_intensity` | int | 1–10, intensity ceiling for the plan |
| `structured_program` | bool | `true` = has followed a structured weight-training program (heavier low-rep sets) |

Removing an injury restriction via chat (e.g. *"my knee is fine now"*) triggers an are-you-sure confirmation before the profile actually changes.

**Workout details object:**

| Field | Type | Description |
|-------|------|-------------|
| `equipment` | string[] | e.g. `["dumbbells", "kettlebells"]` or `["bodyweight"]` |
| `duration` | int | Minutes. If omitted: explicit in message → previous workout in this conversation → profile preference → 30-minute default |
| `body_parts` | string[] | e.g. `["Chest", "Shoulders", "Triceps"]` |
| `include_warmup` / `include_cooldown` | bool | Prepend warmup / append cooldown exercises |
| `post_workout_feedback` | object | Check-in from the just-completed workout, sent with `stage: "RECOMMEND_NEXT"` — `rpe` (1–10, primary progression signal), `discomfort` (`"no_pain"` / `"mild"` / `"sharp"`), and when sharp: `pain_severity` (1–10), `pain_stopped`, `pain_body_parts` |

**Intents** — every message is classified into one of these (returned in the response so your UI can react). There are no magic strings — *"confirm"*, *"let's go"*, and *"start the workout"* all classify as `START_WORKOUT`:

| Intent | Triggered by | Effect |
|--------|--------------|--------|
| `CREATE_WORKOUT` | "make me a workout…" | Semantic search + AI plan generation |
| `MODIFY_WORKOUT` | "make it harder", "remove the squats" | AI edits the current plan |
| `START_WORKOUT` | "let's start", "confirm", "looks good" | Returns the final plan with `workout_plan.action = "CONFIRMED"` |
| `UNDO` / `REDO` | "undo" / "redo" | Instant plan history navigation (no AI call) |
| `ASK_QUESTION` | "what's a superset?" | Conversational answer |
| `DISCUSS_RESULTS` | "how did I do yesterday?" | Discusses the user's saved workout results |
| `RECOMMEND_NEXT` | first message after a completed workout | Recovery-aware next workout using stored history + feedback |
| `UPDATE_PROFILE` | "I weigh 78kg now", "my knee hurts" | Updates the stored profile |
| `OFF_TOPIC` | anything non-fitness | Polite redirect |

**Response — 200 OK:**

```json
{
  "message": "Here's your 30-minute upper-body dumbbell workout! ...",
  "intent": "CREATE_WORKOUT",
  "action": "WORKOUT_UPDATED",
  "session_id": "9c1e37a2-4b7f-4f6e-9a2d-1f2e3d4c5b6a",
  "workout_plan": {
    "step": "PLAN_REFINEMENT",
    "action": null,
    "data": {
      "turn_action": "WORKOUT_UPDATED",
      "can_undo": false,
      "can_redo": false,
      "estimated_duration_seconds": 1820,
      "sequences": [ ... ],
      "exercises": [ ... ]
    }
  }
}
```

| Field | Description |
|-------|-------------|
| `message` | The trainer's reply — render this in your chat UI |
| `intent` | The classified intent (table above) |
| `action` | What this turn actually **did** — `WORKOUT_UPDATED` (plan content changed), `NO_CHANGE`, `QUESTION` (trainer awaits a reply), `PROFILE_UPDATED` (refresh cached profile data), `INFO` (plain reply) |
| `session_id` | The persistent **chat session ID** — store it and send it on subsequent requests |
| `workout_plan` | Present only on turns that involve a plan. `workout_plan.action` is `"CONFIRMED"` once the user starts/confirms the workout. |

> ⚠️ **Two different IDs:** the top-level `session_id` is the persistent chat session — the one you store and send back. The response may also carry a nested `workout_plan.session_id`; that is internal planning state and you never need to send it.

`workout_plan.data.sequences` is the ordered plan: each item is an `"exercise"`, `"warmup"`, `"cooldown"` (with `exercise_id`, `repeats`) or a `"rest"` (with `rest_countdown` seconds). `workout_plan.data.exercises` holds the full exercise objects referenced by the sequences, including media URLs and per-language `translations` — pick the entry matching your requested language, falling back to `"en"`.

**Error responses:**

| Status | Cause |
|--------|-------|
| `400` | Missing `message`, invalid JSON, unsupported advanced option |
| `401` | Missing or invalid JWT |
| `403` | `{ "error": "Subscription required to generate workouts", "code": "not_subscribed" }` — only for generation intents when your company manages subscriptions (see [Session Auth & Managed Subscriptions](/docs/trainer-api/trainer-api-subscriptions)) |
| `404` | `session_id` doesn't exist or belongs to another user |
| `429` | Rate limit exceeded (see [Sessions, Profile & Limits](/docs/trainer-api/trainer-api-sessions)) |
| `500` | AI call or session storage failure — `{ "error": "Failed to process trainer chat", "details": "..." }` |

**Example — create, refine, confirm:**

```bash
# 1. First message (profile inline, preferences in the message)
curl -X POST "https://data.kinestex.com/api/trainer/chat" \
  -H "Authorization: Bearer <jwt>" -H "Content-Type: application/json" \
  -d '{
    "message": "Create a 30-minute dumbbell workout for chest and triceps",
    "stage": "CREATE_WORKOUT",
    "profile_data": { "age": 28, "weight": 75.0, "height": 180.0, "gender": "MALE", "fitness_goals": ["strength"] }
  }'

# 2. Refine (session_id from the previous response)
curl -X POST "https://data.kinestex.com/api/trainer/chat" \
  -H "Authorization: Bearer <jwt>" -H "Content-Type: application/json" \
  -d '{ "session_id": "<session_id>", "message": "make it harder and add more core work" }'

# 3. Confirm — natural language, no magic string required
curl -X POST "https://data.kinestex.com/api/trainer/chat" \
  -H "Authorization: Bearer <jwt>" -H "Content-Type: application/json" \
  -d '{ "session_id": "<session_id>", "message": "looks good, let'\''s start" }'

# 4. Next workout after completing one (recovery-aware, with post-workout feedback)
curl -X POST "https://data.kinestex.com/api/trainer/chat" \
  -H "Authorization: Bearer <jwt>" -H "Content-Type: application/json" \
  -d '{
    "session_id": "<session_id>",
    "stage": "RECOMMEND_NEXT",
    "message": "what should I do next?",
    "workout_details": {
      "post_workout_feedback": { "rpe": 8, "discomfort": "no_pain" }
    }
  }'
```

See the [full TypeScript lifecycle example](/docs/guides/guide-trainer-rest-lifecycle) in Guides & Examples.

---
Source: https://www.kinestex.com/docs/trainer-api/trainer-api-chat · Index: https://www.kinestex.com/llms.txt
