← Long Way Round
API

Drive Long Way Round from your own code

The one metered task, build, is available over HTTP. It takes a table-top chain-reaction machine that does one dull everyday job the long way round - the job and what it needs at the end (a nudge, a drop or a tug), the parts list read line by line, the planned stages in order and the page's decisions. It returns the engineer's notes: the verdict, one note per stage (how to set it up with your own parts, what it hands on, what to watch for and one tweak), the first test, any part to fetch, a safety line and the sign-off.

The division of labour first: the page reads the parts and plans the machine; the model writes the wording. The page reads each parts line for what it can be (a row to topple, a roller, a ramp, a tube, a cup, string, a lever, a pivot, a prop or a weight), leaves out anything unsafe, chains the stages so each one's motion out is the next one's motion in, rates every hand-off, times the run, names the weakest link and any part to fetch, sets the verdict by a fixed rule and sends it all as facts. The model never changes those. Reading, planning and the drawing are free in the browser; only the notes run is metered. After the reply, the page lists anything that does not match. A chain-reaction machine is a toy; not professional 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":"long-way-round"}), 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, job, parts, machine, 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: the engineer's notes are metered and need 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 rules

Every run sends task: build (the one task is build); a missing or different one is still answered as build. All fields are strings.

FieldRequiredWhat it holds
taskyesAlways build.
jobyesthe job in your words | the job needs: ... - the dull job (up to 90 characters) and what the last stage must give it: a nudge, a drop or a tug.
partsyesOne line per thing on your list, in order: P1. the thing in your words | what the page did with it - used in stage N as ... (a row to topple, a roller, a ramp, a tube, a cup, string, a lever, a pivot, a prop or a weight), spare: ..., or left out: not safe for a table-top machine (...). A P+ line is the one part to fetch: P+. a piece of string | to fetch: not on the list, needed for stage N. Up to 12 parts lines; a line is up to 80 characters.
machineyesOne line per stage, in order: Stage N - Kind | parts: ... | starts from: ... | passes on: ... | hand-off risk: low, medium or high. The kinds are Ramp, Topple row, Roll along, Edge drop, Tunnel, Cup and string, Pull the prop and See-saw flick; stage 1 starts from your push and the last stage passes on to the job.
factsyesThe page's decisions, one a line: verdict, stages, run time (about N seconds), weakest link (a stage, or none), hand-offs (how many high, medium and low), fetch, left out as unsafe, spare parts and parts lines not read. Build it with ChainKit.buildInput.
retry_notenoOnly on a reformat retry: why the previous reply was unusable.

The hand-off risks are fixed per kind: a Ramp, a Topple row, a Roll along and an Edge drop are low; a Tunnel, a Cup and string and a Pull the prop are medium; a See-saw flick is high. Anything hot, sharp, glass or with a flame is left out of the plan.

The verdict, checked in this order: short-a-part when the parts cannot reach the job on their own and the plan needs one part that is not on the list; else fiddly when one hand-off is high risk or two or more are medium; else ready.

The reply

One JSON object, no prose around it:

{"task": "build", "verdict": "ready", "stages": [{"n": 1, "setup": "...", "handoff": "...", "watch_for": "...", "tweak": "..."}], "first_test": "...", "fetch": "", "safety": "...", "sign_off": "..."}
FieldWhat it holds
verdictshort-a-part, fiddly or ready - always the page's.
stagesExactly one object per line of machine, in the same order, each {"n": int, "setup": "...", "handoff": "...", "watch_for": "...", "tweak": "..."}. setup is the kind's standard set-up naming this stage's own parts in your words (at most 35 words, nothing added). handoff is one sentence: what moves at the end of the stage and the first thing it meets - a part of the next stage, or the job for the last one. watch_for and tweak are the kind's usual snag and one adjustment, with this stage's parts.
first_testOne sentence: test the weakest link (named as stage N) on its own and adjust it before the full run, or, when there is none, try the full run and restart from wherever it stops.
fetchFor short-a-part: one sentence saying to fetch the part exactly as facts names it, for the stage that needs it, before you set up. Otherwise an empty string.
safetyOne sentence: a steady table, small rollers or weights away from small children, nothing hot, sharp, glass or with a flame - and any part left out as unsafe stays out.
sign_offOne short sentence matching the verdict: fetch the part first (short-a-part); test the weak hand-off first, then the full run (fiddly); or ready for your push, about N seconds as facts gives it (ready).

