← Melt Pool
API

Drive Melt Pool from your own code

The one metered task, diagnose, is available over HTTP. It takes 1 to 5 jar candles - the page's shelf lines (width, wicks, wax left, usual burn, the full-pool time, whether the jar is too wide and the burns too short or too long, the hours and evenings left above the stop line as an estimate, what was seen and the verdict) and the person's note on each candle - and returns a headline, 1 card per candle (the verdict, a cause for every symptom, the root cause, the fix at that root, the patch to skip and the next burn) and a closing line.

The division of labour first: the page does the arithmetic and a fixed rule decides each verdict; the model walks a fixed list of causes per symptom, reads the notes and writes the fixes. After a run the page checks that every verdict follows the rule, that every symptom has a cause the page's order allows, that each root cause follows from the causes given, that every number in the prose is on the shelf or in the notes, that word limits hold, and that no ids leak into the prose.

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":"melt-pool"}), 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, shelf, notes) 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: a 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: diagnose (the one task is diagnose); a missing or different one is still answered as diagnose. Full pool: 1 h per 2.5 cm of width per wick, rounded to the half hour, at least 1 h and at most 4 h; more than 10 cm of width per wick is too wide; a usual burn under the full-pool time is a short burn, over 4 h a long burn. The verdict rule per candle, checked in order: 1.3 cm of wax or less, retire; anything seen, too wide, short burns or long burns, fix-first; otherwise keep-burning.

FieldRequiredWhat it holds
taskyesAlways diagnose.
shelfyesThe page's lines: a house numbers: line, then 1 line per candle such as candle 1 | Fig jar | 8 cm wide | 1 wick | 6 cm of wax left | usual burn 1 h | full pool needs 3 h | too wide: no | short burns: yes | long burns: no | about 42 h and 14 evenings above the stop line (estimate) | seen: tunnel | verdict: fix-first.
notesyes1 line per candle, candle 1 | text or candle 1 | none.
retry_notenoOnly on the page's single reformat retry.

Symptoms are tunnel, soot, big-flame, drowning and mushroom. Each has a fixed list of causes, walked in order, first that holds: tunnel - too-wide, short-burns, draught, unclear; soot and big-flame - long-wick, draught, long-burns, unclear; drowning - short-wick, long-burns, unclear; mushroom - long-burns, long-wick, unclear. Shelf flags decide too-wide, short-burns and long-burns; the note decides the rest.

The reply is one JSON object:

{"task": "diagnose", "headline": "...", "candles": [{"candle": "...", "verdict": "fix-first", "causes": [{"symptom": "tunnel", "cause": "short-burns", "why": "..."}], "root_cause": "short-burns", "root_fix": "...", "skip": "...", "next_burn": "..."}, ...], "closing": "..."}
FieldWhat it holds
headlineHow many candles have each verdict and the most common root cause, at most 18 words.
candles1 per candle in shelf order: candle (the name), verdict (copied), causes (1 per symptom, only for fix-first; each with symptom, cause and why, at most 16 words), root_cause (stop-line, none, a cause id or unclear), root_fix (at most 30 words), skip (the patch to skip, at most 16 words, empty for keep-burning) and next_burn (at most 22 words).
closingOne light next step, at most 20 words.

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

Worked examples - every verdict

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

Mantel of 3, one tunnelling - fix-first, keep-burning and retire

Input:

{
 "task": "diagnose",
 "shelf": "house numbers: full pool = 1 h per 2.5 cm of width per wick, at least 1 h, at most 4 h | stop line = 1.3 cm of wax left | trim the wick to 6 mm\ncandle 1 | Fig and cedar jar | 8 cm wide | 1 wick | 6 cm of wax left | usual burn 1 h | full pool needs 3 h | too wide: no | short burns: yes | long burns: no | about 42 h and 14 evenings above the stop line (estimate) | seen: tunnel | verdict: fix-first\ncandle 2 | Three-wick tin | 12 cm wide | 3 wicks | 4 cm of wax left | usual burn 2 h | full pool needs 1.5 h | too wide: no | short burns: no | long burns: no | about 18 h and 12 evenings above the stop line (estimate) | seen: none | verdict: keep-burning\ncandle 3 | Old pillar jar | 7 cm wide | 1 wick | 1 cm of wax left | usual burn 3 h | full pool needs 3 h | too wide: no | short burns: no | long burns: no | at or below the stop line | seen: soot | verdict: retire",
 "notes": "candle 1 | lit for an hour after work each time, wax stays stuck to the sides\ncandle 2 | melts evenly right to the edge\ncandle 3 | glass has gone black near the top"
}

Reply:

