TypeScript SDK

Package @trainheroic-unofficial/js wraps auth, workouts, roster helpers, athlete reads, analytics, and messaging. Types ship in @trainheroic-unofficial/dto. The runtime runs on Node, workerd, and browsers.

← Developers

Install

npm install @trainheroic-unofficial/js
npm install @trainheroic-unofficial/js

Coach workflow

import { TrainHeroicClient, ExerciseLibrary, buildSession, fetchCoachRoster, } from "@trainheroic-unofficial/js"; import { JsonFileLibraryCache } from "@trainheroic-unofficial/js/node"; const client = new TrainHeroicClient(email, password); const library = new ExerciseLibrary(client, new JsonFileLibraryCache()); for (const athlete of await fetchCoachRoster(client)) { console.log(athlete.name, athlete.id); } const { match } = await library.resolve("Back Squat"); if (!match) throw new Error("Resolve to one exercise first"); const { pwId } = await buildSession(client, { programId: 12345, // from list_teams → group_program date: [2026, 6, 22], blocks: [{ title: "Strength", exercises: [{ id: match.id, sets: 5, reps: 5, weight: 225, rpe: 8 }], }], publish: false, });
import {
  TrainHeroicClient,
  ExerciseLibrary,
  buildSession,
  fetchCoachRoster,
} from "@trainheroic-unofficial/js";
import { JsonFileLibraryCache } from "@trainheroic-unofficial/js/node";

const client = new TrainHeroicClient(email, password);
const library = new ExerciseLibrary(client, new JsonFileLibraryCache());

for (const athlete of await fetchCoachRoster(client)) {
  console.log(athlete.name, athlete.id);
}

const { match } = await library.resolve("Back Squat");
if (!match) throw new Error("Resolve to one exercise first");

const { pwId } = await buildSession(client, {
  programId: 12345, // from list_teams → group_program
  date: [2026, 6, 22],
  blocks: [{
    title: "Strength",
    exercises: [{ id: match.id, sets: 5, reps: 5, weight: 225, rpe: 8 }],
  }],
  publish: false,
});

Athlete reads

import { TrainHeroicClient, resolveAthleteUserId, fetchAthleteProfileSummary, searchExerciseHistory, fetchExerciseHistoryDetail, fetchPersonalRecords, } from "@trainheroic-unofficial/js"; const client = new TrainHeroicClient(email, password); const userId = await resolveAthleteUserId(client); const profile = await fetchAthleteProfileSummary(client, userId); console.log(profile.volume_sum); const [squat] = await searchExerciseHistory(client, "back squat", 1); if (squat) { const id = Number(squat.id); console.log(await fetchExerciseHistoryDetail(client, id, userId)); console.log(await fetchPersonalRecords(client, id)); }
import {
  TrainHeroicClient,
  resolveAthleteUserId,
  fetchAthleteProfileSummary,
  searchExerciseHistory,
  fetchExerciseHistoryDetail,
  fetchPersonalRecords,
} from "@trainheroic-unofficial/js";

const client = new TrainHeroicClient(email, password);
const userId = await resolveAthleteUserId(client);

const profile = await fetchAthleteProfileSummary(client, userId);
console.log(profile.volume_sum);

const [squat] = await searchExerciseHistory(client, "back squat", 1);
if (squat) {
  const id = Number(squat.id);
  console.log(await fetchExerciseHistoryDetail(client, id, userId));
  console.log(await fetchPersonalRecords(client, id));
}

Analytics

Analytics reports are read-only data pulls. TrainHeroic uses POST for these queries, but they do not mutate anything.

import { TrainHeroicClient, analyticsMetricCatalog, queryAnalytics, } from "@trainheroic-unofficial/js"; const client = new TrainHeroicClient(email, password); // Read-only report (TrainHeroic uses POST for these queries). const readiness = await queryAnalytics(client, { metric: "readiness-team", teamId: 42, date: "2026-06-22", }); console.log(readiness); // Scope + required params for every metric key: console.log(analyticsMetricCatalog());
import {
  TrainHeroicClient,
  analyticsMetricCatalog,
  queryAnalytics,
} from "@trainheroic-unofficial/js";

const client = new TrainHeroicClient(email, password);

// Read-only report (TrainHeroic uses POST for these queries).
const readiness = await queryAnalytics(client, {
  metric: "readiness-team",
  teamId: 42,
  date: "2026-06-22",
});
console.log(readiness);

// Scope + required params for every metric key:
console.log(analyticsMetricCatalog());

Messaging

import { TrainHeroicClient, fetchStreams, sendComment, } from "@trainheroic-unofficial/js"; const client = new TrainHeroicClient(email, password); const team = (await fetchStreams(client)).find((s) => s.kind === "team"); if (team && typeof team.stream.id === "number") { const comment = await sendComment(client, team.stream.id, "Great week."); console.log(comment); }
import {
  TrainHeroicClient,
  fetchStreams,
  sendComment,
} from "@trainheroic-unofficial/js";

const client = new TrainHeroicClient(email, password);

const team = (await fetchStreams(client)).find((s) => s.kind === "team");
if (team && typeof team.stream.id === "number") {
  const comment = await sendComment(client, team.stream.id, "Great week.");
  console.log(comment);
}

Full API on npm