← Macro Desk / API
Tokens

Drive Macro Desk from your own code

Everything the web page does is available over HTTP: send a description of the person with the diet facts you computed, and get the same coach's plan back — or send the check-in facts and get the adjustment note. The natural use is a coaching pipeline that regenerates a client's plan when their weight or training week changes, or a weekly job that reads three weigh-ins and writes the adjustment.

One thing to be clear about before the first call: the model never computes the diet. Maintenance calories, the deficit, the macros, the meal template, the phase and the check-in arithmetic are computed by the caller and sent as facts. The model's job is judgement over those facts — goal fit, pitfalls, noise against signal, the prose. See computing the facts yourself; the engine the web page uses ships as a plain script (/diet.js) you can load in node.

Base URL and the envelope

Every endpoint lives under https://api.skillsafe.ai/v1/app-api and every response uses the same envelope, so one helper covers the whole API:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }

The token is minted for this app (the guest endpoint takes {"slug":"macro-desk"} in its body), so no slug header is needed afterwards — send your token as Authorization: Bearer … on every call.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a declared required field (task, about, facts) is missing.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: running is metered and needs a personal token.
404not_foundUnknown job id.
429rate_limitedBack off and retry.

1. Get a token

A guest token is free and enough for /me and /estimate. Running a lane needs a personal token — sign in on the token page and copy it from there.

curl -s -X POST https://api.skillsafe.ai/v1/app-api/guest -H "Content-Type: application/json" -d '{"slug":"macro-desk"}'

2. A tiny client

One helper, one envelope. The samples below reuse it.

curl -s -X GET https://api.skillsafe.ai/v1/app-api/me \
  -H "Authorization: Bearer $SKILLSAFE_TOKEN" \
  -H "Content-Type: application/json"

3. Check the session and the balance

GET /me returns subject_type (user or guest), subject_id and credits. A real user's first run should not 402: compare credits with the estimate's hold_credits before running.

curl -s -X GET https://api.skillsafe.ai/v1/app-api/me \
  -H "Authorization: Bearer $SKILLSAFE_TOKEN" \
  -H "Content-Type: application/json"

4. Price the run — free

POST /estimate with the exact run body returns model (gpt-5.6-terra), model_alias (gpt-terra), markup_bps (1000), hold_credits (reserved, not the price) and min_credits. No job, no charge. The body must be a JSON object: the server does not validate the shape beyond that, so send exactly the fields below.

curl -s -X POST https://api.skillsafe.ai/v1/app-api/estimate \
  -H "Authorization: Bearer $SKILLSAFE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"task": "plan", "about": "34-year-old woman, lifting four years, three hard sessions a week after work and two moderate days. Cook at home; do not track on weekends. Last cut ran 20 weeks and cost strength.", "draft": "", "facts": "<JSON string from Diet.plan(spec) - see below>"}'

The fields both lanes take

FieldTypeMeaning
taskstring, requiredplan or adjust. The lane. A missing or unknown value makes the model pick the closer lane and name it in lane.
aboutstring, requiredThe person in their own words: training, food habits, diet history. Twenty characters or more.
draftstringOptional. The user's current plan or targets, to be checked claim by claim in draft_check.
logstringAdjust lane. What happened since the plan: adherence, sleep, hunger, cycle, travel, weigh-in conditions.
factsstring, requiredA JSON string (not an object): Diet.plan(spec) for the plan lane; {"checkin": Diet.adjust(spec), "plan": summary} for the adjust lane.

Computing the facts yourself

Load /diet.js in node with a stub window and call the same functions the page calls. The plan spec:

global.window = {}; require("vm").runInThisContext(require("fs").readFileSync("diet.js", "utf8"));
const Diet = window.Diet;
const plan = Diet.plan({
  sex: "female", weight: 155, unit: "lb",          // or unit: "kg"
  bodyfat_pct: 26, age: 34, height_cm: 168,        // optional
  goal: "fat_loss",                                // fat_loss | muscle_gain | maintenance
  experience: "intermediate",                      // beginner | intermediate | advanced
  week: ["hard","moderate","hard","moderate","hard","rest","rest"],   // Mon..Sun: rest | light | moderate | hard
  rate_pct: 0.65, weeks: 8,                        // % bodyweight per week, phase length
  vegan: false, fat_g_per_lb: 0.39,                // plant-based uplift; optional fat preference (floor 0.3)
  meals: 5, wake: "06:30", train: "17:30", sleep: "23:00"
});
// plan.days.hard.calories, plan.weekly.average_calories, plan.phase.end_weight_lb, plan.meal_templates.training.rows ...
const checkin = Diet.adjust({ goal: "fat_loss", unit: "lb", weights: [155, 154.6, 154.4, 153.9],
  target_rate_pct: 0.65, current_calories: 1890, weeks_planned: 8, start_weight: 155, fat_g_per_lb: 0.39 });
