# Hyperhuman Content API

> One API for delivery, personalization, and insights across your entire product.

> Last updated: 2026-08-06. Spec version: see `info.version` in `/openapi.json`.

Hyperhuman is the fitness content infrastructure behind modern health, wellness, and fitness products. The Content API is the developer-facing surface for the publish + personalize + insights layer.

- Base URL: `https://content.api.hyperhuman.cc`
- Authentication: send `X-Api-Key: <your_key>` on every request
- All non-2xx responses use a single envelope: `{ "error": { "code": "<StableCode>", "message": "...", "target?": "...", "details?": [...] } }`
- Pagination: prefer **`links.next`** to advance (no `page`). Runtime cursors are `offset`/`limit`; OpenAPI may omit `offset` on shared list DTOs. **Max `50`** on most list endpoints; **`100`** for `GET /v1/orgs/{organizationId}/endusers`. When `limit` is omitted: org **workouts/plans** default to **20**; **video-assets** and **groups** default to **10** — see [AGENTS.md](https://content.api.hyperhuman.cc/AGENTS.md) section 4. Org **plans list** excludes `end-user-ai-generated` (use `GET /v1/plans/{id}`). Full-workout **audio** export locale rules differ from **video** export — see AGENTS §9.

- Localization: pass `locale` (BCP-47, e.g. `en-US`, `fr-FR`) on read endpoints (query) or AI generate endpoints (body); falls back to English when unsupported on reads; generate returns `400` for unsupported locale
- AI / heavy-video endpoints (recommend, generate, adapt, insights, chat, video generation, full video export) count as **10x** rate-limit weight; the health-data batch push and nutrition log writes count as **1x** per request (batch capped at 50 entries)
- **Strict parameters:** Do not send query or body keys that are not in the public OpenAPI operation; unknown keys often return **400** (`ValidationError`). **`/openapi.json` is the curated, productized surface** — not every in-process route may be listed. See [AGENTS.md](https://content.api.hyperhuman.cc/AGENTS.md) sections 11–12.
- **Ids:** Opaque **24-character hex** strings (DB ids); do not document or depend on a specific storage engine name.

## Five capabilities

The API stacks five layers on the same library. Most teams ship steps 1-2 in week one and add the AI layers as their audience signals demand it.

1. **Publish Content** - list and play workouts and plans (`GET /v1/orgs/{organizationId}/workouts`, `GET /v1/orgs/{organizationId}/plans`), and browse workspace exercise videos (`GET /v1/orgs/{organizationId}/video-assets`).
2. **AI Recommend** - rank **existing** library content for the user (`POST /v1/orgs/{organizationId}/workouts/recommend`, `POST /v1/orgs/{organizationId}/plans/recommend`). Prefer this when pairing a quiz/profile to curated team workouts.
3. **AI Generate** - create new content on the fly (`POST /v1/orgs/{organizationId}/workouts/generate`, `POST /v1/orgs/{organizationId}/plans/generate`). Workout generate returns **ephemeral JSON** (no library workout id; play via JSON receiver). Plan generate returns a top-level plan `id` **only** when `endUserProfileDetails.endUserId` is set (store that id); without it there is no plan id. Optional body `locale` (BCP-47); invalid locale returns `400`.
4. **AI Adapt** - evolve existing content (`POST /v1/orgs/{organizationId}/workouts/{workoutId}/adapt`, `POST /v1/orgs/{organizationId}/plans/{planId}/adapt`). Workout adapt is ephemeral JSON. Plan adapt **requires** `endUserProfileId` and returns a persisted adapted program `id`.
5. **AI Insights** - daily digest + per-pillar drill-downs (`GET /v1/orgs/{organizationId}/endusers/{endUserId}/insights/digest`), plus org-scoped **writes** that feed them: batch health-data push (`POST .../endusers/{endUserId}/insights/health-data`) and direct nutrition logging (`POST .../endusers/{endUserId}/nutrition/log`).

In `.../endusers/{endUserId}/...` routes, the `{endUserId}` path segment is **resolved** by the server: it can be a **24-character hex** user id, an **external** id, or a **user email** (see [AGENTS.md](https://content.api.hyperhuman.cc/AGENTS.md) section 6). Responses still use opaque ids; prefer the hex id in new integrations. An identifier that does not resolve to an end user in the organization returns **`404`** (an unknown identifier never produces a `500`).

## Machine-readable specs

- [OpenAPI 3 (JSON)](https://content.api.hyperhuman.cc/openapi.json) - source of truth for SDK and tool/function-calling generation
- [LLM bundle](https://content.api.hyperhuman.cc/llms-full.txt) - auth, errors, pagination, locales, and a per-endpoint summary in one markdown file
- [Agent integration guide](https://content.api.hyperhuman.cc/AGENTS.md) - conventions a coding agent must follow when generating client code
- [Custom player guide](https://content.api.hyperhuman.cc/docs/guides/custom-player.md) - reference implementation for building your own interactive workout player
- [Swagger UI](https://content.api.hyperhuman.cc/docs/api-explorer) - try-it-out browser explorer

## Embedded library pages (member web)

Hosted **workout** and **program** catalog grids on the member web app (production base `https://member.hyperhuman.cc`), not on `https://content.api.hyperhuman.cc`. Paths: `/workouts?orgId=...` and `/plans?orgId=...`. Optional query flags: `showSearch`, `showFilters`, `gridCols` (2-4, default 3), `playbackMode` (`overlay` default vs `dedicated-page` for new-tab playback), `showDuration`, `showDifficulty` — see the **Branded library embed** section in the API overview (Scalar / Swagger intro). Teams copy snippets from **Share → Embed page**. API parity: `GET /v1/orgs/{organizationId}/workouts` and `GET /v1/orgs/{organizationId}/plans`.

## Embedded workout player (Teams host)

Drop-in iframes on `https://team.hyperhuman.cc` — see **Embedded Player** in the API overview. Always frame them (not top-level).

- **Pre-built:** `/embed/workout/{workoutId}?organizationId=...&apiKey=...` → Content API `GET /workouts/{id}` + `/playlist` (+ `/music`); optional sessions/feedback via `sessionMode` / `endUserId`.
- **AI / JSON:** `/embed/workout-json-receiver?organizationId=...` → `HYPERHUMAN_EMBED_INIT` with generate/adapt JSON → media via `GET /v1/video-asset/{exerciseId}` only; **no** PulseMix, **no** sessions/feedback.
- **Iframe host tips:** `allow="autoplay; fullscreen; accelerometer; gyroscope"` + `allowfullscreen`; full-viewport (`100dvh`) for mobile/portrait letterboxing; optional `HYPERHUMAN_HOST_IMMERSIVE` / `HYPERHUMAN_ORIENTATION_TOGGLE`; BGM music-off = pause (not volume) — see overview + custom-player guide.

## Top endpoints (read-mostly)

- `GET /v1/workouts/metadata` - categories, equipment, muscle groups, supported locales
- `GET /v1/orgs/{organizationId}/workouts` - paginated workout library for an org
- `GET /v1/workouts/{workoutId}` - full workout document
- `GET /v1/workouts/{workoutId}/playlist?locale=en-US` - segments with presigned progressive MP4 / M4A media URLs (~7 days; drives the **pre-built** / custom player); class segments may include optional `class.chapters[]` for in-video exercise navigation when chapter analysis has been run
- `GET /v1/workouts/{workoutId}/music` - PulseMix tracks for a **persisted** workout id (not used by JSON-receiver embeds)
- `GET /v1/video-asset/{videoAssetId}` - exercise media by id (JSON-receiver embed path for each `exercise.id` in generate/adapt JSON; optional `locale` for instruction audio)
- `GET /v1/workouts/{workoutId}/sessions/recent` - most recent in-progress session for resume-first UX; needs an end-user identity (Bearer token or `endUserId`/`externalUserId` query fields), else `404`
- `GET /v1/workouts/feedback/options` - rating/difficulty scales (`{ data: { rating, difficulty } }`); submit option **hex ids** on feedback (not labels)
- `GET /v1/workouts/{workoutId}/export/video/stream_url?locale=en-US` - `text/plain` presigned URL to full video export (404 if locale not rendered)
- `GET /v1/workouts/{workoutId}/export/audio/stream_url?locale=en-US` - `text/plain` presigned URL to full narrative audio export
- `GET /v1/orgs/{organizationId}/plans` - paginated training plan library
- `GET /v1/plans/{planId}` - full plan document
- `GET /v1/orgs/{organizationId}/metadata` - org branding (logo, colors, watermark), module flags, and member-app AI visibility flags (`ai*Enabled` — not Content API gates); used by pre-built embed for default branding
- `GET /v1/orgs/{organizationId}/video-assets` - **workspace-only** exercise videos for catalogs/builders (no stock / pay-as-you-go; no `visibility` filter; `single-exercise` rows expose `audioInstructions[]` per locale, each with `assetUri` + optional `scriptText` (verbatim narrator transcript for captions / accessibility / search) - see OpenAPI). DB-level filters: `q` (locale-aware substring), `equipmentIds`, `muscleGroupIds`, `kinds`, `skillLevels`, `executionSides`, `coach`, `collectionNames`. Sort: `date` (alias of `createdAt`), `name`, `kind` with `+`/`-` prefix; default `-date`.
- `GET /v1/orgs/{organizationId}/groups` - exercise collections (circuits / sets)
- `GET /v1/orgs/{organizationId}/endusers/{endUserId}/insights/digest?date=<ISO 8601 date-time>` - daily AI insights digest
- `GET /v1/orgs/{organizationId}/endusers/{endUserId}/insights/pillars/{pillarType}` - drill-down for a specific pillar

## Top endpoints (mutating)

- `POST /v1/orgs/{organizationId}/workouts/recommend` - rank existing workouts for a user (body: `endUserProfileDetails` + optional `locale` only)
- `POST /v1/orgs/{organizationId}/plans/recommend` - rank existing plans for a user
- `POST /v1/orgs/{organizationId}/workouts/generate` - ephemeral AI workout JSON (`single-exercise`|`rest` instances; optional `locale`); play via JSON-receiver iframe + `GET /video-asset/{id}` — no `/playlist`/`/music`/sessions by id
- `POST /v1/orgs/{organizationId}/plans/generate` - AI multi-week plan; top-level `id` only with `endUserProfileDetails.endUserId` (store it; `workoutCollectionSources`; optional `locale`)
- `POST /v1/orgs/{organizationId}/workouts/{workoutId}/adapt` - personalize an existing team workout (`{workoutId}` must belong to `{organizationId}`; otherwise `404`)
- `POST /v1/orgs/{organizationId}/plans/{planId}/adapt` - personalize an existing team plan; **requires** `endUserProfileId` + `workoutSources` (`{planId}` must belong to `{organizationId}`; otherwise `404`)
- `POST /v1/workouts/{workoutId}/sessions/start` - begin tracking a session
- `PATCH /v1/workouts/{workoutId}/sessions/{sessionId}` - update session progress
- `POST /v1/workouts/{workoutId}/sessions/end` - close out a session (abandon; keeps the last PATCHed progress)
- `POST /v1/workouts/{workoutId}/sessions/complete` - force 100% completion (natural finish / "mark as done")
- `POST /v1/workouts/{workoutId}/feedback` - submit session feedback (`{ workoutSessionId, ratingId?, difficultyId?, comment? }`)
- Sessions and feedback are **anonymous by default**; attribute them to an end user **without** a member token by sending `endUserId` / `externalUserId` (+ `organizationId` for cross-org keys) on each call — see [AGENTS.md](https://content.api.hyperhuman.cc/AGENTS.md) section 9. The **pre-built** embedded player's `sessionMode=attributed` uses the same identity; the **JSON-receiver** embed does not call session/feedback APIs (see **Embedded Player** in the API overview).
- `POST /v1/orgs/{organizationId}/content-autopilot` - create a scheduled content automation (generate/clone/publish); manage with `GET`/`PATCH`/`DELETE .../content-autopilot/{taskId}`, `.../activate`, `.../deactivate`, `.../run-now`, and `.../events`
- `POST /v1/chat/conversations` - start an AI coach conversation; continue with `GET`/`POST .../conversations/{conversationId}/messages`, confirm proposed actions with `.../actions/confirm`. `organizationId` and a user identity (`userEmail` **or** `userExternalUUID`) are **required** on every request (body for POST, query for GET/DELETE); not path params. These routes use **`page`-based** pagination
- `POST /v1/orgs/{organizationId}/endusers/{endUserId}/insights/health-data` - batch health-data push (max 50 entries: `activity`, `sleep`, `steps`, `body_metrics`, `heart_rate`, `hydration`, `oxygenation`, `scores`); **always 200** with per-entry `results` + `summary` (check `summary.failed`); idempotent on `(dataType, externalId)`; `externalId` required for `activity`, day-key default (`day-YYYY-MM-DD`) otherwise; backfill max 90 days; counts as **1x**
- `DELETE /v1/orgs/{organizationId}/endusers/{endUserId}/insights/health-data/{dataType}/{externalId}` - delete a pushed entry and recompute the day (wearable-synced activities are protected)
- `POST /v1/orgs/{organizationId}/endusers/{endUserId}/nutrition/log` - log meal macros (at least one of `caloriesKcal`/`proteinG`/`carbsG`/`fatG`); optional `externalId` makes re-POSTs an idempotent **replace** (omit = additive) and enables delete
- `DELETE /v1/orgs/{organizationId}/endusers/{endUserId}/nutrition/log/{externalId}` - remove a logged entry (subtracts from day totals, recomputes nutrition score)

## Common pitfalls (do not invent)

- Workout **generate/adapt** = ephemeral JSON (no workout id). Pass the response as `HYPERHUMAN_EMBED_INIT.workout` to the JSON-receiver **iframe**; media = `GET /v1/video-asset/{exerciseId}`. Plan **generate** top-level `id` only with `endUserProfileDetails.endUserId`. Plan **adapt** requires `endUserProfileId`.
- Source fields: plan generate = `workoutCollectionSources`; plan adapt = `workoutSources`.
- Recommend responses are bare `{ workouts|plans, reasoning }` (not `{ data }`). Per-item `recommendation` is text, not a score.
- Org metadata `ai*Enabled` = member-app visibility, not Content API gates.
- JSON-receiver embeds have **no PulseMix** and **no** session/feedback (`sessionMode` on INIT is ignored for JSON). Always frame the player URLs.
- Feedback `ratingId` / `difficultyId` are **option ids** from `GET /v1/workouts/feedback/options` (24-character hex), not labels like `rating-5-stars` (those → `400`).
- Prefer [`AGENTS.md`](https://content.api.hyperhuman.cc/AGENTS.md) section 9 for the full pitfall list.

## Per-endpoint catalog

A complete catalog (with method, path, summary, tags, and stable `operationId`s) is appended live to `/llms-full.txt` from the current `/openapi.json`. Use that file as the single context drop for an LLM.

---

## Full operation reference

Each block below mirrors a single operation in the OpenAPI document. The `operationId` is stable and suitable for use as a tool / function name.

## Tag: workouts

### GET /v1/workouts/metadata

- operationId: `WorkoutsApi_getWorkoutMetadata`
- summary: Get available workout categories and difficulties

## Workout categories & difficulties

Returns category ids/names and difficulty strings for filters and AI generate `categoryId`.

**Try it:** `GET /v1/workouts/metadata` with `X-Api-Key`. Optional `locale`.

**Related:** `GET /v1/workouts/equipment/metadata`, `GET /v1/workouts/exercises/metadata`, `POST /v1/orgs/{orgId}/workouts/generate`.

**Parameters:**
- `locale` (query) — Preferred language locale (e.g., fr-FR)

**Responses:**
- 200 — Successfully retrieved category and difficulty lists.
- 401 — Authentication failed - API key is missing or invalid.

### GET /v1/workouts/equipment/metadata

- operationId: `WorkoutsApi_getEquipmentMetadata`
- summary: Get available equipment categories and equipment items

## Equipment metadata

Returns equipment categories and items for filters and generate `preferredEquipmentCategoryIds`.

**Try it:** `GET /v1/workouts/equipment/metadata` with `X-Api-Key`. Optional `locale`.

**Related:** `GET /v1/workouts/metadata`, `POST /v1/orgs/{orgId}/workouts/generate`.

**Parameters:**
- `locale` (query) — Preferred language locale (e.g., fr-FR)

**Responses:**
- 200 — Successfully retrieved equipment and category lists.
- 401 — Authentication failed - API key is missing or invalid.

### GET /v1/workouts/{workoutId}

- operationId: `WorkoutsApi_getWorkout`
- summary: Get workout details

## Workout details

Full document for a **persisted** workout id, wrapped as `{ data: { id, name, duration, difficulty, categories, equipment, muscleGroupPercentages, preview, mixBgMusic, videoAudioMode, ... } }`.

**Try it:** `GET /v1/workouts/{workoutId}` with `X-Api-Key`. Optional `locale` for localized display fields.

**Related:** `GET /v1/workouts/{id}/playlist`, `GET /v1/workouts/{id}/music`, `GET /v1/orgs/{orgId}/workouts`.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier (24-character hex string) of the workout to retrieve.
- `locale` (query) — Preferred language locale (e.g., fr-FR)

**Responses:**
- 200 — Workout details retrieved successfully.
- 401 — Authentication failed - API key is missing, invalid, or Bearer token is missing/invalid.
- 404 — The specified workout ID does not exist.

### GET /v1/workouts/{workoutId}/export/video/stream_url

- operationId: `WorkoutsApi_getExportVideoStream`
- summary: Get complete workout video export URL

## Full workout video export

Returns a presigned URL to the full-workout video export for a persisted workout id.

**Response:** **200** `Content-Type: text/plain` — body is a **single** URL string (not JSON), valid ~**7 days**. Linked asset matches the stored render (commonly MP4).

**Locale:** omit → latest available export; provide `locale` → exact locale only (**404** if that locale is not rendered — no English fallback). Still processing / missing → **404** (typically 2–5 minutes after create).

**Rate limit:** **10x**. Prefer `/playlist` for interactive segment playback.

**Related:** `GET /v1/workouts/{id}`, `GET /v1/workouts/{id}/playlist`, `GET /v1/workouts/{id}/export/audio/stream_url`.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier of the workout.
- `locale` (query) — Language locale (e.g., de-DE, fr-FR, en-US). When omitted, the latest available export is returned. When provided, only the exact-locale export is returned; if it does not exist the endpoint returns 404 (no en-US/default fallback).

**Responses:**
- 200 — text/plain body: a single presigned HTTPS URL to the full video export (valid 7 days). Format of the linked asset matches the stored render (commonly MP4; see operation description).
- 401 — Authentication failed - API key is missing, invalid, or Bearer token is missing/invalid.
- 404 — Workout not found, video export not available for the requested locale, or video is still being generated (processing typically takes 2-5 minutes after workout creation).

### GET /v1/workouts/{workoutId}/export/audio/stream_url

- operationId: `WorkoutsApi_getExportAudioStream`
- summary: Get audio-only workout export URL

## Full workout audio export

Returns a presigned URL to the full narrative audio export for a persisted workout id.

**Response:** **200** `Content-Type: text/plain` — body is a **single** URL string (not JSON), valid ~**7 days** (commonly M4A).

**Locale:** omit → resolve with the trainer **preferred locale** (language-family fallback within that language; never cross-language). Provide `locale` → exact match, then same-language family; missing / still processing → **404** (not the same as video export’s “latest any locale / exact-or-404”).

**Rate limit:** **10x**.

**Related:** `GET /v1/workouts/{id}/export/video/stream_url`, `GET /v1/workouts/{id}/playlist`, `GET /v1/workouts/{id}`.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier of the workout.
- `locale` (query) — Language locale for audio narration (e.g., de-DE, fr-FR, en-US). When omitted, uses the trainer preferred locale with same-language family fallback. When provided, exact then family match; if none exists → 404 (never falls back to another language). Differs from video export locale rules.

**Responses:**
- 200 — text/plain body: a single presigned HTTPS URL to the full narrative audio export (valid 7 days; commonly M4A/AAC).
- 401 — Authentication failed - API key is missing, invalid, or Bearer token is missing/invalid.
- 404 — Workout not found, audio export not available for the requested locale, or audio is still being generated (processing typically takes 1-3 minutes after workout creation).

### GET /v1/workouts/exercises/metadata

- operationId: `WorkoutsApi_getExercisesMetadata`
- summary: Get available muscle groups and exercise collections

## Muscle groups & exercise collections

Returns muscle group ids/names and collection names/counts for generate `muscleGroupIds` / `excludeExerciseCollections`.

**Try it:** `GET /v1/workouts/exercises/metadata` with `X-Api-Key`. Optional `locale`.

**Related:** `GET /v1/workouts/metadata`, `POST /v1/orgs/{orgId}/workouts/generate`.

**Parameters:**
- `locale` (query) — Preferred language locale (e.g., fr-FR)

**Responses:**
- 200 — Successfully retrieved list of muscle groups and exercise collections.
- 401 — Authentication failed - API key is missing or invalid.

### GET /v1/orgs/{organizationId}/workouts

- operationId: `OrganizationWorkoutsApi_getOrganizationWorkouts`
- summary: List organization-specific workouts

## Organization Workout Library

Paginated list of **published** workouts for your organization (workspace).

**Try it (minimal):** `GET /v1/orgs/{organizationId}/workouts?limit=20` with header `X-Api-Key`. Use your organization id from **Settings → Integrations**. Leave optional filters empty on the first call.

**Pagination:** `limit` (default **20**, max **50**). Follow `links.next` in the response to advance pages.

**Optional filters:** `visibility` (`all` default), `difficulty`, `categoryIds` (comma-separated hex ids from `GET /v1/workouts/metadata`), `duration` (`min-max` minutes), `q`, `sort` (`+` / `-` prefix, e.g. `-createdAt`), `allowAssessment`, `locale`.

**Related:** `POST /v1/orgs/{orgId}/workouts/generate`, `POST /v1/orgs/{orgId}/workouts/recommend`, `GET /v1/workouts/{id}`, `GET /v1/workouts/metadata`.

**Parameters:**
- `organizationId` (path) (required) — Unique organization (workspace) resource id — opaque 24-character hex string. Use the organization id from Settings → Integrations.
- `limit` (query) — Maximum number of items to return per page.
- `q` (query) — Search text
- `sort` (query) — Fields to sort. Prefix them with - (descending) or + (ascending): +someField,-otherField
- `locale` (query) — BCP-47 locale (e.g. en-US, fr-FR). Localizes text and locale-aware media when this operation supports it. Falls back to English when unsupported.
- `visibility` (query) — Filter workouts by their visibility status. Defaults to `all` when omitted (private and public).
- `difficulty` (query) — Filter workouts by difficulty level.
- `categoryIds` (query) — Filter workouts by one or more workout category IDs (comma-separated 24-character hex DB ids). Discover ids via `GET /v1/workouts/metadata`. Optional tokens prefixed with `custom:` are filtered in memory (e.g. `custom:duration_short`).
- `duration` (query) — Filter workouts by duration range in minutes. Use "min-max" (e.g., "10-30") or an open-ended range like "60-" (60+ min) or "-30" (up to 30 min).
- `allowAssessment` (query) — Filter workouts by assessment support

**Responses:**
- 200 — Successfully retrieved paginated list of workouts for the organization.
- 400 — Invalid pagination or filter parameters.
- 401 — Authentication failed - API key is missing or invalid.
- 404 — The specified organization ID does not exist.

### GET /v1/workouts/{workoutId}/playlist

- operationId: `WorkoutContentApi_getWorkoutPlaylist`
- summary: Retrieve Detailed Workout Playlist Structure

## Workout playlist

Ordered playback segments for a **persisted** workout id. Response: `{ data, presentationStyle?, links? }` where `data` is an array of **single-key** objects keyed by segment kind (`intro`, `exercise`, `multiExercise`, `class`, `promo`, `educational`, `break`, `outro`). Not the AI-generate `instances[]` shape.

**Try it:** `GET /v1/workouts/{workoutId}/playlist` with `X-Api-Key`. Optional `locale` (BCP-47) localizes segment names, equipment, muscle groups, and audio selection (unsupported → English fallback).

**Segments:** `exercise` has timing/prescription + `main.video` / instruction audio (`url`, optional `type` / `locale` / `scriptText`). `class` may include optional `chapters[]` (`startTimeSec` / `endTimeSec` within `main.video`) when analysis ran; chapter text is not re-localized. `multiExercise` is the original-audio block kind (still emitted when present).

**Media:** Video and audio `url` values are time-limited AWS S3 **presigned HTTPS** links to **progressive MP4** / M4A (typically ~**7 days**). Re-fetch the playlist before expiry; do not treat URLs as durable. Not HLS/DASH/ABR — play with HTML5 `<video>` / AVPlayer / ExoPlayer (no hls.js required).

**Related:** `GET /v1/workouts/{id}`, `GET /v1/workouts/{id}/music`, `POST /v1/workouts/{id}/sessions/start`, custom-player guide.

**Parameters:**
- `workoutId` (path) (required) — The unique workout id (24-character hex DB id) whose playlist is required.
- `locale` (query) — Language locale for localized content (BCP-47 format). Affects exercise names, equipment names, muscle group names, and audio selection. Supported: en-US, en-GB, en-AU, fr-FR, de-DE, es-ES, it-IT, pt-PT, he-IL, ro-RO, cs-CZ, fi-FI, lt-LT, nl-NL, pl-PL, ar-XA. Falls back to English if unsupported.

**Responses:**
- 200 — Successfully retrieved the detailed workout playlist structure. `data` is an ordered array; each item is an object with a single key indicating the segment kind (one of `intro`, `exercise`, `multiExercise`, `class`, `promo`, `educational`, `break`, `outro`). The value under that key is the segment payload (see schema for the per-kind shape).
- 401 — Authentication failed. API key or Bearer token is missing, invalid, or expired.
- 404 — The specified workout ID does not exist or a playlist could not be generated for it.

### GET /v1/workouts/{workoutId}/music

- operationId: `WorkoutContentApi_getWorkoutMusic`
- summary: Get workout music playlist

## Workout music playlist

Returns the shuffled PulseMix track pool for a **persisted** workout id (`GET /v1/workouts/{workoutId}/music`). Bare response: `{ tracks, totalDuration, workoutDuration }`.

**Requires a real workout id** from your library (or another persisted workout). The embedded **JSON receiver** path (AI-generated / host-built `{ name, instances }` without a saved id) does **not** call this endpoint and has **no** automatic background music.

**Selection:** trainer music preferences and workout categories; stock fallback when unset. `totalDuration` is the summed pool length (not trimmed to the workout). Empty pool → `200` with `tracks: []` and `totalDuration: 0` (not 404). Pre-signed URLs expire in about 7 days.

Gate playback with the workout's `videoAudioMode` / `mixBgMusic` (see audio policy in the API overview).

**Related:** `GET /v1/workouts/{id}`, `GET /v1/workouts/{id}/playlist`.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier of the workout

**Responses:**
- 200 — Successfully retrieved workout music playlist
- 401 — Authentication failed. API key or Bearer token is missing, invalid, or expired.
- 404 — The specified workout ID does not exist.

### POST /v1/workouts/{workoutId}/sessions/start

- operationId: `WorkoutsSessionsApi_startWorkoutSession`
- summary: Start workout session

## Start a workout session

Creates a session for a **persisted** workout id (**201**). Key-only calls are **anonymous** (`traineeId: null`) unless you attribute with `endUserId` / `externalUserId` (+ `organizationId` for standard/club keys). A Bearer token, when present, wins. Previous incomplete sessions for the same workout may be closed automatically for attributed/authenticated users.

**Lifecycle:** start → PATCH `progressSeconds` every 10–30s → `sessions/end` (keep last progress) or `sessions/complete` (force 100%) → optional feedback. The **pre-built** embed (`/embed/workout/{id}`) calls this lifecycle; the **JSON-receiver** embed does not (no persisted workout id).

**Related:** `PATCH /v1/workouts/{id}/sessions/{sessionId}`, `POST .../sessions/end`, `POST .../sessions/complete`, `POST .../feedback`, API overview **Embedded Player**.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier (24-character hex string) of the workout to start a session for.

**Request body:** application/json

**Responses:**
- 201 — Workout session successfully started. Any previous incomplete sessions for this workout are automatically closed.
- 401 — Authentication failed (API key invalid or Bearer token invalid).

### PATCH /v1/workouts/{workoutId}/sessions/{sessionId}

- operationId: `WorkoutsSessionsApi_patchWorkoutSession`
- summary: Update a workout session

## Update session progress

PATCH an active session with `progressSeconds` (actual playback time, pauses excluded) and/or `workoutSegmentId`. Duration is capped at 2× expected workout length. Prefer PATCH every 10–30s and on segment changes; final PATCH before end/complete.

**Related:** `POST .../sessions/start`, `POST .../sessions/end`, `POST .../sessions/complete`.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier (24-character hex string) of the workout associated with the session.
- `sessionId` (path) (required) — The unique identifier (24-character hex string) of the workout session to update.

**Request body (required):** application/json

**Responses:**
- 200 — Workout session updated successfully.
- 400 — Invalid data provided in the request body.
- 401 — Authentication failed (API key invalid or Bearer token invalid).
- 404 — The specified workout ID or session ID does not exist.
- 409 — Session is already completed and cannot be updated.

### GET /v1/workouts/{workoutId}/sessions/{sessionId}

- operationId: `WorkoutsSessionsApi_getWorkoutSession`
- summary: Get specific workout session details

## Get session by id

Returns one session (`status`, `duration`, `progress`, timestamps). Scoped to caller identity: Bearer wins; for key-only attributed sessions pass `endUserId` / `externalUserId` query fields (+ `organizationId` for standard/club keys).

**Related:** `GET .../sessions/recent`, `PATCH .../sessions/{sessionId}`, `POST .../sessions/end`.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier (24-character hex string) of the workout associated with the session.
- `sessionId` (path) (required) — The unique identifier (24-character hex string) of the workout session to retrieve.
- `endUserId` (query) — Attribute the session to a specific end user, by internal Hyperhuman user id or email. Resolved against the API key team. Ignored when a member Bearer token is present.
- `externalUserId` (query) — Attribute the session to a specific end user by their external UUID (the externalUUID stored on the team membership). Alternative to endUserId.
- `organizationId` (query) — Organization (team) id the end user belongs to. Required for standard/club keys that span organizations; defaults to the API key team for team-scoped keys.

**Responses:**
- 200 — Workout session details retrieved successfully.
- 401 — Authentication failed (API key invalid or Bearer token invalid).
- 404 — The specified workout ID or session ID does not exist (or the session does not belong to the authenticated caller).

### POST /v1/workouts/{workoutId}/sessions/end

- operationId: `WorkoutsSessionsApi_endWorkoutSession`
- summary: End a workout session

## End session (keep last progress)

Marks the session completed using duration from the last PATCH (`progressSeconds`). Does **not** force 100% — use `POST .../sessions/complete` for that. If no PATCH ran, duration is `0`.

**Related:** `PATCH .../sessions/{sessionId}`, `POST .../sessions/complete`, `POST .../feedback`.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier (24-character hex string) of the workout associated with the session.

**Request body (required):** application/json

**Responses:**
- 200 — Workout session successfully marked as completed.
- 400 — Invalid request data (e.g., missing sessionId).
- 401 — Authentication failed (API key invalid or Bearer token invalid).
- 404 — The specified workout ID or session ID does not exist.
- 409 — Session is already completed.

### POST /v1/workouts/{workoutId}/sessions/complete

- operationId: `WorkoutsSessionsApi_completeWorkoutSession`
- summary: Complete workout session with 100% progress

## Complete a session (force 100%)

Marks the session completed at **100%** progress (full workout duration). Use for "mark as done" / offline finish. Prefer `POST .../sessions/end` when you want to keep the last PATCHed `progressSeconds` (abandon or natural stop).

Body requires `sessionId`; re-send attribution fields for key-only attributed sessions.

**Related:** `POST .../sessions/start`, `POST .../sessions/end`, `PATCH .../sessions/{sessionId}`, `POST .../feedback`.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier (24-character hex string) of the workout associated with the session.

**Request body (required):** application/json

**Responses:**
- 200 — Workout session successfully marked as 100% completed.
- 400 — Invalid request data (e.g., missing sessionId).
- 401 — Authentication failed (API key invalid or Bearer token invalid).
- 404 — The specified workout ID or session ID does not exist.
- 409 — The workout session is already marked as completed.

### GET /v1/workouts/{workoutId}/sessions/recent

- operationId: `WorkoutsSessionsApi_getRecentWorkoutSession`
- summary: Get most recent workout session

## Most recent in-progress session

Returns the most recent **started** (not completed) session for this workout — resume-first UX.

**Identity required:** Bearer token **or** `endUserId` / `externalUserId` query fields (+ `organizationId` for standard/club keys). Fully anonymous key-only → **404**.

**Related:** `POST .../sessions/start`, `GET .../sessions/{sessionId}`, `PATCH .../sessions/{sessionId}`.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier (24-character hex string) of the workout for which to find the recent session.
- `endUserId` (query) — Attribute the session to a specific end user, by internal Hyperhuman user id or email. Resolved against the API key team. Ignored when a member Bearer token is present.
- `externalUserId` (query) — Attribute the session to a specific end user by their external UUID (the externalUUID stored on the team membership). Alternative to endUserId.
- `organizationId` (query) — Organization (team) id the end user belongs to. Required for standard/club keys that span organizations; defaults to the API key team for team-scoped keys.

**Responses:**
- 200 — Most recent workout session details retrieved successfully.
- 401 — Authentication failed (API key invalid or Bearer token invalid).
- 404 — No in-progress workout session found for this workout and the resolved end user. Also returned when the call is anonymous (no Bearer token and no endUserId/externalUserId to resolve an identity).

### GET /v1/workouts/feedback/options

- operationId: `WorkoutsFeedbackApi_getWorkoutFeedbackOptions`
- summary: Get workout feedback options

## Feedback options

Returns the rating and difficulty scales for workout feedback. Use the option **ids** (not numeric values) when submitting `POST /v1/workouts/{id}/feedback`.

**Response shape:** `{ "data": { "rating": [{ id, label, value }], "difficulty": [{ id, label, value }] } }`.

**Related:** `POST /v1/workouts/{id}/feedback`, `POST /v1/workouts/{id}/sessions/complete`.

**Responses:**
- 200 — Successfully retrieved feedback options with all available rating scales.
- 401 — Authentication failed - API key is missing or invalid.

### POST /v1/workouts/{workoutId}/feedback

- operationId: `WorkoutsFeedbackApi_submitWorkoutFeedback`
- summary: Submit workout feedback

## Submit workout feedback

Submit feedback for a completed (or abandoned) session on a **persisted** workout id.

**Body:** `workoutSessionId` (required) plus optional `ratingId` / `difficultyId` (option **ids** from `GET /v1/workouts/feedback/options`, not raw numbers), `comment`, `weightUnit`, `exerciseData[]` (`instanceId` + optional `weight` / `reps`; keep playlist order; omit rest segments). Optional attribution fields (`endUserId` / `externalUserId`, plus `organizationId` for standard/club keys) for key-only attributed sessions.

**Try it:** end a session, fetch feedback options, then POST with `workoutSessionId` + a `ratingId`.

**Related:** `GET /v1/workouts/feedback/options`, `POST /v1/workouts/{id}/sessions/end`, `POST /v1/workouts/{id}/sessions/complete`.

**Parameters:**
- `workoutId` (path) (required) — The unique identifier (24-character hex string) of the workout receiving feedback.

**Request body (required):** application/json

**Responses:**
- 201 — Feedback submitted successfully.
- 400 — Invalid feedback data provided (e.g., missing required rating, invalid rating value, missing weightUnit when weights provided, invalid instanceIds). Check feedback options endpoint.
- 401 — Authentication failed (API key invalid or Bearer token invalid if provided).
- 404 — The specified workout ID does not exist.

## Tag: plans

### GET /v1/plans/metadata

- operationId: `PlansApi_getPlanMetadata`
- summary: Get available plan goals and difficulties

## Plan metadata

Returns `goals` (`{ id, name }[]`) and `difficulties` for plan filters / `POST .../plans/generate`.

**Try it:** `GET /v1/plans/metadata?locale=en-US` with `X-Api-Key`. Optional `locale` localizes goal names.

**Related:** `POST /v1/orgs/{orgId}/plans/generate`, `POST /v1/orgs/{orgId}/plans/recommend`, `GET /v1/orgs/{orgId}/plans`, `GET /v1/workouts/metadata`, `GET /v1/workouts/equipment/metadata`.

**Parameters:**
- `locale` (query) — Preferred language locale (e.g., fr-FR)

**Responses:**
- 200 — Successfully retrieved plan goals and difficulty lists.
- 401 — Authentication failed - API key is missing or invalid.

### GET /v1/plans/{planId}

- operationId: `PlansApi_getPlan`
- summary: Get training plan details

## Training plan details

Returns one plan: name, description, difficulty, visibility, `durationInWeeks`, `workoutsPerWeek`, goals, trainer, preview, share link, organization (when applicable).

**Try it:** `GET /v1/plans/{planId}?locale=en-US` with `X-Api-Key`. For the workout list, call `GET /v1/plans/{planId}/workouts`.

**Localization:** optional `locale` affects name, description, goal names (unsupported → English fallback).

**Related:** `GET /v1/orgs/{orgId}/plans`, `GET /v1/plans/{id}/workouts`, `GET /v1/plans/metadata`, `POST /v1/orgs/{orgId}/plans/generate`.

**Parameters:**
- `planId` (path) (required) — The unique identifier (24-character hex string) of the workout plan to retrieve.
- `locale` (query) — Preferred language locale for localized content (e.g., en-US, fr-FR, de-DE). Affects plan name, description, and goal names.

**Responses:**
- 200 — Workout plan details retrieved successfully with duration information.
- 401 — Authentication failed - API key is missing or invalid.
- 404 — The specified plan ID does not exist.

### GET /v1/plans/{planId}/workouts

- operationId: `PlansApi_getPlanWorkouts`
- summary: Get workouts in training plan

## Plan workouts

Returns **all** workouts in the plan as `{ data, links }`, ordered in program sequence. **No** server-side paging of the array — paginate in the client if the UI needs it.

**Try it:** `GET /v1/plans/{planId}/workouts?locale=en-US` with `X-Api-Key`. Optional `locale` localizes workout display fields.

**Related:** `GET /v1/plans/{id}`, `GET /v1/workouts/{id}`, `GET /v1/orgs/{orgId}/plans`, `POST /v1/workouts/{id}/sessions/start`.

**Parameters:**
- `planId` (path) (required) — The unique identifier (24-character hex string) of the workout plan.
- `locale` (query) — Preferred language locale (e.g., fr-FR)

**Responses:**
- 200 — Successfully retrieved complete list of workouts for the plan.
- 401 — Authentication failed - API key is missing or invalid.
- 404 — The specified plan ID does not exist.

### GET /v1/orgs/{organizationId}/plans

- operationId: `OrganizationPlansApi_getOrganizationPlans`
- summary: List organization-specific training plans

## Organization plan library

Paginated **published** plans for your organization (`public` / `private` only). Persisted AI plans with visibility `end-user-ai-generated` are **not listed** here (even with `visibility=all`) — fetch those via `GET /v1/plans/{id}` using the stored plan id from generate/adapt.

**Try it:** `GET /v1/orgs/{organizationId}/plans?limit=20` with `X-Api-Key`. Pagination: `limit` default **20** (max **50**); advance via `links.next`.

**Optional filters:** `visibility`, `difficulties` (CSV), `goalIds` (CSV hex), `durationInWeeks`, `workoutsPerWeek`, `q` (fuzzy; `sort` ignored while set), `sort`, `locale`.

**Related:** `POST /v1/orgs/{orgId}/plans/recommend`, `POST /v1/orgs/{orgId}/plans/generate`, `GET /v1/plans/{id}`, `GET /v1/plans/metadata`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `limit` (query) — Maximum number of items to return per page.
- `q` (query) — Search text
- `sort` (query) — Fields to sort. Prefix them with - (descending) or + (ascending): +someField,-otherField
- `locale` (query) — BCP-47 locale (e.g. en-US, fr-FR). Localizes text and locale-aware media when this operation supports it. Falls back to English when unsupported.
- `visibility` (query) — Filter plans by their visibility status.
- `difficulty` (query) [deprecated] — Filter plans by difficulty level (single value). DEPRECATED: Use 'difficulties' for multiple values.
- `difficulties` (query) — Filter plans by one or more difficulty levels (comma-separated). Preferred over 'difficulty'.
- `goalIds` (query) — Filter plans by one or more goal IDs (provide comma-separated values if multiple).
- `durationInWeeks` (query) — Filter plans by duration in weeks (1-24)
- `workoutsPerWeek` (query) — Filter plans by workouts per week (1-7)

**Responses:**
- 200 — Successfully retrieved paginated list of plans for the organization.
- 400 — Invalid pagination or filter parameters.
- 401 — Authentication failed - API key is missing or invalid.
- 404 — The specified organization ID does not exist.

## Tag: library-exercises

### GET /v1/orgs/{organizationId}/video-assets

- operationId: `OrganizationExercisesApi_getOrganizationExercises`
- summary: List workspace exercise videos for an organization

## Workspace exercise library

Paginated **published workspace-only** exercise videos for catalogs/builders/players (`{ data, links, explanation? }`). Stock / pay-as-you-go are not included. No `visibility` filter.

**Try it:** `GET /v1/orgs/{organizationId}/video-assets?limit=10` with `X-Api-Key`. Pagination: `limit` default **10** (max **50**); advance via `links.next`.

**Filters (AND):** `q` (substring; locale-aware with `?locale=`), `equipmentIds`, `muscleGroupIds`, `kinds`, `skillLevels`, `executionSides`, `coach`, `collectionNames` (CSV plain strings — see DTO). Sort: `date`/`name`/`kind` with `+`/`-` (default `-date`).

**Video type guide (`kinds` CSV):** product **Exercise** → `single-exercise`; **Multi-exercise** → `multi-exercise`; **Class** → `class`; **Promo** → `promo`; **Educational** → `educational`. Allowed values only — unknown tokens → `400`.

**Audio:** `audioInstructions[]` only on `kind: single-exercise` (presigned `assetUri`, optional `scriptText`). Other kinds return `[]`.

**Related:** `GET /v1/orgs/{orgId}/groups`, `GET /v1/workouts/metadata`, `GET /v1/orgs/{orgId}/stock-exercises`.

**Parameters:**
- `organizationId` (path) (required) — Unique organization (workspace) resource id for Hyperhuman Teams - opaque 24-character hex string.
- `limit` (query) — Maximum number of items to return per page.
- `q` (query) — Search text
- `sort` (query) — Sortable fields (use `+` / `-` prefix, comma-separated for multi-key). Allowed: `date` (alias of `createdAt`), `name`, `kind`. Default: `-date` (newest first). Examples: `-date`, `+name`, `-kind,+name`.
- `locale` (query) — BCP-47 locale (e.g. en-US). Localizes text and locale-aware media when supported; falls back to English.
- `equipmentIds` (query) — Comma-separated equipment ids (24-character hex). Returns assets whose `equipment[]` includes ANY of these.
- `muscleGroupIds` (query) — Comma-separated muscle-group ids (24-character hex). Returns assets whose `muscleGroups[]` includes ANY of these.
- `kinds` (query) — Comma-separated video kinds. Allowed: `single-exercise`, `multi-exercise`, `class`, `promo`, `educational`. See the **Video type guide** in the endpoint description for product-name-to-`kind` mapping.
- `skillLevels` (query) — Comma-separated skill levels. Allowed: `beginner`, `intermediate`, `advanced`.
- `executionSides` (query) — Comma-separated execution sides. Allowed: `both`, `left`, `right`, `alternating`.
- `coach` (query) — Coach gender as recorded on the asset.
- `collectionNames` (query) — Comma-separated collection names (exact match, case-sensitive). Rows with empty `collectionName` are excluded when this filter is set.

**Responses:**
- 200 — Paginated workspace exercise videos with previews and metadata.
- 400 — Request validation failed. `error.code` is `ValidationError`; `error.target` is the DTO class name; `error.details[]` lists the offending fields.
- 401 — Missing, malformed, or unknown `X-Api-Key` header.
- 403 — API key is valid but is not authorized for this `organizationId` (the key belongs to a different workspace, or no API key was sent on a guarded route).
- 404 — No workspace exists for the supplied `organizationId`.

## Tag: library-stock-exercises

### GET /v1/orgs/{organizationId}/stock-exercises

- operationId: `OrganizationStockExercisesApi_getStockExercises`
- summary: List browsable stock exercise catalog

## Stock exercise catalog

Paginated **published public single-exercise** stock clips (free + premium) for browse/playback. Not AI-generation fuel. Requires Content API access plus stock-catalog entitlement (or active Ultra yearly). Response omits `trainer` / `bundle` / `organization`.

**Try it:** `GET /v1/orgs/{organizationId}/stock-exercises?limit=10` with `X-Api-Key`. Pagination: `limit` default **10** (max **50**); advance via `links.next`. Each call counts as **10x**.

**Filters:** `q` (substring), `equipmentIds`, `muscleGroupIds`, `skillLevels`, `executionSides`, `coach`, `collectionNames`, plus `availability` (`free-stock` | `premium-stock` | `all-stock`). No `kinds` filter (catalog is hard-scoped to `single-exercise` — sending `kinds` → `400`).

**Related:** `GET /v1/orgs/{orgId}/video-assets`, `GET /v1/workouts/exercises/metadata`.

**Parameters:**
- `organizationId` (path) (required) — Unique organization (workspace) resource id for Hyperhuman Teams - opaque 24-character hex string.
- `limit` (query) — Maximum number of items to return per page.
- `q` (query) — Search text
- `sort` (query) — Sortable fields (use `+` / `-` prefix, comma-separated for multi-key). Allowed: `date` (alias of `createdAt`), `name`, `kind`. Default: `-date` (newest first). Examples: `-date`, `+name`.
- `locale` (query) — BCP-47 locale (e.g. en-US). Localizes text and locale-aware media when supported; falls back to English.
- `equipmentIds` (query) — Comma-separated equipment ids (24-character hex). Returns assets whose `equipment[]` includes ANY of these.
- `muscleGroupIds` (query) — Comma-separated muscle-group ids (24-character hex). Returns assets whose `muscleGroups[]` includes ANY of these.
- `skillLevels` (query) — Comma-separated skill levels. Allowed: `beginner`, `intermediate`, `advanced`.
- `executionSides` (query) — Comma-separated execution sides. Allowed: `both`, `left`, `right`, `alternating`.
- `coach` (query) — Coach gender as recorded on the asset.
- `collectionNames` (query) — Comma-separated collection names (exact match, case-sensitive). Rows with empty `collectionName` are excluded when this filter is set.
- `availability` (query) — Stock tier to return. Allowed: `free-stock`, `premium-stock`, `all-stock`. Default: `all-stock` (free and premium).

**Responses:**
- 200 — Paginated stock single-exercise videos with previews and metadata.
- 400 — Request validation failed. `error.code` is `ValidationError`; `error.target` is the DTO class name; `error.details[]` lists the offending fields.
- 401 — Missing, malformed, or unknown `X-Api-Key` header.
- 403 — API key is not authorized for this `organizationId`, OR the plan lacks Content API access, OR the plan lacks stock catalog access (requires Ultra annual or a custom plan with `stockVideoCatalogApi`).
- 404 — No workspace exists for the supplied `organizationId`.
- 429 — Rate limit exceeded. Each call to this endpoint counts as 10x against your Content API limits.

## Tag: exercise-groups

### GET /v1/orgs/{organizationId}/groups

- operationId: `OrganizationExerciseGroupsApi_getOrganizationExerciseGroups`
- summary: List organization micro workouts (exercise groups)

## Micro Workouts (Exercise Groups)

**Micro workouts** are short, focused exercise collections designed for quick sessions. Use these endpoints to surface them in your app.

Retrieves paginated list of exercise collections (micro workouts) for your organization. Each collection has simple execution details: circuit or sets format, rounds, rest periods, and per-exercise config (time-based or reps-based).

---

### Use Cases

- **White-label Apps** - Show organization micro workouts
- **Quick Workout Browsing** - Circuit and sets-based collections
- **Integration with Workouts** - Collections can be used in workout generation (excludeExerciseCollections)

---

### Execution Details (per collection)

- **kind**: circuit | sets
- **rounds**: Number of rounds
- **roundBreakDuration**: Rest between rounds (ms)
- **exerciseBreakDuration**: Rest between exercises (ms)
- **exerciseConfig**: timeBasedConfig (duration, pace, rpe, amrap) or repsBasedConfig (reps, weightLevel, pace, rpe)

---

### Filtering and Pagination

**Visibility:**
- `public` - Publicly accessible collections (default)
- `private` - Organization-only (requires API key for same org)
- `all` - Both (requires API key for same org)

**Pagination:**
- `offset` - Pagination offset (default: 0)
- `limit` - Items per page (default: 10, max: 50)

---

### Related Endpoints

- Get Group Detail: `GET /v1/orgs/{orgId}/groups/{groupId}` - Full detail with execution config
- Get Group Exercises: `GET /v1/orgs/{orgId}/groups/{groupId}/exercises` - Exercises in collection
- List Workouts: `GET /v1/orgs/{orgId}/workouts` - Organization workouts

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `limit` (query) — Items per page. Defaults to 10, max 50.
- `locale` (query) — BCP-47 locale (e.g. en-US, fr-FR). Localizes text and locale-aware media when this operation supports it. Falls back to English when unsupported.
- `visibility` (query) — Filter groups by their visibility status.
- `offset` (query) — Pagination offset. Defaults to 0.

**Responses:**
- 200 — Successfully retrieved paginated list of micro workouts (exercise collections).
- 401 — Unauthorized
- 403 — API key not authorized for this organization

### GET /v1/orgs/{organizationId}/groups/{groupId}

- operationId: `OrganizationExerciseGroupsApi_getOrganizationExerciseGroupById`
- summary: Get micro workout (exercise group) by ID

## Micro Workout Detail

Returns full detail for a micro workout (exercise collection) belonging to your organization. Includes execution config (circuit/sets, rounds, rest, time-based or reps-based per exercise).

**Response structure:**
- `kind` - circuit | sets
- `rounds`, `roundBreakDuration`, `exerciseBreakDuration`
- `exerciseConfig` - timeBasedConfig (duration, pace, rpe, amrap) or repsBasedConfig (reps, weightLevel, pace, rpe)
- `preview` - Video preview URLs
- `trainer`, `organization`

**Access:** Only collections created by organization trainers. Private collections require API key for the same organization.

---

### Related Endpoints

- List Collections: `GET /v1/orgs/{orgId}/groups` - Browse organization collections
- Get Exercises: `GET /v1/orgs/{orgId}/groups/{groupId}/exercises` - Exercises in this collection

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `groupId` (path) (required) — The unique identifier of the exercise collection (group).
- `locale` (query) — BCP-47 locale (e.g. en-US, fr-FR). Localizes text and locale-aware media when this operation supports it. Falls back to English when unsupported.

**Responses:**
- 200 — Successfully retrieved micro workout (exercise collection) detail.
- 401 — Unauthorized
- 403 — API key not authorized for this organization
- 404 — Group not found or not accessible

### GET /v1/orgs/{organizationId}/groups/{groupId}/exercises

- operationId: `OrganizationExerciseGroupsApi_getOrganizationGroupExercises`
- summary: Get exercises in micro workout

## Get Micro Workout Exercises

Returns the list of exercises in a micro workout (exercise collection). Each exercise includes video preview, metadata (muscle groups, equipment), and trainer info.

**Use this endpoint to:**
- Display the exercise sequence for a micro workout
- Build a preview or playback UI for the collection
- Get exercise details before starting a session

---

### Related Endpoints

- Get Collection: `GET /v1/orgs/{orgId}/groups/{groupId}` - Collection detail with execution config
- List Collections: `GET /v1/orgs/{orgId}/groups` - Browse organization collections

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `groupId` (path) (required) — The unique identifier of the exercise collection (group) whose exercises are requested.
- `locale` (query) — BCP-47 locale (e.g. en-US, fr-FR). Localizes text and locale-aware media when this operation supports it. Falls back to English when unsupported.

**Responses:**
- 200 — Successfully retrieved list of exercises in the micro workout with instance config (reps, duration, pace, rpe).
- 401 — Unauthorized
- 403 — API key not authorized for this organization
- 404 — Group not found or not accessible

## Tag: ai-generation

### POST /v1/orgs/{organizationId}/workouts/generate

- operationId: `OrganizationWorkoutGeneration_generateWorkout`
- summary: Generate personalized on-the-fly workouts based on user profile details, duration preferences, and exercise sources.

## Generate a workout

Creates an AI workout from profile, `categoryId`, duration options, and exercise sources. Prefer `POST .../workouts/recommend` when you only need a match from your existing library.

**Playback / storage:** Response is **ephemeral JSON** (`name`, `instances[]` with `kind` `single-exercise`|`rest`, `totalDurationSeconds`, …) with **no top-level persisted workout `id`**. Not listed by `GET /v1/orgs/{orgId}/workouts`. Play via the JSON-receiver embed (`/embed/workout-json-receiver` + `HYPERHUMAN_EMBED_INIT`) or a custom player; the embed resolves media via `GET /v1/video-asset/{exerciseId}`. Do **not** call `/playlist` or `/music` (need a persisted id). JSON-receiver has **no** PulseMix and **no** session/feedback tracking — use the pre-built `/embed/workout/{id}` for those. Persist the JSON yourself if you need replay/audit later. See API overview **Embedded Player**.

**Try it (minimal):** body `{ "endUserProfileDetails": { "age": 28, "fitnessLevels": ["beginner"] }, "exerciseSources": ["premium_stock"], "durationOptions": { "minMinutes": 10, "maxMinutes": 15 }, "categoryId": "<from GET /v1/workouts/metadata>" }` with `X-Api-Key`.

**Sources:** `premium_stock` (production), `free_stock` (dev/test), `team_exercises` (your catalog). Omit → free+premium stock. Prefer `premium_stock` or `team_exercises` alone in production.

**Exercise collections:** optional `includeExerciseCollections` (allowlist — only these `collectionName` values) or `excludeExerciseCollections` (denylist). Mutually exclusive — both non-empty → `400`. Compose with `exerciseSources` (AND). Prefer **include** to lock trainer/location/brand look; do not simulate include by excluding every other collection. Discover names via `GET /v1/workouts/exercises/metadata`. Filtered pool must have ≥ 10 exercises or request returns `400`.

**Localization:** optional body `locale` (BCP-47). Unsupported → `400`. Additive `*Localized` / `nameTranslations` / `difficultyLabel` when non-English; legacy `name` / `difficulty` stay English.

**Rate limit:** counts as **10x**.

**Related:** `POST /v1/orgs/{orgId}/workouts/recommend`, `POST /v1/orgs/{orgId}/workouts/{workoutId}/adapt`, `GET /v1/video-asset/{id}`, `GET /v1/workouts/metadata`, `GET /v1/workouts/exercises/metadata`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.

**Request body (required):** application/json

**Responses:**
- 200 — Successfully generated workout plan with automatic warm-up and cooldown sections. AI may adjust final difficulty based on exercise availability and safety considerations.
- 400 — Bad Request — invalid input, unsupported locale, include+exclude collections together, or filtered exercise pool below 10 after collection/source filters.
- 401 — Unauthorized - Invalid API key.
- 403 — Forbidden - API key required or insufficient permissions.
- 404 — Not Found - Organization or other resources not found.
- 500 — Internal Server Error - Failed to generate workout.

### POST /v1/orgs/{organizationId}/plans/generate

- operationId: `OrganizationPlanGeneration_generatePlan`
- summary: Generate personalized training programs based on user profile details, duration preferences, and workout collection sources.

## Generate a training plan

Creates an AI multi-week program (typically 4–52 weeks, 1–7 workouts/week) from profile, goals, duration options, and workout collection sources. Prefer `POST .../plans/recommend` for a fast library match, or `GET /v1/orgs/{orgId}/plans` to browse.

**Persistence:** Top-level plan `id` is returned **only** when `endUserProfileDetails.endUserId` is set (active org member). Then **store** that `id` and retrieve with `GET /v1/plans/{id}` / `.../workouts`. Without `endUserId`, the response is program JSON **without** a top-level `id` (cannot call `GET /v1/plans/{id}`). Generated plans are **not** listed by `GET /v1/orgs/{orgId}/plans`.

**Try it:** `GET /v1/plans/metadata` for goal/difficulty ids, optionally `GET /v1/workouts/equipment/metadata`, then POST. For a persisted `id`, include `endUserProfileDetails.endUserId` (see `persisted-with-endUserId` example). Duration: both min/max → AI picks in range; one value → target; omit → defaults to 8 weeks.

**Sources:** body field `workoutCollectionSources` — `template` (platform collections), `team` (your collections). Omit → both. Do **not** send `workoutSources` (that name is only for plan **adapt**).

**Optional body:** `userRequirements` (max 2000 chars), `locale` (BCP-47).

**Localization:** optional `locale`. Unsupported → `400`. Omitted → English legacy fields; translation maps = `{ "en-US": "..." }`. Non-English → AI fills `nameTranslations` / `descriptionTranslations`; nested workouts get `nameLocalized` / `descriptionLocalized`; `difficultyLabel` localized. When a plan `id` is returned, maps are **persisted** — `GET /v1/plans/{id}?locale=...` resolves `name` / `description`. On this generate response, top-level `name` / `description` stay English base. Nested legacy workout fields stay English. `userRequirements` language does not drive output locale.

**Rate limit:** counts as **10x**.

**Related:** `POST /v1/orgs/{orgId}/plans/recommend`, `GET /v1/orgs/{orgId}/plans`, `GET /v1/plans/metadata`, `GET /v1/plans/{id}`, `GET /v1/workouts/equipment/metadata`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.

**Request body (required):** application/json

**Responses:**
- 200 — Successfully generated training program with progressive workout structure.
- 400 — Bad Request - Invalid input parameters (e.g. unsupported locale returns `Unsupported locale: xx-XX`).
- 401 — Unauthorized - Invalid API key.
- 403 — Forbidden - API key required or insufficient permissions.
- 404 — Not Found - Organization or other resources not found.
- 500 — Internal Server Error - Failed to generate program.

## Tag: ai-recommendation

### POST /v1/orgs/{organizationId}/workouts/recommend

- operationId: `OrganizationWorkoutRecommendation_getWorkoutRecommendations`
- summary: Get workout recommendations from existing library

## Recommend workouts from your library

Ranks **existing published workouts** in your organization library for a user profile. Use this when your team already creates workouts and you want to pair the best match (e.g. after a quiz). For a brand-new workout structure, use `POST .../workouts/generate` instead.

**Try it (minimal):** body `{ "endUserProfileDetails": { "age": 30, "fitnessLevels": ["beginner"] } }` with header `X-Api-Key`. Response is a bare object `{ workouts, reasoning }` (not wrapped in `data`). Up to **12** workouts; each item includes a `recommendation` text reason (not a numeric score).

**Body:** only `endUserProfileDetails` and optional `locale` — unknown keys return `400 ValidationError`.

**Rate limit:** counts as **10x** toward hourly/daily limits.

**Related:** `POST /v1/orgs/{orgId}/workouts/generate`, `GET /v1/orgs/{orgId}/workouts`, `GET /v1/workouts/metadata`, `GET /v1/workouts/{id}`.

**Parameters:**
- `organizationId` (path) (required) — Organization/Team unique identifier

**Request body (required):** application/json

**Responses:**
- 200 — Ranked list of recommended workouts with per-item recommendation text and overall reasoning
- 401 — Unauthorized - Invalid API key
- 404 — Organization/Team not found

### POST /v1/orgs/{organizationId}/plans/recommend

- operationId: `OrganizationPlanRecommendation_getPlanRecommendations`
- summary: Get training plan recommendations from existing library

## Recommend plans from your library

Ranks **existing multi-week plans** in your organization library for a user profile. Prefer this over `POST .../plans/generate` when your team already publishes programs and you want the best library match.

**Try it (minimal):** body `{ "endUserProfileDetails": { "age": 30, "fitnessLevels": ["beginner"] } }`. Response is a bare object `{ plans, reasoning }` (not wrapped in `data`). Up to **12** plans; each item includes a `recommendation` text reason (not a numeric score).

**Body:** `endUserProfileDetails`, optional root `goalIds`, optional `locale` — unknown keys return `400 ValidationError`.

**Rate limit:** counts as **10x** toward hourly/daily limits.

**Related:** `POST /v1/orgs/{orgId}/plans/generate`, `GET /v1/orgs/{orgId}/plans`, `GET /v1/plans/metadata`, `GET /v1/plans/{id}`.

**Parameters:**
- `organizationId` (path) (required) — Organization/Team unique identifier

**Request body (required):** application/json

**Responses:**
- 200 — Ranked list of recommended training plans with per-item recommendation text and overall reasoning
- 400 — Bad Request - Invalid input data
- 401 — Unauthorized - Invalid API key
- 404 — Organization/Team not found

## Tag: ai-adaptation

### POST /v1/orgs/{organizationId}/workouts/{workoutId}/adapt

- operationId: `OrganizationWorkoutAdaptation_adaptWorkout`
- summary: Adapt an existing team workout based on user profile and data

## Adapt a team workout

Personalizes an existing organization workout (intensity, duration, exercise substitutions) from profile signals, recent session/feedback history, optional wearable context, and `userGuidance`. Goals and overall structure intent of the source workout are preserved. The source `{workoutId}` must belong to `{organizationId}` (otherwise `404`).

**Playback / storage:** Same as workout **generate** — ephemeral `{ name, instances[], … }` (no top-level workout `id`). Play via the JSON-receiver **iframe** + `HYPERHUMAN_EMBED_INIT` (media via `GET /v1/video-asset/{exerciseId}`) or a custom player. No `/playlist`, `/music`, or sessions/feedback. Prefer `POST .../workouts/recommend` for a library match. See API overview **Embedded Player**.

**Rate limit:** counts as **10x**.

**Related:** `POST /v1/orgs/{orgId}/workouts/generate`, `POST /v1/orgs/{orgId}/workouts/recommend`, `GET /v1/orgs/{orgId}/workouts`, `GET /v1/workouts/{id}`.

**Parameters:**
- `organizationId` (path) (required) — Organization/Team ID
- `workoutId` (path) (required) — Workout ID to adapt

**Request body (required):** application/json

**Responses:**
- 200 — Adapted workout with personalized adjustments
- 400 — Invalid request or workout not eligible for adaptation (only team workouts can be adapted)
- 401 — Unauthorized - Invalid API key
- 404 — Workout or organization not found

### POST /v1/orgs/{organizationId}/plans/{planId}/adapt

- operationId: `OrganizationPlanAdaptation_adaptPlan`
- summary: Adapt an existing program based on user profile and data

## Adapt a training plan

Personalizes an existing organization plan (add/remove/replace/reorder future workouts) from profile signals, session/feedback history, optional wearable context, and `userGuidance`. Completed workouts are not modified. The source `{planId}` must belong to `{organizationId}` (otherwise `404`).

**Required:** `endUserProfileId` (org member hex id). Content API persists the adapted program under that user — omitting it returns `400` (`Provide endUserProfileId`).

**Persistence:** Unlike workout adapt, the response includes a persisted adapted program `id` — **store it** and retrieve with `GET /v1/plans/{id}` / `.../workouts`. Team-owned sources may be copied (`wasDuplicated`, `originalProgramId`). If that user has this plan **active**, only the **next workout slot** is adapted; otherwise the full future schedule is adapted.

**Sources:** body field `workoutSources` (`template` | `team`) — not `workoutCollectionSources` (that name is only for plan **generate**).

**Rate limit:** counts as **10x**.

**Related:** `POST /v1/orgs/{orgId}/plans/generate`, `POST /v1/orgs/{orgId}/plans/recommend`, `GET /v1/plans/{id}`, `GET /v1/orgs/{orgId}/plans`.

**Parameters:**
- `organizationId` (path) (required) — Organization/Team ID
- `planId` (path) (required) — Plan ID to adapt

**Request body (required):** application/json

**Responses:**
- 200 — Adapted program with personalized workout modifications
- 400 — Invalid request or program not found
- 401 — Unauthorized - Invalid API key
- 403 — Forbidden - Cannot adapt programs from other organizations
- 404 — Program or organization not found

## Tag: organization

### GET /v1/orgs/{organizationId}/metadata

- operationId: `OrganizationMetadataApi_getOrganizationMetadata`
- summary: Get organization metadata

## Organization metadata

Public org branding and config: `name`, `handle`, `description`, `website`, `logoUrl` / `watermarkUrl` (320×320 CDN PNGs), brand colors, `clientsJoinSetting` (`OPEN` | `INVITE_ONLY` | `SUBSCRIPTION_REQUIRED`).

**Module / AI flags:** `aiGenerationEnabled`, `aiAdaptationEnabled`, `aiRecommendationEnabled`, `aiChatEnabled`, `insightsEnabled` (and module toggles like `hasAssenseiModuleActive`) are **member-app visibility** — they do **not** alone gate Content API recommend/generate/adapt/chat/insights (those use separate API capability checks).

**Try it:** `GET /v1/orgs/{organizationId}` with `X-Api-Key`.

**Related:** `GET /v1/orgs/{orgId}/join-options`, `GET /v1/orgs/{orgId}/workouts`, `GET /v1/orgs/{orgId}/plans`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.

**Responses:**
- 200 — Successfully retrieved organization metadata with complete branding information.
- 401 — Authentication failed - API key is missing or invalid.
- 404 — The specified organization ID does not exist.

### GET /v1/orgs/{organizationId}/branding/news

- operationId: `OrganizationBrandingNewsApi_getOrganizationBrandingNews`
- summary: Get organization branding news feed

Returns configured team news items for the branding/news section. If no sources are configured, returns an empty list.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `forceRefresh` (query) — Force refresh and bypass cache.

**Responses:**
- 200 — Successfully retrieved organization branding news feed.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — The specified organization ID does not exist.

### GET /v1/orgs/{organizationId}/join-options

- operationId: `OrganizationJoinOptionsApi_getOrganizationJoinOptions`
- summary: Get organization join options

## User Onboarding Configuration

Retrieves information about how users can join your organization, including join settings, subscription requirements, and available plans.

---

### 📋 What's Included

**Join Settings:**
- `clientsJoinSetting`: OPEN, INVITE_ONLY, or SUBSCRIPTION_REQUIRED
- `canJoinDirectly`: Boolean flag for self-signup
- `inviteOnlyMode`: Requires invitation to join
- `requiresSubscription`: Payment required to access

**Free Plans:**
- `hasFreePlanWithAutoJoin`: Free access available
- `freeJoinOptions`: List of free plans with auto-enrollment
- `totalProductsAvailable`: Count of subscription products

---

### 🎯 Join Settings Explained

| Setting | Description | User Flow |
|---------|-------------|-----------|
| **OPEN** | Anyone can join | Self-signup enabled |
| **INVITE_ONLY** | Requires invitation | Admin must invite |
| **SUBSCRIPTION_REQUIRED** | Payment required to join | User subscribes for access |

---

### 💡 Integration Patterns

**Self-Signup Flow (OPEN):**
```
1. GET /v1/orgs/{orgId}/join-options → Check if open
2. POST /v1/orgs/{orgId}/endusers/invite → Create user
3. User receives email/access
```

**Invitation Flow (INVITE_ONLY):**
```
1. Admin invites via dashboard or API
2. User receives invitation
3. User accepts and creates account
```

**Subscription Required:**
```
1. GET /v1/orgs/{orgId}/join-options → Check products
2. Display subscription options to user
3. User subscribes → Access granted
```

---

### 🔗 Related Endpoints

- Organization Metadata: `GET /v1/orgs/{orgId}/metadata`
- Invite Users: `POST /v1/orgs/{orgId}/endusers/invite`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.

**Responses:**
- 200 — Successfully retrieved organization join options and configuration.
- 401 — Authentication failed - API key is missing or invalid.
- 404 — The specified organization ID does not exist.

### GET /v1/orgs/{organizationId}/stats

- operationId: `OrganizationStatsApi_getOrganizationStats`
- summary: Get organization API usage statistics

## Organization API usage statistics

Returns current-hour and daily request counts plus your subscription hourly/daily rate limits for this organization.

AI and heavy-video endpoints (recommend, generate, adapt, insights, chat, full video export, etc.) count as **10x** toward those limits. Prefer caching generated plan/workout ids rather than regenerating on every page load. Plan quotas and packs: [hyperhuman.cc/pricing](https://hyperhuman.cc/pricing).

**Related:** `GET /v1/orgs/{orgId}/metadata`, `GET /v1/orgs/{orgId}/workouts`, `GET /v1/orgs/{orgId}/plans`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.

**Responses:**
- 200 — Successfully retrieved organization API usage statistics.
- 401 — Unauthorized
- 403 — Forbidden
- 404 — Organization not found

## Tag: social

### GET /v1/orgs/{organizationId}/drops

- operationId: `OrganizationDropsApi_getOrganizationDrops`
- summary: List organization social drops

## Organization Social Drops

Retrieves paginated list of ready social drops for your organization. A drop is a publish-ready package (video variants, covers, copy packs) linked to an Exercise, Collection (Micro Workout), Workout, or Program.

**Note:** Only drops with status `ready` are returned. Draft, processing, and failed drops are excluded.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)
- **Capabilities:** List ready drops, get drop detail, export channel bundle (video, cover, copy pack)

---

### Use Cases

- **Social scheduling tools** - List drops for content calendar
- **Content management dashboards** - Browse and export ready assets
- **Bulk export workflows** - Get video + cover + copy for channel publishing
- **Channel-specific discovery** - Filter by aspect ratio and duration

---

### Filtering and Pagination

**Filters:**
- `sourceType` - exercise, exerciseCollection, workout, workoutCollection
- `sourceId` - Filter by source entity ID (use with sourceType for entity-context views)
- `aspectRatio` - 9:16, 1:1, 16:9 (drops that have this variant)
- `duration` - 15, 30, 60 (seconds; drops that have this variant)
- `q` - Search by drop title (min 3 characters)

**Sort:** Use `sort` with +field (asc) or -field (desc), e.g. `-updatedAt`, `+createdAt`

**Pagination:**
- `limit` - Items per page (default: 20, max: 50)
- `offset` - Pagination offset (default: 0)

---

### Best Practices

- **Cache responses** - Drops change when new content is published
- **Use sourceId** - Filter by source when showing drops in entity-context modals
- **Handle empty results** - Graceful fallback when no ready drops exist

---

### Related Endpoints

- Get Drop: `GET /v1/orgs/{orgId}/drops/{dropId}` - Full detail with variants and copy packs
- Export Drop: `POST /v1/orgs/{orgId}/drops/{dropId}/export` - Channel bundle (video, cover, copy)

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `limit` (query) — Items per page. Defaults to 20, max 50.
- `q` (query) — Search by drop title (min 3 characters)
- `sort` (query) — Sort: +updatedAt, -createdAt
- `sourceType` (query) — Filter by source type
- `sourceId` (query) — Filter by source entity ID
- `aspectRatio` (query) — Filter by aspect ratio
- `duration` (query) — Filter by duration in seconds
- `offset` (query) — Pagination offset. Defaults to 0.

**Responses:**
- 200 — Successfully retrieved paginated list of ready drops for the organization.
- 400 — Invalid filter or pagination parameters.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.

### GET /v1/orgs/{organizationId}/drops/{dropId}

- operationId: `OrganizationDropsApi_getOrganizationDropById`
- summary: Get drop by ID

## Drop Detail

Returns full drop detail including video variants with presigned download and poster URLs, and copy packs per channel.

**Response structure:**
- `copyPack` - Copy pack for the default channel (channelPreset)
- `copyPacks` - Copy packs per channel; use `copyPacks[viewChannel] ?? copyPack` for channel switching
- `variants` - Video variants with aspectRatio, duration, downloadLink, posterLink, posterLinksByChannel, size

**URL expiration:** Presigned URLs are valid for 7 days.

---

### Related Endpoints

- List Drops: `GET /v1/orgs/{orgId}/drops` - Browse organization drops
- Export Drop: `POST /v1/orgs/{orgId}/drops/{dropId}/export` - Get channel bundle

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `dropId` (path) (required) — The unique identifier of the drop.

**Responses:**
- 200 — Successfully retrieved drop detail.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — Drop not found or not ready.

### POST /v1/orgs/{organizationId}/drops/{dropId}/export

- operationId: `OrganizationDropsApi_exportDrop`
- summary: Export drop for channel bundle

## Export for Channel Bundle

Returns presigned video download URL, cover image URL, and copy pack for the specified channel. Use for one-click export to Reels, Shorts, TikTok, etc.

**Response contents:**
- `videoDownloadLink` - Presigned S3 URL for MP4 video (valid 7 days)
- `coverLink` - Presigned S3 URL for cover/poster image
- `copyPack` - Hook, caption, hashtags, CTA, poster tagline
- `ctaUrl` - Optional CTA URL from team settings (when configured)

**Channel presets:** ig_reels, ig_stories, ig_carousel, tiktok, yt_shorts, fb_reels, yt_longform. Each has default aspect ratio and duration; override via body.

---

### Integration Example

```javascript
const res = await fetch(
  `/v1/orgs/${orgId}/drops/${dropId}/export`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': apiKey,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ channelPreset: 'ig_reels' }),
  }
);
const { videoDownloadLink, coverLink, copyPack } = await res.json();
// Download video, copy caption to clipboard
```

---

### Best Practices

- **Refresh URLs before expiry** - Presigned URLs valid for 7 days
- **Use channel preset** - Platform-specific defaults (aspect ratio, duration)
- **Handle 400** - Requested variant may not be available for this drop

---

### Related Endpoints

- Get Drop: `GET /v1/orgs/{orgId}/drops/{dropId}` - Full detail before export
- List Drops: `GET /v1/orgs/{orgId}/drops` - Browse organization drops

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `dropId` (path) (required) — The unique identifier of the drop.

**Request body:** application/json

**Responses:**
- 200 — Channel bundle with presigned URLs and copy pack.
- 400 — Requested variant not available for this drop.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — Drop not found or not ready.

## Tag: content-autopilot

### GET /v1/orgs/{organizationId}/content-autopilot

- operationId: `OrganizationContentAutopilotApi_listTasks`
- summary: List content autopilot tasks

## Content Autopilot Tasks

Retrieves a paginated list of content autopilot tasks for your organization. Autopilot tasks automate the creation, adaptation, and publishing of workouts and programs on a configurable schedule.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)

---

### Use Cases

- **Dashboard views** - List all scheduled content automation tasks
- **Status monitoring** - Filter by active/draft to see running vs paused tasks
- **Content type filtering** - Show only workout or program automation tasks
- **Audit and review** - Browse task configurations for the organization

---

### Filtering and Pagination

**Filters:**
- `status` - active, draft
- `scope` - ai_generate, create_similar, publish_existing
- `contentType` - workout, program

**Sort:** `sortBy` (updatedAt, createdAt) + `sortOrder` (ascend, descend)

**Pagination:**
- `limit` - Items per page (default: 20, max: 50)
- `offset` - Pagination offset (default: 0)

---

### Related Endpoints

- Get Task: `GET /v1/orgs/{orgId}/content-autopilot/{taskId}`
- Create Task: `POST /v1/orgs/{orgId}/content-autopilot`
- Task Events: `GET /v1/orgs/{orgId}/content-autopilot/{taskId}/events`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `limit` (query) — Items per page. Defaults to 20, max 50.
- `status` (query) — Filter by task status
- `scope` (query) — Filter by task scope
- `contentType` (query) — Filter by content type
- `sortBy` (query) — Sort by field
- `sortOrder` (query) — Sort order
- `offset` (query) — Pagination offset. Defaults to 0.

**Responses:**
- 200 — Successfully retrieved paginated list of autopilot tasks.
- 400 — Invalid filter or pagination parameters.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.

### POST /v1/orgs/{organizationId}/content-autopilot

- operationId: `OrganizationContentAutopilotApi_createTask`
- summary: Create content autopilot task

## Create Autopilot Task

Creates a new content autopilot task in `draft` status. Use the activate endpoint to start the scheduling.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)

---

### Task Scopes

| Scope | Description | Required Fields |
|-------|-------------|-----------------|
| `ai_generate` | Generate new content using AI | categoryIds/goalIds, difficultyOptions |
| `create_similar` | Adapt an existing workout/program | `sourceContentId` |
| `publish_existing` | Flip visibility to public on schedule | `existingContentId` |

---

### Prompt Guidance Modes

| Mode | Description |
|------|-------------|
| `none` | No additional prompt context |
| `custom` | User-written free text (`customPromptGuidance`) |
| `ai_tuned` | 30-day analytics context appended to prompt |
| `ai_driven` | Fully autonomous - AI decides category, difficulty, duration |

---

### Validation

- `minutesLimit` entitlement must have remaining capacity
- `premiumStock` entitlement required if `includePremiumStock` is true
- `sourceContentId` / `existingContentId` must belong to the organization

---

### Related Endpoints

- Activate: `POST /v1/orgs/{orgId}/content-autopilot/{taskId}/activate`
- Update: `PATCH /v1/orgs/{orgId}/content-autopilot/{taskId}`
- List: `GET /v1/orgs/{orgId}/content-autopilot`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.

**Request body (required):** application/json

**Responses:**
- 201 — Task created in draft status.
- 400 — Invalid input or missing required fields for scope.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — Entitlement limit exceeded or API key not authorized.

### GET /v1/orgs/{organizationId}/content-autopilot/events

- operationId: `OrganizationContentAutopilotApi_listTeamEvents`
- summary: List execution events across all tasks

## Organization Autopilot Events

Retrieves execution history across all content autopilot tasks for the organization. Each event records a task execution outcome: success, failure, or skip.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)

---

### Use Cases

- **Execution dashboard** - View recent automation activity across all tasks
- **Failure monitoring** - Filter by `failed` to identify issues
- **Audit trail** - Review content creation history with content IDs

---

### Filtering

- `taskId` - Filter events to a specific task
- `eventType` - success, failed, skipped_limit, skipped_premium_lost

---

### Related Endpoints

- List Tasks: `GET /v1/orgs/{orgId}/content-autopilot`
- Task Events: `GET /v1/orgs/{orgId}/content-autopilot/{taskId}/events`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `limit` (query) — Items per page. Defaults to 20, max 50.
- `eventType` (query) — Filter by event type
- `taskId` (query) — Filter events to a specific task
- `offset` (query) — Pagination offset. Defaults to 0.

**Responses:**
- 200 — Successfully retrieved execution events.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.

### GET /v1/orgs/{organizationId}/content-autopilot/{taskId}

- operationId: `OrganizationContentAutopilotApi_getTask`
- summary: Get autopilot task by ID

## Autopilot Task Detail

Returns full configuration of a content autopilot task including schedule, content parameters, and prompt guidance settings.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)

---

### Related Endpoints

- List Tasks: `GET /v1/orgs/{orgId}/content-autopilot`
- Update Task: `PATCH /v1/orgs/{orgId}/content-autopilot/{taskId}`
- Task Events: `GET /v1/orgs/{orgId}/content-autopilot/{taskId}/events`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `taskId` (path) (required) — The unique identifier of the autopilot task.

**Responses:**
- 200 — Successfully retrieved autopilot task detail.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — Task not found or deleted.

### PATCH /v1/orgs/{organizationId}/content-autopilot/{taskId}

- operationId: `OrganizationContentAutopilotApi_updateTask`
- summary: Update autopilot task configuration

## Update Autopilot Task

Partially updates an autopilot task's configuration. All fields are optional. Fields not included in the request body remain unchanged.

Notable: update `nextRunAt` to reschedule an active task to a different day without affecting its repeat configuration.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)

---

### Related Endpoints

- Get Task: `GET /v1/orgs/{orgId}/content-autopilot/{taskId}`
- Activate: `POST /v1/orgs/{orgId}/content-autopilot/{taskId}/activate`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `taskId` (path) (required) — The unique identifier of the autopilot task.

**Request body (required):** application/json

**Responses:**
- 200 — Task updated.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — Task not found or deleted.

### DELETE /v1/orgs/{organizationId}/content-autopilot/{taskId}

- operationId: `OrganizationContentAutopilotApi_deleteTask`
- summary: Delete autopilot task (soft delete)

## Delete Autopilot Task

Soft-deletes a content autopilot task. The task will no longer execute and will not appear in list results.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)

---

### Related Endpoints

- List Tasks: `GET /v1/orgs/{orgId}/content-autopilot`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `taskId` (path) (required) — The unique identifier of the autopilot task.

**Responses:**
- 204 — Task deleted.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — Task not found or already deleted.

### POST /v1/orgs/{organizationId}/content-autopilot/{taskId}/activate

- operationId: `OrganizationContentAutopilotApi_activateTask`
- summary: Activate autopilot task

## Activate Task

Sets the task status to `active` and determines the next scheduled execution time.

**Schedule resolution:**
1. If the task already has a `nextRunAt` in the future, it is preserved.
2. Otherwise, `nextRunAt` is computed from the task's `repeatMode`.
3. For `once` mode, defaults to now.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)

---

### Related Endpoints

- Deactivate: `POST /v1/orgs/{orgId}/content-autopilot/{taskId}/deactivate`
- Run Now: `POST /v1/orgs/{orgId}/content-autopilot/{taskId}/run-now`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `taskId` (path) (required) — The unique identifier of the autopilot task.

**Responses:**
- 200 — Task activated.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — Task not found or deleted.

### POST /v1/orgs/{organizationId}/content-autopilot/{taskId}/deactivate

- operationId: `OrganizationContentAutopilotApi_deactivateTask`
- summary: Deactivate autopilot task

## Deactivate Task

Sets the task status to `draft` and clears the `nextRunAt` schedule. The task will stop executing until activated again.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)

---

### Related Endpoints

- Activate: `POST /v1/orgs/{orgId}/content-autopilot/{taskId}/activate`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `taskId` (path) (required) — The unique identifier of the autopilot task.

**Responses:**
- 200 — Task deactivated.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — Task not found or deleted.

### POST /v1/orgs/{organizationId}/content-autopilot/{taskId}/run-now

- operationId: `OrganizationContentAutopilotApi_runNow`
- summary: Manually trigger task execution

## Run Now

Executes a content autopilot task immediately, bypassing the cron schedule. Respects all entitlement limits and daily execution caps (max 20 per user per day).

The task can be in any status (active or draft) -- this allows testing a task configuration before activating it on a schedule.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)

---

### Rate Limit

This endpoint triggers AI content generation and counts as 10x API calls toward your hourly and daily limits.

---

### Related Endpoints

- Activate: `POST /v1/orgs/{orgId}/content-autopilot/{taskId}/activate`
- Task Events: `GET /v1/orgs/{orgId}/content-autopilot/{taskId}/events`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `taskId` (path) (required) — The unique identifier of the autopilot task.

**Responses:**
- 200 — Task executed successfully.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — Entitlement limit exceeded or API key not authorized.
- 404 — Task not found or deleted.

### GET /v1/orgs/{organizationId}/content-autopilot/{taskId}/events

- operationId: `OrganizationContentAutopilotApi_listTaskEvents`
- summary: List execution events for a task

## Task Execution History

Retrieves the execution event log for a specific autopilot task. Each event records a task run outcome.

**Event types:**
- `success` - Content created and published successfully. `resultContentId` contains the new content ID.
- `failed` - Execution failed. `errorMessage` contains the reason.
- `skipped_limit` - Daily execution limit (20/day) reached. Task retries next day.
- `skipped_premium_lost` - Premium stock entitlement lost. Schedule advanced.

---

### Access and Auth

- **Auth:** API key (X-Api-Key header)
- **Scope:** Organization (organizationId)

---

### Related Endpoints

- Get Task: `GET /v1/orgs/{orgId}/content-autopilot/{taskId}`
- Team Events: `GET /v1/orgs/{orgId}/content-autopilot/events`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `taskId` (path) (required) — The unique identifier of the autopilot task.
- `limit` (query) — Items per page. Defaults to 20, max 50.
- `eventType` (query) — Filter by event type
- `offset` (query) — Pagination offset. Defaults to 0.

**Responses:**
- 200 — Successfully retrieved task execution events.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — Task not found or deleted.

## Tag: social-publish

### GET /v1/orgs/{organizationId}/social-publish/connected-platforms

- operationId: `OrganizationSocialPublishApi_getConnectedPlatforms`
- summary: List connected social platforms

Returns which platforms (instagram, tiktok, youtube, facebook) have a stored account id for this organization. Account connection is managed in the Hyperhuman team app, not via this API.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.

**Responses:**
- 200
- 403

### GET /v1/orgs/{organizationId}/social-publish

- operationId: `OrganizationSocialPublishApi_listTasks`
- summary: List scheduled social publish automations

## List social publish tasks

Paginated scheduled social-publish automations for this organization (`{ data, links }`).

**Try it:** `GET /v1/orgs/{organizationId}/social-publish?limit=20` with `X-Api-Key`. Optional `status` (`active` | `draft`). Pagination: `limit` (default **20**, max **50**); advance via `links.next`.

**Related:** `GET .../social-publish/connected-platforms`, `POST .../social-publish`, `PATCH .../social-publish/{taskId}`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `limit` (query) — Maximum number of items to return per page.
- `offset` (query) — Number of items to skip before collecting results (zero-based). Prefer the `links.next` cursor for pagination.
- `status` (query) — Filter by task status
- `sourceMode` (query) — Filter by source mode
- `sortBy` (query) — Sort by field
- `sortOrder` (query) — Sort order
- `search` (query) — Case-insensitive substring match on automation name (min 1 character after trim)

**Responses:**
- 200
- 403

### POST /v1/orgs/{organizationId}/social-publish

- operationId: `OrganizationSocialPublishApi_createTask`
- summary: Create a social publish automation (draft)

## Create a social publish task

Creates a social-publish automation as **draft** (**201**). Body matches the team internal create-social-publish task shape. OAuth connect, run-now, and per-run event listing are **not** on Content API — use the team app / internal routes.

**Next:** `PATCH .../social-publish/{taskId}` with `{ "status": "active" }` to schedule (requires connected accounts for target platforms; otherwise **422**).

**Related:** `GET .../social-publish/connected-platforms`, `GET .../social-publish`, `PATCH .../social-publish/{taskId}`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.

**Request body (required):** application/json

**Responses:**
- 201
- 403

### GET /v1/orgs/{organizationId}/social-publish/{taskId}

- operationId: `OrganizationSocialPublishApi_getTask`
- summary: Get one social publish automation

## Get a social publish task

Returns one automation by `taskId` for this organization (includes schedule fields and last-run outcome when present). **404** if missing or not owned by the org.

**Related:** `GET .../social-publish`, `PATCH .../social-publish/{taskId}`, `DELETE .../social-publish/{taskId}`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `taskId` (path) (required) — The unique identifier of the scheduled social publish automation.

**Responses:**
- 200
- 404

### PATCH /v1/orgs/{organizationId}/social-publish/{taskId}

- operationId: `OrganizationSocialPublishApi_patchTask`
- summary: Update a social publish automation

## Update a social publish task

Partial update. `status: active` schedules the task (requires connected accounts for all target platforms; missing accounts / posting base URL → **422**). `status: draft` pauses and clears `nextRunAt`. Other fields follow the same rules as the team internal API.

**Related:** `GET .../social-publish/connected-platforms`, `GET .../social-publish/{taskId}`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `taskId` (path) (required) — The unique identifier of the scheduled social publish automation.

**Request body (required):** application/json

**Responses:**
- 200
- 422

### DELETE /v1/orgs/{organizationId}/social-publish/{taskId}

- operationId: `OrganizationSocialPublishApi_deleteTask`
- summary: Delete a social publish automation (soft delete)

## Delete a social publish task

Soft-deletes the automation (**204**). Cron will not pick it up afterward; an in-flight run may still finish.

**Related:** `GET .../social-publish`, `PATCH .../social-publish/{taskId}`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `taskId` (path) (required) — The unique identifier of the scheduled social publish automation.

**Responses:**
- 204

## Tag: endusers-ai-insights

### GET /v1/orgs/{organizationId}/endusers/{endUserId}/insights/digest

- operationId: `UserInsightsApi_getUserDailyDigest`
- summary: Get daily AI-powered user insights digest

## Daily Health & Fitness Digest

Returns a personalized daily digest of insight pillars selected from: `health_pulse`, `training`, `recovery`, `movement`, `nutrition`, `body`. The exact set returned depends on which data sources the user has connected and on the current data completeness — typically 3-5 pillars per day.

Each pillar in the digest is intentionally **summary-only**. Coaching signals such as readiness band, recommended intensity, risk triggers, form cue, why-explanations, and alternative actions are surfaced inside the `headline` and `action` strings, but **are not exposed as separate fields on `InsightPillarDto`**. To get the full coaching context for a single pillar, drill into `GET /v1/orgs/{organizationId}/endusers/{endUserId}/insights/pillars/{pillarType}`.

---

### Pillars Overview

| Pillar | Description | Primary Data Source |
|--------|-------------|---------------------|
| **Health Pulse** | Overall health status and main driver | Rook wearables / workout adherence |
| **Training** | Workout progress and next session | Workout sessions, AI assessments |
| **Recovery** | Sleep and recovery readiness | Rook sleep score / workout intensity |
| **Movement** | Daily activity and cardio | Rook activity / workout frequency |
| **Nutrition** | Nutrition quality (when available) | Manual logs, chat logging, photo logs (`POST .../nutrition/photo-log`) |
| **Body** | Body metrics and momentum | Rook body score / workout streak |

---

### Status Indicators

- **Green** (75-100): On track
- **Yellow** (55-74): Watch it
- **Red** (0-54): Priority action needed

---

### Data Completeness

The `dataCompleteness` field (0-1) indicates how much user data is available:
- **0.8+**: Full wearable + workout data
- **0.5-0.8**: Partial data (some wearables or workout history)
- **<0.5**: Limited data (fallback insights provided)

---

### Fallback Behavior

When data is missing:
- Health Pulse calculates a "Hyperhuman Pulse" from workout adherence
- Recovery uses workout intensity to estimate readiness
- Nutrition shows a CTA to start tracking

---

### Enhanced Coaching Signals

The digest includes intelligent coaching signals embedded in pillar headlines:

| Signal | Description | Pillars |
|--------|-------------|---------|
| **Readiness** | Push/Maintain/Recover recommendation based on recovery data | Health Pulse, Recovery |
| **Risk Detection** | Fatigue warning when multiple triggers present | Training |
| **Form Focus** | Primary technique cue from AI assessments | Training |
| **Program Pacing** | On track/behind status for weekly goals | Training |

These signals adjust the `action` field to provide intensity-appropriate recommendations based on current readiness state.

---

### Use Cases

- Daily digest screen in mobile app
- Coach dashboard for client overview
- Notification content generation
- Personalized workout recommendations

---

### Rate Limit

This endpoint counts as 10x API calls toward your hourly and daily limits.

---

### Related Endpoints

- Workout Sessions: `GET /v1/me/workouts/sessions`
- Workout Feedback: `POST /v1/workouts/{id}/feedback`
- AI Assessments: `GET /v1/me/workouts/measured-metrics`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier (24-character hex string) of the organization.
- `endUserId` (path) (required) — User identifier: email, externalUUID, or internal user ID.
- `date` (query) — Date for the digest as an ISO 8601 date-time string (e.g. 2026-01-10T00:00:00.000Z). Past dates within last 7 days and today supported; future or older dates return 400. Defaults to today.
- `forceRefresh` (query) — Force refresh, bypassing cache.

**Responses:**
- 200 — Successfully retrieved daily digest.
- 400 — Invalid date (e.g., future date, more than 7 days in past, or invalid format).
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — User or organization not found.

### GET /v1/orgs/{organizationId}/endusers/{endUserId}/insights/pillars/{pillarType}

- operationId: `UserInsightsApi_getPillarDetail`
- summary: Get detailed view of a specific insight pillar

## Pillar Detail

Returns expanded information for a specific insight pillar, including all available metrics and recommendations.

---

### Available Pillar Types

| Type | Details Included |
|------|------------------|
| `health_pulse` | Score, delta, driver, readiness band, recommended intensity |
| `training` | Last workout, next session, form score, form cue, pacing status, risk level, why explanations, alternative actions |
| `recovery` | Sleep score, main issue, intensity, readiness band, HRV band, why explanations |
| `movement` | Activity level, suggested minutes, cardio trend |
| `nutrition` | Nutrition score (when available), wins/watches |
| `body` | Body score, weekly trend, streak, biggest lever |

---

### Enhanced Coaching Details

When drilling into pillar details, you get actionable coaching context:

**Readiness (Health Pulse and Recovery pillars):**
- `readinessBand`: Training capacity recommendation (`push` | `maintain` | `recover`)
- `readinessMessage`: Explanation of the recommendation
- `recommendedIntensity`: Target workout intensity (`light` | `moderate` | `high`)
- `hrvBand`: HRV relative to personal baseline (`low` | `normal` | `high`)

**Risk Detection (Training pillar):**
- `riskLevel`: Fatigue risk severity (`low` | `moderate` | `high`)
- `riskTriggers`: Array of up to 3 contributing factors
- `alternativeActions`: Safe workout alternatives when risk detected

**Form Coaching (Training pillar):**
- `formCue`: Primary technique focus for today
- `formSuggestion`: Adjustment if form is declining
- `repAccuracy`: Rep accuracy percentage from AI assessment

**Program Pacing (Training pillar):**
- `pacingStatus`: Weekly pacing status (`on_track` | `behind` | `ahead`)
- `pacingMessage`: Weekly progress summary
- `workoutsPlannedThisWeek` / `workoutsCompletedThisWeek`

**Explanations:**
- `why`: Array of up to 3 reasons explaining the recommendation

---

### Use Cases

- Drill-down from digest view
- Detailed pillar cards in UI
- Coach insights for specific areas

---

### Rate Limit

This endpoint counts as 10x API calls toward your hourly and daily limits.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier (24-character hex string) of the organization.
- `endUserId` (path) (required) — User identifier: email, externalUUID, or internal user ID.
- `pillarType` (path) (required) — Type of pillar to get details for.

**Responses:**
- 200 — Successfully retrieved pillar details.
- 400 — Invalid pillar type.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — User, organization, or pillar not found.

### POST /v1/orgs/{organizationId}/endusers/{endUserId}/insights/health-data

- operationId: `OrganizationEndUserHealthDataApi_logHealthData`
- summary: Push health data for an end user (batch)

## Health Data Ingestion

Pushes health data for an end user into the same pipeline that powers wearable (ROOK) syncing - the data feeds daily metrics, AI insights (digest and pillars), and chat context.

Supported `dataType`s per entry:

| dataType | What it feeds | Key fields |
|---|---|---|
| `activity` | Discrete activity sessions (workout-overlap reconciliation + daily activity rollup) | `externalId` (required), `startAt` (required), `activityType`, duration/distance/HR/pace/speed/cadence/elevation/swim fields |
| `sleep` | Sleep score and sleep HR/HRV fields | `sleepScore` or `sleepEfficiency`, `hrvRmssd`, `hrvSdnn`, `hrAvg`, `hrResting`, `hrMax`, `hrMin` |
| `steps` | Stored per-entry (ledger) | `steps` (required) |
| `body_metrics` | Body score (derived from BMI or weight/height) | `weightKg`, `heightCm`, `bmi` |
| `heart_rate` | Day HR/HRV fields | `hrvRmssd`, `hrvSdnn`, `hrAvg`, `hrResting`, `hrMax`, `hrMin` |
| `hydration` | Stored per-entry (ledger) | `waterIntakeMl` (required) |
| `oxygenation` | Stored per-entry (ledger) | `spo2AvgPct` (required) |
| `scores` | Partner-computed 0-100 scores (overall/physical/sleep/body/nutrition) | at least one score field |

`activityType` is a free-form label. Common values: Running, Cycling, Walking, Swimming, Hiking, Yoga, Strength Training, HIIT, Rowing. Include swim fields only for swimming sessions.

**Try it:** pick an Example from the request body dropdown (full day, mixed activities, day vitals, etc.).

**Idempotency:** each entry carries an `externalId` (required for `activity`, defaults to a per-day key for the other types). Re-posting the same `(dataType, externalId)` updates the existing record instead of duplicating it - safe to retry.

**Batch semantics:** up to 50 entries per request, processed independently and in order. The response always returns 200 with a per-entry `results` array (`created` / `updated` / `rejected` + reason) and a summary - check it instead of relying on the HTTP status.

**Merge semantics:** an explicit write wins - fields claimed by your entries overwrite the stored day value (including wearable-synced values); deleting an entry releases the claim. Backfill window: up to 90 days in the past, never in the future.

**Rate limit:** the request counts as 1 API call regardless of batch size.

**Related:** `DELETE .../insights/health-data/{dataType}/{externalId}`, `POST .../nutrition/log`, `GET .../insights/digest`, `GET .../insights/pillars/{pillarType}`.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.

**Request body (required):** application/json

**Responses:**
- 200 — Batch processed; check per-entry results for partial failures
- 400 — Malformed request body (invalid entry shape, unknown dataType, batch size exceeded)
- 401 — Authentication failed - API key is missing or invalid
- 403 — API key is not authorized for this organization
- 404 — User or organization not found

### DELETE /v1/orgs/{organizationId}/endusers/{endUserId}/insights/health-data/{dataType}/{externalId}

- operationId: `OrganizationEndUserHealthDataApi_deleteHealthDataEntry`
- summary: Delete a pushed health data entry

## Health Data Correction

Deletes a previously pushed entry by `(dataType, externalId)` and recomputes the affected day from the remaining entries.

- `activity`: removes the activity session and recomputes the day's activity rollup. Only API-pushed activities can be deleted (wearable-synced sessions are protected).
- Day-scoped types: releases the entry's field claims - fields are recomputed from your remaining entries for that day, or reset when nothing claims them anymore (a later wearable sync restores wearable-sourced values).

For entries posted without an `externalId`, use the day-derived default key `day-YYYY-MM-DD`.

To correct a value instead of removing it, simply re-POST the entry with the same `externalId` - the write is an idempotent upsert.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.
- `dataType` (path) (required) — Data type of the entry to delete.
- `externalId` (path) (required) — The externalId the entry was created with (or the day-derived default "day-YYYY-MM-DD").

**Responses:**
- 204 — Entry deleted and day recomputed
- 400 — Invalid dataType, or the entry is not API-pushed (wearable-synced activities cannot be deleted)
- 401 — Authentication failed - API key is missing or invalid
- 403 — API key is not authorized for this organization
- 404 — User, organization, or health data entry not found

## Tag: endusers-ai-chat

### POST /v1/chat/conversations

- operationId: `ChatApi_startConversation`
- summary: Start a new chat conversation

## Start Conversation

Creates a new conversation with the AI fitness coach and returns the assistant's response to the initial message.

---

### User Identification

Provide at least one of `userEmail` or `userExternalUUID` in the request body to identify the end user.

---

### Organization ID

- Use `"default"` to automatically resolve the organization from the API key
- Or provide a valid 24-character hex organization id

---

### Response

Returns the created conversation metadata and the AI assistant's first response message. The assistant message may include `metadata.contentPreview` (workouts, programs, exercises) or `metadata.pendingAction` when the AI proposes a state-changing action.

---

### Pending Actions

When the AI proposes a state-changing action (e.g. log nutrition, add workouts, generate workout), the assistant message will include `metadata.pendingAction` with an `id`, `type`, `summary`, and `payload`. Use the Confirm Action endpoint to execute it.

---

### Rate Limit

This endpoint counts as 10x API calls toward your hourly and daily limits.

---

### Related Endpoints

- `POST /v1/chat/conversations/{conversationId}/messages` - Send follow-up messages
- `GET /v1/chat/conversations` - List user conversations
- `POST /v1/chat/conversations/{conversationId}/actions/confirm` - Confirm a pending action

**Request body (required):** application/json

**Responses:**
- 201
- 400
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — End user not found in this organization.

### GET /v1/chat/conversations

- operationId: `ChatApi_getConversations`
- summary: Get user conversations

## List Conversations

Returns a paginated list of the end user's chat conversations, ordered by most recent activity.

---

### User Identification

Provide at least one of `userEmail` or `userExternalUUID` as query parameters to identify the end user.

---

### Pagination

| Parameter | Default | Max |
|-----------|---------|-----|
| `page` | 1 | - |
| `limit` | 20 | 100 |

---

### Related Endpoints

- `POST /v1/chat/conversations` - Start a new conversation
- `GET /v1/chat/conversations/{conversationId}/messages` - Get messages in a conversation
- `DELETE /v1/chat/conversations/{conversationId}` - Archive a conversation

**Parameters:**
- `organizationId` (query) (required) — Organization ID - use "default" to use the user's default organization
- `page` (query) — Page number
- `limit` (query) — Items per page
- `userEmail` (query) — User email address to identify the end user
- `userExternalUUID` (query) — External unique identifier for the end user

**Responses:**
- 200
- 400
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — End user not found in this organization.

### GET /v1/chat/conversations/{conversationId}/messages

- operationId: `ChatApi_getConversationMessages`
- summary: Get conversation messages

## Get Messages

Returns a paginated list of messages in a conversation, ordered chronologically.

---

### User Identification

Provide at least one of `userEmail` or `userExternalUUID` as query parameters to identify the end user. The user must be the owner of the conversation.

---

### Message Structure

Each message includes:
- `role`: `"user"` or `"assistant"`
- `content`: The message text
- `status`: `"pending"`, `"delivered"`, or `"failed"`
- `metadata`: Optional object containing `reasoning`, `topics`, `contentPreview`, `pendingAction`, or `confirmedResult`

---

### Pagination

| Parameter | Default | Max |
|-----------|---------|-----|
| `page` | 1 | - |
| `limit` | 50 | 100 |

---

### Related Endpoints

- `POST /v1/chat/conversations/{conversationId}/messages` - Send a new message
- `GET /v1/chat/conversations` - List conversations

**Parameters:**
- `conversationId` (path) (required)
- `organizationId` (query) (required) — Organization ID - use "default" to use the user's default organization
- `page` (query) — Page number
- `limit` (query) — Items per page
- `userEmail` (query) — User email address to identify the end user
- `userExternalUUID` (query) — External unique identifier for the end user

**Responses:**
- 200
- 400
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — End user or conversation not found in this organization.

### POST /v1/chat/conversations/{conversationId}/messages

- operationId: `ChatApi_sendMessage`
- summary: Send message to conversation

## Send Message

Sends a user message to an existing conversation and returns the AI coach's response.

---

### User Identification

Provide at least one of `userEmail` or `userExternalUUID` in the request body to identify the end user. The user must be the owner of the conversation.

---

### Response

Returns three objects:
- `userMessage` - The saved user message
- `assistantMessage` - The AI coach response
- `conversation` - Updated conversation metadata (messageCount, lastMessageAt)

---

### AI Capabilities

The AI coach can:
- List, search, and recommend workouts, programs, and exercises (returned in `metadata.contentPreview`)
- Propose state-changing actions like logging nutrition, adding/assigning workouts and plans, adapting or generating workouts and programs (returned in `metadata.pendingAction`)
- Provide personalized advice based on user profile, training history, and goals

---

### Rate Limit

This endpoint counts as 10x API calls toward your hourly and daily limits.

---

### Related Endpoints

- `POST /v1/chat/conversations` - Start a new conversation
- `POST /v1/chat/conversations/{conversationId}/actions/confirm` - Confirm a pending action
- `GET /v1/chat/conversations/{conversationId}/messages` - Get message history

**Parameters:**
- `conversationId` (path) (required)

**Request body (required):** application/json

**Responses:**
- 201
- 400
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — Conversation not found or does not belong to the user.

### POST /v1/chat/conversations/{conversationId}/actions/confirm

- operationId: `ChatApi_confirmAction`
- summary: Confirm a pending AI action

## Confirm Action

Executes a pending action proposed by the AI coach after user confirmation.

---

### How It Works

1. The AI proposes an action via `assistantMessage.metadata.pendingAction`
2. Your client shows a confirmation UI with the action `summary`
3. User confirms, and you call this endpoint with the `pendingActionId`
4. The action is executed and the result is returned

---

### Supported Action Types

| Type | Description | Response Field |
|------|-------------|----------------|
| `log_nutrition` | Log a meal or nutrition entry | `nutritionOverview` |
| `add_workouts` | Add workouts to user's library | `message` |
| `add_plans` | Add plans to user's library | `message` |
| `assign_workouts` | Assign workouts to the user | `message` |
| `assign_plans` | Assign plans to the user | `message` |
| `adapt_workout` | Adapt a workout on the fly | `adaptedWorkout` |
| `adapt_program` | Adapt a program on the fly | `adaptedProgram` |
| `generate_workout` | Generate a new workout | `generatedWorkout` |
| `generate_program` | Generate a new program | `generatedProgram` |

---

### Adapted/Generated Content

`adaptedWorkout` and `generatedWorkout` use the same response format as `POST /v1/orgs/{orgId}/workouts/{id}/adapt` and `POST /v1/orgs/{orgId}/workouts/generate` respectively. Same applies to program equivalents. Clients can reuse the same types and rendering logic.

---

### Related Endpoints

- `POST /v1/chat/conversations/{conversationId}/messages` - Send a message (may return pendingAction)
- `POST /v1/chat/conversations` - Start a conversation (may return pendingAction)

**Parameters:**
- `conversationId` (path) (required)

**Request body (required):** application/json

**Responses:**
- 200 — Action executed successfully.
- 400 — Action expired or invalid.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — Pending action not found or already used.

### DELETE /v1/chat/conversations/{conversationId}

- operationId: `ChatApi_archiveConversation`
- summary: Archive conversation

## Archive Conversation

Soft-deletes a conversation by setting its status to `archived`. The conversation and its messages are retained in the database but will no longer appear in the conversations list.

---

### User Identification

Provide at least one of `userEmail` or `userExternalUUID` as query parameters to identify the end user. The user must be the owner of the conversation.

---

### Related Endpoints

- `GET /v1/chat/conversations` - List active conversations
- `GET /v1/chat/conversations/{conversationId}/messages` - Get messages before archiving

**Parameters:**
- `conversationId` (path) (required)
- `organizationId` (query) (required) — Organization ID - use "default" to use the user's default organization
- `userEmail` (query) — User email address to identify the end user
- `userExternalUUID` (query) — External unique identifier for the end user

**Responses:**
- 200 — Conversation archived successfully.
- 400
- 401 — Authentication failed - API key is missing or invalid.
- 403 — API key is not authorized for this organization.
- 404 — End user or conversation not found in this organization.

## Tag: endusers-nutrition

### POST /v1/orgs/{organizationId}/endusers/{endUserId}/nutrition/photo-log

- operationId: `OrganizationEndUserNutritionApi_analyzeNutritionPhoto`
- summary: Analyze a meal photo for nutrition logging

## Photo Food Logging - Analyze

Uploads a meal photo and runs vision AI analysis. Returns a pending estimate (per-item food breakdown + macro totals) with a `pendingLogId`.

Nothing is logged at this step. Show the estimate to the user, then call the **Confirm** endpoint. The pending estimate expires after **1 hour** and is single-use. The photo is processed in memory and **never stored**.

Confirmed entries feed the same nutrition data as `POST /v1/me/nutrition/log` and chat logging: day totals, nutrition score, daily digest, and the nutrition insight pillar.

**Rate limit:** Counts as 10x API calls toward your hourly and daily limits.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.

**Request body (required):** multipart/form-data

**Responses:**
- 201
- 400 — Missing/invalid photo, invalid date, or the photo does not contain food
- 401 — Authentication failed - API key is missing or invalid
- 403 — API key is not authorized for this organization
- 404 — User or organization not found
- 502 — Vision analysis unavailable

### POST /v1/orgs/{organizationId}/endusers/{endUserId}/nutrition/photo-log/{pendingLogId}/confirm

- operationId: `OrganizationEndUserNutritionApi_confirmNutritionPhotoLog`
- summary: Confirm a pending photo nutrition log

## Photo Food Logging - Confirm

Confirms a pending estimate created by the analyze endpoint and writes the nutrition entry through the same pipeline as manual and chat logging (day totals summed, nutrition score recomputed, meal description and item breakdown persisted).

Optionally accepts corrected `totals` if the user adjusted the estimate. Single-use: a pending log cannot be confirmed twice; expired pending logs return 400.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.
- `pendingLogId` (path) (required) — The pending photo log id returned by the analyze endpoint.

**Request body (required):** application/json

**Responses:**
- 201
- 400 — Pending photo log has expired
- 401 — Authentication failed - API key is missing or invalid
- 403 — API key is not authorized for this organization
- 404 — Pending photo log not found or already used

### POST /v1/orgs/{organizationId}/endusers/{endUserId}/nutrition/log

- operationId: `OrganizationEndUserNutritionApi_logNutrition`
- summary: Log nutrition for an end user

## Direct Nutrition Logging

Logs a nutrition entry (calories and/or macros) for the user through the same pipeline as `POST /v1/me/nutrition/log`, photo logging, and chat logging: values are added to the day's running totals, the nutrition score is recomputed, and the data feeds the daily digest and the nutrition insight pillar.

At least one of `caloriesKcal`, `proteinG`, `carbsG`, `fatG` is required.

**Idempotency / corrections:** pass a stable `externalId` per meal. Re-posting the same `externalId` replaces the previous contribution instead of adding a duplicate (safe to retry, also handles date corrections), and enables `DELETE .../nutrition/log/{externalId}`. Without an `externalId` every call is additive.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.

**Request body (required):** application/json

**Responses:**
- 201
- 400 — No nutrition values provided or invalid date
- 401 — Authentication failed - API key is missing or invalid
- 403 — API key is not authorized for this organization
- 404 — User or organization not found

### DELETE /v1/orgs/{organizationId}/endusers/{endUserId}/nutrition/log/{externalId}

- operationId: `OrganizationEndUserNutritionApi_deleteNutritionLog`
- summary: Delete a logged nutrition entry

## Nutrition Log Correction

Deletes a nutrition entry previously logged with an `externalId`, subtracting its values from the day's totals (floored at 0) and recomputing the nutrition score.

Only entries created with an `externalId` can be deleted this way.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.
- `externalId` (path) (required) — The externalId the nutrition log entry was created with.

**Responses:**
- 204 — Entry deleted and day totals recomputed
- 401 — Authentication failed - API key is missing or invalid
- 403 — API key is not authorized for this organization
- 404 — User, organization, or nutrition entry not found

## Tag: endusers-plans

### GET /v1/orgs/{organizationId}/endusers/{endUserId}/plans/active/progress

- operationId: `OrganizationEndUserPlansApi_getActivePlanProgress`
- summary: Get plan progress for an end user

## Plan progress

Returns per-workout progress for the end user's active plan. Keys are position indices (as strings); values include completed, latestCompletionDate, workoutName, difficulty, duration, categories, workoutId, positionInPlan.

Only workout sessions completed on or after the active plan's `startDate` are credited toward `completed` and `latestCompletionDate`. Sessions visible elsewhere in the app may predate that window.

When the end user has no active plan, the endpoint responds with `200 OK` and `{ "data": null }` (rather than 404), so callers can render a "no active plan" state without treating it as an error.

---

### Related endpoints

- `GET .../plans/active` - Get active plan details
- `POST .../plans/start` - Start a plan
- `POST .../plans/{planId}/end` - End active plan

**Parameters:**
- `organizationId` (path) (required) — The organization id (24-character hex string).
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.

**Responses:**
- 200 — Plan progress for the end user, or `data: null` when there is no active plan.
- 401
- 403
- 404 — The end user could not be resolved (not found or not a member of this organization).

### GET /v1/orgs/{organizationId}/endusers/{endUserId}/plans/active

- operationId: `OrganizationEndUserPlansApi_getActivePlan`
- summary: Get active plan for an end user

## Active plan

Returns the end user's active workout plan if any, with schedule (workoutsPerWeek, durationInWeeks, currentWeekNumber, daysSinceStart, completedWorkouts, totalWorkouts, completionPercentage).

---

### Related endpoints

- `GET .../plans/active/progress` - Get per-workout progress
- `POST .../plans/start` - Start a plan
- `POST .../plans/{planId}/end` - End active plan

**Parameters:**
- `organizationId` (path) (required) — The organization id (24-character hex string).
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.

**Responses:**
- 200 — Active plan or null.
- 401
- 403
- 404 — The end user could not be resolved (not found or not a member of this organization).

### POST /v1/orgs/{organizationId}/endusers/{endUserId}/plans/start

- operationId: `OrganizationEndUserPlansApi_startPlan`
- summary: Start a plan for an end user

## Start plan

Starts the given plan as the end user's active plan. Any existing active plan is deactivated.

Call this once when the user begins a program (for example at enrollment). If the same plan is already active and `restart` is not `true`, the existing access record is returned without changing `startDate` — safe for idempotent "ensure enrolled" calls.

Plan progress (`GET .../plans/active/progress`) only credits workout sessions completed on or after the plan's `startDate`. Set `restart: true` to reset that progress window intentionally (for example when the user starts the program over).

Optional `autoAdapt: true` enables automatic next-workout adaptation for this active enrollment. Toggle later via `PATCH .../plans/active/settings`.

---

### Related endpoints

- `GET .../plans/active` - Get active plan details
- `GET .../plans/active/progress` - Get plan progress
- `PATCH .../plans/active/settings` - Update active plan settings (`autoAdapt`)
- `POST .../plans/{planId}/end` - End active plan
- `GET /v1/plans/{id}` - Plan catalog details
- `GET /v1/plans/{id}/workouts` - Workouts in plan

**Parameters:**
- `organizationId` (path) (required) — The organization id (24-character hex string).
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.

**Request body (required):** application/json

**Responses:**
- 201 — Plan access record.
- 401
- 403
- 404 — The end user could not be resolved (not found or not a member of this organization).

### PATCH /v1/orgs/{organizationId}/endusers/{endUserId}/plans/active/settings

- operationId: `OrganizationEndUserPlansApi_updateActivePlanSettings`
- summary: Update active plan settings for an end user

## Active plan settings

Updates per-enrollment settings on the user's active plan access record.

| Field | Description |
|-------|-------------|
| `autoAdapt` | When `true`, the next scheduled workout may be adapted automatically after sessions, feedback, digest refresh, or profile changes. When `false`, disables auto-adapt until re-enabled. |

Returns the updated plan access record (same shape as `GET .../plans/active`).

**Parameters:**
- `organizationId` (path) (required) — The organization id (24-character hex string).
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.

**Request body (required):** application/json

**Responses:**
- 200 — Updated plan access record.
- 404

### POST /v1/orgs/{organizationId}/endusers/{endUserId}/plans/{planId}/end

- operationId: `OrganizationEndUserPlansApi_endPlan`
- summary: End an active plan for an end user

## End plan

Ends the specified plan for the end user. The plan must be the user's current active plan. Returns 404 if no active plan matches the given planId.

---

### Related endpoints

- `GET .../plans/active` - Get active plan details
- `GET .../plans/active/progress` - Get plan progress
- `POST .../plans/start` - Start a plan

**Parameters:**
- `organizationId` (path) (required) — The organization id (24-character hex string).
- `endUserId` (path) (required) — The user identifier. Can be email, externalUUID, or internal user ID.
- `planId` (path) (required) — The plan ID to end.

**Responses:**
- 200 — Updated plan access record.
- 401
- 403
- 404

## Tag: endusers-management

### GET /v1/orgs/{organizationId}/endusers

- operationId: `OrganizationEndUsersApi_getOrganizationEndUsers`
- summary: Get organization end users

Retrieves a paginated list of end users associated with the organization.

`endUserId` is present only when the API key is for this organization and the end user has an active (non-suspended) team membership.

### Pagination
Use the standard `offset`/`limit` cursors. The response contains the canonical `links` envelope:
- `links.next` — fully qualified URL for the next page (omitted on the last page)
- `links.total` — total number of matching end users

The top-level `total` field is **deprecated** and kept only for backwards compatibility; new integrations should read `links.total` instead.

**Parameters:**
- `organizationId` (path) (required) — The unique identifier (24-character hex string) of the organization.
- `offset` (query) — Offset for pagination. Defaults to 0.
- `limit` (query) — Number of items per page. Defaults to 20, Max 100.
- `status` (query) — Filter clients by status: "active", "suspended", or "all"
- `sortBy` (query) — Sort clients by "name", "status", or "joined" (default)
- `sortDirection` (query) — Sort direction: "asc" (default) or "desc"

**Responses:**
- 200 — Successfully retrieved list of end users for the organization.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — The API key is not associated with the requested organization.
- 404 — The specified organization ID does not exist.

### DELETE /v1/orgs/{organizationId}/endusers

- operationId: `OrganizationEndUsersApi_removeEndUser`
- summary: Remove end user from organization

## Permanently Remove User

Completely removes a user from your organization. This action is **irreversible** and will:

- Delete all user data and progress
- Remove access to organization content
- Clear workout history and sessions
- Cannot be undone

---

### ⚠️ Important Considerations

**Before Removing:**
- User will lose all progress and data
- Consider suspending instead for temporary access removal
- Backup any important user data first
- Notify user if required by your policies

**Use Cases:**
- GDPR compliance (right to be forgotten)
- Account closure requests
- Data cleanup and maintenance
- User requested permanent deletion

---

### 🔄 Alternative Actions

**Temporary Removal:**
- `PUT /v1/orgs/{orgId}/endusers/suspend` - Temporarily disable access
- User data preserved, can be reactivated later
- Better for temporary access issues

**Permanent Removal:**
- `DELETE /v1/orgs/{orgId}/endusers` - This endpoint
- Complete data deletion, cannot be undone
- Use for final account closure

---

### 💡 Best Practices

✅ **Verify user identity** - Double-check email/UUID before deletion  
✅ **Consider suspension first** - Try suspend before permanent removal  
✅ **Log deletion events** - Track for audit and compliance  
✅ **Handle gracefully** - 404 is expected if user already removed  
❌ **Don't delete active users** - Consider suspending instead

---

### 🔗 Related Endpoints

- Find User: `GET /v1/orgs/{orgId}/endusers/find` - Locate user first
- Suspend User: `PUT /v1/orgs/{orgId}/endusers/suspend` - Temporary removal
- Reactivate User: `PUT /v1/orgs/{orgId}/endusers/reactivate` - Restore suspended user

**Parameters:**
- `organizationId` (path) (required) — The unique identifier (24-character hex string) of the organization.

**Request body (required):** application/json

**Responses:**
- 200 — Successfully removed end user from organization.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — The API key is not associated with the requested organization.
- 404 — End user not found in the organization.

### POST /v1/orgs/{organizationId}/endusers/invite

- operationId: `OrganizationEndUsersApi_inviteEndUsers`
- summary: Invite end users to organization

## Add Users to Your Organization

Send invitations to one or more end users. Creates **pending** invites; each user accepts in the member experience (or via your branded Redirect URL) to gain access to your organization's content and features.

---

### Identifier Options

**Email Only:**
- User invited via email address
- Hyperhuman creates account on first login
- Email must be valid and unique

**Email + External UUID:**
- Map to your existing user system
- Track users across platforms
- Sync user data between systems
- **Recommended for integrations**

---

### Invite email (`sendInviteEmail`)

By default Hyperhuman sends a **transactional invite email** when a new pending invite is created.

| Request `sendInviteEmail` | Organization Invite emails setting | Hyperhuman invite email |
|-----------------------------|------------------------------------|-------------------------|
| omitted | on (default) | sent for new invites |
| omitted | off | not sent |
| `false` | any | not sent for this batch |
| `true` | any | sent for this batch (overrides org off) |

- **Pending invites are always created** whether email is sent or not.
- Response **`inviteEmailSent`** is the effective send **policy** for *new* invites in this request — not a per-user delivery receipt.
- Updating an existing pending invite **never re-sends** email, even when policy is on.
- When you send your own invite email, set `sendInviteEmail: false` (or turn off **Invite emails** in the Hyperhuman team app) and point users at your organization **Redirect URL**.

---

### Typical User Lifecycle

1. **Invite**: `POST /v1/orgs/{orgId}/endusers/invite` → Add users
2. **Verify**: `GET /v1/orgs/{orgId}/endusers/find?email=...` → Check status
3. **List**: `GET /v1/orgs/{orgId}/endusers` → View all users
4. **Manage**: Suspend/reactivate as needed
5. **Remove**: `DELETE /v1/orgs/{orgId}/endusers` → Permanent deletion

---

### Best Practices

- **Batch invitations** — up to 100 users per request
- **Use externalUUID** — map to your user IDs for sync
- **Validate emails** — check format before sending
- **Handle failures** — review the `failed` array
- **Custom invite email** — `sendInviteEmail: false` when you email clients yourself; use your Redirect URL
- **Don't re-invite blindly** — check if the user exists first; pending updates do not re-send email

---

### Error Handling

Common failure reasons:
- `User already exists` - Email already in organization
- `Invalid email format` - Email validation failed
- `Duplicate in batch` - Same email appears twice
- `Organization quota exceeded` - User limit reached

---

### Related Endpoints

- Find User: `GET /v1/orgs/{orgId}/endusers/find`
- List Users: `GET /v1/orgs/{orgId}/endusers`
- Suspend User: `PUT /v1/orgs/{orgId}/endusers/suspend`
- Remove User: `DELETE /v1/orgs/{orgId}/endusers`

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.

**Request body (required):** application/json

**Responses:**
- 201 — Invitation processing complete. Returns success count, any failures, optionally invited with endUserId when the user already has an active membership, and inviteEmailSent (effective transactional invite-email policy for new invites in this request).
- 401 — Authentication failed - API key is missing or invalid.
- 403 — The API key is not associated with the requested organization.

### PUT /v1/orgs/{organizationId}/endusers/suspend

- operationId: `OrganizationEndUsersApi_suspendEndUser`
- summary: Suspend end user in organization

## Temporarily Suspend User Access

Suspends a user's access to your organization content while preserving their data and progress. This is a **reversible action** that:

- Blocks access to workouts and plans
- Preserves user data and progress
- Can be reactivated later
- User cannot log in during suspension

---

### 🎯 When to Suspend

**Temporary Access Issues:**
- Payment/billing problems
- Policy violations (temporary)
- Account security concerns
- User requested break

**vs. Permanent Removal:**
- Suspension: Temporary, data preserved
- Deletion: Permanent, data lost
- Choose suspension for reversible actions

---

### 🔄 User Experience During Suspension

**What Users See:**
- Cannot access organization content
- Login blocked with suspension message
- All data and progress preserved
- Can be reactivated by admin

**What Admins Can Do:**
- View suspended users in user list
- Reactivate anytime with reactivate endpoint
- See suspension status in user details
- Track suspension history

---

### 💡 Best Practices

✅ **Communicate with user** - Explain why and duration  
✅ **Set clear policies** - Define suspension criteria  
✅ **Monitor suspension duration** - Don't leave users suspended indefinitely  
✅ **Use for temporary issues** - Permanent problems may need deletion  
✅ **Document reasons** - Track why users were suspended

---

### 🔗 Related Endpoints

- Find User: `GET /v1/orgs/{orgId}/endusers/find` - Locate user first
- Reactivate User: `PUT /v1/orgs/{orgId}/endusers/reactivate` - Restore access
- Remove User: `DELETE /v1/orgs/{orgId}/endusers` - Permanent deletion
- List Users: `GET /v1/orgs/{orgId}/endusers` - View all users (including suspended)

**Parameters:**
- `organizationId` (path) (required) — The unique identifier (24-character hex string) of the organization.

**Request body (required):** application/json

**Responses:**
- 200 — Successfully suspended end user in organization.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — The API key is not associated with the requested organization.
- 404 — End user not found in the organization.

### PUT /v1/orgs/{organizationId}/endusers/reactivate

- operationId: `OrganizationEndUsersApi_reactivateEndUser`
- summary: Reactivate end user in organization

## Restore Suspended User Access

Reactivates a previously suspended user, restoring full access to your organization content. This action:

- Restores login access immediately
- Preserves all user data and progress
- Returns user to active status
- Can be done anytime after suspension

---

### 🎯 When to Reactivate

**Common Scenarios:**
- Payment issues resolved
- Policy violations addressed
- Security concerns cleared
- User requested restoration
- Administrative error correction

**Prerequisites:**
- User must be currently suspended
- Cannot reactivate already active users
- Cannot reactivate permanently deleted users

---

### 🔄 User Experience After Reactivation

**Immediate Access:**
- User can log in immediately
- Full access to organization content
- All previous data and progress intact
- No data loss from suspension period

**Status Changes:**
- User status changes from "suspended" to "active"
- Appears in active user lists
- Can participate in workouts and plans
- Normal user functionality restored

---

### 💡 Best Practices

✅ **Verify suspension reason resolved** - Ensure original issue is fixed  
✅ **Communicate with user** - Notify them of reactivation  
✅ **Monitor user activity** - Ensure they can access content  
✅ **Update user records** - Track reactivation for audit  
✅ **Test user access** - Verify login and content access works

---

### 🔗 Related Endpoints

- Find User: `GET /v1/orgs/{orgId}/endusers/find` - Check current status
- Suspend User: `PUT /v1/orgs/{orgId}/endusers/suspend` - Suspend if needed again
- List Users: `GET /v1/orgs/{orgId}/endusers` - View all users
- Remove User: `DELETE /v1/orgs/{orgId}/endusers` - Permanent deletion (if needed)

**Parameters:**
- `organizationId` (path) (required) — The unique identifier (24-character hex string) of the organization.

**Request body (required):** application/json

**Responses:**
- 200 — Successfully reactivated end user in organization.
- 401 — Authentication failed - API key is missing or invalid.
- 403 — The API key is not associated with the requested organization.
- 404 — End user not found in the organization.

### GET /v1/orgs/{organizationId}/endusers/find

- operationId: `OrganizationEndUsersApi_getEndUserById`
- summary: Find end user by email or external UUID

## Find Specific User

Retrieve a single user from your organization by email or external UUID. Useful for checking if a user exists before inviting.

---

### 📋 Identifier Options

**By Email:**
```bash
GET /v1/orgs/{orgId}/endusers/find?email=john.doe@acme.com
```

**By External UUID:**
```bash
GET /v1/orgs/{orgId}/endusers/find?externalUUID=ext-user-12345
```

**Required:** Must provide either `email` OR `externalUUID` (not both)

---

### 🎯 Use Cases

✅ **Pre-invite Check** - Verify user doesn't exist before inviting  
✅ **User Lookup** - Find user details for support queries  
✅ **Sync Validation** - Confirm external UUID mapping is correct  
✅ **Duplicate Prevention** - Check before creating user in your system

---

### 💡 Best Practices

✅ **Email is case-insensitive** - john@acme.com = John@acme.com  
✅ **Use external UUID for integrations** - More reliable than email  
✅ **Handle 404 gracefully** - User not found is an expected case  
✅ **Cache lookups** - Reduce API calls for frequently accessed users

---

### 🔗 Related Endpoints

- Invite Users: `POST /v1/orgs/{orgId}/endusers/invite` - Add new users
- List Users: `GET /v1/orgs/{orgId}/endusers` - Browse all users
- Suspend User: `PUT /v1/orgs/{orgId}/endusers/suspend` - Deactivate user
- Remove User: `DELETE /v1/orgs/{orgId}/endusers` - Permanently delete

**Parameters:**
- `organizationId` (path) (required) — The unique identifier of the organization.
- `email` (query) — Email address of the user to find (case-insensitive)
- `externalUUID` (query) — External UUID of the user to find

**Responses:**
- 200 — Successfully retrieved end user profile. endUserId is present only when the end user has an active (non-suspended) team membership.
- 400 — Missing required identifier (email or externalUUID).
- 401 — Authentication failed - API key is missing or invalid.
- 403 — The API key is not associated with the requested organization.
- 404 — End user not found in the organization.