{
 "task": "diagnose",
 "headline": "1 candle needs fixing, 1 should retire, 1 is burning well; too short a burn is the cause.",
 "candles": [
  {
   "candle": "Fig and cedar jar",
   "verdict": "fix-first",
   "causes": [
    {
     "symptom": "tunnel",
     "cause": "short-burns",
     "why": "Usual burn is 1 h, short of the 3 h full pool needs."
    }
   ],
   "root_cause": "short-burns",
   "root_fix": "Let it burn for the full 3 h pool time each time, until the wax melts all the way to the edge.",
   "skip": "Skip adding more short sessions instead of letting it run the full pool time.",
   "next_burn": "Next time, let it burn for the full 3 h pool time before putting it out."
  },
  {
   "candle": "Three-wick tin",
   "verdict": "keep-burning",
   "causes": [],
   "root_cause": "none",
   "root_fix": "Nothing here needs fixing; it is burning the way it should.",
   "skip": "",
   "next_burn": "Next time, light it and let it burn as usual; no changes are needed."
  },
  {
   "candle": "Old pillar jar",
   "verdict": "retire",
   "causes": [],
   "root_cause": "stop-line",
   "root_fix": "Stop lighting it; the wax has reached the stop line, so the glass could overheat, and you can clean out the empty jar to use it again.",
   "skip": "Skip relighting it to squeeze out whatever wax remains at the bottom.",
   "next_burn": "Do not light it again; clean the jar and set it aside."
  }
 ],
 "closing": "Next time you light Fig and cedar jar, let it run the full 3 h pool time."
}

By the window, soot - a jar too wide, causes from a note

Input:

{
 "task": "diagnose",
 "shelf": "house numbers: full pool = 1 h per 2.5 cm of width per wick, at least 1 h, at most 4 h | stop line = 1.3 cm of wax left | trim the wick to 6 mm\ncandle 1 | Big bowl candle | 14 cm wide | 1 wick | 7 cm of wax left | usual burn 4 h | full pool needs 4 h | too wide: yes | short burns: no | long burns: no | about 157 h and 39 evenings above the stop line (estimate) | seen: tunnel | verdict: fix-first\ncandle 2 | Vanilla tumbler | 8 cm wide | 1 wick | 5 cm of wax left | usual burn 3 h | full pool needs 3 h | too wide: no | short burns: no | long burns: no | about 33 h and 11 evenings above the stop line (estimate) | seen: soot, big-flame | verdict: fix-first",
 "notes": "candle 1 | deep hole in the middle, thick wall of wax all round\ncandle 2 | sits on the sill by the open window, never trimmed the wick"
}

Reply:

{
 "task": "diagnose",
 "headline": "2 candles need fixing, and being too wide is the main cause so far.",
 "candles": [
  {
   "candle": "Big bowl candle",
   "verdict": "fix-first",
   "causes": [
    {
     "symptom": "tunnel",
     "cause": "too-wide",
     "why": "Shelf marks it too wide; 14 cm across for 1 wick is a lot to melt."
    }
   ],
   "root_cause": "too-wide",
   "root_fix": "Its 1 wick can't melt 14 cm in 4 h; a foil collar open at the center can help the edges soften while you watch it, though tunnelling may continue.",
   "skip": "Skip letting it run well past 4 h just hoping the edge finally melts.",
   "next_burn": "Next time, add a foil collar open in the middle and still put it out by 4 h."
  },
  {
   "candle": "Vanilla tumbler",
   "verdict": "fix-first",
   "causes": [
    {
     "symptom": "soot",
     "cause": "long-wick",
     "why": "The note says the wick is never trimmed, so it stays long."
    },
    {
     "symptom": "big-flame",
     "cause": "long-wick",
     "why": "It is never trimmed, so the wick burns taller than it should, feeding a bigger flame."
    }
   ],
   "root_cause": "long-wick",
   "root_fix": "Trim the wick down to 6 mm before each time you light it, so the flame stays a sensible size.",
   "skip": "Skip wiping soot off the glass while the wick keeps its current length.",
   "next_burn": "Next time, trim the wick to 6 mm before lighting it."
  }
 ],
 "closing": "Next time you light Big bowl candle, try a foil collar and stop it by 4 h."
}

Long evenings, drowning wick - long burns and a tie for the root

Input:

{
 "task": "diagnose",
 "shelf": "house numbers: full pool = 1 h per 2.5 cm of width per wick, at least 1 h, at most 4 h | stop line = 1.3 cm of wax left | trim the wick to 6 mm\ncandle 1 | Amber travel tin | 6 cm wide | 1 wick | 3 cm of wax left | usual burn 5 h | full pool needs 2.5 h | too wide: no | short burns: no | long burns: yes | about 8 h and 3 evenings above the stop line (estimate) | seen: drowning, mushroom | verdict: fix-first\ncandle 2 | Sea salt jar | 9 cm wide | 2 wicks | 8 cm of wax left | usual burn 2 h | full pool needs 2 h | too wide: no | short burns: no | long burns: no | about 38 h and 19 evenings above the stop line (estimate) | seen: none | verdict: keep-burning",
 "notes": "candle 1 | I trim the wick right down to the wax before lighting\ncandle 2 | none"
}

Reply:

