← Thaw and Tell
API

Drive Thaw and Tell from your own code

The one metered task, read-thaw, is available over HTTP. It takes a short log of cold-finger colour-change episodes (one line each, numbered by the page), the page's own tally of every line, its counts, when it began, the warning signs and one-hand episodes it found, and its verdict. It returns a plain reading of the pattern, the settings that set episodes off (grouped, with episode ids), what the log says helped, three gentle warm-up moves, a note to take to a doctor when the verdict calls for one, and a closing line.

The division of labour first: the page reads each line, finds the warning signs and picks the verdict by a fixed order; the model only writes the words. After a run the page checks that the reply copied the verdict, grouped the episodes the way the page does, quoted each setting and each helping step word for word from the cited line, gave three moves, added a doctor's note only when called for, named every warning-sign episode, and added no numbers, number words, pronouns, diagnoses, medicines or risky warming tips. Not medical advice.

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": "..." } }

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

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required field (task, episodes, tally, verdict and the rest of the field table) 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: the reading is metered and needs a personal sign-in.
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, the input and the verdicts

Every run sends task: read-thaw (the one task is read-thaw); a missing or different one is still answered as read-thaw. The page recognises six settings, most specific first: cold things held, wet hands, vibration, stress, warm-to-cold change, cold air. The verdict, first match wins: any warning sign (a sore, broken or darkened skin, or two hours or more) - see-a-doctor-soon; else one hand only, or it began at age 30 or later - mention-it; else warm-up-plan.

FieldRequiredWhat it holds
taskyesAlways read-thaw.
episodesyes2 to 12 lines such as E1. Waiting at the bus stop in the wind, ... (each at most 220 characters).
episode_countyesHow many lines, as a string.
tallyyesOne line per episode: E1 - colours: white, blue - minutes: 15 - fingers: index, middle - hand: not said - settings: cold air.
summaryyesThe page's counts: pale, blue and red episodes, median and longest minutes.
beganyesbefore age 30, at age 30 or later or not sure.
warning_signs, one_handyesEpisode ids with the words that triggered a warning sign, and the one-hand episodes; (none) when empty.
verdictyesThe page's verdict id.
retry_notenoOnly on the page's single reformat retry.

The reply is one JSON object:

{"verdict": "warm-up-plan", "reading": "...", "triggers": [{"setting": "...", "episodes": ["E2", "E3"], "note": "..."}], "helped": [{"what": "...", "episodes": ["E1"]}], "plan": ["...", "...", "..."], "doctor_note": "", "closing": "..."}
FieldWhat it holds
verdictCopied from the input.
readingThe pattern in plain words, with the episode count in digits (at most 35 words).
triggers1 to 3 groups, each episode under the first setting in its tally line, most episodes first: setting (at most 6 words copied from a cited line), episodes, note (at most 20 words).
helpedWhat the log's own lines say helped: what (at most 8 words copied from the line) and episodes; [] when no line says.
planExactly 3 gentle warm-up moves (at most 24 words each), aimed at the settings in order.
doctor_note, closingEmpty for warm-up-plan; otherwise what to tell a doctor, naming the reasons (at most 45 words). Then a closing line (16).

The page then checks the reply (ThawKit.check) and lists any disagreement under "The page's check".

Worked examples - every verdict

These are the three free samples on the page: the exact input the page sends and the reply that replays for free.

The bus-stop commute - five episodes, began before 30: warm-up-plan

Input:

{
 "task": "read-thaw",
 "episodes": "E1. Waiting at the bus stop in the wind, index and middle fingers went white, then blue, about 15 minutes. Gloves back on and hands in my armpits brought them round\nE2. Took a bag of peas out of the freezer, ring and little fingers white for 5 min\nE3. Holding an iced coffee on the walk to work, white then red, 10 minutes\nE4. Washing up in cold water, all fingers pale for 8 minutes. Running them under lukewarm water helped\nE5. Tense meeting in a cold office, index finger white then red, 12 minutes",
 "episode_count": "5",
 "tally": "E1 - colours: white, blue - minutes: 15 - fingers: index, middle - hand: not said - settings: cold air\nE2 - colours: white - minutes: 5 - fingers: ring, little - hand: not said - settings: cold things held\nE3 - colours: white, red - minutes: 10 - fingers: not said - hand: not said - settings: cold things held, cold air\nE4 - colours: white - minutes: 8 - fingers: all five - hand: not said - settings: cold things held, wet hands\nE5 - colours: white, red - minutes: 12 - fingers: index - hand: not said - settings: stress, cold air",
 "summary": "pale in 5 of 5; blue in 1 of 5; red in 2 of 5; median 10 minutes, longest 15 minutes (5 of 5 timed)",
 "began": "before age 30",
 "warning_signs": "(none)",
 "one_hand": "(none)",
 "verdict": "warm-up-plan"
}

