# 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.
```