The page reads the reply with ReplyKit.parseReply and ReplyKit.normalize, then re-checks it against the plan with ReplyKit.check and lists, under the page's check, a verdict that differs from the page's, a stage with no note, an extra or out-of-order note, a set-up that does not name its stage's parts or runs long, a hand-off that does not reach the next stage's part or the job, a first test that misses the weakest link, a missing or unneeded fetch, a safety line that does not keep the unsafe parts out, a sign-off that does not match the verdict or the run time, and any number not in your list or the plan, a promised result, an extra part, a day or time, or a he or she your list does not give.

Worked examples - every verdict

These are the free samples on the page: the exact input the page sends for each and the engineer's notes that replay for free. Together they cover every verdict - short a part, fiddly and ready to run.

Tea bag into the mug - ready

Input:

{
 "task": "build",
 "job": "drop a tea bag into my mug | the job needs: a drop",
 "parts": "P1. 12 dominoes | used in stage 1 as a row to topple\nP2. a marble | used in stage 2 as a roller\nP3. a wooden ruler | used in stage 2 as a ramp\nP4. a tea bag | used in stage 4 as a weight\nP5. 3 hardback books | used in stage 3 as a row to topple",
 "machine": "Stage 1 - Topple row | parts: P1 12 dominoes (a row to topple) | starts from: your push | passes on: a push to stage 2 | hand-off risk: low\nStage 2 - Ramp | parts: P2 a marble (a roller), P3 a wooden ruler (a ramp) | starts from: a push from stage 1 | passes on: a roll to stage 3 | hand-off risk: low\nStage 3 - Topple row | parts: P5 3 hardback books (a row to topple) | starts from: a roll from stage 2 | passes on: a push to stage 4 | hand-off risk: low\nStage 4 - Edge drop | parts: P4 a tea bag (a weight) | starts from: a push from stage 3 | passes on: a drop to the job | hand-off risk: low",
 "facts": "verdict: ready\nstages: 4\nrun time: about 7 seconds\nweakest link: none\nhand-offs: 0 high, 0 medium, 4 low\nfetch: none\nleft out as unsafe: none\nspare parts: none\nparts lines not read: none"
}

Reply:

{
 "task": "build",
 "verdict": "ready",
 "stages": [
  {
   "n": 1,
   "setup": "Stand your 12 dominoes on end in a line, each a little less than its own height from the next.",
   "handoff": "Your last domino tips over onto your marble, giving it a push.",
   "watch_for": "A gap too wide can let one domino fall without tipping the next one over.",
   "tweak": "Close up the gaps so each domino is a little less than its own height from the next."
  },
  {
   "n": 2,
   "setup": "Prop one end of your wooden ruler up a little so it slopes down towards the next part, and rest your marble at the top.",
   "handoff": "Your marble rolls down your wooden ruler and on into your hardback books.",
   "watch_for": "Your marble might run off the side of your wooden ruler.",
   "tweak": "Make the slope of your wooden ruler a little gentler so your marble rolls straight."
  },
  {
   "n": 3,
   "setup": "Stand your 3 hardback books on end in a line, each a little less than its own height from the next.",
   "handoff": "Your last hardback book tips over onto your tea bag, giving it a push.",
   "watch_for": "A gap too wide can let one hardback book fall without tipping the next one over.",
   "tweak": "Close up the gaps so each hardback book is a little less than its own height from the next."
  },
  {
   "n": 4,
   "setup": "Rest your tea bag at the very edge of the table, right where the push arrives, with your mug waiting just below the edge.",
   "handoff": "Your tea bag tips off the table edge and drops into your mug.",
   "watch_for": "The push might only slide your tea bag along the edge instead of tipping it off.",
   "tweak": "Let your tea bag overhang the edge a little more."
  }
 ],
 "first_test": "Try the full run, and if it stops partway, restart from the stage where it stopped.",
 "fetch": "",
 "safety": "Keep it on a steady table, keep your marble and tea bag away from small children, and use nothing hot, sharp, glass or with a flame.",
 "sign_off": "Give it your push - it's ready to go, about 7 seconds start to finish."
}

Ring the desk bell - fiddly

Input:

