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 OPTIONS — including 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.