JoyVelo

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.

Free for everyone — Pro tier recommended for > 1000 activities / monthBearer-token authJSON or CSV

Authentication

Every V1 endpoint except /api/v1/segments/search requires a bearer token in the Authorization header:

bash
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

GET/api/v1/activitiesactivities:read

List the caller's activities (most recent first).

GET/api/v1/activities/exportactivities:read

Export 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)

bash
curl -H "Authorization: Bearer joy1_live_…" \
  "https://joyvelo.com/api/v1/activities/export?from=2026-01-01&to=2026-07-26&limit=200"
json
{
  "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)

bash
curl -H "Authorization: Bearer joy1_live_…" \
  "https://joyvelo.com/api/v1/activities/export?format=csv&from=2026-01-01" \
  -o joyone-2026.csv
GET/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

bash
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

400bad_requestA query parameter failed to parse (e.g. malformed date).
401missing_bearer / invalid_or_expired_tokenNo Authorization header, or the token didn't verify.
402feature_requiredThe caller's tier doesn't unlock the endpoint.
403insufficient_scopeThe token is valid but missing the required scope.
404not_foundThe resource doesn't exist or belongs to a different user.
429rate_limitedSoft cap (60 req / min per token). Wait and retry with backoff.
500db_errorSupabase query failed. Includes a short reason in error.
503service_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 / to are 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-After header (seconds), or your own exponential backoff if the header is absent.
Looking for the JoyVelo product roadmap? See the public PRD. For the V1 internal API manifest (which is the source of truth for scopes + error contracts), see lib/api-endpoints.ts.