{
 "task": "diagnose",
 "headline": "1 candle needs fixing and 1 is burning well; a wick trimmed too short is the cause.",
 "candles": [
  {
   "candle": "Amber travel tin",
   "verdict": "fix-first",
   "causes": [
    {
     "symptom": "drowning",
     "cause": "short-wick",
     "why": "The note says the wick is trimmed right down to the wax before lighting."
    },
    {
     "symptom": "mushroom",
     "cause": "long-burns",
     "why": "Usual burn is 5 h, well beyond the 4 h house cap."
    }
   ],
   "root_cause": "short-wick",
   "root_fix": "Stop trimming the wick down to the wax; leaving about 6 mm may help, and if drowning continues, this wick could be too small for a 6 cm jar.",
   "skip": "Skip repeatedly relighting a wick that has sunk beneath the wax surface.",
   "next_burn": "Next time, trim the wick to about 6 mm, not down to the wax, before lighting."
  },
  {
   "candle": "Sea salt jar",
   "verdict": "keep-burning",
   "causes": [],
   "root_cause": "none",
   "root_fix": "There is nothing to fix here; it is burning the way it should.",
   "skip": "",
   "next_burn": "Next time, light it and let it burn as you have been; nothing needs to change."
  }
 ],
 "closing": "Next time you light Amber travel tin, trim the wick to about 6 mm, not down to the wax."
}

New candle, not sure why - an unclear cause, a fix with nothing seen

Input:

{
 "task": "diagnose",
 "shelf": "house numbers: full pool = 1 h per 2.5 cm of width per wick, at least 1 h, at most 4 h | stop line = 1.3 cm of wax left | trim the wick to 6 mm\ncandle 1 | Birthday gift candle | 10 cm wide | 1 wick | 9 cm of wax left | usual burn 4 h | full pool needs 4 h | too wide: no | short burns: no | long burns: no | about 108 h and 27 evenings above the stop line (estimate) | seen: big-flame | verdict: fix-first\ncandle 2 | Small votive jar | 5 cm wide | 1 wick | 4 cm of wax left | usual burn 1 h | full pool needs 2 h | too wide: no | short burns: yes | long burns: no | about 9 h and 4 evenings above the stop line (estimate) | seen: none | verdict: fix-first",
 "notes": "candle 1 | none\ncandle 2 | only lit while cooking dinner"
}

Reply:

{
 "task": "diagnose",
 "headline": "2 candles need fixing; burning for too short a spell is the main cause so far.",
 "candles": [
  {
   "candle": "Birthday gift candle",
   "verdict": "fix-first",
   "causes": [
    {
     "symptom": "big-flame",
     "cause": "unclear",
     "why": "Nothing on the shelf or in the note yet points to why the flame is large."
    }
   ],
   "root_cause": "unclear",
   "root_fix": "Hold off on any changes for now; watch closely on the next burn for whatever sign points to what is causing the large flame.",
   "skip": "Skip jumping to a fix before you have had a chance to watch it burn again.",
   "next_burn": "Next time, notice how long the wick is before lighting, whether air moves near it, and how long it burns."
  },
  {
   "candle": "Small votive jar",
   "verdict": "fix-first",
   "causes": [],
   "root_cause": "short-burns",
   "root_fix": "Let Small votive jar run for its full 2 h pool time each lighting, rather than stopping after 1 h.",
   "skip": "Skip squeezing in another quick burn at dinner time for the same short stretch.",
   "next_burn": "Next time, let it burn for the full 2 h pool time instead of stopping after 1 h."
  }
 ],
 "closing": "Next time you light Birthday gift candle, watch closely for what might be causing the large flame."
}

Build the input yourself

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

const K = MeltKit;
const built = K.buildInput(K.form({ candles: [
  { name: "Fig jar", width: 8, left: 6, wicks: 1, burn: 1, seen: { tunnel: true }, notes: "wax stays on the sides" },
  { name: "Tin", width: 12, left: 4, wicks: 3, burn: 2, seen: {}, notes: "" }] }));
built.input.shelf;                      // the page's lines: numbers, flags and the verdict per candle
const wire = K.toWire(built.input);     // POST this to /estimate and /run
// then: const reply = K.normalize(K.parseReply(job.output.output));
// built.facts.list.map(c => c.verdict) // the verdicts the page's rule gives
// K.check(reply, built).notes          // the page's re-check of the reply
// K.md(built, reply, "my shelf")       // 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://melt-pool.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":"melt-pool"}' | 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": "diagnose", "shelf": "house numbers: full pool = 1 h per 2.5 cm of width per wick, ...\ncandle 1 | Birthday gift candle | 10 cm wide | 1 wick | ... | seen: big-flame | verdict: fix-first\n...", "notes": "candle 1 | none\ncandle 2 | only lit while cooking dinner"}
# - 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 melt-pool:<task>:<inputHash>:a<attempt> (the one task is diagnose), 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="melt-pool:diagnose:$(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: melt-pool:diagnose:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json