{
 "task": "build",
 "job": "ring the little bell on my desk | the job needs: a nudge",
 "parts": "P1. a toy car | used in stage 2 as a roller\nP2. 8 dominoes | used in stage 1 as a row to topple\nP3. a wooden spoon | used in stage 4 as a lever\nP4. a pencil | used in stage 4 as a pivot\nP5. 5 paperback books | used in stage 3 as a row to topple\nP6. a candle | left out: not safe for a table-top machine (candle)\nP7. a banana | spare: not a part the page can use",
 "machine": "Stage 1 - Topple row | parts: P2 8 dominoes (a row to topple) | starts from: your push | passes on: a push to stage 2 | hand-off risk: low\nStage 2 - Roll along | parts: P1 a toy car (a roller) | starts from: a push from stage 1 | passes on: a roll to stage 3 | hand-off risk: low\nStage 3 - Topple row | parts: P5 5 paperback books (a row to topple) | starts from: a roll from stage 2 | passes on: a push to stage 4 | hand-off risk: low\nStage 4 - See-saw flick | parts: P3 a wooden spoon (a lever), P4 a pencil (a pivot) | starts from: a push from stage 3 | passes on: a push to the job | hand-off risk: high",
 "facts": "verdict: fiddly\nstages: 4 (asked for 5; these parts stretch to 4)\nrun time: about 7 seconds\nweakest link: stage 4 (See-saw flick, high)\nhand-offs: 1 high, 0 medium, 3 low\nfetch: none\nleft out as unsafe: P6\nspare parts: P7\nparts lines not read: none"
}

Reply:

{
 "task": "build",
 "verdict": "fiddly",
 "stages": [
  {
   "n": 1,
   "setup": "Stand your 8 dominoes on end in a line, each a little less than its own height from the next.",
   "handoff": "Your push starts the row, and the last domino tips onto your toy car.",
   "watch_for": "If a gap is too wide, one domino will fall without reaching the next.",
   "tweak": "Close the gaps so each domino is a little less than its own height from the next."
  },
  {
   "n": 2,
   "setup": "Set your toy car on the flat table, pointing straight at the next part.",
   "handoff": "Your toy car rolls on and reaches your 5 paperback books.",
   "watch_for": "Your toy car might drift sideways and miss the next part.",
   "tweak": "Start your toy car closer and aim it straight."
  },
  {
   "n": 3,
   "setup": "Stand your 5 paperback books on end in a line, each a little less than its own height from the next.",
   "handoff": "Your last paperback book tips over and lands on your wooden spoon.",
   "watch_for": "If a gap is too wide, one paperback book will fall without reaching the next.",
   "tweak": "Close the gaps so each paperback book is a little less than its own height from the next."
  },
  {
   "n": 4,
   "setup": "Lay your wooden spoon across your pencil like a see-saw, with one end raised beside the arriving piece and the other end touching the little bell on your desk.",
   "handoff": "The far end of your wooden spoon flicks up and gives the little bell on your desk a nudge.",
   "watch_for": "If the push lands off the raised end of your wooden spoon, the far end will barely lift.",
   "tweak": "Move your wooden spoon so the arriving piece strikes the very tip of its raised end."
  }
 ],
 "first_test": "Test stage 4's hand-off on its own and adjust it before you try the full run.",
 "fetch": "",
 "safety": "Keep it on a steady table, keep your toy car away from small children, use nothing hot, sharp, glass or with a flame, and keep your candle out of it.",
 "sign_off": "Test the weak hand-off first, then give it the full run."
}

Raise the paper flag - short-a-part

Input:

{
 "task": "build",
 "job": "raise the paper flag on my pencil pot | the job needs: a tug",
 "parts": "P1. a ping pong ball | used in stage 2 as a roller\nP2. a paper cup | used in stage 4 as a cup\nP3. a cardboard tube | used in stage 3 as a tube\nP4. some dominoes | used in stage 1 as a row to topple\nP+. a piece of string | to fetch: not on the list, needed for stage 4",
 "machine": "Stage 1 - Topple row | parts: P4 some dominoes (a row to topple) | starts from: your push | passes on: a push to stage 2 | hand-off risk: low\nStage 2 - Roll along | parts: P1 a ping pong ball (a roller) | starts from: a push from stage 1 | passes on: a roll to stage 3 | hand-off risk: low\nStage 3 - Tunnel | parts: P3 a cardboard tube (a tube) | starts from: a roll from stage 2 | passes on: a roll to stage 4 | hand-off risk: medium\nStage 4 - Cup and string | parts: P2 a paper cup (a cup), P+ a piece of string (string, to fetch) | starts from: a roll from stage 3 | passes on: a pull to the job | hand-off risk: medium",
 "facts": "verdict: short-a-part\nstages: 4\nrun time: about 7 seconds\nweakest link: stage 3 (Tunnel, medium)\nhand-offs: 0 high, 2 medium, 2 low\nfetch: a piece of string\nleft out as unsafe: none\nspare parts: none\nparts lines not read: none"
}

