API recipes
Node 18 or later, no dependencies. Every script reads the key from FITAIO_API_KEY.
// shared.mjs
export const BASE = 'https://fitaio.app/api/v1';
export const headers = { Authorization: `Bearer ${process.env.FITAIO_API_KEY}`, 'Content-Type': 'application/json' };
export async function api(path, init = {}) {
const response = await fetch(`${BASE}${path}`, { ...init, headers });
if (!response.ok) throw new Error(`${init.method ?? 'GET'} ${path} -> ${response.status} ${await response.text()}`);
return response.status === 204 ? null : response.json();
}
export async function* paginate(path, key) {
let cursor = null;
do {
const page = await api(`${path}${path.includes('?') ? '&' : '?'}limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`);
yield* page[key];
cursor = page.next_cursor;
} while (cursor);
}Export every workout to JSON
import { writeFileSync } from 'node:fs';
import { paginate } from './shared.mjs';
const all = [];
for await (const workout of paginate('/workouts', 'workouts')) all.push(workout);
writeFileSync('workouts.json', JSON.stringify(all, null, 2));
console.log(`${all.length} workouts written`);Add ?since=2026-01-01T00:00:00Z to the path to export a window instead of everything.
Log a workout from a script
Look up exercise ids by name, then post one session.
import { api } from './shared.mjs';
async function exerciseId(name) {
const { exercises } = await api(`/exercises?q=${encodeURIComponent(name)}&limit=1`);
if (!exercises[0]) throw new Error(`No exercise matches "${name}"`);
return exercises[0].id;
}
const bench = await exerciseId('bench press');
const workout = await api('/workouts', {
method: 'POST',
body: JSON.stringify({
title: 'Push',
started_at: new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'),
duration_seconds: 3300,
exercises: [{ exercise_id: bench, sets: [{ type: 'warmup', weight_kg: 40, reps: 10 }, { weight_kg: 80, reps: 8 }, { weight_kg: 80, reps: 8 }, { weight_kg: 80, reps: 7 }] }],
}),
});
console.log(`Saved ${workout.id}: ${workout.volume_kg} kg`);Sync body weight from a CSV export
Many scales export date,kg rows. Post each one with logged_at so the trend lines up.
import { readFileSync } from 'node:fs';
import { api } from './shared.mjs';
const rows = readFileSync('weights.csv', 'utf8').trim().split('\n').slice(1);
for (const row of rows) {
const [date, kg] = row.split(',');
await api('/body/weight', { method: 'POST', body: JSON.stringify({ weight_kg: Number(kg), logged_at: `${date}T07:00:00Z` }) });
}
console.log(`${rows.length} entries logged`);Weekly training report
import { api } from './shared.mjs';
const until = new Date();
const since = new Date(until.getTime() - 7 * 86_400_000);
const iso = (date) => date.toISOString().replace(/\.\d{3}Z$/, 'Z');
const stats = await api(`/stats/training?since=${iso(since)}&until=${iso(until)}`);
console.log(`${stats.sessions} sessions, ${stats.completed_sets} sets, ${Math.round(stats.volume_kg)} kg, streak ${stats.streak_days} days`);
for (const group of stats.by_muscle_group) console.log(` ${group.muscle_group}: ${group.sets} sets, ${Math.round(group.volume_kg)} kg`);
const { records } = await api('/stats/records?limit=5');
for (const record of records) console.log(`PR ${record.name}: ${record.weight_kg} kg x ${record.reps} (e1RM ${record.estimated_1rm_kg} kg)`);Prefer an AI assistant doing this for you? The MCP server exposes the same calls as tools.