# Kinellect integration documentation (complete) Source commit: 16cf66585dcc4895f8de9e1f737bf6eab7210af8. This file contains every guide followed by both OpenAPI 3.1 specifications. The specifications are authoritative for field names, types, limits and enums. # Introduction Kinellect is a coaching service for fitness and rehabilitation products. Your application keeps its users, content, subscriptions and user interface. Kinellect adds a coach on top: it holds conversations with your users, builds validated workout drafts from **your** exercise catalog, decides what a user should do today (workout, rest or mobility), reacts to completed workouts, and remembers coaching preferences. Kinellect is **generic**. It contains no knowledge of any particular client. Everything specific to your product arrives through your configuration, the context you send about each user, and the connector endpoints you implement. ## The two directions of integration Integrating means building two things: 1. **Calls from your backend to the Kinellect API.** You activate a configuration, register users, send their context, start conversations, and request workouts and daily plans. 2. **A connector that Kinellect calls.** A small HTTPS service on your side that answers questions about your content and a user's current situation, and performs actions you allow, such as saving a workout. ```mermaid flowchart LR subgraph You["Your platform"] App["Your app / backend"] Conn["Your connector (HTTPS)"] DB[("Your users, catalog,
schedules, history")] end subgraph K["Kinellect"] API["Kinellect API /v1"] W["Durable workers"] end App -- "Bearer API token" --> API API --> W W -- "Bearer connector secret" --> Conn Conn --> DB ``` Your backend calls Kinellect with an **API token** that Kinellect issues to you. Kinellect calls your connector with a **connector secret** that you issue to Kinellect. The two credentials are independent. ## Who owns what | Kinellect owns | You own | | --- | --- | | Coaching conversations and their history | User identity, accounts and authentication | | Coach memory (coaching preferences) | Subscriptions, entitlements and billing | | Workout drafts it generates and daily decisions | Your exercise and workout catalog | | Safety validation of everything it proposes | Accepted workouts, schedules and workout history | | Durable processing, retries and usage accounting | Your user interface and notifications | Kinellect never writes to your systems directly. When a user accepts a workout, Kinellect asks your connector to save it, and your connector decides. ## Important properties - **Asynchronous by design.** Anything that involves a model or your connector runs as a durable **Operation**. You receive `202 Accepted` with an Operation reference and poll it to a terminal state. There are no webhooks in v1. - **Idempotent writes.** Every write takes an `Idempotency-Key`. Retrying the same request with the same key is always safe and returns the original result. - **Strict contracts.** Unknown fields are rejected, and so are values outside documented limits. Connector responses must contain exactly the documented keys. - **Tenant isolation.** Each client works inside its own tenant environment. References that belong to another tenant behave exactly like references that do not exist. - **Truthful output.** Kinellect renders user-facing text from verified sources and recorded outcomes. It never tells a user that something happened unless your connector confirmed it. ## What this documentation contains - **Onboarding** walks through the steps from zero to a working integration. - **Core concepts** and **API conventions** cover the ideas and rules every endpoint shares. - **Quickstart** is one complete request sequence you can run with `curl`. - The **capability guides** cover conversation, workout drafts, daily plans, workout acceptance and workout completion. - **Build your connector** specifies what your HTTPS service must implement. - **Using these docs with an LLM** explains how to hand this material to a coding assistant. - The **API reference** holds both OpenAPI 3.1 specifications: the Kinellect API you call, and the Connector API you implement. # Onboarding These steps take you from nothing to a working integration. Steps 1 and 3 need the Kinellect team; the rest you do yourself. ## 1. Receive your environment and API token The Kinellect team creates a **tenant environment** for you and issues an **API token**. You receive: - the **base URL** of the Kinellect API, `https://api.kinellect.com`, used as `KINELLECT_BASE_URL` throughout these docs; - an **API token**. It is shown once, so store it in your secret manager immediately; - the **scopes** attached to the token (see [Authentication and scopes](#authentication-and-scopes)). Tell the team which capabilities you will use, so the token carries matching scopes. A full integration needs all eight: `configuration:write`, `context:write`, `conversation:write`, `conversation:read`, `workout:write`, `workout:read`, `coaching:write` and `coaching:read`. Each `:read` scope is what lets you poll that capability's Operations. Tokens stay valid until they are revoked, and each one can be revoked on its own. Ask for a new token rather than sharing one between systems. Kinellect cannot show a token again after issuing it; if you lose it, ask for a new one and have the old one revoked. ## 2. Build your connector Build the HTTPS service described in [Build your connector](#build-your-connector). Implement only the endpoints for the capabilities you plan to enable. For example, a conversation-only integration with exercise lookups needs `capabilities`, `context/resolve`, `content/exercises/search` and `content/exercises/resolve`. Your connector must be reachable over public HTTPS before step 4, because activation calls it. ## 3. Register your connector with Kinellect Send the Kinellect team: - your connector **base URL** (HTTPS); - a **connector secret**: a long random bearer token that Kinellect will send in `Authorization: Bearer ...` on every connector call. Share it through a secure channel, never by email. Kinellect stores the secret in its managed secret store and returns a **connector registration reference** (`connector_registration_ref`, a UUID). You cannot change the URL or secret through the API; ask the Kinellect team to register a new one when they change. ## 4. Activate your first client configuration Decide which capabilities to enable and which policies apply, then call: ```http PUT /v1/client-configurations/1/activate ``` Activation checks your choices, calls your connector's `capabilities` endpoint, and stores configuration version 1 as immutable. To change anything later, activate version 2, then 3, and so on. See [Client configuration](#client-configuration). ## 5. Register each user Kinellect calls your users **Subjects**. You register a user by creating their first Conversation Thread with your own user identifier: ```http POST /v1/threads {"external_subject_ref": "your-user-8841", "main": true} ``` The response contains `subject_ref`, Kinellect's UUID for that user. **Store the mapping** from your user id to `subject_ref`: - every other endpoint identifies the user by `subject_ref`; - your connector receives `subject_ref`, not your user id. Repeating the call with the same `external_subject_ref` always yields the same `subject_ref`. > `external_subject_ref` is visible only to Kinellect and your integration. Use an opaque, stable identifier, not an email address or name. ## 6. Keep each user's context current Send the facts Kinellect needs about the user (goal, experience, available days, equipment, safety constraints, recovery) as an immutable, versioned **Subject context**. Push it with `PUT /v1/subjects/{subject_ref}/context/{context_version}` whenever those facts change. You can also let Kinellect pull it from your connector's `context/resolve` endpoint. See [Subject context](#subject-context). ## 7. Use the capabilities With configuration and context in place you can: - hold conversations: [Conversation](#conversation); - request workout drafts: [Workout drafts](#workout-drafts); - request daily plans: [Daily plans](#daily-plans); - save or schedule a proposed workout after explicit user approval: [Workout acceptance](#workout-acceptance); - report completed workouts and receive a coach reaction: [Workout completion](#workout-completion). ## Go-live checklist - [ ] The API token lives in a secret manager and is never shipped to mobile or web clients. All Kinellect calls come from your backend. - [ ] Every write sends a fresh `Idempotency-Key`, and retries reuse the original key. - [ ] Your code handles every Operation state: `completed`, `failed`, `cancelled` and `expired`. - [ ] You honour `429` and its `Retry-After` header. - [ ] Your connector returns exactly the documented keys and answers within 2 seconds. - [ ] Your connector verifies the bearer secret and the `X-Kinellect-Tenant` header on every call. - [ ] You store the mapping between your user ids and `subject_ref`. - [ ] Context is pushed, or resolvable through the connector, before you request daily plans or workout drafts. # Core concepts ## Tenant environment A tenant environment is your isolated space in Kinellect. Your API token, connector registration, configurations, users, conversations and Operations all belong to it. Nothing crosses between tenant environments. A reference from another tenant behaves exactly like one that does not exist, so it returns `404`. ## Client configuration A client configuration describes how Kinellect coaches for your product: who you are, which capabilities are on, which actions are allowed, and the limits that apply. Each configuration **version** is immutable. You activate version `1`, then `2`, and so on. The newest activated version is current. Each Operation records the configuration version it was admitted under, and results report the versions they used. For example, a daily plan decision includes `configuration_version` and `context_version`. Some work rechecks currentness before acting: workout acceptance, for instance, verifies again that the configuration and context still allow the action before your connector is called. | Field | Purpose | | --- | --- | | `client_description`, `product_description` | Plain-language description of your company and product, used to frame coaching (up to 2000 bytes each). | | `coaching_domain` | Short domain label, for example `fitness coaching` or `knee rehabilitation`. | | `tone` | `supportive`, `direct` or `neutral`. | | `terminology` | Up to 20 terms your product uses, for example `session` instead of `workout`. | | `enabled_capabilities` | What Kinellect may do (see the table below). | | `safety_policy` | Currently always `fitness_coach_safety_v1`. | | `action_policy` | Whether Kinellect may execute actions (see [Action policy](#action-policy)). | | `connector_registration_ref` | The registration you received during onboarding. | | `workout_generation_policy` | Structural limits for generated workouts. Required when `workout.generate` is enabled. | | `daily_decision_policy` | Rules for daily plans. Required exactly when `daily.decide` is enabled. | | `operation_admission_policies` | Request-rate and concurrency limits per capability. | | `operation_usage_policies` | Token and estimated-cost ceilings per capability. | ## Capabilities Capabilities switch features on. Some need your connector, and those must also appear in your connector's `capabilities` response or activation fails. | Capability | What it enables | Needs your connector | | --- | --- | --- | | `conversation.respond` | Coaching conversations. | No | | `context.resolve` | Kinellect may pull a user's context from you when none is current. | Yes | | `exercise.search` | Search your exercise catalog. | Yes | | `exercise.resolve` | Look up exercises by reference and verify them. | Yes | | `workout.search` | Find existing workouts in your catalog. | Yes | | `workout.resolve` | Load and verify an existing workout. | Yes | | `workout.prescription.count` | Repetition-based prescriptions (for example 3 sets of 10). | Yes | | `workout.prescription.time` | Time-based prescriptions (for example 3 sets of 30 seconds). | Yes | | `workout.generate` | Generate workout drafts. | No, but requires the bundle below | | `workout.warmup.generate` | Add warm-up sections to generated workouts. | No, requires `workout.generate` | | `workout.cooldown.generate` | Add cool-down sections to generated workouts. | No, requires `workout.generate` | | `daily.decide` | Daily plans: workout, rest or mobility. | Depends on the daily policy | | `workout.save` | Save an accepted workout to the user's library. | Yes | | `workout.save_and_schedule` | Save and schedule an accepted workout atomically. | Yes | | `preference.propose` | The coach may propose a preference change. | No | | `preference.update` | The coach may update a preference in your system. | Yes | | `memory.preference.write` | The coach may remember a preference the user stated explicitly. | No | Activation rejects incomplete combinations with `422`: - **Existing workouts:** `workout.search` or `workout.resolve` requires `workout.search`, `workout.resolve`, `exercise.resolve` and at least one prescription type. - **Generated workouts:** `workout.generate` requires `exercise.search`, `exercise.resolve`, at least one prescription type and a `workout_generation_policy`. The policy's `maximum_total_count_repetitions` must be set exactly when count prescriptions are enabled, and `maximum_total_timed_work_seconds` exactly when time prescriptions are enabled. - **Warm-up and cool-down** sections require `workout.generate`. ## Action policy `action_policy` controls whether Kinellect may carry out actions on your side, such as updating a preference or saving a workout: | Value | Behaviour | | --- | --- | | `auto_execute` | Actions run when requested. Workout acceptance still needs your explicit acceptance request. | | `confirmation_required` | Actions run only with explicit user approval, for example `approval: "explicit_user_approval"` on a workout acceptance. | | `professional_review_required` | Kinellect does not execute actions. Use this when a professional must review first. | | `proposal_only` | Kinellect may propose but never executes. | | `rejected` | No actions. | ## Subjects A Subject is one of your users as Kinellect sees them. Kinellect knows only the opaque `external_subject_ref` you chose and the `subject_ref` UUID it assigned. You create a Subject implicitly with your first `POST /v1/threads` for that `external_subject_ref`. ### Deleting and reactivating a Subject Call `DELETE /v1/subjects/{subject_ref}` with an `Idempotency-Key` when a user leaves your product. It needs the `context:write` scope and returns `200`. Deleting: - places a tombstone on the Subject, which blocks accidental re-registration; - cancels the Subject's queued and running Operations; - erases the Subject's coaching data in the same request: conversations, coach memory, context, workout drafts, daily plans, workout acceptances and completions, and Operations with their results. Only the minimal tombstone and the records needed to answer retried delete requests remain; - makes every new request about that Subject fail with `409` and `detail` `subject_deleted`. This includes new Threads for the same `external_subject_ref`, Turns, context updates, workout requests and completion events. Check `detail` rather than the status alone, because other conflicts also return `409`. Deleting an already deleted Subject also returns `200`. Retrying with the same `Idempotency-Key` returns the original outcome. After deletion, polling one of the Subject's Operations returns `404`, and retrying any earlier request for that Subject returns `409` `subject_deleted`. To bring a returning user back, call `POST /v1/subjects/{subject_ref}/reactivate` with an `Idempotency-Key`. It also needs `context:write` and returns `200`. Reactivating a Subject that is not deleted returns `422`. Erased data can remain in Kinellect's encrypted database backups until those backups expire. The backup retention period will be confirmed when the production environment is provisioned. Reactivating a Subject does not restore erased data. ## Subject context Subject context is your authoritative description of a user at a point in time: goal, experience level, available days, equipment, safety constraints, recovery and, optionally, recent activity for daily plans. Each version is immutable and has a validity window: - a version is **current** while `effective_at <= now < valid_through`; - versions for one Subject must increase, and later versions must not move backwards in time; - when no version is current, Kinellect asks your connector (`context/resolve`) if `context.resolve` is enabled. Otherwise, work that needs context fails with `subject_context_unavailable`. Keep `valid_through` realistic. A window that is too long lets Kinellect coach from stale facts. A window that is too short causes avoidable connector calls or failures. **Safety constraints** are free-text identifiers (for example `avoid-overhead-loading`) matched against the `safety_identifiers` your connector returns for each exercise. **Exercise constraints** are typed: a `hard_exclusion` never appears in a proposal, a `soft_avoidance` is avoided when an alternative exists, and a `lifted` constraint no longer applies. ## Conversation Threads and Turns A Thread is one conversation with one Subject. Mark a user's primary conversation with `main: true`. You send a user message as a **Turn**. Kinellect answers asynchronously, and the answer arrives as the result of an Operation. ## Operations Every request that involves a model or your connector creates a durable **Operation**. The request returns `202 Accepted` at once with an `operation_ref`. You poll the Operation until it reaches a terminal state. ```mermaid stateDiagram-v2 [*] --> queued queued --> running: a worker claims it queued --> cancelled: deadline passed before it ran running --> completed: result stored running --> failed: safe failure (error_code) completed --> expired: retention period ended failed --> expired cancelled --> expired ``` | State | Meaning | What to do | | --- | --- | --- | | `queued` | Accepted and waiting for a worker. | Keep polling. | | `running` | A worker is processing it. | Keep polling. | | `completed` | Finished. `result` holds the outcome. | Use the result. | | `failed` | Finished without a result. `error_code` says why. | Show a safe message. Some endpoints also return `fallback_text`. | | `cancelled` | Stopped before it produced a result. | Treat as failed. | | `expired` | The Operation outlived its retention period, so the result is gone. | Do not rely on old Operations; store what you need when it completes. | Terminal results never change. Polling a completed Operation again returns the same document. ## Admission and usage limits Each capability can carry two kinds of limit, set in the client configuration: - **Admission policies** (`operation_admission_policies`) limit how many Operations of a capability start within a rolling window (`request_window_seconds`, request maximums) and how many run at once (active maximums). A **soft** maximum is recorded for monitoring. A **hard** maximum rejects new requests with `429` when `hard_enforcement_enabled` is `true`. - **Usage policies** (`operation_usage_policies`) cap estimated tokens and cost, per Operation and per UTC day. Kinellect reserves the worst-case usage **before** each model call. When a hard ceiling would be exceeded, Kinellect makes no model call and the Operation ends in a failed state. The exact `error_code` depends on the capability. With `hard_enforcement_enabled: false` a policy only observes. A capability without a usage policy is observed but never capped. Ask the Kinellect team for recommended starting values. # API conventions These rules apply to every endpoint of the Kinellect API. ## Base URL and versioning All paths start with `/v1`. The base URL for your environment is issued during onboarding. The v1 contract changes only in backward-compatible ways, such as new optional fields or new endpoints. A breaking change would ship as a new major version with a new path prefix. Because requests reject unknown fields (see below), a new optional **request** field is something you opt into. Build response parsing to ignore fields you do not recognise, so that new response fields never break you. ## Authentication and scopes Send your API token on every request: ```http Authorization: Bearer ``` A missing or invalid token returns `401`. A valid token without the required scope returns `403`. | Scope | Endpoints | | --- | --- | | `configuration:write` | `PUT /v1/client-configurations/{configuration_version}/activate` | | `context:write` | `PUT /v1/subjects/{subject_ref}/context/{context_version}`, `DELETE /v1/subjects/{subject_ref}`, `POST /v1/subjects/{subject_ref}/reactivate` | | `conversation:write` | `POST /v1/threads`, `POST /v1/threads/{thread_ref}/turns` | | `conversation:read` | `GET /v1/operations/{operation_ref}` | | `workout:write` | `POST /v1/workout-drafts`, `POST /v1/workout-acceptances`, `POST /v1/events` | | `workout:read` | `GET /v1/subjects/{subject_ref}/workout-draft-operations/{operation_ref}`, `.../workout-acceptance-operations/{operation_ref}`, `.../workout-completion-operations/{operation_ref}` | | `coaching:write` | `POST /v1/daily-plans` | | `coaching:read` | `GET /v1/subjects/{subject_ref}/daily-plan-operations/{operation_ref}` | Call Kinellect only from your backend. Never ship the API token to a browser or mobile app. ## Requests and responses - Bodies are JSON (`Content-Type: application/json`) with `snake_case` field names. - **Unknown fields are rejected** with `422`. Send only documented fields. - String limits are **UTF-8 byte** lengths, not character counts. A 200-byte limit fits fewer than 200 characters of non-Latin text. NUL characters are never allowed. - References issued by Kinellect (`subject_ref`, `thread_ref`, `operation_ref` and so on) are UUIDs. Treat them as opaque strings. ## Idempotency Every `POST` and `PUT` requires an `Idempotency-Key` header of 1 to 200 characters. A missing key returns `400`. - Generate a new key, such as a UUID, for each **distinct** request. - Reuse the **same** key only to retry that exact request, for example after a timeout. - Same key, same request: Kinellect returns the original result and creates nothing new. Configuration activation and context writes answer `201` when new and `200` with `"replayed": true` on replay. Thread creation answers `201` either way. Endpoints that start an Operation answer `202` with the original Operation. - Same key, different request: `409 Conflict`. The original is never overwritten. Persist the key with your outgoing request before sending it, so a retry after a crash reuses it. ## Asynchronous Operations Endpoints that involve a model or your connector return `202 Accepted` with an Operation document. Subject-scoped endpoints also return a `Location` header pointing at the Operation to poll. | Created by | Poll with | | --- | --- | | `POST /v1/threads/{thread_ref}/turns` | `GET /v1/operations/{operation_ref}` | | `POST /v1/workout-drafts` | `GET /v1/subjects/{subject_ref}/workout-draft-operations/{operation_ref}` | | `POST /v1/daily-plans` | `GET /v1/subjects/{subject_ref}/daily-plan-operations/{operation_ref}` | | `POST /v1/workout-acceptances` | `GET /v1/subjects/{subject_ref}/workout-acceptance-operations/{operation_ref}` | | `POST /v1/events` | `GET /v1/subjects/{subject_ref}/workout-completion-operations/{operation_ref}` | Poll with backoff: start after about 1 second, increase the delay, cap it at a few seconds, and stop at a terminal state (`completed`, `failed`, `cancelled` or `expired`). Poll responses carry `Cache-Control: no-store`. Polling is read-only and never re-runs work. Some endpoints refuse to start work while related work is still active. For example, a Thread accepts a new Turn only after its previous Turn has finished. Turns, workout drafts and daily plans answer `409` in that situation. Wait for the active Operation to finish, then send a new request with a new key. ## Errors Error bodies use RFC 9457 Problem Details (`application/problem+json`): ```json { "type": "about:blank", "title": "Too Many Requests", "status": 429, "detail": "operation_request_limit_exceeded" } ``` | Status | Meaning | What to do | | --- | --- | --- | | `400` | Missing or invalid `Idempotency-Key`. | Fix the request. | | `401` | Missing or invalid API token. | Check the token. Do not retry blindly. | | `403` | The token lacks the required scope. | Request the scope from Kinellect. | | `404` | Unknown reference, or one that belongs to another tenant. The two cases are indistinguishable by design. | Check the reference. | | `409` | The user (Subject) was deleted, when `detail` is `subject_deleted`. Otherwise an idempotency conflict, version conflict, or an Operation already active. | For `subject_deleted`, stop sending requests for this user, or reactivate the user first. Otherwise do not retry with the same key; resolve the conflict. | | `422` | Invalid or unknown field, value out of range, or a configuration or policy that does not allow the request. | Fix the request. | | `429` | An admission limit was reached. `detail` is `operation_request_limit_exceeded` or `operation_concurrency_limit_exceeded`. | Wait `Retry-After` seconds (1 to 300), then retry with the **same** `Idempotency-Key`. | | `503` | A dependency was unavailable. For example, your connector did not answer during configuration activation. | Retry later with the same key. | Failures that happen after `202` never become HTTP errors. The Operation ends in `failed` with a machine-readable `error_code`, documented per capability in the reference. ## Time and dates - Timestamps are ISO 8601 / RFC 3339. Send UTC (`Z`), and expect UTC back. - `local_date` is a calendar date (`YYYY-MM-DD`) in the user's time zone. Impossible dates are rejected. - `time_zone` is an IANA name such as `Europe/Oslo` or `UTC`. Numeric offsets and abbreviations like `CET` are rejected. # Quickstart This walkthrough activates a configuration, registers one user, stores their context, sends a message and reads the coach's reply. It assumes you finished onboarding steps 1 to 3 and have these values: ```bash export KINELLECT_BASE_URL="https://api.kinellect.com" export KINELLECT_TOKEN="" # needs configuration:write, context:write, conversation:write, conversation:read export CONNECTOR_REGISTRATION_REF="" ``` Every write below uses `uuidgen` for a fresh `Idempotency-Key`. In your code, generate the key once per logical request and keep it for retries. ## 1. Activate configuration version 1 Save this as `activate.json`. It enables conversation, context resolution, exercise lookups and repetition-based workout generation. It also sets a request-rate limit and a daily usage ceiling for conversations. ```json { "client_description": "Northwind Physio runs outpatient physiotherapy clinics and a home-exercise app for its patients.", "product_description": "Patients follow home exercise programmes prescribed by their physiotherapist and log each session in the app.", "coaching_domain": "musculoskeletal rehabilitation", "tone": "supportive", "terminology": ["session", "programme"], "enabled_capabilities": [ "conversation.respond", "context.resolve", "exercise.search", "exercise.resolve", "workout.prescription.count", "workout.generate", "memory.preference.write" ], "safety_policy": "fitness_coach_safety_v1", "action_policy": "confirmation_required", "connector_registration_ref": "3b7e1c52-9a0d-4e1f-8b6a-2f4c9d1e7a30", "workout_generation_policy": { "maximum_supersets": 4, "maximum_exercises_per_superset": 3, "maximum_total_exercises": 8, "minimum_rest_before_seconds": 0, "maximum_rest_before_seconds": 120, "minimum_rest_between_cycles_seconds": 30, "maximum_rest_between_cycles_seconds": 180, "maximum_total_sets": 24, "maximum_total_count_repetitions": 300, "maximum_total_timed_work_seconds": null, "minimum_estimated_duration_seconds": 600, "maximum_estimated_duration_seconds": 2700 }, "daily_decision_policy": null, "operation_admission_policies": [ { "policy_version": 1, "capability": "conversation.respond", "request_window_seconds": 60, "soft_request_maximum": 20, "hard_request_maximum": 30, "soft_active_maximum": 5, "hard_active_maximum": 10, "hard_enforcement_enabled": true } ], "operation_usage_policies": [ { "policy_version": 1, "capability": "conversation.respond", "operation_token_maximum": 60000, "operation_estimated_cost_microunits_maximum": 150000, "daily_token_maximum": 5000000, "daily_estimated_cost_microunits_maximum": 15000000, "hard_enforcement_enabled": true } ] } ``` Replace `connector_registration_ref` with your own value, then send it: ```bash curl -sS -X PUT "$KINELLECT_BASE_URL/v1/client-configurations/1/activate" \ -H "Authorization: Bearer $KINELLECT_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ --data @activate.json ``` `201 Created`: ```json { "configuration_ref": "f0a1b2c3-d4e5-4f60-8172-8394a5b6c7d8", "configuration_version": 1, "enabled_capabilities": [ "conversation.respond", "context.resolve", "exercise.search", "exercise.resolve", "workout.prescription.count", "workout.generate", "memory.preference.write" ], "action_policy": "confirmation_required", "activated_at": "2026-09-25T09:00:00Z", "replayed": false } ``` The usage ceilings above are illustrative. Estimated cost is in **microunits** (millionths of the price currency), so `15000000` means 15.00. Agree real values with the Kinellect team. ## 2. Register a user by creating their main Thread ```bash curl -sS -X POST "$KINELLECT_BASE_URL/v1/threads" \ -H "Authorization: Bearer $KINELLECT_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ --data '{"external_subject_ref": "patient-8841", "main": true}' ``` `201 Created`. Store `subject_ref` against your user `patient-8841`: ```json { "thread_ref": "0d9c8b7a-6f5e-4d3c-9b2a-1f0e9d8c7b6a", "subject_ref": "7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10", "main": true, "created_at": "2026-09-25T09:01:00Z" } ``` ## 3. Store the user's context (version 1) Save as `context.json`: ```json { "effective_at": "2026-09-25T00:00:00Z", "valid_through": "2026-10-02T00:00:00Z", "time_zone": "Europe/Oslo", "local_date": "2026-09-25", "primary_goal": "Return to pain-free running after a left knee injury", "experience": "beginner", "desired_sessions_per_week": 3, "available_days": ["monday", "wednesday", "friday"], "unit_system": "metric", "equipment_refs": ["resistance-band", "step"], "safety_constraints": ["avoid-deep-knee-flexion"], "exercise_constraints": [ { "constraint_ref": "pt-note-2026-09-20", "target": {"type": "exercise_ref", "value": "jump-squat"}, "effect": "hard_exclusion", "state": "active" } ], "completed_workouts": 6, "mobility_summary": "Knee flexion limited to about 100 degrees", "recovery_summary": "Mild swelling after long walks, otherwise recovering well", "blocker": null } ``` ```bash SUBJECT_REF="7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10" curl -sS -X PUT "$KINELLECT_BASE_URL/v1/subjects/$SUBJECT_REF/context/1" \ -H "Authorization: Bearer $KINELLECT_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ --data @context.json ``` `201 Created`: ```json { "subject_ref": "7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10", "context_ref": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "context_version": 1, "effective_at": "2026-09-25T00:00:00Z", "created_at": "2026-09-25T09:02:00Z", "replayed": false } ``` ## 4. Send a message ```bash THREAD_REF="0d9c8b7a-6f5e-4d3c-9b2a-1f0e9d8c7b6a" curl -sS -X POST "$KINELLECT_BASE_URL/v1/threads/$THREAD_REF/turns" \ -H "Authorization: Bearer $KINELLECT_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ --data '{"message": "My knee felt stiff after yesterday. Should I still do my session today?"}' ``` `202 Accepted` with a queued Operation: ```json { "operation_ref": "e4d3c2b1-a09f-4e8d-9c7b-6a5f4e3d2c1b", "state": "queued", "result": null, "error_code": null, "error_message": null, "fallback_text": null, "created_at": "2026-09-25T09:03:00Z", "finished_at": null, "action_receipts": [] } ``` ## 5. Poll for the reply ```bash OPERATION_REF="e4d3c2b1-a09f-4e8d-9c7b-6a5f4e3d2c1b" curl -sS "$KINELLECT_BASE_URL/v1/operations/$OPERATION_REF" \ -H "Authorization: Bearer $KINELLECT_TOKEN" ``` Repeat with backoff until `state` is terminal. A completed Operation carries the coach's Turn in `result`: ```json { "operation_ref": "e4d3c2b1-a09f-4e8d-9c7b-6a5f4e3d2c1b", "state": "completed", "result": { "turn_ref": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "sequence": 2, "role": "assistant", "message": "Some stiffness the day after is common while the knee is recovering. ...", "created_at": "2026-09-25T09:03:07Z", "assistant_schema_version": "conversation_grounding_v1" }, "error_code": null, "error_message": null, "fallback_text": null, "created_at": "2026-09-25T09:03:00Z", "finished_at": "2026-09-25T09:03:07Z", "action_receipts": [] } ``` Show `result.message` to the user. If the Operation `failed`, show `fallback_text` when present, otherwise your own safe message. ## Next steps - Request a structured workout: [Workout drafts](#workout-drafts). - Decide what the user should do today: [Daily plans](#daily-plans). - Implement the rest of your connector: [Build your connector](#build-your-connector). # Conversation Requires `conversation.respond`. A conversation is a Thread of Turns between one user and the coach. ## Flow 1. `POST /v1/threads` with `external_subject_ref` (and `main: true` for the user's primary conversation). This also registers the user the first time. 2. `POST /v1/threads/{thread_ref}/turns` with the user's `message` (up to 16000 bytes). You receive `202` and an Operation. 3. Poll `GET /v1/operations/{operation_ref}` until the state is terminal. 4. Show `result.message` from a completed Operation. A Thread processes one Turn at a time. Sending another Turn while the previous one is still running returns `409`. ## What the coach uses For each Turn the coach uses your current configuration, the user's current context (resolved through your connector when none is current), the user's remembered coaching preferences, recent Turns in the Thread, and only the capabilities you enabled. With `exercise.search` and `exercise.resolve` it can look up exercises in your catalog. It cannot reach anything else: no arbitrary web access, no database access, no other users. ## Action receipts When the coach takes or proposes an action during a Turn, the Operation lists it in `action_receipts`: | `tool` | Meaning | | --- | --- | | `propose_preference_update` | The coach suggested a preference change for the user to confirm. | | `update_preferences` | The coach asked your connector to update a preference (`preference.update`). | | `remember_explicit_preference` | The coach remembered a preference the user stated explicitly (`memory.preference.write`). | Each receipt has an `outcome` (`succeeded`, `failed`, `proposed` or `unconfirmed`), a `safe_code`, and an `outcome_text` that you may show to the user. The text is written by Kinellect code, never by the model, so it only claims what actually happened. `unconfirmed` means the result is uncertain: the action may or may not have taken effect on your side. Remembered preferences are limited to four kinds: `preferred_session_length`, `training_days`, `equipment_preference` and `coaching_tone`. ## Failures A failed conversation Operation has one of these `error_code` values: `provider_unavailable`, `provider_invalid_response`, `connector_unavailable`, `client_configuration_missing`, `subject_context_unavailable`, `execution_deadline_exceeded` or `execution_attempts_exhausted`. `error_message` is a fixed safe sentence. When present, `fallback_text` is a safe reply you can show instead of a coach answer. # Workout drafts Requires `workout.generate` and its bundle: `exercise.search`, `exercise.resolve`, at least one prescription type, and a `workout_generation_policy`. Add `workout.warmup.generate` and `workout.cooldown.generate` to get warm-up and cool-down sections. A workout draft is a structured workout built only from exercises in **your** catalog and validated against the user's context, safety constraints and your generation policy. A draft is a proposal. Nothing reaches your system until the user accepts it (see [Workout acceptance](#workout-acceptance)). ## Request ```bash curl -sS -X POST "$KINELLECT_BASE_URL/v1/workout-drafts" \ -H "Authorization: Bearer $KINELLECT_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ --data '{"subject_ref": "7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10"}' ``` The body contains only `subject_ref`. Kinellect uses the current configuration and context and decides how to execute. You cannot choose a model or prompt. Poll the `Location` URL, `GET /v1/subjects/{subject_ref}/workout-draft-operations/{operation_ref}`. ## Result A completed Operation contains `draft_ref` and `draft`: ```json { "title": "Knee-friendly strength", "warmup_supersets": [], "supersets": [ { "order": 1, "rest_between_cycles_seconds": 60, "workout_exercises": [ { "exercise_ref": "glute-bridge", "exercise_content_version": "v3", "order": 1, "sets": 3, "rest_before_seconds": 0, "prescription": {"type": "count", "repetitions": 12} }, { "exercise_ref": "banded-side-step", "exercise_content_version": "v2", "order": 2, "sets": 3, "rest_before_seconds": 30, "prescription": {"type": "count", "repetitions": 10} } ] } ], "cooldown_supersets": [] } ``` The Operation also reports `catalog_version`, the totals (`total_sets`, `total_count_repetitions`, `total_timed_work_seconds`, `estimated_duration_seconds`) and `validation_codes`. `validated` means the draft passed every check. Codes such as `regression_substitution` or `constraint_conflict` explain adjustments the validator made, for example swapping in an easier exercise to respect a constraint. Every exercise carries the `exercise_content_version` your connector reported, so you can detect catalog changes before showing or saving the draft. ## Failures Failed drafts carry an `error_code`, for example `subject_context_unavailable`, `exercise_catalog_unavailable`, `empty_candidate_set` (no usable exercises for this user), `draft_validation_failed` or `execution_deadline_exceeded`. `error_message` is always the fixed sentence "The workout draft could not be completed safely." The API reference lists every code. # Daily plans Requires `daily.decide` and a `daily_decision_policy`. A daily plan answers "what should this user do today?" with one of three outcomes: `workout`, `rest` or `mobility`. Rules decide the outcome deterministically from your policy and the user's context. A model is used only when the policy allows generating a new workout. ## Policy `daily_decision_policy` defines: - `enabled_outcomes`: any non-empty subset of `workout`, `rest` and `mobility`; - `workout_source_policy` (when `workout` is enabled): `existing_only` picks a workout from your catalog, `generation_only` generates one, and `existing_preferred` tries your catalog first and falls back to generation; - `non_workout_preference`: whether `rest` or `mobility` wins when both are possible; - `rest_fallback_codes`: when to fall back to rest (`recovery_unavailable`, `workout_content_unavailable`); - `recovery_max_age_seconds` and `activity_max_age_seconds`: how fresh recovery and activity data must be. Conditional fields must be **omitted** when they do not apply. An explicit `null` is invalid. The workout source you pick needs its capability bundle: existing workouts need `workout.search`, `workout.resolve`, `exercise.resolve` and a prescription type, and generation needs the workout generation bundle. ## Daily context Daily plans need `daily_context` in the user's current Subject context: ```json { "week_start": "2026-09-21", "history_through": "2026-09-25T07:00:00Z", "completed_workout_dates": ["2026-09-22", "2026-09-24"], "completed_mobility_dates": ["2026-09-23"], "preferred_days": [1, 3, 5], "recovery_state": "ready", "recovery_observed_at": "2026-09-25T06:30:00Z", "recovery_valid_through": "2026-09-25T20:00:00Z" } ``` - `week_start` is the Monday of the week that contains `local_date`, in the user's time zone. - Completion lists cover the period from `week_start` minus 7 days through `history_through`. A date repeated twice means two sessions that day. Each list holds at most 28 entries. - `preferred_days` are ISO weekdays (1 = Monday) and must be a subset of `available_days`. - `recovery_state` is `ready`, `mobility_only`, `rest_only` or `unknown`. Kinellect decides only when the context's `local_date` is today in the user's time zone and the activity and recovery data are fresh under your policy. Otherwise the plan fails, with codes such as `local_date_changed`, `activity_history_unavailable` or `recovery_unavailable`. ## Request and result ```bash curl -sS -X POST "$KINELLECT_BASE_URL/v1/daily-plans" \ -H "Authorization: Bearer $KINELLECT_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ --data '{"subject_ref": "7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10"}' ``` Poll `GET /v1/subjects/{subject_ref}/daily-plan-operations/{operation_ref}`. A completed Operation's `result` is an immutable decision with: - `outcome` and machine-readable `reason_codes` (for example `preferred_workout_day`, `weekly_target_met` or `recovery_rest_only`); - `reason_text`, a safe explanation you can show; - `existing_workout` or `generated_workout`, when the outcome is `workout`; - the pinned `configuration_version`, `context_version`, `local_date` and `time_zone`. Each poll also returns `currentness`: `current`, or why the decision no longer matches the user's situation (`context_changed`, `local_date_changed`, `configuration_changed`, `activity_expired` and others). The decision itself never changes. When it is no longer current, request a new plan. # Workout acceptance Requires `workout.save` and/or `workout.save_and_schedule`, and your connector's `actions/workouts/accept` endpoint. Acceptance turns a proposed workout (a workout draft or a daily plan's workout) into a real workout in your system. It is always an explicit request from you, and Kinellect never infers approval. ## Request ```json { "subject_ref": "7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10", "selection": { "source_kind": "standalone_generated", "source_operation_ref": "22222222-2222-4222-8222-222222222222", "source_ref": "33333333-3333-4333-8333-333333333333", "action": "save_and_schedule", "local_date": "2026-09-26", "time_zone": "Europe/Oslo", "approval": "explicit_user_approval" } } ``` - `source_kind` is `standalone_generated` (a workout draft), `daily_generated` or `daily_existing` (a daily plan's workout). `source_operation_ref` and `source_ref` identify it. For a draft, send the draft Operation's `operation_ref` and its `draft_ref`. For a daily plan, send the daily-plan Operation's `operation_ref` and the decision's `decision_ref`. - `action` is `save` or `save_and_schedule`. For `save_and_schedule`, `local_date` and `time_zone` are required, and `time_zone` must equal the user's context. For `save`, send both as `null`. - `approval: "explicit_user_approval"` attests that the user approved **this exact** workout, action, date and time zone. Under `confirmation_required` it is mandatory. Under `auto_execute` it may be `null`. Under `professional_review_required`, `proposal_only` and `rejected` the action is never executed. Send it to `POST /v1/workout-acceptances` and poll `GET /v1/subjects/{subject_ref}/workout-acceptance-operations/{operation_ref}`. ## Result A completed Operation holds exactly one business outcome: | `result.status` | Meaning | | --- | --- | | `confirmed` | Your connector saved (and scheduled) the workout. Includes your `receipt_ref`, `workout_ref`, `workout_content_version`, `confirmed_at`, and for scheduling your `schedule_ref` and `schedule_content_version`. | | `conflict` | Your connector refused because of a conflict, for example the date is already taken. Contains only a `safe_code`. | | `rejected` | Your connector refused for a business reason. Contains only a `safe_code`. | A `failed` Operation's `error_code` tells you why nothing was confirmed. `source_noncurrent`, `approval_required` and `action_denied` mean Kinellect never called your connector. **`connector_unconfirmed`** means Kinellect sent the command but never received a valid answer, so the workout **may** exist on your side. Check your own records for the command's `action_ref` before asking the user to accept again. Kinellect retries only within the original Operation, always with the same `action_ref` and `Idempotency-Key`, so an idempotent connector never creates duplicates (see [Build your connector](#build-your-connector)). Acceptance never moves or cancels schedules. # Workout completion Report completed workouts so the coach can react and learn. This endpoint only records a snapshot of your history and derives a reaction. It never changes your workout records. ## Request `POST /v1/events` with a `workout.completed` event (requires `workout:write`): ```json { "event_ref": "5f4e3d2c-1b0a-4f9e-8d7c-6b5a4f3e2d1c", "event_type": "workout.completed", "schema_version": 1, "subject_ref": "7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10", "activity_ref": "session-99102", "activity_revision": 1, "occurred_at": "2026-09-25T17:40:00Z", "workout_ref": "knee-programme-week-2", "duration_seconds": 1860, "exercise_results": [ { "exercise_ref": "glute-bridge", "exercise_content_version": "v3", "status": "performed", "prescription_type": "count", "prescribed_value": 12, "completed_value": 12 }, { "exercise_ref": "banded-side-step", "exercise_content_version": "v2", "status": "skipped", "prescription_type": "count", "prescribed_value": 10, "completed_value": null } ], "feedback": {"rating": "tough", "note": "Side steps hurt a little on the outside of the knee"} } ``` - `event_ref` is a UUID you generate once per event. - `activity_ref` is your identifier for the session. To correct a session, send a new event with the same `activity_ref` and a higher `activity_revision`. Older revisions become `superseded`, and a stale revision is refused with `422`. - `feedback.rating` is `positive`, `neutral` or `tough`. `note` is free text or `null`. ## Result Poll `GET /v1/subjects/{subject_ref}/workout-completion-operations/{operation_ref}`. For a `completed`, `current` Operation: - `reaction` is a short coach message you can show; - `notification_suggested` tells you whether the reaction is worth a push notification. You own notifications and decide whether to send one. Superseded results never expose `reaction` or `notification_suggested`. A failed Operation reports `model_failed`, `operation_cancelled` or `operation_expired`. Pain or difficulty reported in feedback can shape later coaching, for example by temporarily avoiding an exercise. # Build your connector Your connector is an HTTPS service that Kinellect calls to read your content and a user's context, and to carry out actions on your side. The full contract is the **Connector API** specification in the reference. This page covers what matters most when building it. ## Endpoints by capability All paths are fixed and relative to the base URL you registered. | Endpoint | Needed for | | --- | --- | | `GET /ai-coach-connector/v1/capabilities` | Always. It is called when you activate a configuration. | | `POST /ai-coach-connector/v1/context/resolve` | `context.resolve` | | `POST /ai-coach-connector/v1/content/exercises/search` | `exercise.search` | | `POST /ai-coach-connector/v1/content/exercises/resolve` | `exercise.resolve` | | `POST /ai-coach-connector/v1/content/workouts/search` | `workout.search` | | `POST /ai-coach-connector/v1/content/workouts/resolve` | `workout.resolve` | | `POST /ai-coach-connector/v1/actions/preferences/update` | `preference.update` | | `POST /ai-coach-connector/v1/actions/workouts/accept` | `workout.save`, `workout.save_and_schedule` | The two prescription capabilities, `workout.prescription.count` and `workout.prescription.time`, have no endpoint of their own. Advertise them in `capabilities` when your catalog supports repetition-based or time-based prescriptions. ## Every request Kinellect sends: ```http Authorization: Bearer Accept: application/json Accept-Encoding: identity Content-Type: application/json X-Kinellect-Tenant: Idempotency-Key: # action endpoints only ``` On every call, verify the bearer secret with a constant-time comparison, and check that `X-Kinellect-Tenant` is the tenant environment you expect. `subject_ref` in request bodies is **Kinellect's** user reference (a UUID), not your user id. Look up your user through the mapping you stored when you created the user's first Thread. ## Every response Kinellect validates connector responses strictly, because they feed safety decisions: - **Exact keys.** Each object must contain exactly the documented keys: no more, no fewer. An extra field such as `"debug": true` makes the whole response invalid. - **Optional means omit.** Keys documented as optional (for example `verification_facts`, `relationships`, `daily_context`, `warmup_supersets`) are either present with a valid value or absent. Do not send `null` for them. - **Status.** Answer business outcomes with HTTP `200` and a JSON object. Any status outside 2xx counts as a failure. - **Limits.** String limits are UTF-8 bytes. Lists have maximum sizes. The body is at most 131072 bytes, uncompressed, with no NUL characters. Timestamps are UTC ending in `Z` or `+00:00`. - **No redirects.** Kinellect does not follow them. A response that breaks these rules is treated as unavailable. The Operation that needed it fails safely, with codes such as `connector_invalid_response`, `exercise_catalog_invalid` or `connector_unavailable`, depending on the capability. ## Timeouts and retries - Kinellect allows 2 seconds to connect and between bytes, and 5 seconds per attempt, always within the deadline of the Operation that needs the answer. - After HTTP `408`, `429`, `502`, `503`, `504` or a network error, Kinellect retries **once**, waiting 0.1 to 1 second. It honours a `Retry-After` header within that range. Other failures are not retried. - Capability discovery during activation has a 10-second budget. If it fails, activation returns `503`. Design every endpoint to answer well within 2 seconds, and keep them idempotent so that a retry is harmless. ## Content endpoints **Exercises.** Return at most 20 exercises per response. For each exercise you want Kinellect to use in generated or verified workouts, include `verification_facts`: - `active` and `available`: Kinellect uses only exercises that are both. - `prescription_types`: `count`, `time` or both. - `safety_identifiers`: identifiers that are matched against the user's `safety_constraints` and exercise constraints. Use the same vocabulary in both places. - `generation_facts`: allowed repetition and duration ranges. A range is either fully `null` or fully set, with `1 <= minimum <= maximum`. Add `relationships` with `progression_refs`, `regression_refs` and `related_refs` so Kinellect can substitute a safer regression when a constraint rules an exercise out. **Versions.** `content_version`, `catalog_version`, `item_content_version` and `exercise_content_version` are your own version labels. Change them when content changes. Kinellect pins them in results, so you can tell whether a proposal still matches your catalog. **Workouts.** `workouts/resolve` answers `resolved` with the full workout, or `missing`, `inactive` or `changed` with `"workout": null`. Superset and exercise `order` values start at 1 and have no gaps. Resolved supersets have no `rest_between_cycles_seconds` key. ## Action endpoints Action endpoints change data on your side, so they must be **idempotent**: - Store the `Idempotency-Key` together with your result. - The same key with the same command returns the stored result without acting again. - The same key with a different command is a conflict. For workout acceptance, answer with a `conflict` outcome whose `safe_code` is `command_conflict`, and never overwrite the original. **Preference update.** Apply the change and return `succeeded: true` with your `receipt_ref`. If you decline, return `succeeded: false` with a `safe_code` and HTTP 200. **Workout acceptance.** Kinellect sends an immutable command containing the complete workout (for a generated workout) or your `existing_workout_ref`, the action, and for scheduling the `local_date` and `time_zone`. `save_and_schedule` must be **atomic**: when the date conflicts, create neither the workout nor the schedule, and answer `conflict` with `safe_code` `schedule_conflict`. Answer `confirmed`, `conflict` or `rejected` with HTTP 200. The accept operation in the Connector API reference defines the exact command and outcome shapes. ## Testing your connector - Validate each response against the Connector API schemas, with `additionalProperties: false` enforced. - Test the empty cases: an unknown user in `context/resolve` returns `{"available": false}`, unknown exercise references are simply left out, and an unknown workout returns `missing`. - Replay each action with the same `Idempotency-Key` and confirm nothing is applied twice. - Measure latency under load. Keep p99 well below 2 seconds. # Using these docs with an LLM These docs are published in forms that coding assistants and agents can read directly: | File | Contents | Use it when | | --- | --- | --- | | `llms.txt` | A short index of every document, following the llms.txt convention. | An agent should discover what exists. | | `llms-full.txt` | Every guide on this page plus both complete OpenAPI specifications, in one Markdown file. | You want to give an assistant the whole contract in one go. | | `openapi/kinellect-api-v1.json` | OpenAPI 3.1 for the Kinellect API you call. | Generating a client, or validating requests. | | `openapi/kinellect-connector-v1.json` | OpenAPI 3.1 for the connector you implement. | Generating server stubs, or validating responses. | Paste `llms-full.txt` into your assistant's context, or point an agent at it. It is self-contained. ## Suggested instructions for a coding assistant ```text You are implementing an integration with Kinellect. The attached llms-full.txt is the complete contract. Rules: - Never invent endpoints, fields, enum values or status codes. Use only what the OpenAPI specs define. - Kinellect API requests reject unknown fields. Send only documented fields. - Every POST/PUT needs an Idempotency-Key. Reuse the key only to retry the identical request. - 202 responses start an Operation. Poll it with backoff until queued/running ends. - Handle 429 by waiting Retry-After, then retrying with the same Idempotency-Key. - Connector responses must contain exactly the documented keys. Optional keys are omitted, never null. - subject_ref is Kinellect's UUID for a user. Keep a mapping from our user id to subject_ref. - The Kinellect API token is a backend secret. Never put it in client-side code. ``` ## Invariants checklist Use this list to review generated code: 1. Each write sends an `Idempotency-Key` that is persisted before the request and reused on retry. 2. No request body contains a field that is missing from the specification. 3. Every Operation state is handled: `queued`, `running`, `completed`, `failed`, `cancelled` and `expired`. 4. Byte limits are checked in UTF-8 bytes, not characters. 5. Timestamps are sent as UTC. `time_zone` is an IANA name. 6. Connector responses are built from explicit field lists, never by serialising internal objects that may carry extra fields. 7. Connector action endpoints store and replay results by `Idempotency-Key`. 8. The connector verifies the bearer secret and the `X-Kinellect-Tenant` header. # Limits of v1 Plan your integration around these current limits. Ask the Kinellect team about timelines. - **No self-service provisioning.** API tokens, scopes and connector registrations are issued by the Kinellect team. - **No webhooks.** Results are delivered by polling Operations. - **Registering a user means creating a Thread.** There is no separate "create subject" endpoint. `POST /v1/threads` both creates the conversation and registers the user. - **Backups keep erased data until they expire.** `DELETE /v1/subjects/{subject_ref}` erases the user's coaching data immediately (see [Deleting and reactivating a Subject](#deleting-and-reactivating-a-subject)), but encrypted backups keep it until they expire. The retention period is confirmed at production provisioning. - **No listing endpoints.** The API does not list users, Threads or past Operations. Store the references you need when you receive them. - **One safety policy.** `safety_policy` currently accepts only `fitness_coach_safety_v1`. - **English.** Coaching output is currently English. # Appendix A: Kinellect API (OpenAPI 3.1, YAML) ```yaml openapi: 3.1.0 info: title: Kinellect API version: 1.0.0 description: 'The API your backend calls to use Kinellect coaching. Read the integration guide for concepts, onboarding and conventions. Every POST and PUT requires an Idempotency-Key header. Requests reject unknown fields. Work that involves a model or your connector returns 202 with an Operation to poll. Generated from the Kinellect source at commit `16cf66585dcc`.' paths: /v1/subjects/{subject_ref}: delete: summary: Delete or replay deletion of one Subject description: Requires the context:write scope. Deletion atomically tombstones the Subject, cancels its queued or running Operations and erases its coaching data, keeping only a minimal tombstone. Later polls of erased Operations return 404 and replays of earlier Subject requests return 409 subject_deleted. security: - bearerAuth: [] parameters: - name: subject_ref in: path required: true schema: type: string format: uuid - $ref: '#/components/parameters/IdempotencyKey' responses: '200': description: Subject deleted, already deleted, or an identical transition replayed headers: Cache-Control: schema: type: string const: no-store content: application/json: schema: type: 'null' '400': description: A valid Idempotency-Key header is required '401': description: Authentication required '403': description: Missing context:write scope '404': description: Unknown or cross-tenant Subject '409': description: Idempotency conflict tags: - Subject lifecycle operationId: deleteSubject /v1/subjects/{subject_ref}/reactivate: post: summary: Reactivate or replay reactivation of one deleted Subject description: Requires the context:write scope. security: - bearerAuth: [] parameters: - name: subject_ref in: path required: true schema: type: string format: uuid - $ref: '#/components/parameters/IdempotencyKey' responses: '200': description: Subject reactivated or an identical transition replayed headers: Cache-Control: schema: type: string const: no-store content: application/json: schema: type: 'null' '400': description: A valid Idempotency-Key header is required '401': description: Authentication required '403': description: Missing context:write scope '404': description: Unknown or cross-tenant Subject '409': description: Idempotency conflict '422': description: Subject is already active tags: - Subject lifecycle operationId: reactivateSubject /v1/client-configurations/{configuration_version}/activate: put: summary: Activate or replay one immutable tenant coaching configuration version description: Requires the configuration:write scope. The opaque connector registration must have been provisioned for this tenant by an operator; endpoint and credential ownership cannot be created or changed through this API. security: - bearerAuth: [] parameters: - name: configuration_version in: path required: true schema: type: integer minimum: 1 - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ActivateClientConfiguration' responses: '200': description: Existing identical version replayed. An exact replay does not call the connector. content: application/json: schema: $ref: '#/components/schemas/ClientConfigurationActivation' '201': description: Configuration activated content: application/json: schema: $ref: '#/components/schemas/ClientConfigurationActivation' '400': description: A valid Idempotency-Key header is required, or the request body is not valid JSON. '401': description: Missing or invalid API token content: application/problem+json: schema: type: object additionalProperties: false required: - type - title - status - detail properties: type: const: about:blank title: const: Unauthorized status: const: 401 detail: const: A valid API token is required. '403': description: Missing configuration:write scope '409': description: Idempotency key reused with different content, the same version already stored with different content, or a version that is not newer than the active version '422': description: Invalid or unknown request field, incomplete capability bundle or mismatched policy, duplicate policy capability, unavailable connector registration, connector contract version other than the registered version, or an enabled connector capability that the connector does not advertise '503': description: Connector capability discovery failed (unreachable, non-2xx, invalid response, or not completed within 10 seconds), or API-token authentication is temporarily unavailable tags: - Configuration operationId: activateClientConfiguration /v1/subjects/{subject_ref}/context/{context_version}: put: summary: Store or replay one immutable authoritative subject-context version description: Requires the context:write scope. A snapshot is current exactly while effective_at <= now < valid_through. security: - bearerAuth: [] parameters: - name: subject_ref in: path required: true schema: type: string format: uuid - name: context_version in: path required: true schema: type: integer minimum: 1 - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PutSubjectContext' responses: '200': description: Existing identical context replayed content: application/json: schema: $ref: '#/components/schemas/SubjectContextWrite' '201': description: Context stored content: application/json: schema: $ref: '#/components/schemas/SubjectContextWrite' '403': description: Missing context:write scope '404': description: Unknown or cross-tenant Subject '409': description: Idempotency, version, monotonicity, or Subject tombstone conflict content: application/problem+json: schema: $ref: '#/components/schemas/ConflictProblem' '422': description: Invalid or unknown request field tags: - Subject context operationId: putSubjectContext /v1/threads: post: summary: Create or replay a tenant-scoped Conversation Thread description: Requires the conversation:write scope. security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - external_subject_ref properties: external_subject_ref: type: string minLength: 1 maxLength: 200 pattern: ^[^\u0000]*$ description: Limited to 200 UTF-8 bytes; NUL bytes are not allowed. x-max-utf8-bytes: 200 main: type: boolean default: false responses: '201': description: Thread created or replayed content: application/json: schema: $ref: '#/components/schemas/ConversationThread' '409': description: Idempotency or Subject tombstone conflict content: application/problem+json: schema: $ref: '#/components/schemas/ConflictProblem' '422': description: Invalid request tags: - Conversation operationId: createThread /v1/threads/{thread_ref}/turns: post: summary: Accept one immutable user Turn description: Requires the conversation:write scope. Processing continues durably after the request returns. security: - bearerAuth: [] parameters: - name: thread_ref in: path required: true schema: type: string format: uuid - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - message properties: message: type: string minLength: 1 maxLength: 16000 pattern: ^[^\u0000]*$ description: Limited to 16000 UTF-8 bytes; NUL bytes are not allowed. x-max-utf8-bytes: 16000 responses: '202': description: Durable Operation accepted content: application/json: schema: $ref: '#/components/schemas/ConversationOperation' '404': description: Thread not found '409': description: Idempotency, active-Operation, or Subject tombstone conflict content: application/problem+json: schema: $ref: '#/components/schemas/ConflictProblem' '422': description: Invalid request '429': $ref: '#/components/responses/OperationAdmissionRejected' tags: - Conversation operationId: createTurn /v1/operations/{operation_ref}: get: summary: Poll a Conversation Operation description: Requires the conversation:read scope. Unknown and cross-tenant references are indistinguishable. security: - bearerAuth: [] parameters: - name: operation_ref in: path required: true schema: type: string format: uuid responses: '200': description: One of queued, running, completed, failed, cancelled, or expired headers: Cache-Control: schema: type: string const: no-store content: application/json: schema: $ref: '#/components/schemas/ConversationOperation' '404': description: Unknown or cross-tenant Operation headers: Cache-Control: schema: type: string const: no-store content: application/problem+json: schema: $ref: '#/components/schemas/NotFoundProblem' tags: - Conversation operationId: getConversationOperation /v1/workout-drafts: post: summary: Request or replay one validated workout draft description: Requires the workout:write scope. The server pins the execution mode when the durable Operation is accepted; callers cannot select a provider, model, prompt, or execution mode. security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RequestWorkoutDraft' responses: '202': description: Durable workout-generation Operation accepted headers: Location: schema: type: string Cache-Control: schema: type: string const: no-store content: application/json: schema: $ref: '#/components/schemas/WorkoutGenerationOperation' '403': description: Missing workout:write scope '404': description: Unknown or cross-tenant Subject '409': description: Idempotency, active-Operation, or Subject tombstone conflict content: application/problem+json: schema: $ref: '#/components/schemas/ConflictProblem' '422': description: Invalid or unknown request field '429': $ref: '#/components/responses/OperationAdmissionRejected' tags: - Workout drafts operationId: requestWorkoutDraft /v1/subjects/{subject_ref}/workout-draft-operations/{operation_ref}: get: summary: Poll a subject-scoped workout-generation Operation description: Requires the workout:read scope. Unknown and cross-tenant Subject and Operation references are indistinguishable. security: - bearerAuth: [] parameters: - name: subject_ref in: path required: true schema: type: string format: uuid - name: operation_ref in: path required: true schema: type: string format: uuid responses: '200': description: One of queued, running, completed, failed, cancelled, or expired headers: Cache-Control: schema: type: string const: no-store content: application/json: schema: $ref: '#/components/schemas/WorkoutGenerationOperation' '403': description: Missing workout:read scope '404': description: Unknown or cross-tenant Subject or Operation headers: Cache-Control: schema: type: string const: no-store content: application/problem+json: schema: $ref: '#/components/schemas/NotFoundProblem' tags: - Workout drafts operationId: getWorkoutDraftOperation /v1/daily-plans: post: summary: Request or replay one daily coaching decision description: Requires the coaching:write scope. The server pins real_ai only when the daily policy permits nested generation; otherwise the pin is null; callers cannot select a provider, model, prompt, or execution mode. security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RequestDailyPlan' responses: '202': description: Durable daily-coaching Operation accepted headers: Location: schema: type: string Cache-Control: schema: type: string const: no-store content: application/json: schema: $ref: '#/components/schemas/DailyPlanOperation' '403': description: Missing coaching:write scope '404': description: Unknown or cross-tenant Subject '409': description: Idempotency, active-Operation, or Subject tombstone conflict content: application/problem+json: schema: $ref: '#/components/schemas/ConflictProblem' '422': description: Invalid or unknown request field '429': $ref: '#/components/responses/OperationAdmissionRejected' tags: - Daily plans operationId: requestDailyPlan /v1/subjects/{subject_ref}/daily-plan-operations/{operation_ref}: get: summary: Poll a subject-scoped daily-coaching Operation description: Requires the coaching:read scope. Unknown and cross-tenant Subject and Operation references are indistinguishable. security: - bearerAuth: [] parameters: - name: subject_ref in: path required: true schema: type: string format: uuid - name: operation_ref in: path required: true schema: type: string format: uuid responses: '200': description: One of queued, running, completed, failed, cancelled, or expired headers: Cache-Control: schema: type: string const: no-store content: application/json: schema: $ref: '#/components/schemas/DailyPlanOperation' '403': description: Missing coaching:read scope '404': description: Unknown or cross-tenant Subject or Operation headers: Cache-Control: schema: type: string const: no-store content: application/problem+json: schema: $ref: '#/components/schemas/NotFoundProblem' tags: - Daily plans operationId: getDailyPlanOperation /v1/workout-acceptances: post: summary: Request or replay explicit workout acceptance description: Requires workout:write, authenticated tenant identity and Idempotency-Key. Returns a durable Operation; a separate worker dispatches the immutable saved source. Save and atomic save_and_schedule require their independently activated capabilities. confirmation_required needs explicit_user_approval for the exact selection; auto_execute still requires this request. Professional-review, proposal-only and rejected policies deny execution. Admission and pre-marker first dispatch recheck source, context, configuration, capability, policy and date/zone using application time. After possible dispatch, bounded recovery retains the original command, registration, action identity and connector key. Same-key same-request replay returns the original Operation even if currentness changed; changed request conflicts. This endpoint does not generate content, infer approval, or move or cancel schedules. security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RequestWorkoutAcceptance' responses: 202: description: Durable acceptance Operation accepted or replayed headers: Location: schema: type: string Cache-Control: schema: type: string const: no-store content: application/json: schema: $ref: '#/components/schemas/WorkoutAcceptanceOperation' 401: description: Authentication required 403: description: Missing workout:write scope 404: description: Unknown or cross-scope Subject or source 409: description: Idempotency or Subject tombstone conflict content: application/problem+json: schema: $ref: '#/components/schemas/ConflictProblem' 422: description: Invalid or unknown request field or inadmissible selection 429: $ref: '#/components/responses/OperationAdmissionRejected' '400': description: A valid Idempotency-Key header is required. tags: - Workout acceptance operationId: requestWorkoutAcceptance /v1/subjects/{subject_ref}/workout-acceptance-operations/{operation_ref}: get: summary: Poll a subject-scoped workout acceptance Operation description: 'Requires workout:read. Unknown and cross-tenant or cross-Subject references are indistinguishable and retain native Symfony Problem Details with detail: Not Found. Completed means a validated matching connector business result: only confirmed contains receipt and workout references/versions, plus schedule references/versions for save_and_schedule. Conflict and rejected contain only their safe business code. Exhausted uncertain recovery is failed with connector_unconfirmed and no result; the action may have taken effect. Polling does not dispatch another action.' security: - bearerAuth: [] parameters: - name: subject_ref in: path required: true schema: type: string format: uuid - name: operation_ref in: path required: true schema: type: string format: uuid responses: 200: description: Subject-scoped acceptance Operation headers: Cache-Control: schema: type: string const: no-store content: application/json: schema: $ref: '#/components/schemas/WorkoutAcceptanceOperation' 401: description: Authentication required 403: description: Missing workout:read scope 404: description: Unknown or cross-scope Subject or Operation headers: Cache-Control: schema: type: string const: no-store content: application/problem+json: schema: $ref: '#/components/schemas/NotFoundProblem' tags: - Workout acceptance operationId: getWorkoutAcceptanceOperation /v1/events: post: description: Requires workout:write. Records one complete client-owned workout.completed snapshot and queues derived processing without changing canonical client history. security: - bearerAuth: [] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/WorkoutCompletionRequest' responses: '202': description: Queued or exact replay. headers: Location: schema: type: string Cache-Control: schema: const: no-store content: application/json: schema: $ref: '#/components/schemas/WorkoutCompletionOperation' '409': description: Event, activity-revision, Idempotency-Key fingerprint, or Subject tombstone conflict. content: application/problem+json: schema: $ref: '#/components/schemas/ConflictProblem' '422': description: Invalid snapshot, unavailable Subject, or stale revision. '429': description: Tenant and workout-completion capability admission denied. headers: Retry-After: required: true schema: type: integer minimum: 1 maximum: 300 content: application/problem+json: schema: type: object additionalProperties: false required: - type - title - status - detail properties: type: const: about:blank title: type: string status: const: 429 detail: enum: - operation_request_limit_exceeded - operation_concurrency_limit_exceeded tags: - Workout completion operationId: submitWorkoutCompletion summary: Report one completed workout /v1/subjects/{subject_ref}/workout-completion-operations/{operation_ref}: get: description: Requires workout:read. Polls one Subject-scoped completion Operation. security: - bearerAuth: [] parameters: - name: subject_ref in: path required: true schema: type: string format: uuid - name: operation_ref in: path required: true schema: type: string format: uuid responses: '200': description: Current Operation state. headers: Cache-Control: schema: const: no-store content: application/json: schema: $ref: '#/components/schemas/WorkoutCompletionOperation' '404': description: Missing or cross-scope Operation. tags: - Workout completion operationId: getWorkoutCompletionOperation summary: Poll a subject-scoped workout-completion Operation components: responses: OperationAdmissionRejected: description: Tenant and capability request-rate or active-concurrency admission denied. headers: Retry-After: required: true schema: type: integer minimum: 1 maximum: 300 content: application/problem+json: schema: type: object additionalProperties: false required: - type - title - status - detail properties: type: const: about:blank title: type: string status: const: 429 detail: enum: - operation_request_limit_exceeded - operation_concurrency_limit_exceeded securitySchemes: bearerAuth: type: http scheme: bearer description: API token issued by the Kinellect team. Scopes are listed on each operation. parameters: IdempotencyKey: name: Idempotency-Key in: header required: true schema: type: string minLength: 1 maxLength: 200 schemas: RequestWorkoutAcceptance: type: object additionalProperties: false required: - subject_ref - selection properties: subject_ref: type: string format: uuid selection: $ref: '#/components/schemas/WorkoutAcceptanceSelection' examples: - subject_ref: 77777777-7777-4777-8777-777777777777 selection: source_kind: standalone_generated source_operation_ref: 22222222-2222-4222-8222-222222222222 source_ref: 33333333-3333-4333-8333-333333333333 action: save local_date: null time_zone: null approval: explicit_user_approval - subject_ref: 77777777-7777-4777-8777-777777777777 selection: source_kind: standalone_generated source_operation_ref: 22222222-2222-4222-8222-222222222222 source_ref: 33333333-3333-4333-8333-333333333333 action: save_and_schedule local_date: '2026-09-10' time_zone: Europe/Oslo approval: explicit_user_approval WorkoutAcceptanceSelection: type: object additionalProperties: false required: - source_kind - source_operation_ref - source_ref - action - local_date - time_zone - approval properties: source_kind: enum: - standalone_generated - daily_generated - daily_existing source_operation_ref: type: string format: uuid source_ref: type: string format: uuid action: enum: - save - save_and_schedule local_date: oneOf: - type: string format: date pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ description: Strict YYYY-MM-DD calendar date; impossible dates are rejected. - type: 'null' time_zone: oneOf: - type: string minLength: 1 maxLength: 100 description: Named Subject IANA time zone, including UTC. Numeric offsets and abbreviations are rejected. Must equal authoritative Subject context. - type: 'null' approval: enum: - explicit_user_approval - null description: Attests actual user approval of the exact enclosing Subject, source, action, date and time zone. Changing any bound selection requires new approval. Null does not satisfy confirmation_required. oneOf: - properties: action: const: save local_date: type: 'null' time_zone: type: 'null' - properties: action: const: save_and_schedule local_date: type: string format: date pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ description: Strict YYYY-MM-DD calendar date; impossible dates are rejected. time_zone: type: string minLength: 1 maxLength: 100 description: Named Subject IANA time zone, including UTC. Numeric offsets and abbreviations are rejected. Must equal authoritative Subject context. description: Acceptance is an explicit action. workout.save and workout.save_and_schedule require their own independently activated capability and connector support. auto_execute permits null approval; professional_review_required, proposal_only and rejected deny first execution even with approval. Caller and attestation bind the canonical request fingerprint. No replacement content or recovery fields are accepted. WorkoutAcceptanceOutcome: oneOf: - type: object additionalProperties: false required: - status - receipt_ref - workout_ref - workout_content_version - confirmed_at properties: status: const: confirmed receipt_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ workout_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ workout_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ confirmed_at: type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. - type: object additionalProperties: false required: - status - receipt_ref - workout_ref - workout_content_version - confirmed_at - schedule_ref - schedule_content_version properties: status: const: confirmed receipt_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ workout_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ workout_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ confirmed_at: type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. schedule_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ schedule_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ - type: object additionalProperties: false required: - status - safe_code properties: status: const: conflict safe_code: enum: - schedule_conflict - command_conflict - type: object additionalProperties: false required: - status - safe_code properties: status: const: rejected safe_code: const: action_rejected description: Only confirmed exposes real client receipt and effects. Scheduled references appear only for save_and_schedule. Inapplicable fields are omitted, never null. A conflict or rejection contains only status and safe_code. WorkoutAcceptanceOperation: type: object additionalProperties: false required: - operation_ref - state - result - error_code - error_message - created_at - finished_at properties: operation_ref: type: string format: uuid state: enum: - queued - running - completed - failed - cancelled - expired result: oneOf: - $ref: '#/components/schemas/WorkoutAcceptanceOutcome' - type: 'null' error_code: enum: - source_unavailable - source_noncurrent - capability_unavailable - approval_required - action_denied - connector_unconfirmed - execution_deadline_exceeded - execution_attempts_exhausted - operation_cancelled - operation_expired - null error_message: type: - string - 'null' created_at: type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. finished_at: oneOf: - type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. - type: 'null' description: One generic durable Operation lifecycle. completed contains exactly one confirmed, conflict or rejected business outcome. No unconfirmed business status exists. Unknown and cross-scope references are non-disclosing. Until a validated result commits, pending result and errors remain null. A terminal connector_unconfirmed failure has no result and cannot assure absence of a remote effect. Cancellation and expiry likewise do not prove absence of a remote effect. Execution-limit errors apply only before dispatch; exhaustion after possible dispatch without a validated result is connector_unconfirmed. oneOf: - properties: state: const: queued result: type: 'null' error_code: type: 'null' error_message: type: 'null' finished_at: type: 'null' - properties: state: const: running result: type: 'null' error_code: type: 'null' error_message: type: 'null' finished_at: type: 'null' - properties: state: const: completed result: $ref: '#/components/schemas/WorkoutAcceptanceOutcome' error_code: type: 'null' error_message: type: 'null' finished_at: type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. - properties: state: const: failed result: type: 'null' error_code: enum: - source_unavailable - source_noncurrent - capability_unavailable - approval_required - action_denied - connector_unconfirmed - execution_deadline_exceeded - execution_attempts_exhausted error_message: type: string finished_at: type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. - properties: state: const: cancelled result: type: 'null' error_code: const: operation_cancelled error_message: const: Workout acceptance was cancelled. finished_at: type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. - properties: state: const: expired result: type: 'null' error_code: const: operation_expired error_message: const: Workout acceptance expired. finished_at: type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. allOf: - if: properties: error_code: const: source_unavailable then: properties: error_message: const: The workout source is unavailable. - if: properties: error_code: const: source_noncurrent then: properties: error_message: const: The workout source is no longer current. - if: properties: error_code: const: capability_unavailable then: properties: error_message: const: The requested workout action is unavailable. - if: properties: error_code: const: approval_required then: properties: error_message: const: Explicit user approval is required. - if: properties: error_code: const: action_denied then: properties: error_message: const: The requested workout action is not permitted. - if: properties: error_code: const: connector_unconfirmed then: properties: error_message: const: The client outcome could not be confirmed; the action may have taken effect. - if: properties: error_code: const: execution_deadline_exceeded then: properties: error_message: const: Workout acceptance exceeded its execution time limit. - if: properties: error_code: const: execution_attempts_exhausted then: properties: error_message: const: Workout acceptance exhausted its execution attempts. - if: properties: error_code: const: operation_cancelled then: properties: error_message: const: Workout acceptance was cancelled. - if: properties: error_code: const: operation_expired then: properties: error_message: const: Workout acceptance expired. ActivateClientConfiguration: type: object additionalProperties: false required: - client_description - product_description - coaching_domain - tone - terminology - enabled_capabilities - safety_policy - action_policy - connector_registration_ref properties: client_description: type: string minLength: 1 maxLength: 2000 x-max-utf8-bytes: 2000 product_description: type: string minLength: 1 maxLength: 2000 x-max-utf8-bytes: 2000 coaching_domain: type: string minLength: 1 maxLength: 100 x-max-utf8-bytes: 100 tone: enum: - supportive - direct - neutral terminology: type: array maxItems: 20 uniqueItems: true items: type: string minLength: 1 maxLength: 100 x-max-utf8-bytes: 100 enabled_capabilities: type: array minItems: 1 maxItems: 17 uniqueItems: true items: enum: - conversation.respond - daily.decide - context.resolve - exercise.search - exercise.resolve - workout.search - workout.resolve - workout.save - workout.save_and_schedule - workout.prescription.count - workout.prescription.time - workout.generate - workout.warmup.generate - workout.cooldown.generate - preference.propose - preference.update - memory.preference.write safety_policy: const: fitness_coach_safety_v1 action_policy: enum: - auto_execute - confirmation_required - professional_review_required - proposal_only - rejected connector_registration_ref: type: string format: uuid description: Opaque reference to an operator-provisioned connector registration owned by this tenant environment. workout_generation_policy: description: Required and non-null when workout.generate is enabled; otherwise it may be omitted or null for backward-compatible non-generation configurations. oneOf: - $ref: '#/components/schemas/WorkoutGenerationPolicy' - type: 'null' daily_decision_policy: oneOf: - $ref: '#/components/schemas/DailyDecisionPolicy' - type: 'null' operation_admission_policies: type: array maxItems: 6 uniqueItems: true items: $ref: '#/components/schemas/OperationAdmissionPolicy' operation_usage_policies: type: array maxItems: 6 description: Optional; omitted means an empty list. Each capability may appear at most once; a duplicate capability returns 422. items: $ref: '#/components/schemas/OperationUsagePolicy' allOf: - if: properties: enabled_capabilities: contains: const: daily.decide then: required: - daily_decision_policy properties: daily_decision_policy: $ref: '#/components/schemas/DailyDecisionPolicy' else: properties: daily_decision_policy: type: 'null' - if: required: - daily_decision_policy properties: daily_decision_policy: type: object required: - workout_source_policy properties: workout_source_policy: const: existing_only then: properties: enabled_capabilities: allOf: - contains: const: workout.search - contains: const: workout.resolve - contains: const: exercise.resolve anyOf: - contains: const: workout.prescription.count - contains: const: workout.prescription.time - if: required: - daily_decision_policy properties: daily_decision_policy: type: object required: - workout_source_policy properties: workout_source_policy: const: generation_only then: properties: enabled_capabilities: allOf: - contains: const: workout.generate - contains: const: exercise.search - contains: const: exercise.resolve anyOf: - contains: const: workout.prescription.count - contains: const: workout.prescription.time workout_generation_policy: $ref: '#/components/schemas/WorkoutGenerationPolicy' required: - workout_generation_policy - if: required: - daily_decision_policy properties: daily_decision_policy: type: object required: - workout_source_policy properties: workout_source_policy: const: existing_preferred then: properties: enabled_capabilities: allOf: - contains: const: workout.search - contains: const: workout.resolve - contains: const: workout.generate - contains: const: exercise.search - contains: const: exercise.resolve anyOf: - contains: const: workout.prescription.count - contains: const: workout.prescription.time workout_generation_policy: $ref: '#/components/schemas/WorkoutGenerationPolicy' required: - workout_generation_policy ClientConfigurationActivation: type: object additionalProperties: false required: - configuration_ref - configuration_version - enabled_capabilities - action_policy - activated_at - replayed properties: configuration_ref: type: string format: uuid configuration_version: type: integer minimum: 1 enabled_capabilities: type: array items: type: string action_policy: type: string activated_at: type: string format: date-time replayed: type: boolean OperationAdmissionPolicy: type: object additionalProperties: false required: - policy_version - capability - request_window_seconds - soft_request_maximum - hard_request_maximum - soft_active_maximum - hard_active_maximum - hard_enforcement_enabled properties: policy_version: const: 1 capability: enum: - conversation.respond - daily.decide - workout.generate - workout.accept - workout.complete - coach_memory.compile request_window_seconds: type: integer minimum: 1 maximum: 86400 soft_request_maximum: type: integer minimum: 1 maximum: 1000000 hard_request_maximum: type: integer minimum: 1 maximum: 1000000 soft_active_maximum: type: integer minimum: 1 maximum: 1000000 hard_active_maximum: type: integer minimum: 1 maximum: 1000000 hard_enforcement_enabled: type: boolean OperationUsagePolicy: type: object additionalProperties: false description: Token and estimated-cost ceilings for one Operation capability, each set for a single Operation and for a day. required: - policy_version - capability - operation_token_maximum - operation_estimated_cost_microunits_maximum - daily_token_maximum - daily_estimated_cost_microunits_maximum - hard_enforcement_enabled properties: policy_version: const: 1 capability: enum: - conversation.respond - daily.decide - workout.generate - workout.accept - workout.complete - coach_memory.compile operation_token_maximum: type: integer minimum: 1 maximum: 1000000000 operation_estimated_cost_microunits_maximum: type: integer minimum: 1 maximum: 1000000000000 daily_token_maximum: type: integer minimum: 1 maximum: 1000000000 daily_estimated_cost_microunits_maximum: type: integer minimum: 1 maximum: 1000000000000 hard_enforcement_enabled: type: boolean examples: - policy_version: 1 capability: conversation.respond operation_token_maximum: 10000 operation_estimated_cost_microunits_maximum: 10000 daily_token_maximum: 20000 daily_estimated_cost_microunits_maximum: 20000 hard_enforcement_enabled: true PutSubjectContext: type: object additionalProperties: false required: - effective_at - valid_through - time_zone - local_date - primary_goal - experience - desired_sessions_per_week - available_days - unit_system - equipment_refs - safety_constraints - completed_workouts - mobility_summary - recovery_summary properties: effective_at: type: string format: date-time description: Must be strictly earlier than valid_through. valid_through: type: string format: date-time description: Exclusive currentness bound. time_zone: type: string local_date: type: string format: date primary_goal: type: string minLength: 1 maxLength: 1000 x-max-utf8-bytes: 1000 experience: type: string minLength: 1 maxLength: 100 x-max-utf8-bytes: 100 desired_sessions_per_week: type: integer minimum: 0 maximum: 14 available_days: type: array maxItems: 7 items: enum: - monday - tuesday - wednesday - thursday - friday - saturday - sunday unit_system: enum: - metric - imperial equipment_refs: type: array maxItems: 50 items: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 safety_constraints: type: array maxItems: 30 items: type: string minLength: 1 maxLength: 500 x-max-utf8-bytes: 500 exercise_constraints: type: array maxItems: 30 items: $ref: '#/components/schemas/ExerciseConstraint' completed_workouts: type: integer minimum: 0 maximum: 1000 mobility_summary: type: string minLength: 1 maxLength: 1000 x-max-utf8-bytes: 1000 recovery_summary: type: string minLength: 1 maxLength: 1000 x-max-utf8-bytes: 1000 blocker: type: - string - 'null' maxLength: 1000 x-max-utf8-bytes: 1000 daily_context: oneOf: - $ref: '#/components/schemas/DailyContext' - type: 'null' ExerciseConstraint: type: object additionalProperties: false required: - constraint_ref - target - effect - state properties: constraint_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 target: type: object additionalProperties: false required: - type - value properties: type: enum: - exercise_ref - safety_identifier value: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 effect: enum: - hard_exclusion - soft_avoidance state: enum: - active - lifted SubjectContextWrite: type: object additionalProperties: false required: - subject_ref - context_ref - context_version - effective_at - created_at - replayed properties: subject_ref: type: string format: uuid context_ref: type: string format: uuid context_version: type: integer minimum: 1 effective_at: type: string format: date-time created_at: type: string format: date-time replayed: type: boolean ConversationThread: type: object additionalProperties: false required: - thread_ref - subject_ref - main - created_at properties: thread_ref: type: string format: uuid subject_ref: type: string format: uuid main: type: boolean created_at: type: string format: date-time ConversationTurn: type: object additionalProperties: false required: - turn_ref - sequence - role - message - created_at - assistant_schema_version properties: turn_ref: type: string format: uuid sequence: type: integer minimum: 1 role: const: assistant message: type: string minLength: 1 maxLength: 16000 created_at: type: string format: date-time assistant_schema_version: const: conversation_grounding_v1 ConversationOperation: oneOf: - $ref: '#/components/schemas/QueuedOperation' - $ref: '#/components/schemas/RunningOperation' - $ref: '#/components/schemas/CompletedOperation' - $ref: '#/components/schemas/FailedOperation' - $ref: '#/components/schemas/CancelledOperation' - $ref: '#/components/schemas/ExpiredOperation' OperationFields: type: object additionalProperties: false required: - operation_ref - state - result - error_code - error_message - fallback_text - created_at - finished_at - action_receipts properties: operation_ref: type: string format: uuid state: enum: - queued - running - completed - failed - cancelled - expired result: oneOf: - $ref: '#/components/schemas/ConversationTurn' - type: 'null' error_code: type: - string - 'null' error_message: type: - string - 'null' fallback_text: type: - string - 'null' maxLength: 16000 created_at: type: string format: date-time finished_at: type: - string - 'null' format: date-time action_receipts: type: array items: $ref: '#/components/schemas/ConversationActionReceipt' ConversationActionReceipt: type: object additionalProperties: false required: - tool - outcome - safe_code - receipt_ref - content_version - outcome_text properties: tool: enum: - propose_preference_update - update_preferences - remember_explicit_preference outcome: enum: - succeeded - failed - proposed - unconfirmed safe_code: type: string receipt_ref: type: - string - 'null' maxLength: 200 content_version: type: - string - 'null' maxLength: 200 outcome_text: type: string description: Code-owned truthful rendering; never provider prose. QueuedOperation: allOf: - $ref: '#/components/schemas/OperationFields' - type: object properties: state: const: queued result: type: 'null' error_code: type: 'null' error_message: type: 'null' fallback_text: type: 'null' finished_at: type: 'null' RunningOperation: allOf: - $ref: '#/components/schemas/OperationFields' - type: object properties: state: const: running result: type: 'null' error_code: type: 'null' error_message: type: 'null' fallback_text: type: 'null' finished_at: type: 'null' CompletedOperation: allOf: - $ref: '#/components/schemas/OperationFields' - type: object properties: state: const: completed result: $ref: '#/components/schemas/ConversationTurn' error_code: type: 'null' error_message: type: 'null' fallback_text: type: 'null' finished_at: type: string format: date-time FailedOperation: allOf: - $ref: '#/components/schemas/OperationFields' - type: object properties: state: const: failed result: type: 'null' error_code: enum: - provider_unavailable - provider_invalid_response - connector_unavailable - client_configuration_missing - subject_context_unavailable - execution_deadline_exceeded - execution_attempts_exhausted error_message: const: The conversation response could not be completed safely. fallback_text: type: - string - 'null' maxLength: 16000 finished_at: type: string format: date-time CancelledOperation: allOf: - $ref: '#/components/schemas/OperationFields' - type: object properties: state: const: cancelled result: type: 'null' error_code: const: operation_cancelled error_message: const: The conversation operation was cancelled before completion. fallback_text: type: - string - 'null' maxLength: 16000 finished_at: type: string format: date-time ExpiredOperation: allOf: - $ref: '#/components/schemas/OperationFields' - type: object properties: state: const: expired result: type: 'null' error_code: const: operation_expired error_message: const: The conversation operation is no longer available. fallback_text: type: - string - 'null' maxLength: 16000 finished_at: type: string format: date-time RequestWorkoutDraft: type: object additionalProperties: false required: - subject_ref properties: subject_ref: type: string format: uuid WorkoutGenerationPolicy: type: object additionalProperties: false required: - maximum_supersets - maximum_exercises_per_superset - maximum_total_exercises - minimum_rest_before_seconds - maximum_rest_before_seconds - minimum_rest_between_cycles_seconds - maximum_rest_between_cycles_seconds - maximum_total_sets - maximum_total_count_repetitions - maximum_total_timed_work_seconds - minimum_estimated_duration_seconds - maximum_estimated_duration_seconds properties: maximum_supersets: type: integer minimum: 1 maximum: 20 maximum_exercises_per_superset: type: integer minimum: 1 maximum: 20 maximum_total_exercises: type: integer minimum: 1 maximum: 20 minimum_rest_before_seconds: type: integer minimum: 0 maximum: 3600 maximum_rest_before_seconds: type: integer minimum: 0 maximum: 3600 minimum_rest_between_cycles_seconds: type: integer minimum: 0 maximum: 3600 maximum_rest_between_cycles_seconds: type: integer minimum: 0 maximum: 3600 maximum_total_sets: type: integer minimum: 1 maximum: 400 maximum_total_count_repetitions: type: - integer - 'null' minimum: 1 maximum: 400000 maximum_total_timed_work_seconds: type: - integer - 'null' minimum: 1 maximum: 2880000 minimum_estimated_duration_seconds: type: - integer - 'null' minimum: 0 maximum: 604800 maximum_estimated_duration_seconds: type: - integer - 'null' minimum: 0 maximum: 604800 WorkoutDraft: type: object additionalProperties: false required: - title - warmup_supersets - supersets - cooldown_supersets properties: title: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 warmup_supersets: type: array maxItems: 20 items: $ref: '#/components/schemas/WorkoutDraftSuperset' supersets: type: array minItems: 1 maxItems: 20 items: $ref: '#/components/schemas/WorkoutDraftSuperset' cooldown_supersets: type: array maxItems: 20 items: $ref: '#/components/schemas/WorkoutDraftSuperset' WorkoutDraftSuperset: type: object additionalProperties: false required: - order - rest_between_cycles_seconds - workout_exercises properties: order: type: integer minimum: 1 maximum: 20 rest_between_cycles_seconds: type: integer minimum: 0 maximum: 3600 workout_exercises: type: array minItems: 1 maxItems: 20 items: $ref: '#/components/schemas/WorkoutDraftExercise' WorkoutDraftExercise: type: object additionalProperties: false required: - exercise_ref - exercise_content_version - order - sets - rest_before_seconds - prescription properties: exercise_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 exercise_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 order: type: integer minimum: 1 maximum: 20 sets: type: integer minimum: 1 maximum: 20 rest_before_seconds: type: integer minimum: 0 maximum: 3600 prescription: oneOf: - $ref: '#/components/schemas/CountPrescription' - $ref: '#/components/schemas/TimePrescription' CountPrescription: type: object additionalProperties: false required: - type - repetitions properties: type: const: count repetitions: type: integer minimum: 1 maximum: 1000 TimePrescription: type: object additionalProperties: false required: - type - duration_seconds properties: type: const: time duration_seconds: type: integer minimum: 1 maximum: 7200 WorkoutGenerationOperation: type: object additionalProperties: false required: - operation_ref - state - draft_ref - draft - error_code - error_message - catalog_version - execution_mode - validation_codes - total_sets - total_count_repetitions - total_timed_work_seconds - estimated_duration_seconds - created_at - finished_at properties: operation_ref: type: string format: uuid state: enum: - queued - running - completed - failed - cancelled - expired draft_ref: type: - string - 'null' format: uuid draft: oneOf: - $ref: '#/components/schemas/WorkoutDraft' - type: 'null' error_code: enum: - client_configuration_unavailable - subject_context_unavailable - exercise_catalog_unavailable - exercise_catalog_invalid - empty_candidate_set - provider_unavailable - provider_invalid_response - draft_validation_failed - execution_mode_unavailable - execution_deadline_exceeded - execution_attempts_exhausted - operation_cancelled - operation_expired - workout_generation_failed - null error_message: description: The workout draft could not be completed safely. oneOf: - type: 'null' - type: string const: The workout draft could not be completed safely. catalog_version: type: - string - 'null' maxLength: 200 execution_mode: enum: - deterministic - real_ai validation_codes: type: array uniqueItems: true items: enum: - validated - regression_substitution - constraint_conflict - no_safe_alternative - structure_invalid - warmup_unsupported - cooldown_unsupported - exercise_missing - exercise_changed - exercise_inactive - exercise_unavailable - generation_facts_missing - prescription_unsupported - prescription_out_of_range - equipment_incompatible - safety_conflict - rest_out_of_range - volume_exceeded - duration_unverifiable - duration_out_of_range total_sets: type: - integer - 'null' minimum: 0 total_count_repetitions: type: - integer - 'null' minimum: 0 total_timed_work_seconds: type: - integer - 'null' minimum: 0 estimated_duration_seconds: type: - integer - 'null' minimum: 0 created_at: type: string format: date-time finished_at: type: - string - 'null' format: date-time ConflictProblem: type: object additionalProperties: false required: - type - title - status - detail properties: type: type: string title: type: string status: const: 409 detail: enum: - Conflict - subject_deleted description: subject_deleted means the Subject is deleted and must be reactivated before new Subject-owned requests. Conflict covers the other conflicts listed for the operation. NotFoundProblem: type: object additionalProperties: false required: - type - title - status - detail example: type: about:blank title: Not Found status: 404 detail: Not Found properties: type: const: about:blank title: const: Not Found status: const: 404 detail: const: Not Found RequestDailyPlan: type: object additionalProperties: false required: - subject_ref properties: subject_ref: type: string format: uuid DailyDecisionPolicy: type: object additionalProperties: false required: - enabled_outcomes - rest_fallback_codes - recovery_max_age_seconds - activity_max_age_seconds properties: enabled_outcomes: type: array minItems: 1 maxItems: 3 uniqueItems: true items: enum: - workout - rest - mobility workout_source_policy: enum: - existing_only - existing_preferred - generation_only non_workout_preference: enum: - rest - mobility rest_fallback_codes: type: array maxItems: 2 uniqueItems: true items: enum: - recovery_unavailable - workout_content_unavailable recovery_max_age_seconds: type: integer minimum: 60 maximum: 86400 activity_max_age_seconds: type: integer minimum: 60 maximum: 86400 oneOf: - properties: enabled_outcomes: minItems: 1 maxItems: 1 items: enum: - rest non_workout_preference: enum: - rest rest_fallback_codes: maxItems: 1 items: enum: - recovery_unavailable required: - non_workout_preference allOf: - not: required: - workout_source_policy - properties: enabled_outcomes: minItems: 1 maxItems: 1 items: enum: - mobility non_workout_preference: enum: - mobility rest_fallback_codes: maxItems: 0 required: - non_workout_preference allOf: - not: required: - workout_source_policy - properties: enabled_outcomes: minItems: 1 maxItems: 1 items: enum: - workout workout_source_policy: enum: - existing_only - existing_preferred - generation_only rest_fallback_codes: maxItems: 0 required: - workout_source_policy allOf: - not: required: - non_workout_preference - properties: enabled_outcomes: minItems: 2 maxItems: 2 items: enum: - rest - mobility non_workout_preference: enum: - rest - mobility rest_fallback_codes: maxItems: 1 items: enum: - recovery_unavailable required: - non_workout_preference allOf: - not: required: - workout_source_policy - properties: enabled_outcomes: minItems: 2 maxItems: 2 items: enum: - workout - rest workout_source_policy: enum: - existing_only - existing_preferred - generation_only non_workout_preference: enum: - rest rest_fallback_codes: maxItems: 2 items: enum: - recovery_unavailable - workout_content_unavailable required: - workout_source_policy - non_workout_preference - properties: enabled_outcomes: minItems: 2 maxItems: 2 items: enum: - workout - mobility workout_source_policy: enum: - existing_only - existing_preferred - generation_only non_workout_preference: enum: - mobility rest_fallback_codes: maxItems: 0 required: - workout_source_policy - non_workout_preference - properties: enabled_outcomes: minItems: 3 maxItems: 3 items: enum: - workout - rest - mobility workout_source_policy: enum: - existing_only - existing_preferred - generation_only non_workout_preference: enum: - rest - mobility rest_fallback_codes: maxItems: 2 items: enum: - recovery_unavailable - workout_content_unavailable required: - workout_source_policy - non_workout_preference description: Seven enabled-outcome subsets. Conditional fields must be omitted when inapplicable; explicit null is invalid. Choices follow the deterministic table in ADR 0009. Existing-only requires workout.search, workout.resolve, exercise.resolve and a supported prescription. Generation-only requires workout.generate, exercise.search, exercise.resolve, a supported prescription and the existing generation policy. Existing-preferred requires both bundles. DailyContext: type: object additionalProperties: false required: - week_start - history_through - completed_workout_dates - completed_mobility_dates - preferred_days - recovery_state - recovery_observed_at - recovery_valid_through properties: week_start: type: string format: date history_through: type: string format: date-time completed_workout_dates: type: array maxItems: 28 items: type: string format: date completed_mobility_dates: type: array maxItems: 28 items: type: string format: date preferred_days: type: array maxItems: 7 uniqueItems: true items: type: integer minimum: 1 maximum: 7 recovery_state: enum: - ready - mobility_only - rest_only - unknown recovery_observed_at: type: string format: date-time recovery_valid_through: type: string format: date-time description: week_start is the Monday containing parent local_date in its IANA time_zone. Convert history_through to that time_zone before deriving the inclusive history interval from week_start minus seven local calendar days through the local date of history_through, which cannot exceed parent local_date. Repeated completion dates represent separate sessions, with at most 28 entries in each completion list. Only current-week workout entries count toward the weekly target; mobility entries do not. Preferred ISO weekdays are unique and must be a subset of parent available_days. Recovery observed_at < valid_through. Admission requires matching current local date, effective_at <= now < valid_through and history_through <= now < history_through + activity_max_age_seconds. Recovery is usable only for ready, mobility_only or rest_only with observed_at <= now < min(recovery_valid_through, observed_at + recovery_max_age_seconds). These cross-field calendar, timezone and freshness relations are enforced by the application. Date-only entries assert session completeness through history_through without inventing intra-day timestamps; a completed workout today prevents selection of another workout. DailyExistingWorkoutExercise: type: object additionalProperties: false required: - exercise_ref - exercise_content_version - order - sets - rest_before_seconds - prescription_type - prescription_value properties: exercise_ref: type: string minLength: 1 maxLength: 200 exercise_content_version: type: string minLength: 1 maxLength: 200 order: type: integer minimum: 1 maximum: 20 sets: type: integer minimum: 1 maximum: 20 rest_before_seconds: type: integer minimum: 0 maximum: 3600 prescription_type: enum: - count - time prescription_value: type: integer minimum: 1 maximum: 7200 allOf: - if: properties: prescription_type: const: count then: properties: prescription_value: maximum: 1000 DailyExistingWorkout: type: object additionalProperties: false required: - workout_ref - catalog_version - warmup_supersets - supersets - cooldown_supersets properties: workout_ref: type: string minLength: 1 maxLength: 200 catalog_version: type: string minLength: 1 maxLength: 200 warmup_supersets: type: array maxItems: 20 items: $ref: '#/components/schemas/DailyExistingWorkoutSuperset' supersets: type: array minItems: 1 maxItems: 20 items: $ref: '#/components/schemas/DailyExistingWorkoutSuperset' cooldown_supersets: type: array maxItems: 20 items: $ref: '#/components/schemas/DailyExistingWorkoutSuperset' DailyExistingWorkoutSuperset: type: object additionalProperties: false required: - order - workout_exercises properties: order: type: integer minimum: 1 maximum: 20 workout_exercises: type: array minItems: 1 maxItems: 20 items: $ref: '#/components/schemas/DailyExistingWorkoutExercise' DailyPlanDecision: type: object additionalProperties: false required: - decision_ref - configuration_ref - context_ref - configuration_version - context_version - schema_version - outcome - reason_codes - reason_text - sources - workout_source - existing_workout - existing_workout_verified_at - existing_workout_catalog_version - generated_workout - workout_generation_execution_mode - generation_execution_mode - generation_provider - generation_model - generation_schema_version - generation_catalog_version - decided_at - local_date - week_start - time_zone - policy_fingerprint properties: decision_ref: type: string format: uuid configuration_ref: type: string format: uuid context_ref: type: string format: uuid configuration_version: type: integer minimum: 1 context_version: type: integer minimum: 1 schema_version: const: 1 outcome: enum: - workout - rest - mobility reason_codes: type: array minItems: 1 maxItems: 17 uniqueItems: true items: enum: - weekly_target_met - workout_completed_today - preferred_workout_day - deferred_to_preferred_day - target_at_risk - target_unattainable - day_unavailable - workout_disabled - recovery_rest_only - recovery_mobility_only - context_blocker - recovery_unavailable - non_workout_preference - only_eligible_non_workout - existing_workout_verified - generated_workout_validated - workout_content_unavailable reason_text: type: string minLength: 1 maxLength: 4000 sources: type: array minItems: 1 maxItems: 13 items: type: object additionalProperties: false required: - code - reference - version - fields properties: code: enum: - daily_policy - activity_history - availability - recovery - context_blocker - existing_workout - generated_workout - preference_memory - primary_goal - equipment reference: type: string minLength: 1 maxLength: 200 version: type: string minLength: 1 maxLength: 200 fields: type: array minItems: 1 items: type: string minLength: 1 maxLength: 200 maxItems: 4 allOf: - if: properties: code: const: daily_policy then: properties: fields: const: - daily_decision_policy - if: properties: code: const: activity_history then: properties: fields: const: - desired_sessions_per_week - daily_context.week_start - daily_context.history_through - daily_context.completed_workout_dates - if: properties: code: const: availability then: properties: fields: const: - available_days - daily_context.preferred_days - local_date - time_zone - if: properties: code: const: recovery then: properties: fields: const: - daily_context.recovery_state - daily_context.recovery_observed_at - daily_context.recovery_valid_through - if: properties: code: const: context_blocker then: properties: fields: const: - blocker - if: properties: code: const: existing_workout then: properties: fields: const: - supersets - if: properties: code: const: generated_workout then: properties: fields: const: - draft - if: properties: code: const: preference_memory then: properties: fields: const: - preference_name - text - source_turn_ref - if: properties: code: const: primary_goal then: properties: fields: const: - primary_goal - if: properties: code: const: equipment then: properties: fields: const: - equipment_refs workout_source: enum: - existing_workout - generated_workout - null existing_workout: oneOf: - $ref: '#/components/schemas/DailyExistingWorkout' - type: 'null' existing_workout_verified_at: oneOf: - type: string format: date-time - type: 'null' existing_workout_catalog_version: oneOf: - type: string minLength: 1 maxLength: 200 - type: 'null' generated_workout: oneOf: - $ref: '#/components/schemas/WorkoutDraft' - type: 'null' workout_generation_execution_mode: enum: - deterministic - real_ai - null generation_execution_mode: enum: - deterministic - real_ai - null generation_provider: oneOf: - type: string minLength: 1 maxLength: 100 - type: 'null' generation_model: oneOf: - type: string minLength: 1 maxLength: 200 - type: 'null' generation_schema_version: oneOf: - type: string minLength: 1 maxLength: 100 - type: 'null' generation_catalog_version: oneOf: - type: string minLength: 1 maxLength: 200 - type: 'null' decided_at: type: string format: date-time local_date: type: string format: date week_start: type: string format: date time_zone: type: string minLength: 1 maxLength: 100 policy_fingerprint: type: string pattern: ^[0-9a-f]{64}$ description: Immutable safe result. decided_at and receipt verification use application time; Operation lifecycle uses native time. Never compare these clock domains. decided_at in time_zone must equal local_date. Polling never rewrites this document. Memory evidence appears only for actual generation. Source fields are code-owned pinned evidence. oneOf: - properties: workout_source: const: null outcome: enum: - rest - mobility generated_workout: type: 'null' generation_execution_mode: type: 'null' generation_provider: type: 'null' generation_model: type: 'null' generation_schema_version: type: 'null' generation_catalog_version: type: 'null' existing_workout: type: 'null' existing_workout_verified_at: type: 'null' existing_workout_catalog_version: type: 'null' - properties: workout_source: const: existing_workout outcome: enum: - workout generated_workout: type: 'null' generation_execution_mode: type: 'null' generation_provider: type: 'null' generation_model: type: 'null' generation_schema_version: type: 'null' generation_catalog_version: type: 'null' existing_workout: not: type: 'null' existing_workout_verified_at: not: type: 'null' existing_workout_catalog_version: not: type: 'null' - properties: workout_source: const: generated_workout outcome: enum: - workout generated_workout: not: type: 'null' generation_execution_mode: not: type: 'null' generation_provider: not: type: 'null' generation_model: not: type: 'null' generation_schema_version: not: type: 'null' generation_catalog_version: not: type: 'null' existing_workout: type: 'null' existing_workout_verified_at: type: 'null' existing_workout_catalog_version: type: 'null' workout_generation_execution_mode: enum: - real_ai - deterministic allOf: - if: properties: workout_source: const: generated_workout workout_generation_execution_mode: const: deterministic then: properties: generation_execution_mode: const: deterministic - if: properties: workout_source: const: generated_workout workout_generation_execution_mode: const: real_ai then: properties: generation_execution_mode: const: real_ai - if: properties: workout_source: const: existing_workout then: properties: reason_codes: contains: const: existing_workout_verified else: properties: reason_codes: not: contains: const: existing_workout_verified - if: properties: workout_source: const: generated_workout then: properties: reason_codes: contains: const: generated_workout_validated else: properties: reason_codes: not: contains: const: generated_workout_validated DailyPlanOperation: type: object additionalProperties: false required: - operation_ref - state - result - error_code - error_message - currentness - created_at - finished_at properties: operation_ref: type: string format: uuid state: enum: - queued - running - completed - failed - cancelled - expired result: oneOf: - $ref: '#/components/schemas/DailyPlanDecision' - type: 'null' error_code: enum: - client_configuration_unavailable - subject_context_unavailable - local_date_changed - activity_history_unavailable - recovery_unavailable - no_eligible_outcome - workout_content_unavailable - connector_unavailable - connector_invalid_response - provider_unavailable - provider_invalid_response - execution_mode_unavailable - execution_deadline_exceeded - execution_attempts_exhausted - operation_cancelled - operation_expired - decision_contract_invalid - null error_message: oneOf: - type: string minLength: 1 maxLength: 200 - type: 'null' currentness: enum: - current - context_unavailable - context_changed - local_date_changed - configuration_changed - activity_expired - recovery_changed - null created_at: type: string format: date-time finished_at: oneOf: - type: string format: date-time - type: 'null' oneOf: - properties: state: const: queued finished_at: type: 'null' result: type: 'null' currentness: type: 'null' error_code: type: 'null' error_message: type: 'null' - properties: state: const: running finished_at: type: 'null' result: type: 'null' currentness: type: 'null' error_code: type: 'null' error_message: type: 'null' - properties: state: const: completed finished_at: type: string format: date-time result: $ref: '#/components/schemas/DailyPlanDecision' currentness: enum: - current - context_unavailable - context_changed - local_date_changed - configuration_changed - activity_expired - recovery_changed error_code: type: 'null' error_message: type: 'null' - properties: state: const: failed finished_at: type: string format: date-time result: type: 'null' currentness: type: 'null' error_code: enum: - client_configuration_unavailable - subject_context_unavailable - local_date_changed - activity_history_unavailable - recovery_unavailable - no_eligible_outcome - workout_content_unavailable - connector_unavailable - connector_invalid_response - provider_unavailable - provider_invalid_response - execution_mode_unavailable - execution_deadline_exceeded - execution_attempts_exhausted - operation_cancelled - operation_expired - decision_contract_invalid error_message: type: string minLength: 1 maxLength: 200 - properties: state: const: cancelled finished_at: type: string format: date-time result: type: 'null' currentness: type: 'null' error_code: const: operation_cancelled error_message: type: string minLength: 1 maxLength: 200 - properties: state: const: expired finished_at: type: string format: date-time result: type: 'null' currentness: type: 'null' error_code: const: operation_expired error_message: type: string minLength: 1 maxLength: 200 allOf: - if: properties: error_code: const: client_configuration_unavailable then: properties: error_message: const: An active daily coaching configuration is required. - if: properties: error_code: const: subject_context_unavailable then: properties: error_message: const: Current subject context is required. - if: properties: error_code: const: local_date_changed then: properties: error_message: const: Request a new daily plan for the current local date. - if: properties: error_code: const: activity_history_unavailable then: properties: error_message: const: Current activity history is required. - if: properties: error_code: const: recovery_unavailable then: properties: error_message: const: Current recovery information is required. - if: properties: error_code: const: no_eligible_outcome then: properties: error_message: const: The daily policy and current context permit no daily outcome. - if: properties: error_code: const: workout_content_unavailable then: properties: error_message: const: Suitable workout content is unavailable. - if: properties: error_code: const: connector_unavailable then: properties: error_message: const: The client connector is temporarily unavailable. - if: properties: error_code: const: connector_invalid_response then: properties: error_message: const: The client connector returned unusable information. - if: properties: error_code: const: provider_unavailable then: properties: error_message: const: Workout generation is temporarily unavailable. - if: properties: error_code: const: provider_invalid_response then: properties: error_message: const: Workout generation returned an unusable result. - if: properties: error_code: const: execution_mode_unavailable then: properties: error_message: const: The requested workout generation setting is unavailable. - if: properties: error_code: const: execution_deadline_exceeded then: properties: error_message: const: The daily plan execution deadline was exceeded. - if: properties: error_code: const: execution_attempts_exhausted then: properties: error_message: const: The daily plan execution attempt limit was reached. - if: properties: error_code: const: operation_cancelled then: properties: error_message: const: The daily plan operation was cancelled. - if: properties: error_code: const: operation_expired then: properties: error_message: const: The daily plan operation has expired. - if: properties: error_code: const: decision_contract_invalid then: properties: error_message: const: A valid daily decision could not be established. WorkoutCompletionRequest: type: object additionalProperties: false required: - event_ref - event_type - schema_version - subject_ref - activity_ref - activity_revision - occurred_at - workout_ref - duration_seconds - exercise_results - feedback properties: event_ref: type: string format: uuid event_type: const: workout.completed schema_version: const: 1 subject_ref: type: string format: uuid activity_ref: type: string minLength: 1 maxLength: 255 activity_revision: type: integer minimum: 1 occurred_at: type: string format: date-time workout_ref: type: string minLength: 1 maxLength: 255 duration_seconds: type: integer minimum: 0 maximum: 604800 exercise_results: type: array minItems: 1 maxItems: 100 items: $ref: '#/components/schemas/WorkoutExerciseResult' feedback: $ref: '#/components/schemas/WorkoutFeedback' WorkoutExerciseResult: type: object additionalProperties: false required: - exercise_ref - exercise_content_version - status - prescription_type - prescribed_value - completed_value properties: exercise_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 exercise_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 status: enum: - performed - skipped prescription_type: enum: - count - time prescribed_value: type: integer minimum: 1 completed_value: type: - integer - 'null' minimum: 0 WorkoutFeedback: type: object additionalProperties: false required: - rating - note properties: rating: enum: - positive - neutral - tough note: type: - string - 'null' minLength: 1 maxLength: 2000 WorkoutCompletionOperation: type: object additionalProperties: false required: - operation_ref - state - activity_ref - activity_revision - currentness - reaction - notification_suggested - error_code - error_message - created_at - finished_at properties: operation_ref: type: string format: uuid state: enum: - queued - running - completed - failed - cancelled - expired activity_ref: type: string activity_revision: type: integer minimum: 1 currentness: enum: - current - superseded description: Superseded results never expose reaction or notification fields. reaction: type: - string - 'null' description: Present only for a current completed Operation; null for every other lifecycle state. notification_suggested: type: - boolean - 'null' description: Present only for a current completed Operation; null for every other lifecycle state. error_code: type: - string - 'null' enum: - model_failed - operation_cancelled - operation_expired - null error_message: type: - string - 'null' created_at: type: string format: date-time finished_at: type: - string - 'null' format: date-time servers: - url: https://{kinellect_host} description: Your Kinellect environment. The host is issued during onboarding. variables: kinellect_host: default: api.kinellect.com description: Issued during onboarding. security: - bearerAuth: [] tags: - name: Configuration description: Activate immutable, versioned client configurations. - name: Subject context description: Store the authoritative facts about a user that coaching relies on. - name: Subject lifecycle description: Delete a user (tombstone and cancel their work) or reactivate a deleted user. - name: Conversation description: Threads, Turns and conversation Operations. Creating a Thread also registers the user. - name: Workout drafts description: Validated workout proposals built from your catalog. - name: Daily plans description: Deterministic daily workout, rest or mobility decisions. - name: Workout acceptance description: Save or schedule a proposed workout through your connector after explicit approval. - name: Workout completion description: Report completed workouts and receive a coach reaction. ``` # Appendix B: Connector API you implement (OpenAPI 3.1, YAML) ```yaml openapi: 3.1.0 info: title: Kinellect Connector API (implemented by you) version: 1.0.0 description: "The Connector API is the set of HTTPS endpoints that **your** service implements and **Kinellect** calls.\n\ Kinellect never reads your database. Every fact it needs about a user (current context), your content\n(exercises and\ \ workouts) and every action it takes on your side (saving a preference, saving or scheduling\na workout) goes through\ \ these endpoints.\n\n**Transport rules that apply to every endpoint**\n\n- Kinellect calls `{your connector base URL}`\ \ + the fixed path shown for each operation. You register one HTTPS\n base URL and one bearer secret with Kinellect during\ \ onboarding. No per-endpoint URLs are configurable.\n- Every request carries `Authorization: Bearer `,\ \ `Accept: application/json`,\n `Accept-Encoding: identity`, `Content-Type: application/json` and `X-Kinellect-Tenant:\ \ `.\n Mutating actions also carry `Idempotency-Key`.\n- Reply with HTTP 2xx and a JSON **object** body.\ \ Any other status is treated as a failure.\n- **Responses must contain exactly the documented keys.** Kinellect compares\ \ the complete key set of every\n object. A missing key *or an extra key* makes the whole response invalid. Optional\ \ keys are either present\n or absent as documented; they are never replaced by `null` unless the schema says the value\ \ is nullable.\n- Do not compress responses (`Content-Encoding` must be absent or `identity`), do not redirect (redirects\ \ are\n not followed), keep bodies at or below 131072 bytes and never include NUL characters.\n- String limits are UTF-8\ \ **byte** lengths, not character counts. Empty strings are invalid unless stated.\n- Kinellect waits at most 2 seconds\ \ for a connection or between bytes and at most 5 seconds per attempt. It\n retries **once** after HTTP 408, 429, 502,\ \ 503, 504 or a network failure, honouring `Retry-After` between\n 0.1 and 1 second, and only while the calling operation's\ \ deadline allows it. Make endpoints fast and\n idempotent.\n- `subject_ref` in every request is the **Kinellect subject\ \ reference** (a UUID) that Kinellect returned when\n you created the user's first Conversation Thread. Keep a mapping\ \ from your own user id to this value.\n" paths: /ai-coach-connector/v1/capabilities: get: summary: Declare the connector contract version and supported capabilities description: Called when a client configuration version is activated, with a 10 second deadline. A failed, timed-out or invalid response makes activation return 503. Activation returns 422 when contract_version differs from the registered connector contract version or when an enabled connector capability (context.resolve, exercise.search, exercise.resolve, workout.search, workout.resolve, workout.save, workout.save_and_schedule, workout.prescription.count, workout.prescription.time or preference.update) is absent from capabilities. security: - connectorBearer: [] parameters: - $ref: '#/components/parameters/TenantHeader' responses: '200': description: Connector capabilities content: application/json: schema: $ref: '#/components/schemas/ConnectorCapabilities' examples: capabilities: value: contract_version: 1 content_version: catalog-2026-09-20 capabilities: - context.resolve - exercise.search - exercise.resolve - workout.prescription.count - workout.prescription.time - preference.update operationId: getConnectorCapabilities tags: - Discovery /ai-coach-connector/v1/context/resolve: post: summary: Return the Subject's current authoritative context description: 'Return the same facts that PUT /v1/subjects/{subject_ref}/context/{context_version} accepts, plus context_version, content_version and provenance. Return {"available": false} with no other key when no context exists; available: true is invalid. daily_context and exercise_constraints are optional keys; daily_context may also be null.' security: - connectorBearer: [] parameters: - $ref: '#/components/parameters/TenantHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SubjectRequest' example: subject_ref: 7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10 responses: '200': description: Current context or explicit unavailability content: application/json: schema: oneOf: - $ref: '#/components/schemas/ResolvedSubjectContext' - $ref: '#/components/schemas/SubjectContextUnavailable' examples: available: value: context_version: 12 content_version: profile-2026-09-24 provenance: client_profile effective_at: '2026-09-25T05:00:00Z' valid_through: '2026-09-26T05:00:00Z' time_zone: Europe/Oslo local_date: '2026-09-25' primary_goal: Build pain-free pulling strength over 8 weeks experience: beginner desired_sessions_per_week: 3 available_days: - monday - wednesday - friday unit_system: metric equipment_refs: - resistance-band - pull-up-bar safety_constraints: - avoid-overhead-loading completed_workouts: 14 mobility_summary: Limited shoulder flexion on the left side recovery_summary: Sleeping well, no soreness reported blocker: null available_with_optional_keys: value: context_version: 13 content_version: profile-2026-09-25 provenance: client_profile effective_at: '2026-09-25T05:00:00+00:00' valid_through: '2026-09-26T05:00:00+00:00' time_zone: Europe/Oslo local_date: '2026-09-25' primary_goal: Build pain-free pulling strength over 8 weeks experience: beginner desired_sessions_per_week: 3 available_days: - monday - wednesday - friday unit_system: metric equipment_refs: - resistance-band - pull-up-bar safety_constraints: - avoid-overhead-loading completed_workouts: 14 mobility_summary: Limited shoulder flexion on the left side recovery_summary: Sleeping well, no soreness reported blocker: Wait for clearance after the shoulder check-up daily_context: week_start: '2026-09-21' history_through: '2026-09-25T05:00:00Z' completed_workout_dates: - '2026-09-21' - '2026-09-23' completed_mobility_dates: - '2026-09-16' preferred_days: - 1 - 3 - 5 recovery_state: ready recovery_observed_at: '2026-09-25T05:00:00Z' recovery_valid_through: '2026-09-26T05:00:00Z' exercise_constraints: - constraint_ref: shoulder-overhead target: type: safety_identifier value: overhead-loading effect: hard_exclusion state: active unavailable: value: available: false operationId: resolveSubjectContext tags: - Context /ai-coach-connector/v1/content/exercises/search: post: summary: Search the exercise catalog for one Subject description: Return at most 20 exercises matching a free-text query written by Kinellect. security: - connectorBearer: [] parameters: - $ref: '#/components/parameters/TenantHeader' requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - subject_ref - query properties: subject_ref: $ref: '#/components/schemas/SubjectRef' query: type: string minLength: 1 maxLength: 500 x-max-utf8-bytes: 500 example: subject_ref: 7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10 query: scapular pull responses: '200': description: Matching exercises from one catalog version content: application/json: schema: $ref: '#/components/schemas/ExerciseCatalogResult' examples: with_optional_facts: value: content_version: catalog-2026-09-20 exercises: - exercise_ref: scapular-pull-up display_name: Scapular pull-up movement_pattern: vertical_pull equipment_refs: - pull-up-bar verification_facts: item_content_version: v7 active: true available: true prescription_types: - count safety_identifiers: [] generation_facts: minimum_count: 5 maximum_count: 15 count_duration_seconds_per_repetition: 3 minimum_duration_seconds: null maximum_duration_seconds: null relationships: progression_refs: - pull-up regression_refs: - band-pull-apart related_refs: [] projection_only: value: content_version: catalog-2026-09-20 exercises: - exercise_ref: band-pull-apart display_name: Band pull-apart movement_pattern: horizontal_pull equipment_refs: - resistance-band operationId: searchExercises tags: - Content /ai-coach-connector/v1/content/exercises/resolve: post: summary: Resolve exercises by reference description: Return the current projection of the requested exercises; omit references that are unknown. security: - connectorBearer: [] parameters: - $ref: '#/components/parameters/TenantHeader' requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - subject_ref - exercise_refs properties: subject_ref: $ref: '#/components/schemas/SubjectRef' exercise_refs: type: array minItems: 1 maxItems: 20 uniqueItems: true items: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 example: subject_ref: 7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10 exercise_refs: - scapular-pull-up - dead-hang responses: '200': description: Resolved exercises from one catalog version content: application/json: schema: $ref: '#/components/schemas/ExerciseCatalogResult' examples: resolved: value: content_version: catalog-2026-09-20 exercises: - exercise_ref: scapular-pull-up display_name: Scapular pull-up movement_pattern: vertical_pull equipment_refs: - pull-up-bar verification_facts: item_content_version: v7 active: true available: true prescription_types: - count safety_identifiers: [] - exercise_ref: dead-hang display_name: Dead hang movement_pattern: hang equipment_refs: - pull-up-bar verification_facts: item_content_version: v2 active: true available: true prescription_types: - time safety_identifiers: - grip-load generation_facts: minimum_count: null maximum_count: null count_duration_seconds_per_repetition: null minimum_duration_seconds: 10 maximum_duration_seconds: 60 operationId: resolveExercises tags: - Content /ai-coach-connector/v1/content/workouts/search: post: summary: Search existing workouts description: Return at most limit unique workout references from one catalog version. The request carries no cursor. security: - connectorBearer: [] parameters: - $ref: '#/components/parameters/TenantHeader' requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - subject_ref - query - limit properties: subject_ref: $ref: '#/components/schemas/SubjectRef' query: type: string minLength: 1 maxLength: 500 x-max-utf8-bytes: 500 limit: type: integer minimum: 1 maximum: 20 example: subject_ref: 7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10 query: upper body beginner limit: 3 responses: '200': description: One page of workout references content: application/json: schema: $ref: '#/components/schemas/WorkoutSearchPage' examples: page: value: catalog_version: workouts-2026-09-20 workout_refs: - upper-a - upper-b next_cursor: null operationId: searchWorkouts tags: - Content /ai-coach-connector/v1/content/workouts/resolve: post: summary: Resolve one existing workout description: Return status resolved with the full workout, or missing, inactive or changed with workout null. security: - connectorBearer: [] parameters: - $ref: '#/components/parameters/TenantHeader' requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - subject_ref - workout_ref - expected_catalog_version properties: subject_ref: $ref: '#/components/schemas/SubjectRef' workout_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 expected_catalog_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 example: subject_ref: 7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10 workout_ref: upper-a expected_catalog_version: workouts-2026-09-20 responses: '200': description: Resolution outcome content: application/json: schema: $ref: '#/components/schemas/WorkoutResolution' examples: resolved: value: status: resolved workout: workout_ref: upper-a catalog_version: workouts-2026-09-20 warmup_supersets: - order: 1 workout_exercises: - exercise_ref: arm-circles exercise_content_version: v1 order: 1 sets: 1 rest_before_seconds: 0 prescription: type: time duration_seconds: 30 supersets: - order: 1 workout_exercises: - exercise_ref: scapular-pull-up exercise_content_version: v7 order: 1 sets: 3 rest_before_seconds: 60 prescription: type: count repetitions: 8 - exercise_ref: dead-hang exercise_content_version: v2 order: 2 sets: 2 rest_before_seconds: 45 prescription: type: time duration_seconds: 30 cooldown_supersets: [] missing: value: status: missing workout: null inactive: value: status: inactive workout: null changed: value: status: changed workout: null operationId: resolveWorkout tags: - Content /ai-coach-connector/v1/actions/preferences/update: post: summary: Update one Subject preference description: Bind the Idempotency-Key durably to the exact request; a retry with the same key must return the original result without applying the change twice. Report a business refusal as succeeded false in a 200 response. Kinellect records the update as confirmed only when succeeded is true and receipt_ref is not null; succeeded true with receipt_ref null is recorded as unconfirmed. security: - connectorBearer: [] parameters: - $ref: '#/components/parameters/TenantHeader' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: - subject_ref - preference_name - value - evidence_turn_ref - input_fingerprint properties: subject_ref: $ref: '#/components/schemas/SubjectRef' preference_name: enum: - preferred_session_length - training_days - equipment_preference - coaching_tone value: type: string minLength: 1 maxLength: 500 x-max-utf8-bytes: 500 evidence_turn_ref: type: string format: uuid description: Conversation Turn whose user text requested the change. input_fingerprint: type: string minLength: 64 maxLength: 64 description: Lowercase hexadecimal SHA-256 digest of the proposed update. example: subject_ref: 7c1f2a4e-2b8d-4f3a-9a51-0d6e4b3c2a10 preference_name: preferred_session_length value: 30 minutes evidence_turn_ref: 5b0f9c7e-0a3c-4d8e-8f21-6a1b2c3d4e5f input_fingerprint: 9f2c1e7a4b8d3f6e0a5c2b9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f responses: '200': description: Update receipt content: application/json: schema: $ref: '#/components/schemas/PreferenceUpdateReceipt' examples: applied: value: succeeded: true receipt_ref: preference-receipt-8812 content_version: profile-2026-09-25 safe_code: null refused: value: succeeded: false receipt_ref: null content_version: null safe_code: value_not_supported operationId: updatePreference tags: - Actions /ai-coach-connector/v1/actions/workouts/accept: post: summary: Accept the immutable workout command description: 'Requires the selected action capability workout.save or workout.save_and_schedule independently. Bind the idempotency key and action_ref durably to the exact command. Same-key same-command replay returns the original stable result; changed-command reuse returns command_conflict and never overwrites the original. Save of an existing workout confirms its reference without a copy. Save and schedule is atomic: schedule_conflict creates neither a workout nor a schedule and leaves any existing workout untouched. Return confirmed, conflict or rejected in a successful HTTP 200 response. Transport errors, timeout, malformed/schema-invalid JSON or mismatched identity yield no result; retry retains the immutable command, action identity and key. Eventual public exhaustion after possible dispatch is failed/connector_unconfirmed, never a connector status or assurance of no remote change.' security: - connectorBearer: [] parameters: - $ref: '#/components/parameters/TenantHeader' - name: Idempotency-Key in: header required: true schema: type: string minLength: 1 maxLength: 200 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/WorkoutAcceptanceCommand' examples: standalone_generated_save: value: contract_version: 1 action_ref: 11111111-1111-4111-8111-111111111111 subject_ref: opaque-subject source: source_kind: standalone_generated source_operation_ref: 22222222-2222-4222-8222-222222222222 source_ref: 33333333-3333-4333-8333-333333333333 configuration_ref: 44444444-4444-4444-8444-444444444444 configuration_version: 2 context_ref: 55555555-5555-4555-8555-555555555555 context_version: 3 source_content_fingerprint: 5c9c784359cfb0585ad3b4708aef518720b26a516a71d59337f21ff488584346 action: save schedule: null approval: explicit_user_approval generated_workout: title: Strength warmup_supersets: - order: 1 rest_between_cycles_seconds: 0 workout_exercises: - exercise_ref: warmup exercise_content_version: v1 order: 1 sets: 1 rest_before_seconds: 0 prescription: type: time duration_seconds: 30 supersets: - order: 1 rest_between_cycles_seconds: 45 workout_exercises: - exercise_ref: exercise exercise_content_version: v2 order: 1 sets: 3 rest_before_seconds: 10 prescription: type: count repetitions: 12 cooldown_supersets: [] existing_workout_ref: null workout_catalog_version: null exercise_catalog_version: exercises-v1 existing_verified_at: null command_fingerprint: d88d7650ad4fd157230bd5ab10d2c3c995e863a6232cf79042aa9acadb8edd2f standalone_generated_save_and_schedule: value: contract_version: 1 action_ref: 11111111-1111-4111-8111-111111111111 subject_ref: opaque-subject source: source_kind: standalone_generated source_operation_ref: 22222222-2222-4222-8222-222222222222 source_ref: 33333333-3333-4333-8333-333333333333 configuration_ref: 44444444-4444-4444-8444-444444444444 configuration_version: 2 context_ref: 55555555-5555-4555-8555-555555555555 context_version: 3 source_content_fingerprint: 5c9c784359cfb0585ad3b4708aef518720b26a516a71d59337f21ff488584346 action: save_and_schedule schedule: local_date: '2026-09-10' time_zone: Europe/Oslo approval: explicit_user_approval generated_workout: title: Strength warmup_supersets: - order: 1 rest_between_cycles_seconds: 0 workout_exercises: - exercise_ref: warmup exercise_content_version: v1 order: 1 sets: 1 rest_before_seconds: 0 prescription: type: time duration_seconds: 30 supersets: - order: 1 rest_between_cycles_seconds: 45 workout_exercises: - exercise_ref: exercise exercise_content_version: v2 order: 1 sets: 3 rest_before_seconds: 10 prescription: type: count repetitions: 12 cooldown_supersets: [] existing_workout_ref: null workout_catalog_version: null exercise_catalog_version: exercises-v1 existing_verified_at: null command_fingerprint: f313c47fd8d0ba032a0d23bb0dafa64767350b496f6b0ac0c4b036324e379e16 daily_generated_save: value: contract_version: 1 action_ref: 11111111-1111-4111-8111-111111111111 subject_ref: opaque-subject source: source_kind: daily_generated source_operation_ref: 22222222-2222-4222-8222-222222222222 source_ref: 33333333-3333-4333-8333-333333333333 configuration_ref: 44444444-4444-4444-8444-444444444444 configuration_version: 2 context_ref: 55555555-5555-4555-8555-555555555555 context_version: 3 source_content_fingerprint: 5c9c784359cfb0585ad3b4708aef518720b26a516a71d59337f21ff488584346 action: save schedule: null approval: explicit_user_approval generated_workout: title: Strength warmup_supersets: - order: 1 rest_between_cycles_seconds: 0 workout_exercises: - exercise_ref: warmup exercise_content_version: v1 order: 1 sets: 1 rest_before_seconds: 0 prescription: type: time duration_seconds: 30 supersets: - order: 1 rest_between_cycles_seconds: 45 workout_exercises: - exercise_ref: exercise exercise_content_version: v2 order: 1 sets: 3 rest_before_seconds: 10 prescription: type: count repetitions: 12 cooldown_supersets: [] existing_workout_ref: null workout_catalog_version: null exercise_catalog_version: exercises-v1 existing_verified_at: null command_fingerprint: 45b7ce873c716a95d6be94cdee11a7b837805bb3e9ae7cada4d9c57a44c4ea5f daily_generated_save_and_schedule: value: contract_version: 1 action_ref: 11111111-1111-4111-8111-111111111111 subject_ref: opaque-subject source: source_kind: daily_generated source_operation_ref: 22222222-2222-4222-8222-222222222222 source_ref: 33333333-3333-4333-8333-333333333333 configuration_ref: 44444444-4444-4444-8444-444444444444 configuration_version: 2 context_ref: 55555555-5555-4555-8555-555555555555 context_version: 3 source_content_fingerprint: 5c9c784359cfb0585ad3b4708aef518720b26a516a71d59337f21ff488584346 action: save_and_schedule schedule: local_date: '2026-09-10' time_zone: Europe/Oslo approval: explicit_user_approval generated_workout: title: Strength warmup_supersets: - order: 1 rest_between_cycles_seconds: 0 workout_exercises: - exercise_ref: warmup exercise_content_version: v1 order: 1 sets: 1 rest_before_seconds: 0 prescription: type: time duration_seconds: 30 supersets: - order: 1 rest_between_cycles_seconds: 45 workout_exercises: - exercise_ref: exercise exercise_content_version: v2 order: 1 sets: 3 rest_before_seconds: 10 prescription: type: count repetitions: 12 cooldown_supersets: [] existing_workout_ref: null workout_catalog_version: null exercise_catalog_version: exercises-v1 existing_verified_at: null command_fingerprint: 542554c884a3cd80283f5d388a36f72b8d873b7abfb900c8c8761d838530fa98 daily_existing_save: value: contract_version: 1 action_ref: 11111111-1111-4111-8111-111111111111 subject_ref: opaque-subject source: source_kind: daily_existing source_operation_ref: 22222222-2222-4222-8222-222222222222 source_ref: 33333333-3333-4333-8333-333333333333 configuration_ref: 44444444-4444-4444-8444-444444444444 configuration_version: 2 context_ref: 55555555-5555-4555-8555-555555555555 context_version: 3 source_content_fingerprint: 5c9c784359cfb0585ad3b4708aef518720b26a516a71d59337f21ff488584346 action: save schedule: null approval: explicit_user_approval generated_workout: null existing_workout_ref: client-workout-1 workout_catalog_version: workouts-v1 exercise_catalog_version: exercises-v1 existing_verified_at: '2026-09-09T12:00:00.000000+00:00' command_fingerprint: dd951c01234e246e289303d74f80c29465a71cfcff264a4670fcd06bd4d3aad1 daily_existing_save_and_schedule: value: contract_version: 1 action_ref: 11111111-1111-4111-8111-111111111111 subject_ref: opaque-subject source: source_kind: daily_existing source_operation_ref: 22222222-2222-4222-8222-222222222222 source_ref: 33333333-3333-4333-8333-333333333333 configuration_ref: 44444444-4444-4444-8444-444444444444 configuration_version: 2 context_ref: 55555555-5555-4555-8555-555555555555 context_version: 3 source_content_fingerprint: 5c9c784359cfb0585ad3b4708aef518720b26a516a71d59337f21ff488584346 action: save_and_schedule schedule: local_date: '2026-09-10' time_zone: Europe/Oslo approval: explicit_user_approval generated_workout: null existing_workout_ref: client-workout-1 workout_catalog_version: workouts-v1 exercise_catalog_version: exercises-v1 existing_verified_at: '2026-09-09T12:00:00.000000+00:00' command_fingerprint: 4d3d1f6033c78e21f4c41aa7c8cf504c1c942d869c375fe8a85510c85bed20b3 responses: 200: description: Confirmed, conflict or rejected business result matching the saved command identity. content: application/json: schema: $ref: '#/components/schemas/WorkoutAcceptanceResult' examples: confirmed_save: value: contract_version: 1 action_ref: 11111111-1111-4111-8111-111111111111 command_fingerprint: d88d7650ad4fd157230bd5ab10d2c3c995e863a6232cf79042aa9acadb8edd2f status: confirmed receipt_ref: receipt-1 workout_ref: workout-1 workout_content_version: v1 confirmed_at: '2026-09-09T12:00:00.000000+00:00' confirmed_scheduled: value: contract_version: 1 action_ref: 11111111-1111-4111-8111-111111111111 command_fingerprint: f313c47fd8d0ba032a0d23bb0dafa64767350b496f6b0ac0c4b036324e379e16 status: confirmed receipt_ref: receipt-1 workout_ref: workout-1 workout_content_version: v1 confirmed_at: '2026-09-09T12:00:00.000000+00:00' schedule_ref: schedule-1 schedule_content_version: v2 conflict: value: contract_version: 1 action_ref: 11111111-1111-4111-8111-111111111111 command_fingerprint: f313c47fd8d0ba032a0d23bb0dafa64767350b496f6b0ac0c4b036324e379e16 status: conflict safe_code: schedule_conflict rejected: value: contract_version: 1 action_ref: 11111111-1111-4111-8111-111111111111 command_fingerprint: f313c47fd8d0ba032a0d23bb0dafa64767350b496f6b0ac0c4b036324e379e16 status: rejected safe_code: action_rejected tags: - Actions operationId: acceptWorkout components: securitySchemes: connectorBearer: type: http scheme: bearer description: The connector secret you shared with Kinellect during onboarding. parameters: TenantHeader: name: X-Kinellect-Tenant in: header required: true schema: type: string format: uuid IdempotencyKey: name: Idempotency-Key in: header required: true schema: type: string minLength: 1 maxLength: 200 schemas: SubjectRef: type: string format: uuid description: Kinellect Subject reference, the same value as subject_ref in the public API. SubjectRequest: type: object additionalProperties: false required: - subject_ref properties: subject_ref: $ref: '#/components/schemas/SubjectRef' ConnectorCapabilities: type: object additionalProperties: false required: - contract_version - content_version - capabilities properties: contract_version: const: 1 description: Must equal the registered connector contract version. content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 capabilities: type: array maxItems: 20 items: type: string minLength: 1 maxLength: 100 x-max-utf8-bytes: 100 SubjectContextUnavailable: type: object additionalProperties: false required: - available properties: available: const: false ResolvedSubjectContext: type: object additionalProperties: false description: effective_at must be earlier than valid_through, and both must use a UTC offset (Z or +00:00). When daily_context is present, available_days must hold lowercase English weekday names and the daily_context calendar rules apply. required: - context_version - content_version - provenance - effective_at - valid_through - time_zone - local_date - primary_goal - experience - desired_sessions_per_week - available_days - unit_system - equipment_refs - safety_constraints - completed_workouts - mobility_summary - recovery_summary - blocker properties: context_version: type: integer minimum: 1 content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 provenance: type: string minLength: 1 maxLength: 100 x-max-utf8-bytes: 100 effective_at: type: string format: date-time maxLength: 40 x-max-utf8-bytes: 40 valid_through: type: string format: date-time maxLength: 40 x-max-utf8-bytes: 40 time_zone: type: string minLength: 1 maxLength: 100 x-max-utf8-bytes: 100 description: IANA time zone identifier. local_date: type: string format: date primary_goal: type: string minLength: 1 maxLength: 1000 x-max-utf8-bytes: 1000 experience: type: string minLength: 1 maxLength: 100 x-max-utf8-bytes: 100 desired_sessions_per_week: type: integer minimum: 0 maximum: 14 available_days: type: array maxItems: 7 items: type: string minLength: 1 maxLength: 32 x-max-utf8-bytes: 32 unit_system: type: string minLength: 1 maxLength: 20 x-max-utf8-bytes: 20 equipment_refs: type: array maxItems: 50 items: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 safety_constraints: type: array maxItems: 30 items: type: string minLength: 1 maxLength: 500 x-max-utf8-bytes: 500 completed_workouts: type: integer minimum: 0 maximum: 1000 mobility_summary: type: string minLength: 1 maxLength: 1000 x-max-utf8-bytes: 1000 recovery_summary: type: string minLength: 1 maxLength: 1000 x-max-utf8-bytes: 1000 blocker: type: - string - 'null' minLength: 1 maxLength: 1000 x-max-utf8-bytes: 1000 daily_context: oneOf: - $ref: '#/components/schemas/DailyContext' - type: 'null' exercise_constraints: type: array maxItems: 30 description: constraint_ref values and targets are unique within the list. items: $ref: '#/components/schemas/ExerciseConstraint' ExerciseCatalogResult: type: object additionalProperties: false required: - content_version - exercises properties: content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 exercises: type: array maxItems: 20 items: $ref: '#/components/schemas/ExerciseProjection' ExerciseProjection: type: object additionalProperties: false description: verification_facts and relationships are optional keys. required: - exercise_ref - display_name - movement_pattern - equipment_refs properties: exercise_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 display_name: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 movement_pattern: type: string minLength: 1 maxLength: 100 x-max-utf8-bytes: 100 equipment_refs: type: array maxItems: 30 items: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 verification_facts: $ref: '#/components/schemas/ExerciseVerificationFacts' relationships: $ref: '#/components/schemas/ExerciseRelationships' ExerciseVerificationFacts: type: object additionalProperties: false description: generation_facts is an optional key. required: - item_content_version - active - available - prescription_types - safety_identifiers properties: item_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 active: type: boolean available: type: boolean prescription_types: type: array minItems: 1 maxItems: 2 uniqueItems: true items: enum: - count - time safety_identifiers: type: array maxItems: 30 uniqueItems: true items: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 generation_facts: $ref: '#/components/schemas/ExerciseGenerationFacts' ExerciseGenerationFacts: type: object additionalProperties: false description: A count or duration range is either entirely null or complete with minimum <= maximum. count_duration_seconds_per_repetition requires a count range. required: - minimum_count - maximum_count - count_duration_seconds_per_repetition - minimum_duration_seconds - maximum_duration_seconds properties: minimum_count: type: - integer - 'null' minimum: 1 maximum: 1000 maximum_count: type: - integer - 'null' minimum: 1 maximum: 1000 count_duration_seconds_per_repetition: type: - integer - 'null' minimum: 1 maximum: 600 minimum_duration_seconds: type: - integer - 'null' minimum: 1 maximum: 7200 maximum_duration_seconds: type: - integer - 'null' minimum: 1 maximum: 7200 ExerciseRelationships: type: object additionalProperties: false description: A reference appears in at most one list and never names the owning exercise. required: - progression_refs - regression_refs - related_refs properties: progression_refs: type: array maxItems: 20 uniqueItems: true items: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 regression_refs: type: array maxItems: 20 uniqueItems: true items: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 related_refs: type: array maxItems: 20 uniqueItems: true items: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 WorkoutSearchPage: type: object additionalProperties: false required: - catalog_version - workout_refs - next_cursor properties: catalog_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 workout_refs: type: array maxItems: 20 uniqueItems: true description: At most the requested limit. items: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 next_cursor: type: - string - 'null' minLength: 1 maxLength: 500 x-max-utf8-bytes: 500 WorkoutResolution: oneOf: - type: object additionalProperties: false required: - status - workout properties: status: const: resolved workout: $ref: '#/components/schemas/ResolvedWorkout' - type: object additionalProperties: false required: - status - workout properties: status: enum: - missing - inactive - changed workout: type: 'null' ResolvedWorkout: type: object additionalProperties: false description: warmup_supersets and cooldown_supersets are optional keys. The three sections hold at most 20 supersets together; superset order starts at 1 and is contiguous within each section. The whole workout holds at most 20 exercises, and each exercise_ref appears once. required: - workout_ref - catalog_version - supersets properties: workout_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 catalog_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 warmup_supersets: type: array maxItems: 20 items: $ref: '#/components/schemas/ResolvedWorkoutSuperset' supersets: type: array minItems: 1 maxItems: 20 items: $ref: '#/components/schemas/ResolvedWorkoutSuperset' cooldown_supersets: type: array maxItems: 20 items: $ref: '#/components/schemas/ResolvedWorkoutSuperset' ResolvedWorkoutSuperset: type: object additionalProperties: false description: Exercise order starts at 1 and is contiguous within the superset. required: - order - workout_exercises properties: order: type: integer minimum: 1 maximum: 20 workout_exercises: type: array minItems: 1 maxItems: 20 items: $ref: '#/components/schemas/ResolvedWorkoutExercise' ResolvedWorkoutExercise: type: object additionalProperties: false required: - exercise_ref - exercise_content_version - order - sets - rest_before_seconds - prescription properties: exercise_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 exercise_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 order: type: integer minimum: 1 maximum: 20 sets: type: integer minimum: 1 maximum: 20 rest_before_seconds: type: integer minimum: 0 maximum: 3600 prescription: oneOf: - $ref: '#/components/schemas/CountPrescription' - $ref: '#/components/schemas/TimePrescription' CountPrescription: type: object additionalProperties: false required: - type - repetitions properties: type: const: count repetitions: type: integer minimum: 1 maximum: 1000 TimePrescription: type: object additionalProperties: false required: - type - duration_seconds properties: type: const: time duration_seconds: type: integer minimum: 1 maximum: 7200 PreferenceUpdateReceipt: type: object additionalProperties: false required: - succeeded - receipt_ref - content_version - safe_code properties: succeeded: type: boolean receipt_ref: type: - string - 'null' minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 content_version: type: - string - 'null' minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 safe_code: type: - string - 'null' minLength: 1 maxLength: 100 x-max-utf8-bytes: 100 WorkoutAcceptanceCommand: type: object additionalProperties: false required: - contract_version - action_ref - subject_ref - source - configuration_ref - configuration_version - context_ref - context_version - source_content_fingerprint - action - schedule - approval - generated_workout - existing_workout_ref - workout_catalog_version - exercise_catalog_version - existing_verified_at - command_fingerprint properties: contract_version: const: 1 action_ref: type: string format: uuid subject_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ source: type: object additionalProperties: false required: - source_kind - source_operation_ref - source_ref properties: source_kind: enum: - standalone_generated - daily_generated - daily_existing source_operation_ref: type: string format: uuid source_ref: type: string format: uuid configuration_ref: type: string format: uuid configuration_version: type: integer minimum: 1 context_ref: type: string format: uuid context_version: type: integer minimum: 1 source_content_fingerprint: type: string pattern: ^[0-9a-f]{64}$ action: enum: - save - save_and_schedule schedule: oneOf: - type: object additionalProperties: false required: - local_date - time_zone properties: local_date: type: string format: date pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ description: Strict YYYY-MM-DD calendar date; impossible dates are rejected. time_zone: type: string minLength: 1 maxLength: 100 description: Named Subject IANA time zone, including UTC. Numeric offsets and abbreviations are rejected. Must equal authoritative Subject context. - type: 'null' approval: enum: - explicit_user_approval - null generated_workout: oneOf: - $ref: '#/components/schemas/WorkoutDraft' - type: 'null' existing_workout_ref: oneOf: - type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ - type: 'null' workout_catalog_version: oneOf: - type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ - type: 'null' exercise_catalog_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ existing_verified_at: oneOf: - type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. - type: 'null' command_fingerprint: type: string pattern: ^[0-9a-f]{64}$ allOf: - oneOf: - properties: action: const: save schedule: type: 'null' - properties: action: const: save_and_schedule schedule: type: object additionalProperties: false required: - local_date - time_zone properties: local_date: type: string format: date pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ description: Strict YYYY-MM-DD calendar date; impossible dates are rejected. time_zone: type: string minLength: 1 maxLength: 100 description: Named Subject IANA time zone, including UTC. Numeric offsets and abbreviations are rejected. Must equal authoritative Subject context. - oneOf: - properties: source: properties: source_kind: enum: - standalone_generated - daily_generated generated_workout: $ref: '#/components/schemas/WorkoutDraft' existing_workout_ref: type: 'null' workout_catalog_version: type: 'null' existing_verified_at: type: 'null' - properties: source: properties: source_kind: const: daily_existing generated_workout: type: 'null' existing_workout_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ workout_catalog_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ existing_verified_at: type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. description: Exact immutable saved command. Source identity, configuration/context references and versions, verified content and approval are fingerprint-bound. Generated sources preserve the saved draft hierarchy with contiguous section-local ordering and at most 20 exercises total, without duplicate exercise references. Existing sources name the verified client workout and catalog versions and do not copy it. command_fingerprint is SHA-256 of the canonical UTF-8 JSON in the property order shown, excluding command_fingerprint, with no whitespace or escaped slashes/Unicode. Decoder requires these canonical command bytes; replay never rebuilds or replaces them. WorkoutAcceptanceResult: description: 'Exactly three matching response identity fields bind the saved command. No Subject, source, action, schedule or approval echo is allowed. Only confirmed includes real receipts and successful effects. Select the confirmed field set from the saved command action: save omits schedule references; save_and_schedule requires both. Existing-workout confirmation must retain its original workout reference. All three business outcomes complete the generic Operation.' oneOf: - type: object additionalProperties: false required: - contract_version - action_ref - command_fingerprint - status - receipt_ref - workout_ref - workout_content_version - confirmed_at properties: contract_version: const: 1 action_ref: type: string format: uuid command_fingerprint: type: string pattern: ^[0-9a-f]{64}$ status: const: confirmed receipt_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ workout_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ workout_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ confirmed_at: type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. - type: object additionalProperties: false required: - contract_version - action_ref - command_fingerprint - status - receipt_ref - workout_ref - workout_content_version - confirmed_at - schedule_ref - schedule_content_version properties: contract_version: const: 1 action_ref: type: string format: uuid command_fingerprint: type: string pattern: ^[0-9a-f]{64}$ status: const: confirmed receipt_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ workout_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ workout_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ confirmed_at: type: string format: date-time pattern: (Z|\+00:00)$ description: UTC timestamp. schedule_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ schedule_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 pattern: ^[^\u0000]*$ - type: object additionalProperties: false required: - contract_version - action_ref - command_fingerprint - status - safe_code properties: contract_version: const: 1 action_ref: type: string format: uuid command_fingerprint: type: string pattern: ^[0-9a-f]{64}$ status: const: conflict safe_code: enum: - schedule_conflict - command_conflict - type: object additionalProperties: false required: - contract_version - action_ref - command_fingerprint - status - safe_code properties: contract_version: const: 1 action_ref: type: string format: uuid command_fingerprint: type: string pattern: ^[0-9a-f]{64}$ status: const: rejected safe_code: const: action_rejected WorkoutDraft: type: object additionalProperties: false required: - title - warmup_supersets - supersets - cooldown_supersets properties: title: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 warmup_supersets: type: array maxItems: 20 items: $ref: '#/components/schemas/WorkoutDraftSuperset' supersets: type: array minItems: 1 maxItems: 20 items: $ref: '#/components/schemas/WorkoutDraftSuperset' cooldown_supersets: type: array maxItems: 20 items: $ref: '#/components/schemas/WorkoutDraftSuperset' WorkoutDraftSuperset: type: object additionalProperties: false required: - order - rest_between_cycles_seconds - workout_exercises properties: order: type: integer minimum: 1 maximum: 20 rest_between_cycles_seconds: type: integer minimum: 0 maximum: 3600 workout_exercises: type: array minItems: 1 maxItems: 20 items: $ref: '#/components/schemas/WorkoutDraftExercise' WorkoutDraftExercise: type: object additionalProperties: false required: - exercise_ref - exercise_content_version - order - sets - rest_before_seconds - prescription properties: exercise_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 exercise_content_version: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 order: type: integer minimum: 1 maximum: 20 sets: type: integer minimum: 1 maximum: 20 rest_before_seconds: type: integer minimum: 0 maximum: 3600 prescription: oneOf: - $ref: '#/components/schemas/CountPrescription' - $ref: '#/components/schemas/TimePrescription' DailyContext: type: object additionalProperties: false required: - week_start - history_through - completed_workout_dates - completed_mobility_dates - preferred_days - recovery_state - recovery_observed_at - recovery_valid_through properties: week_start: type: string format: date history_through: type: string format: date-time completed_workout_dates: type: array maxItems: 28 items: type: string format: date completed_mobility_dates: type: array maxItems: 28 items: type: string format: date preferred_days: type: array maxItems: 7 uniqueItems: true items: type: integer minimum: 1 maximum: 7 recovery_state: enum: - ready - mobility_only - rest_only - unknown recovery_observed_at: type: string format: date-time recovery_valid_through: type: string format: date-time description: week_start is the Monday containing parent local_date in its IANA time_zone. Convert history_through to that time_zone before deriving the inclusive history interval from week_start minus seven local calendar days through the local date of history_through, which cannot exceed parent local_date. Repeated completion dates represent separate sessions, with at most 28 entries in each completion list. Only current-week workout entries count toward the weekly target; mobility entries do not. Preferred ISO weekdays are unique and must be a subset of parent available_days. Recovery observed_at < valid_through. Admission requires matching current local date, effective_at <= now < valid_through and history_through <= now < history_through + activity_max_age_seconds. Recovery is usable only for ready, mobility_only or rest_only with observed_at <= now < min(recovery_valid_through, observed_at + recovery_max_age_seconds). These cross-field calendar, timezone and freshness relations are enforced by the application. Date-only entries assert session completeness through history_through without inventing intra-day timestamps; a completed workout today prevents selection of another workout. ExerciseConstraint: type: object additionalProperties: false required: - constraint_ref - target - effect - state properties: constraint_ref: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 target: type: object additionalProperties: false required: - type - value properties: type: enum: - exercise_ref - safety_identifier value: type: string minLength: 1 maxLength: 200 x-max-utf8-bytes: 200 effect: enum: - hard_exclusion - soft_avoidance state: enum: - active - lifted servers: - url: https://{connector_host} description: Your connector base URL, registered with Kinellect during onboarding. variables: connector_host: default: connector.example.com description: Host (and optional path prefix) of your HTTPS connector. security: - connectorBearer: [] tags: - name: Discovery description: Capability discovery performed when a client configuration is activated. - name: Context description: Authoritative, current facts about one user. - name: Content description: Your exercise and workout catalog. - name: Actions description: Changes Kinellect asks you to make on the user's behalf. ```