TypeScript SDK
A typed client for every op, with retries, idempotency keys and typed errors.
@meterapp/gutlog-sdk is the TypeScript client for Gutlog: one typed method per op, generated from the op catalog, so its inputs match the API reference exactly. Zero runtime dependencies; it runs in Node 20+ and edge runtimes. Keep the API key on a server: it reads your health data. Prefer another language? Generate a client from /openapi.json; every op is POST /api/v1/{op} with a JSON body.
npm install @meterapp/gutlog-sdkVerify installation
Read public pricing without an API key before connecting your organization:
import { Gutlog } from "@meterapp/gutlog-sdk";
const pricing = await new Gutlog().getPricing<{ plans: { name: string }[] }>();console.log(pricing.plans.map((plan) => plan.name));Log a meal
Create an API key in the web app (or with npx @meterapp/gutlog login) and export it as GUTLOG_API_KEY. The SDK reads this variable or an explicit apiKey; it does not read the CLI's saved login. GUTLOG_HOST overrides the origin. This logs a snack and reads the day back:
import { Gutlog } from "@meterapp/gutlog-sdk";
const gutlog = new Gutlog(); // reads GUTLOG_API_KEY
const meal = await gutlog.call("log_meal", { items: [ { name: "greek yogurt", grams: 170, ingredients: [{ name: "milk", confidence: 0.95 }] }, { name: "blueberries", portion: { quantity: 0.5, unit: "cup" } }, ],});const day = await gutlog.call("get_day", {});console.log(meal, day);Every op
// Every op is callable by name, and as a typed method (camelCase) once the SDK release includes it:await gutlog.call("get_day", { date: "2026-11-03" });await gutlog.getOnboarding();
// Narrow the result type where you need it:const day = await gutlog.call<{ totals_display: { kcal: number; protein_g: number } }>("get_day", {});Inputs are typed and documented from each op's schema; results are plain JSON objects. The package is regenerated from the live catalog before each release, so it never names an op the API does not have.
Retries and idempotency
Rate limits (429), server errors and network failures are retried with exponential backoff (twice by default; maxRetries changes it), honouring Retry-After. Reads are always retried. Writes are retried only when they are safe: ops that take an idempotency_key get one automatically, the same on every attempt.
// Retries of a write never apply twice: the SDK fills idempotency_key once per call// (for ops that take one) and reuses it on every retry. Pass your own to make a retry// from another process safe too.await gutlog.call("log_meal", draft, { idempotencyKey: `photo-${photo.id}` });Errors
A failed call throws GutlogError with the HTTP status, the error code, the request id (quote it when reporting an issue) and the whole problem body.
import { Gutlog, GutlogError } from "@meterapp/gutlog-sdk";
try { await gutlog.call("log_meal", { items: [{ name: "apple", portion: { quantity: 1, unit: "medium" } }] });} catch (error) { if (error instanceof GutlogError) { // error.status, error.code ("invalid_input", "plan_limit"…), error.requestId, error.problem if (error.status === 402) console.log("Upgrade:", error.problem.checkout_url); } throw error;}