Reply:

{
 "verdict": "warm-up-plan",
 "reading": "Across 5 episodes, all started pale, 1 turned blue and 2 turned red, lasting a median of 10 minutes and a longest of 15 minutes.",
 "triggers": [
  {
   "setting": "Holding an iced coffee",
   "episodes": [
    "E2",
    "E3",
    "E4"
   ],
   "note": "While holding something cold - peas, iced coffee, washing water - fingers went white for 5, 8 or 10 minutes."
  },
  {
   "setting": "the bus stop in the wind",
   "episodes": [
    "E1"
   ],
   "note": "At the bus stop in the wind, index and middle fingers went white then blue for 15 minutes."
  },
  {
   "setting": "Tense meeting in a cold office",
   "episodes": [
    "E5"
   ],
   "note": "During a tense meeting in a cold office, the index finger went white then red for 12 minutes."
  }
 ],
 "helped": [
  {
   "what": "Gloves back on and hands in my armpits",
   "episodes": [
    "E1"
   ]
  },
  {
   "what": "Running them under lukewarm water helped",
   "episodes": [
    "E4"
   ]
  }
 ],
 "plan": [
  "When you need to hold something cold or wash up, wear gloves or use a cloth, and warm fingers in lukewarm water afterward.",
  "Before going out into cold air or wind, put on gloves and warm hands first, tucking them under your arms if they turn pale.",
  "In a tense moment in a cold room, try slow, steady breathing and keep a layer on your hands to stay warm."
 ],
 "doctor_note": "",
 "closing": "This plan may help your fingers feel warmer in these situations."
}

The frosty cycle and the drill - one hand only, began at 30 or later: mention-it

Input:

{
 "task": "read-thaw",
 "episodes": "E1. Cycling to work on a frosty morning, fingers white then blue on the handlebars, 25 minutes\nE2. Only my right hand went white after using the hammer drill, then throbbing red, 15 min\nE3. Supermarket chiller aisle, middle and ring fingers pale, 6 minutes\nE4. Rainy walk home, all fingers white, then blue, 20 minutes. Holding a warm mug of tea helped",
 "episode_count": "4",
 "tally": "E1 - colours: white, blue - minutes: 25 - fingers: not said - hand: not said - settings: cold things held, cold air\nE2 - colours: white, red - minutes: 15 - fingers: not said - hand: one hand only - settings: vibration\nE3 - colours: white - minutes: 6 - fingers: middle, ring - hand: not said - settings: warm-to-cold change\nE4 - colours: white, blue - minutes: 20 - fingers: all five - hand: not said - settings: wet hands, cold air",
 "summary": "pale in 4 of 4; blue in 2 of 4; red in 1 of 4; median 17.5 minutes, longest 25 minutes (4 of 4 timed)",
 "began": "at age 30 or later",
 "warning_signs": "(none)",
 "one_hand": "E2",
 "verdict": "mention-it"
}

Reply:

{
 "verdict": "mention-it",
 "reading": "Across the 4 episodes, pale showed every time, blue in 2 of 4, and red in 1 of 4. Minutes had a median of 17.5 and a longest of 25.",
 "triggers": [
  {
   "setting": "on the handlebars",
   "episodes": [
    "E1"
   ],
   "note": "Cycling and holding the handlebars, fingers went white then blue for 25 minutes."
  },
  {
   "setting": "using the hammer drill",
   "episodes": [
    "E2"
   ],
   "note": "Using the hammer drill, your right hand went white then red for 15 minutes."
  },
  {
   "setting": "Supermarket chiller aisle",
   "episodes": [
    "E3"
   ],
   "note": "In the supermarket chiller aisle, your middle and ring fingers went pale for 6 minutes."
  }
 ],
 "helped": [
  {
   "what": "Holding a warm mug of tea",
   "episodes": [
    "E4"
   ]
  }
 ],
 "plan": [
  "Warm your hands before you set off, and put a layer between your hands and the handlebars, such as gloves.",
  "Before using vibrating tools, warm your hands first, and pause to move your arms if your fingers turn pale.",
  "When moving into a cold aisle, warm your hands beforehand and keep a layer over your fingers."
 ],
 "doctor_note": "Consider mentioning this at a routine appointment: E2 affected one hand only, and you say your colour changes began at age 30 or later.",
 "closing": "It's worth mentioning these colour changes next time you see your doctor."
}

