Strava Activities API

Public read-only JSON API exposing your full Strava activity history. Designed to be consumed by an AI running coach. Runs on Cloudflare Workers + D1 + Hono.

Sync & coverage

Pagination & cache-busting

API — path-based params

Pagination, limits, and the cache-bust token live in the URL path. This is for HTTP clients that can only follow links found as paths (e.g. Claude's web_fetch, which won't follow URLs with query strings unless they appear verbatim in user input). All /n/..., /p/.../..., and /cb-... segments are optional and order-fixed.

GET /api/v2/activities/p/1/50

{
  "next_url":      "https://coach.christofferlarsson.se/api/v2/activities/p/1/50/cb-1729459200123-a4f2",
  "next_page_url": "https://coach.christofferlarsson.se/api/v2/activities/p/2/50/cb-1729459200456-9b1c",
  "page": 1,
  "per_page": 50,
  "count": 50,
  "activity_18234782549": "https://coach.christofferlarsson.se/api/v2/activities/18234782549/cb-1729459200789-1a2b",
  "activity_18221202225": "https://coach.christofferlarsson.se/api/v2/activities/18221202225/cb-1729459200790-3c4d",
  "...": "one activity_{id} field per activity on the page (placed BEFORE activities[])",
  "activities": [ { "id": 18234782549, ... }, ... ]
}

Slim list payload. The list response only returns the fields useful for scanning history: id, name, type, sport_type, start_date, description, distance, moving_time, elapsed_time, total_elevation_gain, average_speed, average_heartrate, max_heartrate, average_cadence, average_watts, suffer_score, workout_type, plus url and streams_url. Heavy or rarely-needed fields (notably map.summary_polyline, lat/lng, splits, laps, segment efforts, social counts, gear) are stripped to keep responses small enough that web_fetch doesn't truncate. Fetch /api/v2/activities/{id} for the full detail.

Top-level activity URL fields (experimental). The paged list response (/api/v2/activities[/p/...]) also includes one extra top-level field per activity on the page, named activity_{id} (e.g. activity_18234782549), whose value is a direct URL to that activity's detail endpoint with a fresh cb-token. These fields are placed before the activities array so they survive even if the array gets truncated by HTTP clients. Tokens regenerate per request. Same data is also available inside activities[i].url.

Cache invalidation (force re-fetch from Strava)

Activity detail is cached for 24h and streams are cached indefinitely. The summary list is re-synced at most once per 5 min. If you edited an activity on Strava (rename, change type, fix HR, add description, etc.) and want the API to pick it up immediately, hit one of these endpoints. All accept GET so Claude's web_fetch works.

Strava's read API allows ~100 calls per 15 min. Use /sync/refresh when you only need updated names/types — it costs 1–2 calls. Use /{id}/refresh for a specific activity (1 call). Use /latest/refresh sparingly (N ≤ 30) to avoid burning the budget.

Schedule (training plan)

Reads the upcoming training plan from a Google Sheet (via service account). Cached for 10 minutes in D1, with stale-fallback if Sheets is unreachable.

A mobile-friendly server-rendered view of today + the next 13 days is available at /schema.

Google Sheets API

Read and write cells in a Google Sheet via a service account. To use: enable the Google Sheets API in your Google Cloud project, create a service account + JSON key, store it as the GOOGLE_SERVICE_ACCOUNT_JSON Worker secret, and share the sheet with the service account's client_email (Editor for write access, Viewer for read-only).

Notes