Skip to main content

GET /api/plans

Returns every published, approved Kamee training plan. Read-only.

GET https://kamee.fit/api/plans

Requires an X-Api-Key header — see Authentication.

An OpenAPI 3.1 description is available at /openapi.yaml if you want to generate a client.

Query parameters

ParameterTypeRequiredDescription
disciplinestrength | runningNoFilters the catalog. Any other value returns 400. Omit for everything.

Any other query parameter is ignored — and, importantly, cannot be used to bypass the cache. See Caching and freshness.

Ordering

Results are sorted by discipline ascending, then title ascending. This ordering is stable and you can rely on it. There is no pagination; you always get the full matching set.

Response

{
generatedAt: string // ISO 8601 UTC
plans: Plan[]
}

Plan

FieldTypeNullableNotes
idstringNoUUID. Stable; safe to store as your own foreign key.
titlestringNo
summarystringYesOne- or two-sentence description.
coverUrlstringYesPublic image URL. null when the plan has no cover — render a fallback.
levelstringNoOne of none, beginner, intermediate, advanced. Plans without a level are reported as none, so the enum is closed and never null.
disciplinestringNostrength or running.
disciplineLabelstringNoDisplay name for discipline: Workouts for strength, Outdoor for running. Use this rather than capitalising discipline yourself.
weeksCountnumberNoProgramme length in weeks.
daysPerWeeknumberYes
estMinutesPerSessionnumberYes
webUrlstringNohttps://kamee.fit/plans/{id}. Always safe to render.
appUrlstringNokamee://plan/{id}. Only works where the app is installed — see Linking to plans.
generatedAt is not a freshness signal

It is stamped when the origin builds the response, not when the response reaches you. A CDN-served copy can carry a generatedAt roughly 25 hours old. Do not use it to decide whether your cache is stale.

Example

{
"generatedAt": "2026-07-17T12:00:00.000Z",
"plans": [
{
"id": "3f2c8a1e-6b7d-4e9f-a1b2-c3d4e5f60718",
"title": "Couch to 5K",
"summary": "Nine weeks from the couch to your first 5K.",
"coverUrl": "https://ywkqixaobbjxdncvnqav.supabase.co/storage/v1/object/public/plan-covers/couch-to-5k.jpg",
"level": "beginner",
"discipline": "running",
"disciplineLabel": "Outdoor",
"weeksCount": 9,
"daysPerWeek": 3,
"estMinutesPerSession": 30,
"webUrl": "https://kamee.fit/plans/3f2c8a1e-6b7d-4e9f-a1b2-c3d4e5f60718",
"appUrl": "kamee://plan/3f2c8a1e-6b7d-4e9f-a1b2-c3d4e5f60718"
}
]
}

Response headers

Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff
Cache-Control: public, max-age=300
Vary: X-Api-Key

Status codes

CodeMeaning
200Success.
400discipline was present but not strength or running.
401Missing or wrong X-Api-Key.
405Any method other than GET or OPTIONSincluding HEAD.
500Server-side failure.
HEAD returns 405

HEAD is explicitly rejected, so it is not a valid health check for this endpoint. Use a GET with discipline=running if you need a cheap liveness probe — it is CDN-cached and inexpensive.

See Errors for response bodies and handling advice.

Methods

OPTIONS returns 204. It emits CORS headers only if an origin has been configured; by default there are none. See Calling from a browser.