The outdoor market stall - a slow-healing sore and a two-hour episode: see-a-doctor-soon

Input:

{
 "task": "read-thaw",
 "episodes": "E1. Working the outdoor market stall, thumb and index finger white then blue, 40 minutes\nE2. Unloading frozen boxes at the stall, all fingers pale, 30 minutes. Warm water and swinging my arms helped\nE3. A small sore on my index fingertip that has been slow to heal\nE4. Windy evening packing up the stall, white, blue then red, 2 hours before the colour came back",
 "episode_count": "4",
 "tally": "E1 - colours: white, blue - minutes: 40 - fingers: thumb, index - hand: not said - settings: cold air\nE2 - colours: white - minutes: 30 - fingers: all five - hand: not said - settings: cold things held\nE3 - colours: not said - minutes: not said - fingers: index - hand: not said - settings: none recognised\nE4 - colours: white, blue, red - minutes: 120 - fingers: not said - hand: not said - settings: cold air",
 "summary": "pale in 3 of 4; blue in 2 of 4; red in 1 of 4; median 40 minutes, longest 120 minutes (3 of 4 timed)",
 "began": "before age 30",
 "warning_signs": "E3: \"sore\"\nE4: \"2 hours\"",
 "one_hand": "(none)",
 "verdict": "see-a-doctor-soon"
}

Reply:

{
 "verdict": "see-a-doctor-soon",
 "reading": "Across 4 episodes, most turned pale, blue showed in 2, and red in 1; minutes were timed in 3, with a median of 40 and a longest of 120.",
 "triggers": [
  {
   "setting": "Working the outdoor market stall",
   "episodes": [
    "E1",
    "E4"
   ],
   "note": "Both happened at the stall outdoors, colour turning white then blue."
  },
  {
   "setting": "Unloading frozen boxes at the stall",
   "episodes": [
    "E2"
   ],
   "note": "All fingers turned pale while unloading frozen boxes, lasting 30 minutes."
  }
 ],
 "helped": [
  {
   "what": "Warm water and swinging my arms",
   "episodes": [
    "E2"
   ]
  }
 ],
 "plan": [
  "Before heading out to the stall, warm your body and hands first, and wear gloves or a windproof layer for the cold air.",
  "When handling frozen boxes, wear gloves to keep a layer between your hands and the cold, and keep them dry.",
  "After time in the wind at the stall, warm your hands again before continuing, rather than waiting until they feel numb."
 ],
 "doctor_note": "Please book a doctor soon. Bring up E3, where the log notes \"sore,\" and E4, where it says \"2 hours.\"",
 "closing": "This log is worth bringing to a doctor soon - please don't wait."
}

Build the input yourself

Load the engine in node (global.window = global; require("./thawkit.js")), then:

const K = ThawKit;
const built = K.buildInput(K.form({ log: "Walking the dog in the wind, fingers white then red, 10 minutes\nHolding a cold can, index finger pale, 4 min", began: "before-30" }));
built.facts.count, built.facts.verdict  // the page's episode count and verdict
const wire = K.toWire(built.input);     // POST this to /estimate and /run
// then: const reply = K.normalize(K.parseReply(job.output.output));
// K.check(reply, built).notes          // the page's re-check of the reply
// K.md(built, reply, "my log")         // the reading as Markdown

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://thaw-and-tell.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":"thaw-and-tell"}' | 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-thaw", "episodes": "E1. Cycling to work on a frosty morning, ...\nE2. ...", "episode_count": "4", "tally": "E1 - colours: white, blue - minutes: 25 - ...", "summary": "pale in 4 of 4; ...", "began": "at age 30 or later", "warning_signs": "(none)", "one_hand": "E2", "verdict": "mention-it"}
# - see the field table
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 thaw-and-tell:<task>:<inputHash>:a<attempt> (the one task is read-thaw), 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="thaw-and-tell:read-thaw:$(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: thaw-and-tell:read-thaw:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json