The HTTP API behind JoyOne, JoyAnalyze, JoyTrain, and the rest of the JoyVelo platform. Use it to wire your activities into CI, push analysis-complete events to your own backend, or bulk-import a season of rides for offline analysis.
11 shipped · 1 preview · 12 endpoints total. All times are ISO 8601 UTC. All IDs are ULIDs.
TL;DR
Sign in to joyvelo.com in your browser, then call POST /api/v1/auth/token with a signed-in cookie to mint a bearer.
Send that bearer in the Authorization header. Required scopes appear in the table below.
Read endpoints are live today. Project write / patch and webhook registration are preview — handlers return 501 until the G19 webhook UI ships.
1. Authentication
Issue, list, and revoke long-lived bearer tokens for scripts and CI.
POST/api/v1/auth/tokenshippedsigned-in session
Issue a new API token
Create a long-lived bearer for scripts / CI. The plaintext token is returned EXACTLY ONCE — the server only stores the SHA-256 hash. Use a signed-in Supabase session (cookie) to call this endpoint.
Parameters
Name
In
Type
Required
Description
label
body
string
yes
Human-readable label; shown in the token list.e.g. CI pipeline
Returns the full project document — metadata, slots, per-stream labels, bias tables, KPI summary. Will be served from the same RLS-gated `projects` table the v1 list endpoint reads.
POST/api/v1/projectsshippedscope: projects:write
Create a project
Body must follow the `kind:"joyanalyze-projects"`, `version:1` JSON envelope (see "JSON envelopes" below). The server strips the `id` and assigns a fresh one. Gated to Pro+ via `stream_slots_20`.
Errors
Status
Code
Meaning
400
name_required
Body `name` is empty after trim.
400
unsupported_kind
Only `kind:"joyanalyze"` is supported in V3.0.
401
unauthorized
Missing or invalid bearer.
402
insufficient_tier
Token has `projects:write` but the user is on `free`; upgrade required.
Soft-delete: sets `payload.deleted_at`. The schema does not have a real `deleted_at` column yet (added in the next migration). Gated to Pro+ via `stream_slots_20`.
Errors
Status
Code
Meaning
401
unauthorized
Missing or invalid bearer.
402
insufficient_tier
Token has `projects:write` but the user is on `free`.
Push analysis-complete events to your own endpoint. HMAC-SHA-256 signed; 5-minute replay window. Preview until G19.
POST/api/v1/webhooksshippedscope: webhooks:write
Register a webhook
Body: { url, events?, secret? }. Server auto-generates the HMAC secret if absent (returned EXACTLY ONCE in the response). URL must be https:// (or http://localhost:… for dev). Gated to Pro+ via `webhooks`.
Errors
Status
Code
Meaning
400
invalid_url
URL must be https:// (or http://localhost:… for dev).
400
invalid_events
`events[]` had no known event types.
401
unauthorized
Missing or invalid bearer.
402
insufficient_tier
Token has `webhooks:write` but the user is on `free` or `pro`; Pro+ required.
Returns metadata only (id, url, event_filter, active, created_at, updated_at). Secret is never re-served after the initial POST response. Gated to Pro+ via `webhooks`.
Errors
Status
Code
Meaning
401
unauthorized
Missing or invalid bearer.
402
insufficient_tier
Token has `webhooks:read` but the user is on `free` or `pro`; Pro+ required.
Every token is a subset of the six scopes below. The verification layer rejects any request that asks for a scope the token doesn't carry. activities:write and the two webhook scopes are reserved for upcoming G13 / G19 features; their endpoints return 501 today.
projects:read
Read project metadata + slot labels.
projects:write
Create / update / delete projects.
activities:read
Read the JoyOne activity archive.
activities:write
Create / update / delete activities (preview).
webhooks:read
List your registered webhooks (preview).
webhooks:write
Register / revoke webhooks (preview).
JSON envelopes
Every persisted document carries a top-level kind discriminator and a version integer. New versions add fields; old clients ignore unknown fields.
joyanalyze-projectsv1
Project export (G4) + the upcoming POST /api/v1/projects body.
joyanalyze-segment-templatesv1
Segment template library (G10) export/import.
Webhook signing
Every analysis-complete POST carries a single header:
v1 is the HMAC-SHA-256 hex of${t}.${rawBody} using the shared secret returned at registration. Verify on the receiver side:
Reject if the timestamp is more than 5 minutes from your local clock (replay protection).
Compute the HMAC against the raw request body — do not pre-parse JSON.
Compare with a constant-time string compare.
Reference implementation: lib/joyanalyze-webhook.ts (browser + Node 18+ via SubtleCrypto).
Error model
Every non-2xx response is a JSON object with a single error field — the human-readable code. Status codes follow HTTP norms: 400 for bad input, 401 for missing auth, 403 for insufficient scope, 404 for unknown id, 409 for conflicts, 429 for rate-limit, 5xx for server fault. We never leak Supabase internals in the body.
The URL is the version (/api/v1/...). Within a version we only add fields and endpoints; we never rename or repurpose them. A v2 cut will ship alongside a parallel v1 maintenance window of at least 6 months.
See the PRD for the rollout plan; JoyAnalyze PRD for the feature-level roadmap (V1 free, V2 Pro, V3 Pro+).
Product tiers — V1 / V2 / V3
JoyAnalyze ships in three product tiers. The tier controls UI limits (how many files per project, how many streams you can compare at once); the API surface above is the same for every tier, with the only difference being which scopes your token carries.
V1.0Free
2 files per project
Original V1.0 PRD cap. The current default is 3 slots (A/B/C) so free users can compare a power meter + HR strap + cadence sensor without upgrade friction. Pro/Pro+ users can add up to 20 slots per project via the "+ Add stream" button.
API access: Read-only bearer token (projects:read, activities:read).
V2.0Pro
20 streams per project
Up to 20 files per project, advanced cleaning rules, ANT+ / BLE sensor library, custom zones, JoyOne / Reviews / JoyTrain deep linking, PNG / SVG / CSV export, share-link tiers + TTL, iframe embed.
Adds one-click PDF reports, R² / SEE / Mahalanobis / CdA / dynamic-response latency, batch upload / batch project / batch report, team comments + activity log + tags, custom metric DSL, this public API, HMAC-signed webhooks.
API access: All 6 scopes, including webhooks:read + webhooks:write.
Tier enforcement lives in the UI for the V1.0 cap (stream-add button) and in the Vercel cron + Supabase row-level security for the V2 / V3 feature gates. Bearer-token scope checks are the source of truth on the API side: a V1.0 token can never mutate state regardless of what the UI lets through.
Generated from lib/api-endpoints.ts. The manifest and the route handlers stay in sync because the page reads from the same source the tests assert against.