V3.0 Open API
JoyOne data, on your terms
The Open API exposes your JoyOne training data over HTTPS with a bearer token. Pull the same activities you see in the dashboard into your own dashboards, notebooks, or scheduled jobs. Public segment search is unauthenticated and free to use.
Authentication
Every V1 endpoint except /api/v1/segments/search requires a bearer token in the Authorization header:
Authorization: Bearer joy1_live_…
Tokens are scoped. A token can have any subset of activities:read, activities:write, segments:read, projects:read, projects:write, webhooks:write. Each endpoint lists the scopes it requires.
Generate a token at JoyOne settings → API tokens (coming soon) or via POST /api/v1/auth/token.
Endpoints
/api/v1/activitiesactivities:readList the caller's activities (most recent first).
/api/v1/activities/exportactivities:readExport the caller's activities as JSON or CSV. Use this for scheduled backups or one-off dumps.
Query
formatcsv | json = jsonResponse shape. csv downloads as text/csv.fromYYYY-MM-DDInclusive lower bound on startDate.toYYYY-MM-DDInclusive upper bound on startDate.limitint = 1000Hard cap 5000. Results past the cap are dropped; x-has-more tells you.Example (JSON)
curl -H "Authorization: Bearer joy1_live_…" \ "https://joyvelo.com/api/v1/activities/export?from=2026-01-01&to=2026-07-26&limit=200"
{
"activities": [
{ "id": "act_…", "sport": "Ride", "startDate": "2026-07-15T08:00:00.000Z",
"distanceMeters": 30000, "movingTimeSeconds": 3600,
"averagePower": 220, "normalizedPower": 240, … }
],
"count": 1,
"hasMore": false,
"columns": ["id","source","name","sport","startDate", …]
}Example (CSV)
curl -H "Authorization: Bearer joy1_live_…" \ "https://joyvelo.com/api/v1/activities/export?format=csv&from=2026-01-01" \ -o joyone-2026.csv
/api/v1/segments/searchnone (public)Search the public JoyOne segment library. No auth required.
Query
regionsluge.g. "french-alps", "pyrenees".qstringFree-text search on the segment name.minDistkmMinimum distance in km.maxDistkmMaximum distance in km.minClimbmMinimum elevation gain in metres.maxClimbmMaximum elevation gain in metres.limitint = 50Hard cap 200.Example
curl "https://joyvelo.com/api/v1/segments/search?region=french-alps&minClimb=800&limit=20"
The segment library is populated over time as JoyOne users publish their rides. As of V3.0 the table is sparse; expect results to fill in as the community grows.
Error codes
bad_requestA query parameter failed to parse (e.g. malformed date).missing_bearer / invalid_or_expired_tokenNo Authorization header, or the token didn't verify.feature_requiredThe caller's tier doesn't unlock the endpoint.insufficient_scopeThe token is valid but missing the required scope.not_foundThe resource doesn't exist or belongs to a different user.rate_limitedSoft cap (60 req / min per token). Wait and retry with backoff.db_errorSupabase query failed. Includes a short reason in error.service_unavailableCloud auth or DB is unreachable. Retry with backoff.Limits & best practices
- Rate limit — 60 requests per minute per token. Headers:
x-ratelimit-remaining,x-ratelimit-reset. - Result cap — 5000 rows per call on the export route. Use the date range filters for larger pulls.
- Pagination — V1 uses
limit+x-has-more.from/toare your friends for page-through. - Caching — segments responses cache for 60 s. Activities responses set
cache-control: no-store; respect the user's data freshness. - Backoff — on 429, sleep for the
Retry-Afterheader (seconds), or your own exponential backoff if the header is absent.