← Three Tissue
Three Tissue API
Tokens

Drive Three Tissue from your own code

Both metered lanes are available over HTTP. A natural use is a training log or a clinic portal that already holds DEXA results: your code sends the scans with the page's figures and flags, and gets back a plain-language read, or a review of the person's plan for the next scan with every action graded by strength of evidence.

The division of labour first: the page reads the numbers and flags; the model explains and grades; the page checks the model against its own numbers. Parsing the pasted report, unit conversion, ALMI, the lean and fat mass indexes, the android / gynoid ratio, the left-right limb gaps, the change since the last scan and every flag (T-score and Z-score ranges, ALMI against the EWGSOP2 cutoff, lean share of weight lost, visceral fat, limb gaps, noise) come from scankit.js, which runs in node as well as in the browser. Its checkRead and checkPlan functions then hold each reply to the scans: each tissue's trend against the measured change, each region against the measured left-right gap, every clinician flag carried through, and a plan verdict that matches its own count of changes and drops. Nothing here is a diagnosis.

Get a token on the token page (step 2 below also shows how to mint a guest token in code).

Base URL and the envelope

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:

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

The token is minted for this app (the guest call takes {"slug":"three-tissue"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare: never wrap it as {"input": {…}}.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or task 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: both lanes are metered and need a personal token.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field comes first

Every run sends task, one of two lanes over the same scans. Run read first when you can: plan answers its findings through read_findings.

taskReturns
readtitle, verdict (clear / watch / ask), summary; tissues [{tissue, trend, read}] - exactly bone, lean, fat, trend up / down / flat / single; regions [{region, status, note}] - exactly arms, legs, trunk, status balanced / lopsided / no-data; findings [{topic, level, text}] (1-5, topic muscle, fat, visceral, bone, balance or noise; level ask, watch or note); questions (1-4 strings).
plantitle, verdict (keep / adjust / rethink), summary; actions [{action, stance, evidence, why}] (3-7; stance keep, change, add or drop; evidence strong, moderate, low or very-low); targets [{metric, direction, amount, note}] (estimates); next_scan {weeks, why}; cautions (0-3).
VerdictMeaning
clearThe page flagged nothing at the watch or ask level.
watchSomething worth watching: low bone mass range, lean mass a large share of weight lost, fat up while lean fell, a limb more than 10% lighter than the other, visceral fat over a rough line.
askWorth taking to a clinician: a T-score at or below -2.5, a Z-score at or below -2.0, or ALMI under the EWGSOP2 cutoff. Not a diagnosis.
keepNo action in the review is a change or a drop (additions do not count).
adjustOne or two actions are changes or drops.
rethinkThree or more actions are changes or drops.

The declared input schema carries scalars only, so object-valued fields travel JSON-encoded as strings (ScanKit.toWire does this). Only task is required by the schema.

Input fields

FieldTypeNotes
taskstringread or plan.
todaystringYYYY-MM-DD.
profilestring (JSON){sex, age, height_cm}; sex is female, male or "".
scansstring (JSON)Oldest first, at most 6: [{date, label, values}]; values in kg unless named: weight, fat_pct, fat_kg, lean_kg, bmc_kg, vat_g, vat_cm2, android_pct, gynoid_pct, ag_ratio, bmd, t_score / t_site, z_score / z_site, and regional arm_l_lean, leg_r_fat, trunk_lean and so on.
page_factsstring (JSON){latest: {alm_kg, almi, lmi, fmi, ag_ratio, arm_asym, leg_asym, share}, change, overall, noise_kg, reference}; change is the latest scan against the one before.
page_readsstring (JSON)[{level, code, area, text}]; level is ask, watch or info.
page_verdictstringThe page's own reading: clear, watch or ask.
goalstringThe person's words; clipped to 400 characters on a word boundary.
plan_textstringplan only: what they are doing until the next scan; over 1,600 characters the page keeps the start and the end and marks the cut.
read_findingsstring (JSON or "")plan only: the read's {verdict, tissues: [{tissue, trend}], regions: [lopsided region names], findings: [{topic, level, text}]}, or "" when no read was run.
retry_notestringOnly on a reformat retry.

The reply

The job's output.output is a string holding one JSON object. Parse it with ScanKit.parseReply, then normalize(reply, task), then check it: checkRead(reply, state) returns {notes, counts, regions} (the notes shown under "Checked by the page" and the balanced / lopsided / no-data counts); checkPlan(reply, state) returns {notes, expected, tally} (the verdict its stances imply and the count of each stance).

Worked examples

Replies produced against the app's prompt for the sample scans (made-up people and numbers; lists shortened; the full replies ship in example-result.js). The page replays each of them free from its sample buttons. Inputs are shown with the long fields folded.

task: read - Recomp, two scans - clear

Input:

{
 "task": "read",
 "today": "2026-10-20",
 "profile": "{\"sex\":\"female\",\"age\":34,\"height_cm\":167}",
 "scans": "[{\"date\":\"2026-06-02\",\"label\":\"2026-06-02\",\"values\":{\"android_pct\":26.4,\"arm_l_fat\":1.1,\"arm_l_lean\":2.3,\"arm_r_fat\":1.08,\"arm_r_lean\":2.38,\"...\":\"15 more\"}},\"... 1 earlier\"]",
 "page_facts": "{\"latest\":{\"alm_kg\":20,\"almi\":7.17,\"lmi\":15.5,\"fmi\":6.4,\"ag_ratio\":0.75,\"arm_asym\":{\"pct\":3.4,\"lower\":\"left\"},\"leg_asym\":{\"pct\":1.6,\"lower\":\"left\"},\"share\":{\"bone\":3.8,\"lean\":68.2,\"fat\":28}},\"change\":{\"from\":\"2026-02-10\",\"to\":\"2026-06-02\",\"days\":112,\"weight\":-0.7,\"fat_kg\":-2.1,\"lean_kg\":1.4,\"fat_pct\":-3,\"bmc_kg\":0.01,\"vat_g\":-50,\"almi\":0.3},\"...\":\"overall, noise_kg, reference\"}",
 "page_reads": "[]",
 "page_verdict": "clear",
 "goal": "Keep building muscle and get a bit leaner by summer - not chasing the scale."
}

Reply:

{
 "task": "read",
 "title": "Muscle up, fat down, bones steady",
 "verdict": "clear",
 "summary": "This pair of scans is clear - nothing here needs a clinician's attention. Lean mass rose from 41.9 kg to 43.3 kg while fat mass fell from 19.9 kg to 17.8 kg over the 112 days between scans, which fits the goal of building muscle while getting a bit leaner. Bone and the left-right balance in the arms and legs are steady. Keep up the current approach and watch for the same direction of change at the next scan.",
 "tissues": [
  {
   "tissue": "bone",
   "trend": "flat",
   "read": "Bone mineral content was 2.40 kg at the first scan and 2.41 kg at the second, a change of about 0.01 kg, which counts as steady rather than a real shift. The spine Z-score, which compares bone density with a reference population, moved from 0.4 to 0.5, an unremarkable value with no flag raised."
  },
  {
   "tissue": "lean",
   "trend": "up",
   "read": "Lean mass rose from 41.9 kg to 43.3 kg over the 112 days between scans, a gain of 1.4 kg, which is above the 1 kg noise floor, so this looks like a real increase. Arm and leg lean mass relative to height (ALMI) works out to 7.17 kg/m2 and total arm-and-leg lean mass to 20 kg, both above the 5.5 kg/m2 and 15 kg reference lines used to flag low muscle for her sex. This fits the goal of building muscle."
  },
  {
   "tissue": "fat",
   "trend": "down",
   "read": "Fat mass fell from 19.9 kg to 17.8 kg, a 2.1 kg drop, and body fat percentage dropped from 31% to 28%, both well past the noise floor, so this is a real decrease. Visceral fat, the fat around the organs in the abdomen, also fell, from 210 g to 160 g, comfortably under the rough 1,000 g line used as a watch point here."
  }
 ],
 "regions": [
  {
   "region": "arms",
   "status": "balanced",
   "note": "The left arm's lean mass is about 3% lower than the right, inside the balanced range, nothing to flag."
  },
  {
   "region": "legs",
   "status": "balanced",
   "note": "The left leg's lean mass is about 2% lower than the right, also inside the balanced range."
  },
  "..."
 ],
 "findings": [
  {
   "topic": "muscle",
   "level": "note",
   "text": "Lean mass rose 1.4 kg between scans, and arm-and-leg lean mass works out to 20 kg (an index of 7.17 kg/m2), both above the reference lines for low muscle - a good sign for the goal of building muscle."
  },
  {
   "topic": "fat",
   "level": "note",
   "text": "Fat mass and body fat percentage both dropped (19.9 to 17.8 kg; 31% to 28%), matching the goal of getting a bit leaner."
  },
  "..."
 ],
 "questions": [
  "The first scan did not break fat down by arm and leg the way the second one did - can that be requested consistently at future scans?",
  "Is a visceral fat drop from 210 g to 160 g over the 112 days between scans typical, or on the faster side?"
 ]
}

task: read - Steady cut, three scans - watch

Input:

{
 "task": "read",
 "today": "2026-10-20",
 "profile": "{\"sex\":\"male\",\"age\":45,\"height_cm\":180}",
 "scans": "[{\"date\":\"2026-08-18\",\"label\":\"2026-08-18\",\"values\":{\"android_pct\":38.5,\"arm_l_fat\":1.6,\"arm_l_lean\":3.8,\"arm_r_fat\":1.6,\"arm_r_lean\":3.9,\"...\":\"15 more\"}},\"... 2 earlier\"]",
 "page_facts": "{\"latest\":{\"alm_kg\":28.2,\"almi\":8.7,\"lmi\":18.3,\"fmi\":8.1,\"ag_ratio\":1.17,\"arm_asym\":{\"pct\":2.6,\"lower\":\"left\"},\"leg_asym\":{\"pct\":11.9,\"lower\":\"right\"},\"share\":{\"bone\":4,\"lean\":66.5,\"fat\":29.4}},\"change\":{\"from\":\"2026-05-26\",\"to\":\"2026-08-18\",\"days\":84,\"weight\":-3.6,\"fat_kg\":-1.4,\"lean_kg\":-2.2,\"fat_pct\":-0.4,\"bmc_kg\":0,\"vat_g\":-130,\"almi\":-0.28,\"lean_share_of_loss_pct\":61},\"...\":\"overall, noise_kg, reference\"}",
 "page_reads": "[{\"level\":\"watch\",\"code\":\"asym_leg\",\"area\":\"legs\",\"text\":\"The right leg carries 11.9% less lean mass than the other (over 10%, a rule of thumb). Positioning on the table can cause some of this; an old injury can show up here.\"},{\"level\":\"watch\",\"code\":\"vat_high\",\"area\":\"visceral\",\"text\":\"Visceral fat of 1,120 g is over about 1,000 g, a commonly cited rough line for high (scanner makers and studies use different ranges).\"},\"... 2 more\"]",
 "page_verdict": "watch",
 "goal": "Get under 85 kg and keep the muscle I have."
}

Reply:

{
 "task": "read",
 "title": "Fat and weight down, watch muscle loss",
 "verdict": "watch",
 "summary": "Across the three scans, weight fell from 96 kg to 89 kg, with the pace quickening over the most recent 84 days: 3.6 kg lost, 2.2 kg (61%) of it lean mass - well above the one-quarter rule of thumb often used, so the verdict here is watch rather than clear. Visceral fat is still above the commonly used 1,000 g line, at 1,120 g, and the right leg carries 11.9% less lean mass than the left. The main next step is to make sure resistance training is in place so future loss comes more from fat than muscle.",
 "tissues": [
  {
   "tissue": "bone",
   "trend": "flat",
   "read": "Bone mineral content has held steady at 3.6 kg across all three scans, and the latest total-body Z-score is -0.2, within the normal range, so there is nothing here to flag this time."
  },
  {
   "tissue": "lean",
   "trend": "down",
   "read": "Lean mass has fallen from 62.6 kg to 59.2 kg over the three scans, including a 2.2 kg drop in the most recent 84 days - 61% of the 3.6 kg lost in that period, more than the rough quarter often used as an upper line. Arm-and-leg lean mass relative to height (ALMI) is 8.7, above the page's reference cutoff of 7, so overall muscle quantity is not a concern yet, even though the share of weight loss from muscle is worth watching."
  },
  {
   "tissue": "fat",
   "trend": "down",
   "read": "Fat mass has dropped from 29.8 kg to 26.2 kg (31% to 29.4% of weight) over the three scans, continuing in the latest 84-day gap with a 1.4 kg fall. Visceral fat has also fallen, from 1,420 g to 1,120 g, though it remains above the roughly 1,000 g line often used as a watch point."
  }
 ],
 "regions": [
  {
   "region": "arms",
   "status": "balanced",
   "note": "Arm lean mass is balanced: the left arm has 2.6% less lean mass than the right, well under the usual 10% gap used as a rule of thumb."
  },
  {
   "region": "legs",
   "status": "lopsided",
   "note": "The right leg carries 11.9% less lean mass than the left, over the usual 10% gap - this can reflect which leg is dominant, positioning on the scan table, or an old injury, though none was mentioned here."
  },
  "..."
 ],
 "findings": [
  {
   "topic": "muscle",
   "level": "watch",
   "text": "Of the 3.6 kg lost in the most recent 84 days, 2.2 kg (61%) was lean mass - more than the one-quarter rule of thumb often used as an upper line, which is worth watching given the goal of keeping muscle."
  },
  {
   "topic": "visceral",
   "level": "watch",
   "text": "Visceral fat is 1,120 g, above the roughly 1,000 g line commonly used as a watch point, though it has fallen from 1,420 g at the first scan and 1,250 g at the second."
  },
  "..."
 ],
 "questions": [
  "Is the right leg's lower lean mass likely from an old injury, positioning, or just which leg I favor day to day?",
  "Given how much lean mass I lost in the last 84 days, would more protein or resistance training help keep more of it?",
  "..."
 ]
}

task: read - First scan at 61 - ask a clinician

Input:

{
 "task": "read",
 "today": "2026-10-20",
 "profile": "{\"sex\":\"female\",\"age\":61,\"height_cm\":160}",
 "scans": "[{\"date\":\"2026-09-29\",\"label\":\"2026-09-29\",\"values\":{\"android_pct\":44,\"arm_l_fat\":1.9,\"arm_l_lean\":1.75,\"arm_r_fat\":1.9,\"arm_r_lean\":1.82,\"...\":\"14 more\"}}]",
 "page_facts": "{\"latest\":{\"alm_kg\":13.92,\"almi\":5.44,\"lmi\":14.6,\"fmi\":11.2,\"ag_ratio\":0.97,\"arm_asym\":{\"pct\":3.8,\"lower\":\"left\"},\"leg_asym\":{\"pct\":1,\"lower\":\"left\"},\"share\":{\"bone\":2.8,\"lean\":55.1,\"fat\":42.1}},\"change\":\"(one scan)\",\"...\":\"overall, noise_kg, reference\"}",
 "page_reads": "[{\"level\":\"ask\",\"code\":\"t_low\",\"area\":\"bone\",\"text\":\"Lowest T-score -2.7 (Femoral neck) is at or below -2.5 - the range the WHO definition calls osteoporosis. Only a clinician can diagnose it; take the full report to one.\"},{\"level\":\"ask\",\"code\":\"almi_low\",\"area\":\"muscle\",\"text\":\"ALMI 5.44 kg/m2 is under the 5.5 kg/m2 reference cutoff for low muscle mass in women (EWGSOP2, 2019). Low muscle mass alone is not a diagnosis; a clinician also checks strength.\"},\"... 2 more\"]",
 "page_verdict": "ask",
 "goal": "Lose 8 kg before my daughter's wedding in 10 weeks."
}

Reply:

{
 "task": "read",
 "title": "First Scan Flags Bone and Muscle",
 "verdict": "ask",
 "summary": "This first scan flags two things worth a clinician's attention: a T-score of -2.7 at the femoral neck, in the range the WHO definition calls osteoporosis, and an ALMI (arm and leg lean mass relative to height) of 5.44 kg/m2, under the reference cutoff used to flag low muscle mass in women. Fat sits a bit more around the middle than the hips, but visceral fat itself is still under the usual reference line. Before making a big push toward the 8 kg, 10-week goal, it is worth bringing the full report to a clinician to go over these numbers.",
 "tissues": [
  {
   "tissue": "bone",
   "trend": "single",
   "read": "Bone mineral density is 0.612 g/cm2, with a T-score of -2.7 at the femoral neck - at or below -2.5, the range the WHO definition calls osteoporosis. That is not a diagnosis by itself; a clinician should review the full report. Bone makes up about 2.8% of total body weight here, from 1.9 kg of bone mineral."
  },
  {
   "tissue": "lean",
   "trend": "single",
   "read": "Lean mass is 37.5 kg, about 55% of body weight, with 13.92 kg of that in the arms and legs combined. That works out to an ALMI of 5.44 kg/m2, under the 5.5 kg/m2 reference cutoff used to flag low muscle mass in women - worth asking a clinician about, since they would also check strength."
  },
  {
   "tissue": "fat",
   "trend": "single",
   "read": "Fat mass is 28.6 kg, about 42% of body weight. The android-to-gynoid ratio is 0.97, above the commonly cited 0.8 line, meaning fat sits more around the middle than the hips, though visceral fat itself (890 g) is still below the rough 1,000 g reference line."
  }
 ],
 "regions": [
  {
   "region": "arms",
   "status": "balanced",
   "note": "The left arm has about 3.8% less lean mass than the right - within the usual range, so not a concern on its own."
  },
  {
   "region": "legs",
   "status": "balanced",
   "note": "The left leg has about 1% less lean mass than the right - essentially even."
  },
  "..."
 ],
 "findings": [
  {
   "topic": "bone",
   "level": "ask",
   "text": "The T-score at the femoral neck is -2.7, at or below -2.5 - the range the WHO definition calls osteoporosis. This is not a diagnosis; take the full report to a clinician to confirm and talk through next steps."
  },
  {
   "topic": "muscle",
   "level": "ask",
   "text": "ALMI is 5.44 kg/m2, under the 5.5 kg/m2 reference cutoff used to flag low muscle mass in women. A clinician would also check strength before saying more."
  },
  "..."
 ],
 "questions": [
  "What does a T-score of -2.7 at the femoral neck mean for my bone health, and what would you recommend next?",
  "My arm and leg muscle mass relative to height is below the typical reference range - would a strength check help clarify things?",
  "..."
 ]
}

task: plan - Recomp - keep

Input:

{
 "task": "plan",
 "today": "2026-10-20",
 "profile": "{\"sex\":\"female\",\"age\":34,\"height_cm\":167}",
 "scans": "[{\"date\":\"2026-06-02\",\"label\":\"2026-06-02\",\"values\":{\"android_pct\":26.4,\"arm_l_fat\":1.1,\"arm_l_lean\":2.3,\"arm_r_fat\":1.08,\"arm_r_lean\":2.38,\"...\":\"15 more\"}},\"... 1 earlier\"]",
 "page_facts": "{\"latest\":{\"alm_kg\":20,\"almi\":7.17,\"lmi\":15.5,\"fmi\":6.4,\"ag_ratio\":0.75,\"arm_asym\":{\"pct\":3.4,\"lower\":\"left\"},\"leg_asym\":{\"pct\":1.6,\"lower\":\"left\"},\"share\":{\"bone\":3.8,\"lean\":68.2,\"fat\":28}},\"change\":{\"from\":\"2026-02-10\",\"to\":\"2026-06-02\",\"days\":112,\"weight\":-0.7,\"fat_kg\":-2.1,\"lean_kg\":1.4,\"fat_pct\":-3,\"bmc_kg\":0.01,\"vat_g\":-50,\"almi\":0.3},\"...\":\"overall, noise_kg, reference\"}",
 "page_reads": "[]",
 "page_verdict": "clear",
 "goal": "Keep building muscle and get a bit leaner by summer - not chasing the scale.",
 "plan_text": "Lifting 3 times a week, full body (squat, hinge, press, pull), adding weight when I reach the top of the rep range.\nProtein about 110 g a day.\nCreatine 3-5 g daily.\n8,000-9,000 steps.\nSleep about 7.5 hours.\nA small deficit of about 200 kcal on rest days only.",
 "read_findings": "{\"verdict\":\"clear\",\"tissues\":[{\"tissue\":\"bone\",\"trend\":\"flat\"},{\"tissue\":\"lean\",\"trend\":\"up\"},{\"tissue\":\"fat\",\"trend\":\"down\"}],\"regions\":[],\"findings\":[{\"topic\":\"muscle\",\"level\":\"note\",\"text\":\"Lean mass rose 1.4 kg between scans, and arm-and-leg lean mass works out to 20 kg (an index of 7.17 kg/m2), both above the reference lines for low muscle - a good sign for the goal of building muscle.\"},\"...\"]}"
}

Reply:

{
 "task": "plan",
 "title": "Keep your current recomp plan",
 "verdict": "keep",
 "summary": "Verdict: keep. Lean mass rose 1.4 kg while fat mass fell 2.1 kg over the 112 days between scans, so the current mix of lifting, protein, creatine and a small deficit limited to rest days is already doing what it should. Nothing here needs to change before the next scan.",
 "actions": [
  {
   "action": "Keep lifting three times a week, full body (squat, hinge, press, pull), adding weight once you reach the top of the rep range.",
   "stance": "keep",
   "evidence": "strong",
   "why": "Lean mass rose 1.4 kg between scans while you trained this way - this is the habit most likely driving that gain."
  },
  {
   "action": "Keep protein at about 110 g a day.",
   "stance": "keep",
   "evidence": "moderate",
   "why": "At your latest weight of 63.5 kg that is roughly 1.7 g per kg, comfortably covering the 1.2-1.6 g per kg range linked to building and keeping lean mass."
  },
  {
   "action": "Keep the creatine at 3-5 g a day.",
   "stance": "keep",
   "evidence": "moderate",
   "why": "Creatine alongside resistance training like yours has reasonably consistent evidence for supporting muscle gains."
  },
  "..."
 ],
 "targets": [
  {
   "metric": "lean mass",
   "direction": "up",
   "amount": "about 1-2 kg",
   "note": "A pace similar to the 1.4 kg gained between the last two scans is realistic by the next one."
  },
  {
   "metric": "fat mass",
   "direction": "down",
   "amount": "about 1-2 kg",
   "note": "In line with the 2.1 kg already lost, without pushing the deficit any harder."
  }
 ],
 "next_scan": {
  "weeks": 16,
  "why": "That matches the 112-day gap between your last two scans, long enough for a real change in lean and fat mass to show past normal scan-to-scan noise."
 },
 "cautions": [
  "Keep the deficit limited to rest days as planned - stretching it to every day would push the pace faster than is useful for keeping lean mass."
 ]
}

task: plan - Steady cut - adjust

Input:

{
 "task": "plan",
 "today": "2026-10-20",
 "profile": "{\"sex\":\"male\",\"age\":45,\"height_cm\":180}",
 "scans": "[{\"date\":\"2026-08-18\",\"label\":\"2026-08-18\",\"values\":{\"android_pct\":38.5,\"arm_l_fat\":1.6,\"arm_l_lean\":3.8,\"arm_r_fat\":1.6,\"arm_r_lean\":3.9,\"...\":\"15 more\"}},\"... 2 earlier\"]",
 "page_facts": "{\"latest\":{\"alm_kg\":28.2,\"almi\":8.7,\"lmi\":18.3,\"fmi\":8.1,\"ag_ratio\":1.17,\"arm_asym\":{\"pct\":2.6,\"lower\":\"left\"},\"leg_asym\":{\"pct\":11.9,\"lower\":\"right\"},\"share\":{\"bone\":4,\"lean\":66.5,\"fat\":29.4}},\"change\":{\"from\":\"2026-05-26\",\"to\":\"2026-08-18\",\"days\":84,\"weight\":-3.6,\"fat_kg\":-1.4,\"lean_kg\":-2.2,\"fat_pct\":-0.4,\"bmc_kg\":0,\"vat_g\":-130,\"almi\":-0.28,\"lean_share_of_loss_pct\":61},\"...\":\"overall, noise_kg, reference\"}",
 "page_reads": "[{\"level\":\"watch\",\"code\":\"asym_leg\",\"area\":\"legs\",\"text\":\"The right leg carries 11.9% less lean mass than the other (over 10%, a rule of thumb). Positioning on the table can cause some of this; an old injury can show up here.\"},{\"level\":\"watch\",\"code\":\"vat_high\",\"area\":\"visceral\",\"text\":\"Visceral fat of 1,120 g is over about 1,000 g, a commonly cited rough line for high (scanner makers and studies use different ranges).\"},\"... 2 more\"]",
 "page_verdict": "watch",
 "goal": "Get under 85 kg and keep the muscle I have.",
 "plan_text": "About 1,900 kcal a day.\nRunning 5 km four mornings a week, spin class twice a week.\nProtein roughly 90 g a day.\nNo weights since my right knee surgery in 2024 - just physio band exercises once a week.\nTwo coffees, no alcohol on weekdays.",
 "read_findings": "{\"verdict\":\"watch\",\"tissues\":[{\"tissue\":\"bone\",\"trend\":\"flat\"},{\"tissue\":\"lean\",\"trend\":\"down\"},{\"tissue\":\"fat\",\"trend\":\"down\"}],\"regions\":[\"legs\"],\"findings\":[{\"topic\":\"muscle\",\"level\":\"watch\",\"text\":\"Of the 3.6 kg lost in the most recent 84 days, 2.2 kg (61%) was lean mass - more than the one-quarter rule of thumb often used as an upper line, which is worth watching given the goal of keeping muscle.\"},\"...\"]}"
}

Reply:

{
 "task": "plan",
 "title": "Keep the diet, add strength and protein",
 "verdict": "adjust",
 "summary": "This plan gets an adjust, not a rethink: the calorie target and cardio routine are doing their job, but protein and strength training haven't kept pace. In the last 84 days, 61% of the 3.6 kg lost was lean mass - well above the usual one-quarter benchmark - so raising protein and rebuilding structured leg-inclusive strength work, with sign-off for the right knee, should shift future loss back toward fat.",
 "actions": [
  {
   "action": "Keep eating around 1,900 kcal a day - the pace of loss is safe, no need to cut further.",
   "stance": "keep",
   "evidence": "strong",
   "why": "Weight has fallen about 7 kg over the last 168 days without the pace running ahead of safe limits, in line with the goal of getting under 85 kg."
  },
  {
   "action": "Keep the four weekly 5 km runs and two spin classes.",
   "stance": "keep",
   "evidence": "moderate",
   "why": "Visceral fat has dropped from 1,420 g to 1,120 g across the three scans, and this routine is likely part of why."
  },
  {
   "action": "Raise protein from about 90 g a day to roughly 105-140 g a day (about 1.2-1.6 g per kg of your current weight), spread across meals.",
   "stance": "change",
   "evidence": "moderate",
   "why": "Lean mass fell 2.2 kg in the last 84 days while protein was running on the low side for your weight; more protein alongside training helps hold onto muscle during weight loss."
  },
  "..."
 ],
 "targets": [
  {
   "metric": "weight",
   "direction": "down",
   "amount": "about 3-5 kg",
   "note": "Continuing close to the pace already seen (7 kg over the last 168 days) would put you close to or under 85 kg by the next scan."
  },
  {
   "metric": "lean mass",
   "direction": "hold",
   "amount": "little to no further loss",
   "note": "More protein and resuming structured strength work should shift future loss toward fat instead of muscle."
  },
  "..."
 ],
 "next_scan": {
  "weeks": 16,
  "why": "Long enough for the added strength work and higher protein to show up as a change in lean mass, a bit beyond the roughly 12-week gaps already used between scans."
 },
 "cautions": [
  "Get sign-off from your physio or the surgeon who did your 2024 knee procedure before adding real load to the right leg.",
  "If the right leg's lean mass gap hasn't narrowed by the next scan, mention it to that clinician along with the visceral fat reading."
 ]
}

task: plan - First scan at 61 - rethink

Input:

{
 "task": "plan",
 "today": "2026-10-20",
 "profile": "{\"sex\":\"female\",\"age\":61,\"height_cm\":160}",
 "scans": "[{\"date\":\"2026-09-29\",\"label\":\"2026-09-29\",\"values\":{\"android_pct\":44,\"arm_l_fat\":1.9,\"arm_l_lean\":1.75,\"arm_r_fat\":1.9,\"arm_r_lean\":1.82,\"...\":\"14 more\"}}]",
 "page_facts": "{\"latest\":{\"alm_kg\":13.92,\"almi\":5.44,\"lmi\":14.6,\"fmi\":11.2,\"ag_ratio\":0.97,\"arm_asym\":{\"pct\":3.8,\"lower\":\"left\"},\"leg_asym\":{\"pct\":1,\"lower\":\"left\"},\"share\":{\"bone\":2.8,\"lean\":55.1,\"fat\":42.1}},\"change\":\"(one scan)\",\"...\":\"overall, noise_kg, reference\"}",
 "page_reads": "[{\"level\":\"ask\",\"code\":\"t_low\",\"area\":\"bone\",\"text\":\"Lowest T-score -2.7 (Femoral neck) is at or below -2.5 - the range the WHO definition calls osteoporosis. Only a clinician can diagnose it; take the full report to one.\"},{\"level\":\"ask\",\"code\":\"almi_low\",\"area\":\"muscle\",\"text\":\"ALMI 5.44 kg/m2 is under the 5.5 kg/m2 reference cutoff for low muscle mass in women (EWGSOP2, 2019). Low muscle mass alone is not a diagnosis; a clinician also checks strength.\"},\"... 2 more\"]",
 "page_verdict": "ask",
 "goal": "Lose 8 kg before my daughter's wedding in 10 weeks.",
 "plan_text": "1,100 kcal a day with 16:8 fasting.\n20,000 steps every day.\nDetox tea twice a day.\nCollagen powder instead of protein shakes.\nSkipping my calcium tablet to save calories.\nNo weights - I am worried about my bones.",
 "read_findings": "{\"verdict\":\"ask\",\"tissues\":[{\"tissue\":\"bone\",\"trend\":\"single\"},{\"tissue\":\"lean\",\"trend\":\"single\"},{\"tissue\":\"fat\",\"trend\":\"single\"}],\"regions\":[],\"findings\":[{\"topic\":\"bone\",\"level\":\"ask\",\"text\":\"The T-score at the femoral neck is -2.7, at or below -2.5 - the range the WHO definition calls osteoporosis. This is not a diagnosis; take the full report to a clinician to confirm and talk through next steps.\"},\"...\"]}"
}

Reply:

{
 "task": "plan",
 "title": "This Plan Needs Significant Changes",
 "verdict": "rethink",
 "summary": "This plan needs significant changes: the calorie target sits below a safe floor, a weaker protein source has replaced a stronger one, and weight training has been dropped even though it is one of the best-supported ways to protect both muscle and bone. With a T-score of -2.7 in the range the WHO definition calls osteoporosis and arm-and-leg muscle below the reference cutoff for your height, the priorities are enough calories and protein, keeping the calcium tablet rather than skipping it, and light, supervised resistance training instead of none. The 8 kg in 10 weeks goal is also faster than a generally safe pace, so some flexibility may be needed.",
 "actions": [
  {
   "action": "Raise your daily calories to at least 1,200 kcal - you can keep the 16:8 eating window if it works for you.",
   "stance": "change",
   "evidence": "moderate",
   "why": "Your current 1,100 kcal target is below the 1,200 kcal floor usually considered safe, and eating too little makes it harder to protect lean mass that is already flagged as low for your height."
  },
  {
   "action": "Keep the 20,000 steps a day - it is solid, low-impact activity.",
   "stance": "keep",
   "evidence": "moderate",
   "why": "Regular walking supports a calorie deficit and is easy on the joints, which is a reasonable choice given your bone results."
  },
  {
   "action": "Drop the detox tea; swap it for water or a plain tea you enjoy.",
   "stance": "drop",
   "evidence": "very-low",
   "why": "Detox teas have no good evidence for fat loss or body composition change, so the twice-daily habit is not doing anything useful."
  },
  "..."
 ],
 "targets": [
  {
   "metric": "weight",
   "direction": "down",
   "amount": "about 6-8 kg",
   "note": "Keeps the pace at or under roughly 1% of your 68 kg body weight a week; getting all 8 kg by the 10-week wedding would mean moving faster than that."
  },
  {
   "metric": "lean mass",
   "direction": "hold",
   "amount": "aim for no net loss",
   "note": "Protecting what you have matters since arm-and-leg muscle is already below the reference range for your height."
  },
  "..."
 ],
 "next_scan": {
  "weeks": 16,
  "why": "Sixteen weeks is enough time for a real, noise-beating change in fat and lean mass to show up; bone density usually moves more slowly than that."
 },
 "cautions": [
  "See a clinician about the bone density and muscle results - especially the T-score of -2.7, in the osteoporosis range - before starting a big calorie cut or new strength training.",
  "Losing all 8 kg in 10 weeks would be faster than the usual safe pace for your weight; it is fine if the wedding arrives before the goal is fully met."
 ]
}

Build the input yourself

Load the script in node (global.window = {}; require("./scankit.js")), then:

const K = ScanKit;
const st = K.state({
  today: "2026-10-20", sex: "female", age: "52", height: "165 cm",
  goal: "Keep my muscle through menopause.",
  scans: "## 2026-04-01\nTotal mass: 70.2 kg\nFat mass: 25.1 kg\nLean mass: 42.6 kg\n## 2026-09-30\nTotal mass: 69.0 kg\nFat mass: 23.4 kg\nLean mass: 43.1 kg",
  plan: "Lifting twice a week, 100 g protein a day, 8,000 steps."
});
console.log(st.change, st.reads, st.verdict);
const readWire = K.toWire(K.buildInput(st, "read", { today: "2026-10-20" }).input);        // POST to /estimate and /run
// later: const read = K.normalize(K.parseReply(job.output.output), "read"); K.checkRead(read, st);
const planWire = K.toWire(K.buildInput(st, "plan", { today: "2026-10-20", read }).input);  // carries plan_text and read_findings
// later: const plan = K.normalize(K.parseReply(job2.output.output), "plan"); K.checkPlan(plan, st);

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://three-tissue.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"three-tissue"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "read", "today": "2026-10-20", "profile": "{\"sex\":\"male\",\"age\":45,\"height_cm\":180}", "scans": "[...]", "page_facts": "{...}", "page_reads": "[...]", "page_verdict": "watch", "goal": "Get under 85 kg and keep the muscle I have."}
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses three-tissue:<lane>:<inputHash>:a<attempt> (lane = read or plan), and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="three-tissue:read:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}' 

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: three-tissue:read:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json