npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dofek/zwift

v0.1.2

Published

Unofficial Zwift API client using reverse-engineered Keycloak authentication

Readme

@dofek/zwift

Unofficial TypeScript client for Zwift's private game-client API, including account sign-in, profiles, activities, fitness streams, and power curves.

This package is not affiliated with, endorsed by, or supported by Zwift. It uses private endpoints and observed game-client headers rather than a supported public API contract; any of them may change without notice.

Review Zwift's Terms of Service and obtain prior authorization where required. The current terms restrict unauthorized applications and automated interaction with the Zwift platform. Use only with an account and data you are authorized to access.

Requirements

  • Node.js 22.14 or newer

Install

npm install @dofek/zwift

Quick start

import { ZwiftClient } from "@dofek/zwift";

const username = process.env.ZWIFT_USERNAME;
const password = process.env.ZWIFT_PASSWORD;
if (!username || !password) {
  throw new Error("Set ZWIFT_USERNAME and ZWIFT_PASSWORD");
}

const tokens = await ZwiftClient.signIn(username, password);

// getAuthenticatedProfile() does not use the constructor's athlete ID.
const bootstrapClient = new ZwiftClient(tokens.accessToken, "me");
const profile = await bootstrapClient.getAuthenticatedProfile();

const client = new ZwiftClient(tokens.accessToken, String(profile.id));
const activities = await client.getActivities(0, 20);

console.log({
  athleteId: profile.id,
  activityCount: activities.length,
});

This resolves the athlete ID from /api/profiles/me before making endpoints that require /api/profiles/{athleteId}.

Token lifecycle

signIn and refreshToken each return:

{
  accessToken: string;
  refreshToken: string;
  expiresIn: number; // seconds
}

ZwiftClient does not refresh itself. Persist the refresh token securely, refresh before expiresIn elapses, and construct a new client:

const refreshed = await ZwiftClient.refreshToken(tokens.refreshToken);
const refreshedClient = new ZwiftClient(
  refreshed.accessToken,
  String(profile.id),
);

Replace the stored refresh token with the one returned by every successful refresh. Treat passwords and both tokens as secrets.

Both static authentication methods and the constructor accept an optional compatible fetch implementation as their final argument.

Client API

Authentication and constants:

  • ZwiftClient.signIn(username, password, fetch?)
  • ZwiftClient.refreshToken(refreshToken, fetch?)
  • ZWIFT_AUTH_URL
  • ZWIFT_API_BASE

Authenticated client methods:

  • getAuthenticatedProfile() fetches the current account's profile.
  • getProfile() fetches the constructor's athlete ID.
  • getActivities(start = 0, limit = 20) lists that athlete's activities.
  • getActivityDetail(activityId) requests detail with snapshots.
  • getFitnessData(url) fetches a fitness-stream URL.
  • getPowerCurve() fetches the authenticated athlete's power profile.

getFitnessData sends the bearer token to the supplied URL. Pass only a URL returned by Zwift, such as activity.fitnessData?.fullDataUrl; never pass untrusted user input.

Types and deep imports

Response types are exported from types:

import type {
  ZwiftActivityDetail,
  ZwiftActivitySummary,
  ZwiftFitnessData,
  ZwiftPowerCurve,
  ZwiftProfile,
  ZwiftTokenResponse,
} from "@dofek/zwift/types";

Provider-neutral parsing helpers are exported from parsing:

import {
  mapZwiftSport,
  parseZwiftActivity,
  parseZwiftFitnessData,
  type ParsedZwiftActivity,
  type ParsedZwiftStreamSample,
} from "@dofek/zwift/parsing";

parseZwiftFitnessData(data, activityStart) aligns the observed parallel sample arrays and converts centimeters to meters and centimeters per second to meters per second.

Rate limits and errors

Actual package behavior:

  • HTTP 429 throws ProviderRateLimitError from @dofek/provider-http/rate-limit. Its retryAfterSeconds property is parsed from Retry-After when present.
  • HTTP 502, 503, and 504 throw ProviderServiceUnavailableError from the same module.
  • Other unsuccessful authentication and API responses throw Error containing the HTTP status; most methods also include the response body.
  • The client does not automatically sleep, refresh a token, or retry.
  • Successful private responses are represented by TypeScript interfaces, not runtime-validated schemas. Be prepared for upstream shape changes.

Observed private protocol

  • Auth URL: https://secure.zwift.com/auth/realms/zwift/protocol/openid-connect/token
  • API base: https://us-or-rly101.zwift.com
  • Observed client ID: Zwift Game Client
  • Sign-in grant: password
  • Refresh grant: refresh token
  • Observed request identity headers: Platform: OSX, Source: Game Client, and a macOS game-client User-Agent
  • API authorization: Authorization: Bearer <access token>

These details document observed behavior; they are not promises made by Zwift.

Project