Reply:

{
 "task": "build",
 "verdict": "short-a-part",
 "stages": [
  {
   "n": 1,
   "setup": "Stand your dominoes on end in a line, each a little less than its own height from the next.",
   "handoff": "Your push tips the row of dominoes along, and the last domino tips onto your ping pong ball.",
   "watch_for": "A gap too wide, so one domino falls without reaching the next.",
   "tweak": "Close the gaps so each domino is a little less than its own height from the next."
  },
  {
   "n": 2,
   "setup": "Set your ping pong ball on the flat table, pointing straight at your cardboard tube.",
   "handoff": "Your ping pong ball rolls across the table and into your cardboard tube.",
   "watch_for": "Your ping pong ball drifts sideways and misses.",
   "tweak": "Start your ping pong ball closer and aim it straight."
  },
  {
   "n": 3,
   "setup": "Lay your cardboard tube on its side in your ping pong ball's path, its open end square to where your ping pong ball arrives, so it runs through the tube.",
   "handoff": "Your ping pong ball runs through your cardboard tube and out into your paper cup.",
   "watch_for": "Your ping pong ball hits the rim of your cardboard tube instead of the opening.",
   "tweak": "Move the opening of your cardboard tube right up to where your ping pong ball arrives."
  },
  {
   "n": 4,
   "setup": "Tie your string to your paper cup and lay the cup on its side half over the table edge, open end facing where your ping pong ball arrives.",
   "handoff": "Your ping pong ball tips your paper cup off the table edge, and the falling cup pulls the string, tugging to raise the paper flag on your pencil pot.",
   "watch_for": "Your paper cup does not tip off the table edge when your ping pong ball arrives.",
   "tweak": "Set your paper cup a little further over the table edge, so your ping pong ball's weight tips it."
  }
 ],
 "first_test": "Test stage 3's hand-off on its own and adjust it before you try the full run.",
 "fetch": "Fetch a piece of string for stage 4 before you set up.",
 "safety": "Keep your machine on a steady table, keep your ping pong ball away from small children, and use nothing hot, sharp, glass or with a flame.",
 "sign_off": "Fetch your string first, then set the stages up."
}

Key into the dish - fiddly

Input:

{
 "task": "build",
 "job": "drop my spare key into the dish | the job needs: a drop",
 "parts": "P1. a ball of wool | used in stage 4 as string\nP2. a plastic cup | used in stage 4 as a cup\nP3. a marble | used in stage 2 as a roller\nP4. a ruler | used in stage 2 as a ramp\nP5. a chopstick | used in stage 5 as a prop\nP6. a spare key | used in stage 5 as a weight\nP7. 10 dominoes | used in stage 1 as a row to topple\nP8. a kitchen roll tube | used in stage 3 as a tube",
 "machine": "Stage 1 - Topple row | parts: P7 10 dominoes (a row to topple) | starts from: your push | passes on: a push to stage 2 | hand-off risk: low\nStage 2 - Ramp | parts: P3 a marble (a roller), P4 a ruler (a ramp) | starts from: a push from stage 1 | passes on: a roll to stage 3 | hand-off risk: low\nStage 3 - Tunnel | parts: P8 a kitchen roll tube (a tube) | starts from: a roll from stage 2 | passes on: a roll to stage 4 | hand-off risk: medium\nStage 4 - Cup and string | parts: P2 a plastic cup (a cup), P1 a ball of wool (string) | starts from: a roll from stage 3 | passes on: a pull to stage 5 | hand-off risk: medium\nStage 5 - Pull the prop | parts: P5 a chopstick (a prop), P6 a spare key (a weight) | starts from: a pull from stage 4 | passes on: a drop to the job | hand-off risk: medium",
 "facts": "verdict: fiddly\nstages: 5 (asked for 6; these parts stretch to 5)\nrun time: about 8 seconds\nweakest link: stage 3 (Tunnel, medium)\nhand-offs: 0 high, 3 medium, 2 low\nfetch: none\nleft out as unsafe: none\nspare parts: none\nparts lines not read: none"
}

