# Shape Calendar API Shape is a training calendar for endurance athletes. This API lets you read and write activity data. ## Authentication All requests require the user's API token in the Authorization header: Authorization: Bearer The token starts with "shape_". The user will provide it directly. ## Base URL https://shapecalendar.com/api/v1 ## Endpoints ### List activities GET /activities?from=2026-03-01&to=2026-03-31 Query parameters (all optional): - from: ISO date string, start of range - to: ISO date string, end of range - sportType: filter by type (run, bike, swim, hike, yoga, nordicski, strength, other) - completed: "true" or "false" - includePaired: "true" to include planned activities that have a paired completed activity (hidden by default) - limit: number of results (default 50, max 200) - offset: pagination offset (default 0) Returns: { activities: [...], total, limit, offset } ### Get activity GET /activities/:id Returns: { activity: {...} } ### Create activity POST /activities Content-Type: application/json Required fields: - date: ISO date string (e.g. "2026-03-28" or "2026-03-28T08:00:00Z") - title: string - sportType: one of run, bike, swim, hike, yoga, nordicski, strength, other Optional fields: - description: string - distance: number (meters) - duration: number (seconds) - speed: number (m/s) - heartRate: number (bpm) - power: number (watts) - load: number (training stress score) - elevationGain: number (meters) - completed: boolean - target: one of distance, time, pace, steps - steps: array of structured workout steps (see below). Set target to "steps" when using this. Returns: { activity: {...} } with status 201 ### Update activity PATCH /activities/:id Content-Type: application/json Send only the fields you want to change. Same fields as create, all optional. Returns: { activity: {...} } ### Delete activity DELETE /activities/:id Returns: { deleted: true } ### Batch create activities POST /activities/batch Content-Type: application/json Body: { activities: [{ date, title, sportType, ... }, ...] } Up to 100 activities per request. Same fields as single create. Returns: { activities: [...] } with status 201 ### Batch update activities PATCH /activities/batch Content-Type: application/json Body: { activities: [{ id: "id1", completed: true }, { id: "id2", title: "New title" }, ...] } Up to 100 activities per request. Each object must include id, plus any fields to update. Returns: { updated: number } ### Batch delete activities DELETE /activities/batch Content-Type: application/json Body: { ids: ["id1", "id2", ...] } Up to 100 IDs per request. All must belong to the authenticated user. Returns: { deleted: number } ## Activity object { "id": "abc-123", "date": "2026-03-28", "title": "Easy morning run", "description": "Recovery run", "sportType": "run", "distance": 5000, "duration": 1800, "speed": 2.78, "heartRate": 140, "power": null, "load": 45, "elevationGain": 50, "completed": true, "target": "distance", "source": "strava", "externalId": "12345", "createdAt": 1711612800000, "updatedAt": 1711612800000 } ## Structured workout steps Activities can include structured workout steps for interval training. Set target to "steps" and provide a steps array. ### Step types warmup, cooldown, interval, recovery, repeat ### End conditions (how a step ends) lap.button, time, distance, iterations, heart.rate, power, open ### Target types (what to aim for during a step) no.target, heart.rate, pace, power, speed, cadence ### Step object { "stepType": "interval", "displayName": "400m fast", "description": null, "endCondition": "distance", "endConditionValue": 400, "targetType": "pace", "targetValueOne": 210, "targetValueTwo": 240, "zoneNumber": null, "secondaryTargetType": null, "secondaryTargetValueOne": null, "secondaryTargetValueTwo": null, "secondaryZoneNumber": null } ### Repeat group (for intervals) { "stepType": "repeat", "numberOfIterations": 5, "skipLastRestStep": false, "workoutSteps": [ { "stepType": "interval", "displayName": "400m fast", "endCondition": "distance", "endConditionValue": 400, "targetType": "pace", "targetValueOne": 210, "targetValueTwo": 240 }, { "stepType": "recovery", "displayName": "90s jog", "endCondition": "time", "endConditionValue": 90, "targetType": "no.target" } ] } ### Example: 5x400m interval workout { "date": "2026-03-30", "title": "400m Repeats", "sportType": "run", "distance": 4000, "duration": 2400, "target": "steps", "steps": [ { "stepType": "warmup", "displayName": "Warmup jog", "endCondition": "time", "endConditionValue": 600, "targetType": "no.target" }, { "stepType": "repeat", "numberOfIterations": 5, "workoutSteps": [ { "stepType": "interval", "displayName": "400m fast", "endCondition": "distance", "endConditionValue": 400, "targetType": "pace", "targetValueOne": 210, "targetValueTwo": 240 }, { "stepType": "recovery", "displayName": "90s jog", "endCondition": "time", "endConditionValue": 90, "targetType": "no.target" } ] }, { "stepType": "cooldown", "displayName": "Cooldown jog", "endCondition": "time", "endConditionValue": 600, "targetType": "no.target" } ] } ## Sport types run, bike, swim, hike, yoga, nordicski, strength, other ## Errors All errors return: { error: "message" } - 400: Validation error (bad request body or query params) - 401: Missing or invalid token - 404: Activity not found - 429: Rate limit exceeded (30 req/min, 200 req/hr) ## Tips for LLMs - Always include a date range when listing activities to avoid fetching everything - Use PATCH to update single fields (e.g. mark completed) rather than sending the full object - Use batch endpoints when creating or deleting multiple activities at once - Distance is always in meters, duration in seconds - Activities with source "strava", "garmin", or "wahoo" were imported from external services - The completed field distinguishes planned workouts (false) from completed ones (true) - When a planned activity is completed, both exist linked via completedActivityId/plannedActivityId. By default the list endpoint hides the paired planned one — use includePaired=true to see both ## Creating workouts — conventions - Keep titles short. Don't include distance/duration in the title: "Easy 5k run" → "Easy run", "bike 100km" → "Bike" - Set target based on the workout goal: "5k run" → distance, "20min ride" → time, "5k in 20min" → pace (both distance and duration given), structured intervals → steps - Only include steps for workouts with specific interval structure (e.g. "5x400m at 5k pace with 60s rest"). Don't create steps for simple descriptions like "5k run" or "20min easy bike ride" - Never include steps for strength training workouts — use the description field instead - Load is Training Stress Score (TSS): (duration_seconds / 3600) * relative_effort * 100, where relative effort ranges from 0.25 (very easy) to 1.50 (very hard). Example: 30min easy run = (1800/3600) * 0.5 * 100 = 25 TSS