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.