{
  "openapi": "3.1.0",
  "info": {
    "title": "Meso API",
    "version": "0.1.0",
    "summary": "Read and drive a member's block-periodized strength program.",
    "description": "The Meso API lets agents and developers read a member's block-periodized strength program, log sets, request same-stimulus exercise swaps, toggle equipment/travel mode, and read public product data (pricing, methodology).\n\n**Status: in development — not yet publicly available.** The Meso web app is available now; this HTTP API is still being built and is served from `https://api.meso.fitness` once released. This document is the authoritative description of that surface and is published now so agents can plan integrations. Public read-only operations (`getPricing`, `getMethodology`) require no authentication. Member-scoped operations require an OAuth 2.1 bearer token — see https://meso.fitness/auth.md.\n\n**Versioning & deprecation policy.** The API is versioned in the URL path (`/v1/...`). Breaking changes ship under a new path version (`/v2`). When a version or operation is deprecated, responses carry a `Deprecation: true` header and a `Sunset: <HTTP-date>` header (RFC 8594) giving the earliest removal date, and the deprecation is announced at https://meso.fitness/developers.html at least 6 months ahead. Non-breaking additions can occur within a version without notice.\n\n**Reliability conventions.** Write operations accept an `Idempotency-Key` header so retries are safe. List endpoints use cursor pagination. Long-running work returns `202 Accepted` with a job to poll. All responses carry `RateLimit-*` headers (RFC draft) and `429` responses include `Retry-After`.",
    "termsOfService": "https://meso.fitness/terms.html",
    "contact": {
      "name": "Meso",
      "email": "hello@meso.app",
      "url": "https://meso.fitness/contact.html"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "externalDocs": {
    "description": "Developer portal, auth walkthrough, and agent discovery index",
    "url": "https://meso.fitness/developers.html"
  },
  "servers": [
    {
      "url": "https://api.meso.fitness",
      "description": "Production (in development — not yet publicly available)"
    },
    {
      "url": "https://sandbox.api.meso.fitness",
      "description": "Sandbox / test environment (in development — no production data, safe for agents to exercise)"
    }
  ],
  "security": [
    {
      "oauth2": [
        "program:read"
      ]
    }
  ],
  "tags": [
    {
      "name": "Public",
      "description": "Unauthenticated product data."
    },
    {
      "name": "Program",
      "description": "Read and regenerate a member's periodized program."
    },
    {
      "name": "Logging",
      "description": "Log completed sets for the current session."
    },
    {
      "name": "Jobs",
      "description": "Poll long-running operations."
    }
  ],
  "paths": {
    "/v1/pricing": {
      "get": {
        "operationId": "getPricing",
        "summary": "Get pricing and availability",
        "description": "Return Meso's pricing model and availability. One tier, sold as a cycle pass (one purchase per twelve-week training cycle); €9.99 per twelve-week training cycle. three free program generations, no time limit. No authentication required.",
        "tags": [
          "Public"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Current pricing.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pricing"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/methodology": {
      "get": {
        "operationId": "getMethodology",
        "summary": "Get methodology summary",
        "description": "Return a summary of Meso's 9-pillar, evidence-based methodology, optionally scoped to a single pillar. No authentication required.",
        "tags": [
          "Public"
        ],
        "security": [],
        "parameters": [
          {
            "name": "pillar",
            "in": "query",
            "required": false,
            "description": "Optional pillar slug to scope the response (e.g. `volume-landmarks`, `autoregulation`, `deloads`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Methodology summary.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Methodology"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/program": {
      "get": {
        "operationId": "getProgram",
        "summary": "Get the current program",
        "description": "Return the authenticated member's current block-periodized program and where today falls in the macrocycle.",
        "tags": [
          "Program"
        ],
        "security": [
          {
            "oauth2": [
              "program:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The member's current program.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/program/regenerate": {
      "post": {
        "operationId": "regenerateProgram",
        "summary": "Regenerate the program block (async)",
        "description": "Trigger regeneration of the affected sessions/block for the authenticated member (e.g. after an equipment or goal change). Regeneration is long-running, so this returns `202 Accepted` with a job to poll via `getJob`.",
        "tags": [
          "Program"
        ],
        "security": [
          {
            "oauth2": [
              "program:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Regeneration accepted; poll the job for completion.",
            "headers": {
              "Location": {
                "description": "URL of the job resource to poll.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/jobs/{jobId}": {
      "get": {
        "operationId": "getJob",
        "summary": "Poll a long-running job",
        "description": "Return the status of an asynchronous job (e.g. a program regeneration). Poll until `status` is `succeeded` or `failed`; `result` holds a reference to the produced resource when finished.",
        "tags": [
          "Jobs"
        ],
        "security": [
          {
            "oauth2": [
              "program:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "description": "The job identifier returned by the operation that started the work.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current job state.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/exercises/swap": {
      "post": {
        "operationId": "swapExercise",
        "summary": "Swap an exercise",
        "description": "Replace an exercise in the current session with a ranked same-muscle, same-stimulus alternative filtered to available equipment. Progression carries over.",
        "tags": [
          "Program"
        ],
        "security": [
          {
            "oauth2": [
              "program:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SwapRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The chosen alternative and its ranked runners-up.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SwapResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "No suitable alternative for the available equipment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/equipment-mode": {
      "put": {
        "operationId": "setEquipmentMode",
        "summary": "Set equipment / travel mode",
        "description": "Set Equipment Access (`full_gym`, `home-garage`, or `minimalist`) and/or Travel Mode for the authenticated member, then regenerate affected sessions.",
        "tags": [
          "Program"
        ],
        "security": [
          {
            "oauth2": [
              "program:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EquipmentModeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated program after regeneration.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/sets": {
      "get": {
        "operationId": "listSets",
        "summary": "List logged sets (paginated)",
        "description": "Return the authenticated member's logged sets, most recent first, using cursor pagination. Pass the `next_cursor` from a response as `cursor` to fetch the next page.",
        "tags": [
          "Logging"
        ],
        "security": [
          {
            "oauth2": [
              "program:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of logged sets.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "operationId": "logSet",
        "summary": "Log a completed set",
        "description": "Log a completed set (weight, reps, RPE) for the authenticated member's current session. Feeds autoregulation for subsequent sessions.",
        "tags": [
          "Logging"
        ],
        "security": [
          {
            "oauth2": [
              "logs:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetLog"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Set recorded.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetLogResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid set payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/sets/batch": {
      "post": {
        "operationId": "batchLogSets",
        "summary": "Log multiple sets in one request",
        "description": "Log an array of completed sets in a single call, so an agent recording a whole session does not loop one request per set. Each item is processed independently; the response reports per-item success/failure in the same order.",
        "tags": [
          "Logging"
        ],
        "security": [
          {
            "oauth2": [
              "logs:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchSetRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-item results (some items may have failed; check each `status`).",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchSetResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 with PKCE. Authorization-server metadata: https://meso.fitness/.well-known/oauth-authorization-server . Protected-resource metadata: https://meso.fitness/.well-known/oauth-protected-resource . Agent auth walkthrough: https://meso.fitness/auth.md",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://auth.meso.fitness/oauth2/authorize",
            "tokenUrl": "https://auth.meso.fitness/oauth2/token",
            "refreshUrl": "https://auth.meso.fitness/oauth2/token",
            "scopes": {
              "program:read": "Read the member's program",
              "program:write": "Regenerate program, swap exercises, set equipment mode",
              "logs:write": "Log completed sets",
              "profile:read": "Read the member's profile"
            }
          }
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "A client-generated unique key (e.g. a UUID). Retrying a write with the same key returns the original result instead of performing the operation twice — safe for agent retries on network failure.",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "Opaque pagination cursor from a previous response's `next_cursor`. Omit for the first page.",
        "schema": {
          "type": "string"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Maximum items per page (1–100).",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        }
      }
    },
    "headers": {
      "RateLimitLimit": {
        "description": "Request quota for the current window (RFC draft RateLimit headers).",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests remaining in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Seconds until the current window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying (sent on 429).",
        "schema": {
          "type": "integer"
        }
      },
      "Deprecation": {
        "description": "Present and `true` when the operation/version is deprecated (RFC 8594).",
        "schema": {
          "type": "boolean"
        }
      },
      "Sunset": {
        "description": "HTTP-date after which a deprecated operation/version may be removed (RFC 8594).",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid bearer token.",
        "headers": {
          "WWW-Authenticate": {
            "description": "Points agents at the protected-resource metadata.",
            "schema": {
              "type": "string"
            },
            "example": "Bearer resource_metadata=\"https://meso.fitness/.well-known/oauth-protected-resource\""
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests — back off and retry after `Retry-After` seconds.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error": {
        "description": "Error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Structured error. Agents should branch on `error.code`, not the HTTP status alone.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable error code.",
                "examples": [
                  "unauthorized",
                  "invalid_request",
                  "not_found",
                  "no_alternative",
                  "rate_limited"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation."
              },
              "hint": {
                "type": "string",
                "description": "How to resolve the error."
              },
              "docs": {
                "type": "string",
                "format": "uri",
                "description": "Link to relevant documentation."
              }
            }
          }
        }
      },
      "Pricing": {
        "type": "object",
        "description": "Meso is sold as a cycle pass: one purchase covers one twelve-week training cycle. There is no billing interval, no plan list and nothing that renews.",
        "required": [
          "model",
          "currency",
          "price",
          "tiers",
          "cycleWeeks"
        ],
        "properties": {
          "model": {
            "type": "string",
            "enum": [
              "cycle_pass"
            ]
          },
          "unit": {
            "type": "string",
            "examples": [
              "cycle"
            ]
          },
          "currency": {
            "type": "string",
            "examples": [
              "EUR"
            ]
          },
          "price": {
            "type": "number",
            "examples": [
              9.99
            ],
            "description": "Price of one cycle pass, VAT-inclusive, in `currency`."
          },
          "priceStatus": {
            "type": "string",
            "enum": [
              "final",
              "not_final"
            ],
            "examples": [
              "final"
            ]
          },
          "cycleWeeks": {
            "type": "integer",
            "examples": [
              12
            ],
            "description": "Length of the training cycle one pass covers."
          },
          "phases": {
            "type": "integer",
            "examples": [
              4
            ],
            "description": "Phases in a cycle. Their individual lengths vary by goal; only the total is a published fact."
          },
          "territoryPricing": {
            "type": "boolean",
            "examples": [
              true
            ]
          },
          "tiers": {
            "type": "integer",
            "examples": [
              1
            ]
          },
          "freeGenerations": {
            "type": "integer",
            "examples": [
              3
            ],
            "description": "Program generations available without a pass. There is no time limit."
          },
          "freeTierTimeLimitDays": {
            "type": [
              "integer",
              "null"
            ],
            "examples": [
              null
            ],
            "description": "Always null: the free tier is capped by generations, not by a clock."
          }
        }
      },
      "Methodology": {
        "type": "object",
        "required": [
          "summary"
        ],
        "properties": {
          "summary": {
            "type": "string"
          },
          "pillar": {
            "type": "string"
          },
          "citations": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      },
      "Program": {
        "type": "object",
        "required": [
          "blockId",
          "week",
          "phase"
        ],
        "properties": {
          "blockId": {
            "type": "string"
          },
          "week": {
            "type": "integer",
            "description": "1-indexed week within the current block."
          },
          "weeksInBlock": {
            "type": "integer"
          },
          "phase": {
            "type": "string",
            "enum": [
              "accumulation",
              "intensification",
              "peak",
              "deload"
            ]
          },
          "isDeloadWeek": {
            "type": "boolean"
          },
          "sessions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Session"
            }
          }
        }
      },
      "Session": {
        "type": "object",
        "required": [
          "id",
          "exercises"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "day": {
            "type": "string"
          },
          "exercises": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Exercise"
            }
          }
        }
      },
      "Exercise": {
        "type": "object",
        "required": [
          "id",
          "name",
          "sets"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "primaryMuscle": {
            "type": "string"
          },
          "sets": {
            "type": "integer"
          },
          "repRange": {
            "type": "string",
            "examples": [
              "6-8",
              "10-12"
            ]
          },
          "targetRPE": {
            "type": "number",
            "examples": [
              8
            ]
          }
        }
      },
      "SwapRequest": {
        "type": "object",
        "required": [
          "exerciseId"
        ],
        "properties": {
          "exerciseId": {
            "type": "string",
            "description": "The exercise to replace."
          },
          "reason": {
            "type": "string",
            "description": "Optional reason (e.g. equipment busy, discomfort)."
          }
        }
      },
      "SwapResult": {
        "type": "object",
        "required": [
          "chosen"
        ],
        "properties": {
          "chosen": {
            "$ref": "#/components/schemas/Exercise"
          },
          "alternatives": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Exercise"
            }
          }
        }
      },
      "EquipmentModeRequest": {
        "type": "object",
        "properties": {
          "equipmentAccess": {
            "type": "string",
            "enum": [
              "full_gym",
              "home-garage",
              "minimalist"
            ]
          },
          "travelMode": {
            "type": "boolean"
          }
        },
        "minProperties": 1
      },
      "SetLog": {
        "type": "object",
        "required": [
          "exerciseId",
          "weight",
          "reps"
        ],
        "properties": {
          "exerciseId": {
            "type": "string"
          },
          "weight": {
            "type": "number",
            "description": "Load in kilograms."
          },
          "reps": {
            "type": "integer"
          },
          "rpe": {
            "type": "number",
            "description": "Rating of perceived exertion, 1–10.",
            "minimum": 1,
            "maximum": 10
          }
        }
      },
      "SetLogResult": {
        "type": "object",
        "required": [
          "id",
          "recordedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "recordedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SetPage": {
        "type": "object",
        "description": "A cursor-paginated page of logged sets.",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SetLogResult"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor for the next page, or null when there are no more results."
          },
          "has_more": {
            "type": "boolean"
          }
        }
      },
      "Job": {
        "type": "object",
        "description": "An asynchronous job. Poll `getJob` until terminal.",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "type": [
              "object",
              "null"
            ],
            "description": "Reference to the produced resource when `status` is `succeeded`.",
            "properties": {
              "href": {
                "type": "string",
                "format": "uri"
              },
              "type": {
                "type": "string",
                "examples": [
                  "program"
                ]
              }
            }
          },
          "error": {
            "$ref": "#/components/schemas/Error"
          }
        }
      },
      "BatchSetRequest": {
        "type": "object",
        "required": [
          "operations"
        ],
        "properties": {
          "operations": {
            "type": "array",
            "description": "Up to 100 set-log operations processed in order.",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/SetLog"
            }
          }
        }
      },
      "BatchSetResult": {
        "type": "object",
        "required": [
          "results"
        ],
        "properties": {
          "results": {
            "type": "array",
            "description": "One entry per input operation, in the same order.",
            "items": {
              "type": "object",
              "required": [
                "status"
              ],
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "created",
                    "error"
                  ]
                },
                "set": {
                  "$ref": "#/components/schemas/SetLogResult"
                },
                "error": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}