Reply:

{
 "task": "build",
 "verdict": "fiddly",
 "stages": [
  {
   "n": 1,
   "setup": "Stand your 10 dominoes on end in a line, each a little less than its own height from the next.",
   "handoff": "Your last domino tips over and pushes your marble.",
   "watch_for": "If the gap between any two of your dominoes is too wide, one will fall without reaching the next.",
   "tweak": "Close the gaps so each domino stands a little less than its own height from the next."
  },
  {
   "n": 2,
   "setup": "Prop one end of your ruler up a little so it slopes down towards the next part, and rest your marble at the top.",
   "handoff": "Your marble rolls down your ruler and into the opening of your kitchen roll tube.",
   "watch_for": "Your marble might run off the side of your ruler.",
   "tweak": "Make the slope a little gentler so your marble rolls straight."
  },
  {
   "n": 3,
   "setup": "Lay your kitchen roll tube on its side in your marble's path, with its open end square to your marble as it arrives, so your marble runs straight through.",
   "handoff": "Your marble runs through your kitchen roll tube and out into your plastic cup.",
   "watch_for": "Your marble might hit the rim of your kitchen roll tube instead of the opening.",
   "tweak": "Move the opening of your kitchen roll tube right up to where your marble arrives."
  },
  {
   "n": 4,
   "setup": "Tie your ball of wool to your plastic cup and lay the cup on its side half over the table edge, open end facing where your marble arrives.",
   "handoff": "Your marble tips your plastic cup off the table edge, and the falling cup pulls your ball of wool, sliding your chopstick back.",
   "watch_for": "Your plastic cup might not tip off the edge when your marble arrives.",
   "tweak": "Set your plastic cup a little further over the edge so your marble's weight tips it."
  },
  {
   "n": 5,
   "setup": "Lay your chopstick so it sticks out past the table edge, rest your spare key on its outer end, and tie the other end of your ball of wool to your chopstick, so a pull slides it back and your spare key drops.",
   "handoff": "Your chopstick slides back and your spare key drops into the dish.",
   "watch_for": "The knot in your ball of wool might slip, or your chopstick might stick.",
   "tweak": "Tie the knot tight and let your chopstick only just hold your spare key."
  }
 ],
 "first_test": "Test stage 3 on its own and adjust it before you give the whole thing a full run.",
 "fetch": "",
 "safety": "Keep it on a steady table, keep your marble and your spare key away from small children, and use nothing hot, sharp, glass or with a flame.",
 "sign_off": "Test stage 3 first, then give it the full run."
}

Build the input yourself

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

const K = ChainKit, R = ReplyKit;
const built = K.buildInput(K.form({ job: "drop a tea bag into my mug", payoff: "drop", want: "4",
  text: "12 dominoes\na marble\na wooden ruler\na tea bag\n3 hardback books" }));  // built.ok is false, with built.note, until there is a job and two parts
const wire = K.toWire(built.input);   // POST this to /estimate and /run; built.analysis holds parts, stages, verdict, weak, secs, fetch, unsafe, spare
// then: const reply = R.normalize(R.parseReply(job.output.output));
//       const notes = R.check(reply, built).notes;   // the page's check: [{ level, text }]

Form keys: job (the dull job, up to 90 characters), payoff (what the job needs at the end: push for a nudge, drop or pull for a tug), want (how many stages to aim for, 3 to 7; fewer when the parts run out) and text (one thing on your table a line, up to 1,200 characters). Leading markers such as 1., (2) or - are dropped and blank lines skipped; only the first 12 parts are read, and a line over 80 characters is cut on a word.

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="YOUR_TOKEN"   # from https://long-way-round.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":"long-way-round"}' | 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": "build", "job": "drop a tea bag into my mug | the job needs: a drop", "parts": "P1. 12 dominoes | used in stage 1 as a row to topple ...", "machine": "Stage 1 - Topple row | parts: P1 12 dominoes (a row to topple) | starts from: your push ...", "facts": "verdict: ready ...", ...}
# - 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 task and the input: a retry with the same key never bills twice. The page uses long-way-round:<task>:<inputHash>:a<attempt> (the one task is build), 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="long-way-round:build:$(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: long-way-round:build:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json