// checkin.observed_lb_per_week, checkin.kcal_per_day_change, checkin.new_calories, checkin.verdict ...

Send JSON.stringify(plan) as facts for the plan lane; for the adjust lane send JSON.stringify({ checkin, plan: summary }) where summary is the plan's targets (goal, weight, rate, weeks, calories by day type — see Recon.planSummary in /recon.js).

The plan request body, in full

Every field app.js sends, with facts abbreviated:

{
  "task": "plan",
  "about": "34-year-old woman, lifting four years, three hard sessions a week after work and two moderate days. Cook at home; do not track on weekends. Last cut ran 20 weeks and cost strength.",
  "draft": "",
  "facts": "<JSON string from Diet.plan(spec) - see below>"
}

The adjust request body, in full

{
  "task": "adjust",
  "about": "(same as the plan)",
  "draft": "",
  "log": "Hit the numbers on weekdays; two untracked restaurant dinners each weekend; travelled in week 3.",
  "facts": "<JSON string: {\"checkin\": Diet.adjust(spec), \"plan\": summary}>"
}

Worked example: the adjust lane

curl -s -X POST https://api.skillsafe.ai/v1/app-api/estimate \
  -H "Authorization: Bearer $SKILLSAFE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"task": "adjust", "about": "(same as the plan)", "draft": "", "log": "Hit the numbers on weekdays; two untracked restaurant dinners each weekend; travelled in week 3.", "facts": "<JSON string: {\"checkin\": Diet.adjust(spec), \"plan\": summary}>"}'

5. Run it, then poll

POST /run returns {job_id}; GET /jobs/{job_id} until status is succeeded or failed. Send an Idempotency-Key header derived from the input and the lane so a retry never double-bills. The reply's output.output is the JSON text described in the contract below; charged_credits is the actual cost.

curl -s -X POST https://api.skillsafe.ai/v1/app-api/run \
  -H "Authorization: Bearer $SKILLSAFE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"task": "plan", "about": "34-year-old woman, lifting four years, three hard sessions a week after work and two moderate days. Cook at home; do not track on weekends. Last cut ran 20 weeks and cost strength.", "draft": "", "facts": "<JSON string from Diet.plan(spec) - see below>"}'
curl -s -X GET https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID \
  -H "Authorization: Bearer $SKILLSAFE_TOKEN" \
  -H "Content-Type: application/json"

6. Or stream it

POST /run-stream is server-sent events: job, then tick heartbeats, then done with the full output. Browsers receive ticks rather than text deltas, so build progress on elapsed time and parse the output from done.

curl -N -s -X POST https://api.skillsafe.ai/v1/app-api/run-stream \
  -H "Authorization: Bearer $SKILLSAFE_TOKEN" -H "Accept: text/event-stream" \
  -H "Content-Type: application/json" -d '{"task": "plan", "about": "34-year-old woman, lifting four years, three hard sessions a week after work and two moderate days. Cook at home; do not track on weekends. Last cut ran 20 weeks and cost strength.", "draft": "", "facts": "<JSON string from Diet.plan(spec) - see below>"}'
# events: job (the job id), tick (heartbeat), done (the full output). Browsers receive ticks, not deltas.

The output contract

One JSON object. Common keys on every reply: lane, title, headline, verdict, summary, notes_on_input[], risks[], next_steps[], draft_check[] {claim, status: agrees|disagrees|unverifiable, note}.

LaneVerdictBody
planready · ready_with_changes · not_advisableplan_text (paragraphs, every number from facts) · goal_fit {rating: good|questionable|wrong_phase, why} · priorities[] {level: calories|macros|timing|food_composition|supplements, note} · food_guidance[] · pitfalls[] {pitfall, applies: yes|no|unclear, note} (ten, each judged)
adjusthold · cut · add · end_phase · waitadjustment_text · trend_reading · macro_change {macro: fat|carbs|none, direction: cut|add|none, why} · phase_status {status: continue|complete|stop_early, why} · adherence_flags[]

The web page re-reads every number in plan_text / adjustment_text / trend_reading against the facts it sent (Recon.reconcile in /recon.js) and shows the disagreements; do the same in a pipeline before trusting a figure.

Derived from the agent skill @borisghidaglia/rp-diet, an index of The Renaissance Diet 2.0. Not medical advice.