{"openapi":"3.1.0","info":{"title":"SisuLift Public API","version":"1.1.0","summary":"Public, unauthenticated endpoints of the SisuLift AI fitness app","description":"SisuLift (https://www.sisulift.app) is an AI-powered training and nutrition app. This specification covers the publicly reachable endpoints.\n\n## Versioning & deprecation policy\nPublic endpoints are versioned in the URL path (`/api/v1/...`). Breaking changes are only introduced under a new path version (`/api/v2/...`). When an endpoint is deprecated, its responses carry `Deprecation` and `Sunset` headers at least 90 days before removal, and the change is noted in this document. Unversioned legacy paths (e.g. `/api/app-version`) remain available for installed native apps.\n\n## Rate limiting\nPublic endpoints return draft-standard `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and `RateLimit-Policy` headers (60 requests/60 s per client). A 429 response includes `Retry-After`.\n\n## Errors\nAll error responses are JSON objects with a machine-readable `error` code (see the Error schema).\n\n## Authentication\nThe full product API is first-party and requires a signed-in user session (Supabase JWT bearer token); third-party OAuth client registration is not yet self-service — contact support (https://www.sisulift.app/contact) if you are building an integration. OAuth metadata: /.well-known/oauth-authorization-server (RFC 8414), /.well-known/oauth-protected-resource (RFC 9728).\n\nMachine-readable site content: /llms.txt, /llms-full.txt, /agent.txt. Developer resource index: /developers.","contact":{"name":"SisuLift support","url":"https://www.sisulift.app/contact","email":"hello@sisulift.app"}},"servers":[{"url":"https://www.sisulift.app"}],"paths":{"/api/v1/exercises":{"get":{"operationId":"listExercises","summary":"List public exercises","description":"Lists the public SisuLift exercise library (the same ~300 exercises as the public exercise pages at /liikkeet). Supports free-text search in Finnish and English, and filtering by muscle group and equipment. Data is cached for up to 24 hours.","security":[],"parameters":[{"name":"q","in":"query","required":false,"description":"Free-text search matched against Finnish and English names","schema":{"type":"string"}},{"name":"muscle","in":"query","required":false,"description":"Primary muscle group filter: Finnish name, English name or id (e.g. \"Chest\", \"Rinta\")","schema":{"type":"string"}},{"name":"equipment","in":"query","required":false,"description":"Equipment filter, e.g. barbell, dumbbell, machine, bodyweight","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size (1–100, default 50)","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"offset","in":"query","required":false,"description":"Pagination offset (default 0)","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of exercises","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer","description":"Total matches for the query"},"count":{"type":"integer","description":"Items in this page"},"offset":{"type":"integer"},"limit":{"type":"integer"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Exercise"}}},"required":["total","count","offset","limit","items"]}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/exercises/{slug}":{"get":{"operationId":"getExercise","summary":"Get one public exercise","description":"Returns full details for one public exercise: step-by-step instructions in Finnish and English, tips, movement pattern flags and animation/thumbnail URLs. The slug may be the Finnish or English public slug from the list endpoint.","security":[],"parameters":[{"name":"slug","in":"path","required":true,"description":"Public slug (Finnish or English), e.g. \"penkkipunnerrus\" or \"bench-press\"","schema":{"type":"string"}}],"responses":{"200":{"description":"Exercise details","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExerciseDetail"}}}},"404":{"description":"No public exercise with the given slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/app-version":{"get":{"operationId":"getAppVersion","summary":"Current mobile app version and update policy","description":"Returns the latest released native app version, the minimum supported version and store URLs. Optional `platform` selects android (default) or ios.","security":[],"parameters":[{"name":"platform","in":"query","required":false,"description":"Target platform","schema":{"type":"string","enum":["android","ios"],"default":"android"}}],"responses":{"200":{"description":"Version information","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppVersion"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/app-version":{"get":{"operationId":"getAppVersionLegacy","summary":"Legacy unversioned alias of /api/v1/app-version","description":"Unversioned path kept for installed native apps. New integrations should use /api/v1/app-version. This alias is not scheduled for removal; if that changes, responses will carry Deprecation and Sunset headers at least 90 days in advance.","security":[],"responses":{"200":{"description":"Version information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppVersion"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/md":{"get":{"operationId":"getMarkdownVariant","summary":"Markdown variant of a public page","description":"Returns a markdown representation of a public page. Also reachable by requesting any public page with an `Accept: text/markdown` header (acceptmarkdown.com-style negotiation).","security":[],"parameters":[{"name":"path","in":"query","required":false,"description":"Site-relative path of the page, e.g. `/` or `/en/features`. Defaults to `/`.","schema":{"type":"string","default":"/"}}],"responses":{"200":{"description":"Markdown content","content":{"text/markdown":{"schema":{"type":"string"}}}},"400":{"description":"Invalid path parameter (markdown body)","content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No public page at the given path (markdown body with recovery links)","content":{"text/markdown":{"schema":{"type":"string"}}}},"default":{"$ref":"#/components/responses/Error"}}}},"/openapi.json":{"get":{"operationId":"getOpenApiSpec","summary":"This OpenAPI specification","description":"Returns this OpenAPI 3.1 document describing the public SisuLift API surface.","security":[],"responses":{"200":{"description":"OpenAPI 3.1 document","content":{"application/json":{"schema":{"type":"object","description":"OpenAPI 3.1 document","properties":{"openapi":{"type":"string"},"info":{"type":"object"},"paths":{"type":"object"}},"required":["openapi","info","paths"]}}}},"default":{"$ref":"#/components/responses/Error"}}}}},"components":{"schemas":{"Error":{"type":"object","description":"Standard JSON error shape. `error` is a stable machine-readable code (e.g. `not_found`, `invalid_request`, `unauthorized`, `rate_limited`, `failed`).","properties":{"error":{"type":"string","examples":["not_found"]},"message":{"type":"string","description":"Human-readable explanation"},"hint":{"type":"string","description":"How to resolve or where to look next"},"docs":{"type":"string","format":"uri","description":"Link to relevant documentation"}},"required":["error"]},"Exercise":{"type":"object","description":"A public exercise from the SisuLift exercise library","properties":{"name":{"type":"string","description":"Finnish name","examples":["Penkkipunnerrus"]},"nameEn":{"type":"string","description":"English name","examples":["Bench Press"]},"slug":{"type":"string","description":"Stable Finnish slug (path parameter for the detail endpoint)"},"slugEn":{"type":"string","description":"Stable English slug (also accepted by the detail endpoint)"},"url":{"type":"string","format":"uri","description":"Public Finnish HTML page"},"urlEn":{"type":"string","format":"uri","description":"Public English HTML page"},"equipment":{"type":["string","null"],"description":"Equipment identifier, e.g. barbell, dumbbell, machine"},"category":{"type":["string","null"],"description":"Exercise category, e.g. strength, cardio"},"techniqueDemand":{"type":["string","null"],"description":"How technically demanding the movement is"},"muscleGroup":{"type":["object","null"],"description":"Primary muscle group","properties":{"name":{"type":"string","description":"Finnish name"},"nameEn":{"type":"string","description":"English name"}},"required":["name","nameEn"]}},"required":["name","nameEn","slug","slugEn","url","urlEn"]},"ExerciseDetail":{"allOf":[{"$ref":"#/components/schemas/Exercise"},{"type":"object","properties":{"instructions":{"type":"array","items":{"type":"string"},"description":"Step-by-step instructions (Finnish)"},"instructionsEn":{"type":"array","items":{"type":"string"},"description":"Step-by-step instructions (English)"},"tips":{"type":["string","null"],"description":"Coaching tips (Finnish)"},"tipsEn":{"type":["string","null"],"description":"Coaching tips (English)"},"movementPattern":{"type":["string","null"],"description":"Movement pattern, e.g. horizontal_push"},"isUnilateral":{"type":"boolean"},"isTimed":{"type":"boolean"},"isExplosive":{"type":"boolean"},"thumbnailUrl":{"type":"string","format":"uri","description":"Static thumbnail image"},"animationUrl":{"type":"string","format":"uri","description":"Exercise animation"}},"required":["instructions","instructionsEn","isUnilateral","isTimed","isExplosive","thumbnailUrl","animationUrl"]}]},"AppVersion":{"type":"object","description":"Latest released native app version and update policy","properties":{"latestVersion":{"type":"string","examples":["1.29.0"]},"minVersion":{"type":"string","examples":["1.0.0"]},"showAfter":{"type":"string","format":"date-time","description":"Do not prompt updates before this time"},"buildTime":{"type":["string","null"],"format":"date-time"},"updateUrl":{"type":"object","properties":{"android":{"type":"string","format":"uri"},"ios":{"type":"string","format":"uri"}},"required":["android","ios"]}},"required":["latestVersion","minVersion"]}},"headers":{"RateLimit-Limit":{"description":"Requests allowed in the current window (60/60 s per client)","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests remaining in the current window","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the window resets","schema":{"type":"integer"}}},"responses":{"Error":{"description":"Error (JSON object with a machine-readable code)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Unknown API path","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests (Retry-After and RateLimit headers set)","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"description":"Seconds to wait before retrying","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"securitySchemes":{"supabaseBearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Supabase-issued user JWT. All first-party authenticated endpoints accept this token. Tokens are obtained by signing in to SisuLift; there is no public token endpoint for machines yet."},"oauth2":{"type":"oauth2","description":"Scoped access areas of the SisuLift data model. Third-party OAuth client registration is not yet self-service; the scopes below declare the least-privilege areas an integration can request via support (https://www.sisulift.app/contact).","flows":{"authorizationCode":{"authorizationUrl":"https://pbminquovljsmotplbxc.supabase.co/auth/v1/authorize","tokenUrl":"https://pbminquovljsmotplbxc.supabase.co/auth/v1/token","scopes":{"profile:read":"Read the authenticated user’s profile and settings","workouts:read":"Read the authenticated user’s workout programs and logged workouts","workouts:write":"Create and modify the authenticated user’s workouts","nutrition:read":"Read the authenticated user’s nutrition log and targets","nutrition:write":"Log meals and modify nutrition data for the authenticated user","progress:read":"Read the authenticated user’s progress analytics and records"}}}}}}}