@dofek/velohero
v0.1.4
Published
Unofficial VeloHero API client using reverse-engineered session authentication
Downloads
371
Maintainers
Readme
@dofek/velohero
Unofficial TypeScript client for VeloHero's private workout-export API.
This package is not affiliated with, endorsed by, or supported by VeloHero. The endpoints and response shapes were observed from VeloHero's web application, are not a supported public API contract, and may change without notice. Use only with an account and data you are authorized to access.
Requirements
- Node.js 22.14 or newer
Install
npm install @dofek/veloheroQuick start
import { VeloHeroClient } from "@dofek/velohero";
const username = process.env.VELOHERO_USERNAME;
const password = process.env.VELOHERO_PASSWORD;
if (!username || !password) {
throw new Error("Set VELOHERO_USERNAME and VELOHERO_PASSWORD");
}
const { sessionCookie, userId } = await VeloHeroClient.signIn(
username,
password,
);
const client = new VeloHeroClient(sessionCookie);
const workouts = await client.getWorkouts("2026-01-01", "2026-01-31");
const firstWorkout = workouts[0]
? await client.getWorkout(workouts[0].id)
: null;
console.log({ userId, workoutCount: workouts.length, firstWorkout });Dates passed to getWorkouts use YYYY-MM-DD.
Authentication lifecycle
VeloHeroClient.signIn(username, password) posts form data to the observed
/sso endpoint and returns:
{
sessionCookie: string; // "VeloHero_session=<session token>"
userId: string;
}Pass sessionCookie to the constructor. Treat it like a password: do not log
it or expose it to a browser. The private response does not provide expiry
metadata, and this package has no session-refresh method. When VeloHero rejects
an expired session, sign in again and construct a new client.
Both the constructor and signIn accept an optional fetch implementation as
their final argument for compatible runtimes and network-level tests.
Public API
The package root exports VeloHeroClient:
VeloHeroClient.signIn(username, password, fetch?)new VeloHeroClient(sessionCookie, fetch?)client.getWorkouts(dateFrom, dateTo)client.getWorkout(id)
Additional modules are available through documented deep imports:
import {
parseDurationToSeconds,
parseVeloHeroWorkout,
type ParsedVeloHeroWorkout,
} from "@dofek/velohero/parsing";
import {
mapVeloHeroSport,
VELOHERO_SPORT_MAP,
} from "@dofek/velohero/sports";
import type {
VeloHeroSsoResponse,
VeloHeroWorkout,
VeloHeroWorkoutsResponse,
} from "@dofek/velohero/types";parseVeloHeroWorkout converts VeloHero's string-valued export record to a
provider-neutral activity summary while retaining observed metrics in raw.
Rate limits and errors
Observed client behavior:
- HTTP
429throwsProviderRateLimitErrorfrom@dofek/provider-http/rate-limit. ItsretryAfterSecondsproperty is parsed fromRetry-Afterwhen present. - HTTP
502,503, and504throwProviderServiceUnavailableErrorfrom the same module. - Other unsuccessful sign-in and API responses throw
Errorcontaining the HTTP status and response body. - The client does not automatically sleep or retry. Callers decide whether and when an operation is safe to repeat.
- Successful private responses are represented by TypeScript interfaces, not runtime-validated schemas. Be prepared for upstream shape changes.
Observed private protocol
- Base URL:
https://app.velohero.com - Sign-in:
POST /ssousing form fieldsuser,pass, andview=json - Authentication:
VeloHero_sessioncookie - Workout list:
GET /export/workouts/json - Workout detail:
GET /export/workouts/json/{id}
These details document observed behavior; they are not promises made by VeloHero.
