← Who Spun It
API

Drive Who Spun It from your own code

The one metered task, spin-check, is available over HTTP. It takes a paper spinner on a needle (the kind often shown as a telekinesis or mind-power wheel), what someone thinks turns it, up to 6 logged rounds (cup on or off, hands away / near / close, breath normal or held, room air still or moving, and how much it turned) and the page's facts, and returns the verdict, a note for every round, why that push can turn light paper, why the next round is worth trying, a headline and a closing line.

The division of labour first: the page pairs the rounds, a fixed rule decides the verdict and the page picks the next single change; the model explains them. After a run the page checks that the verdict follows the rule, that every counted round has its note in order, that the next step names the page's starting round, that every number comes from the rounds or notes, 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":"who-spun-it"}), 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, setup, idea, rounds, 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: 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: spin-check (the one task is spin-check); a missing or different one is still answered as spin-check. A pair is 2 counted rounds that differ in exactly 1 condition; it is decisive when the spin differs, 1 round reached a slow turn or more and the round with more push (cup off, hands closer, normal breathing, moving air) turned more, odd when more push gave less spin, faint when neither got past a wobble, and flat when the spin was the same. The verdict rule, checked in order: nothing past a wobble, no-spin; a slow turn or more with the cup off and hands away, room-air; no decisive pair, need-a-pair; otherwise the decisive pair that changed hands, then breath, then air, then the cup decides: warm-hands, your-breath, room-air or moving-air. The next change always alters 1 condition from 1 logged round (or repeats a round, or asks to check the spinner).

FieldRequiredWhat it holds
taskyesAlways spin-check.
setupyesThe spinner and what it sits on, up to 140 characters.
ideayesWhat someone thinks turns it, up to 120 characters, or none.
roundsyes1 line per counted round: round 1 | cup: off (...) | hands: close (...) | breath: normal | air: still (...) | spin: steady spin | note: ....
factsyesThe page's lines: verdict:, deciding pair:, spin with cup off and hands away:, repeats:, other pairs:, suspects: (warm hands, your breath, room air) and next change:.
retry_notenoOnly on the page's single reformat retry.

The reply is one JSON object:

{"task": "spin-check", "verdict": "warm-hands", "headline": "...", "reads": [{"round": 1, "read": "..."}, ...], "physics": "...", "next_why": "...", "closing": "..."}
FieldWhat it holds
verdictno-spin, room-air, need-a-pair, warm-hands, your-breath or moving-air, copied from the facts.
headlineThe verdict in plain words, at most 16 words.
reads1 entry per counted round, in order: round (its number) and read, at most 25 words.
physicsWhy that push can turn light paper on a needle point, at most 45 words.
next_whyThe page's next change, naming its round, and what each result would mean, at most 30 words.
closing1 light sentence that fits the verdict, at most 20 words.

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

Worked examples - every verdict

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

A foil spinner that only wobbles - no-spin

Input:

{
 "task": "spin-check",
 "setup": "foil square folded corner to corner, balanced on a pin pushed into a wine cork",
 "idea": "a video said staring at it makes it turn",
 "rounds": "round 1 | cup: off (spinner open to the room) | hands: away (no hand within 50 cm) | breath: normal | air: still (fans, heaters and vents off, doors and windows shut) | spin: none | note: none\nround 2 | cup: off (spinner open to the room) | hands: near (5 to 50 cm) | breath: normal | air: still (fans, heaters and vents off, doors and windows shut) | spin: wobble | note: tiny wobble when I leaned in\nround 3 | cup: off (spinner open to the room) | hands: close (under 5 cm, not touching) | breath: normal | air: still (fans, heaters and vents off, doors and windows shut) | spin: wobble | note: none",
 "facts": "verdict: no-spin\ndeciding pair: none\nspin with cup off and hands away: none\nrepeats: none\nother pairs: round 1 vs round 2 | changed: hands away -> near | spin: none -> wobble | faint ; round 1 vs round 3 | changed: hands away -> close | spin: none -> wobble | faint ; round 2 vs round 3 | changed: hands near -> close | spin: wobble -> wobble | flat\nsuspects: warm hands = made no clear difference | your breath = not tested yet | room air = not tested yet\nnext change: repeat round 3 with the air moving and nothing else changed, to see whether a stronger push turns it at all"
}

Reply:

{
 "task": "spin-check",
 "verdict": "no-spin",
 "headline": "No round beat a wobble yet, so there's no clear push to explain.",
 "reads": [
  {
   "round": 1,
   "read": "Hands away, air still: nothing moved. This is the baseline that rounds 2 and 3 are compared against."
  },
  {
   "round": 2,
   "read": "Hands near gave only a tiny wobble when you leaned in - barely more than hands away, and the same as closer in round 3."
  },
  {
   "round": 3,
   "read": "Hands brought close still only wobbled, no better than hands near in round 2, though still more than hands away in round 1."
  }
 ],
 "physics": "None of these rounds sent a current strong enough to catch the folds and turn the foil - the needle point is nearly frictionless, so even a very light push can tip it, but nothing here was quite enough.",
 "next_why": "Round 3 repeats with the air moving, to see if a stronger push can turn it - turning points to air, no change means air isn't enough either.",
 "closing": "Worth a look with more air moving - who knows what the next round might show."
}

It turned before anyone sat down - room-air

Input:

{
 "task": "spin-check",
 "setup": "printer paper square creased into a tent, on a sewing needle in a pencil eraser",
 "idea": "my flatmate reckons it reacts to whoever is nearest",
 "rounds": "round 1 | cup: off (spinner open to the room) | hands: away (no hand within 50 cm) | breath: normal | air: still (fans, heaters and vents off, doors and windows shut) | spin: slow turn | note: started turning before I sat down\nround 2 | cup: off (spinner open to the room) | hands: close (under 5 cm, not touching) | breath: normal | air: still (fans, heaters and vents off, doors and windows shut) | spin: steady spin | note: none",
 "facts": "verdict: room-air\ndeciding pair: none\nspin with cup off and hands away: round 1 | spin: slow turn\nrepeats: none\nother pairs: round 1 vs round 2 | changed: hands away -> close | spin: slow turn -> steady spin | decisive\nsuspects: warm hands = points here | your breath = not tested yet | room air = points here\nnext change: repeat round 1 with the cup on and nothing else changed, to see whether shutting out the air around it stops the spin"
}

Reply:

{
 "task": "spin-check",
 "verdict": "room-air",
 "headline": "Even with hands well away, the room's air alone turned it this time.",
 "reads": [
  {
   "round": 1,
   "read": "Hands away and cup off still gave a slow turn before you sat down; round 2's closer hands spun it faster."
  },
  {
   "round": 2,
   "read": "Hands brought close turned round 1's slow turn into a steady spin - the clearest jump, pointing to warmth or moving air."
  }
 ],
 "physics": "A spinner on a needle point has almost no friction, so even a small stray draught - from a vent, heater, open door or someone passing by - can catch the tent's slanted folds and spin it, hand or no hand nearby.",
 "next_why": "Round 1 repeats with the cup on, to see if blocking outside air stops the spin - stopping points to the room's air, continuing means something else is at play.",
 "closing": "Let's see if shutting the air out with the cup keeps the spin going or stops it."
}

Three things changed at once - need-a-pair

Input:

{
 "task": "spin-check",
 "setup": "sticky note folded into a little pyramid on a needle stuck in a blob of putty",
 "idea": "focusing hard on it",
 "rounds": "round 1 | cup: on (a clear cup or jar over the spinner) | hands: away (no hand within 50 cm) | breath: held or face turned away | air: still (fans, heaters and vents off, doors and windows shut) | spin: none | note: none\nround 2 | cup: off (spinner open to the room) | hands: close (under 5 cm, not touching) | breath: normal | air: still (fans, heaters and vents off, doors and windows shut) | spin: steady spin | note: I was concentrating really hard\nround 3 | cup: off (spinner open to the room) | hands: near (5 to 50 cm) | breath: held or face turned away | air: moving (a fan, heater, vent, open door or window) | spin: slow turn | note: the radiator had come on",
 "facts": "verdict: need-a-pair\ndeciding pair: none\nspin with cup off and hands away: none\nrepeats: none\nother pairs: none\nsuspects: warm hands = not tested yet | your breath = not tested yet | room air = not tested yet\nnext change: repeat round 3 with the cup on and nothing else changed, so the next round differs from round 3 in only 1 condition"
}

Reply:

{
 "task": "spin-check",
 "verdict": "need-a-pair",
 "headline": "These rounds each changed more than 1 thing, so no single cause stands out yet.",
 "reads": [
  {
   "round": 1,
   "read": "With the cup on, hands away and your breath held, nothing moved - this round has no partner that changes only 1 condition yet."
  },
  {
   "round": 2,
   "read": "Cup off, hands close, breath normal gave a steady spin, but more than 1 condition differs from round 1, so no pair yet."
  },
  {
   "round": 3,
   "read": "Air moving after the radiator came on gave a slow turn, but more than 1 condition differs from the others, so no pair yet."
  }
 ],
 "physics": "A pyramid this light turns on almost no push, since the needle point barely resists it. But here every round changed more than 1 thing at once, so there's no way yet to tell which change - hands, breath or air - did it.",
 "next_why": "Round 3 repeats with the cup now on, hands and breath unchanged, to test the air - stopping points to air, still turning points elsewhere.",
 "closing": "Time for a round that changes just 1 thing, so a clearer pattern might show up."
}

Hands closer, more spin - warm-hands

Input:

{
 "task": "spin-check",
 "setup": "square of tracing paper folded twice, balanced on a needle in a cork on the kitchen table",
 "idea": "my nephew says it is mind power",
 "rounds": "round 1 | cup: off (spinner open to the room) | hands: near (5 to 50 cm) | breath: held or face turned away | air: still (fans, heaters and vents off, doors and windows shut) | spin: wobble | note: hands about a ruler away\nround 2 | cup: off (spinner open to the room) | hands: close (under 5 cm, not touching) | breath: held or face turned away | air: still (fans, heaters and vents off, doors and windows shut) | spin: steady spin | note: took a few seconds to get going\nround 3 | cup: off (spinner open to the room) | hands: close (under 5 cm, not touching) | breath: normal | air: still (fans, heaters and vents off, doors and windows shut) | spin: steady spin | note: none",
 "facts": "verdict: warm-hands\ndeciding pair: round 1 vs round 2 | changed: hands near -> close | spin: wobble -> steady spin | decisive\nspin with cup off and hands away: none\nrepeats: none\nother pairs: round 2 vs round 3 | changed: breath held -> normal | spin: steady spin -> steady spin | flat\nsuspects: warm hands = points here | your breath = made no clear difference | room air = not tested yet\nnext change: repeat round 2 with the cup on and nothing else changed, to see whether shutting out the air around it stops the spin"
}

Reply:

{
 "task": "spin-check",
 "verdict": "warm-hands",
 "headline": "Moving your hands closer clearly sped it up, pointing to warmth from your hands.",
 "reads": [
  {
   "round": 1,
   "read": "With hands near - about a ruler away - and your breath held, it only wobbled; round 2 shows what closer hands then did."
  },
  {
   "round": 2,
   "read": "Hands brought close turned the wobble in round 1 into a steady spin, taking a few seconds to start - the clearest jump here."
  },
  {
   "round": 3,
   "read": "Breathing normally instead of holding it, with hands still close, gave the same steady spin as round 2 - breath made no clear difference."
  }
 ],
 "physics": "Tracing paper this light needs almost no push to move, since the needle point has so little friction. Air warmed by your hands near the folds rises and drifts past them, enough on its own to spin it steadily.",
 "next_why": "Round 2 repeats with the cup on, to see if shutting out the air stops the spin - stopping fits warm hands, still turning points elsewhere.",
 "closing": "Let's see if the cup keeps that warmth-driven spin going or brings it to a stop."
}

Leaning right over it - your-breath

Input:

{
 "task": "spin-check",
 "setup": "paper tent cut from a receipt, on a pushpin standing in a cork",
 "idea": "my sister says I am doing it with my mind",
 "rounds": "round 1 | cup: off (spinner open to the room) | hands: near (5 to 50 cm) | breath: normal | air: still (fans, heaters and vents off, doors and windows shut) | spin: slow turn | note: I was leaning right over it\nround 2 | cup: off (spinner open to the room) | hands: near (5 to 50 cm) | breath: held or face turned away | air: still (fans, heaters and vents off, doors and windows shut) | spin: none | note: turned my face to the side",
 "facts": "verdict: your-breath\ndeciding pair: round 1 vs round 2 | changed: breath normal -> held | spin: slow turn -> none | decisive\nspin with cup off and hands away: none\nrepeats: none\nother pairs: none\nsuspects: warm hands = not tested yet | your breath = points here | room air = not tested yet\nnext change: repeat round 1 with your hands away and nothing else changed, to take your hands out of the picture"
}

Reply:

{
 "task": "spin-check",
 "verdict": "your-breath",
 "headline": "Holding your breath stopped the spin, so your breath looks like the main push.",
 "reads": [
  {
   "round": 1,
   "read": "Hands near and breathing normally as you leaned right over it gave a slow turn - holding your breath in round 2 stopped it."
  },
  {
   "round": 2,
   "read": "Hands still near, but holding your breath and turning your face to the side stopped it completely - the clearest change in this pair."
  }
 ],
 "physics": "A receipt-paper tent this light spins with barely any push, since the needle point offers almost no resistance. Breath is a warm, moving current, and leaning over it sends that current straight at the folds; holding your breath removes it.",
 "next_why": "Round 1 repeats with your hands now away, to rule hands out of the picture - keeps turning still points to your breath, stopping would bring warm hands back in.",
 "closing": "Let's see if it still turns with your hands well out of the way this time."
}

Under the cup it stops - moving-air

Input:

{
 "task": "spin-check",
 "setup": "thin card square folded into 4 vanes, on a needle in an eraser",
 "idea": "none",
 "rounds": "round 1 | cup: off (spinner open to the room) | hands: close (under 5 cm, not touching) | breath: normal | air: still (fans, heaters and vents off, doors and windows shut) | spin: steady spin | note: none\nround 2 | cup: on (a clear cup or jar over the spinner) | hands: close (under 5 cm, not touching) | breath: normal | air: still (fans, heaters and vents off, doors and windows shut) | spin: none | note: no movement at all for a minute",
 "facts": "verdict: moving-air\ndeciding pair: round 1 vs round 2 | changed: cup off -> on | spin: steady spin -> none | decisive\nspin with cup off and hands away: none\nrepeats: none\nother pairs: none\nsuspects: warm hands = not tested yet | your breath = not tested yet | room air = not tested yet\nnext change: repeat round 1 with your breath held and nothing else changed, to take your breath out of the picture"
}

Reply:

{
 "task": "spin-check",
 "verdict": "moving-air",
 "headline": "Covering it with the cup stopped the spin, pointing to outside air so far.",
 "reads": [
  {
   "round": 1,
   "read": "With the cup off and hands close, it spun steadily - putting the cup on in round 2 stopped it completely."
  },
  {
   "round": 2,
   "read": "Putting the cup on with hands still close killed the spin completely for a minute - the clearest change yet, pointing outside the cup."
  }
 ],
 "physics": "A card this light needs only the faintest push to spin, since the needle point has hardly any friction. The cup seals out the room's air, and with it on the vanes had nothing to catch, so the push came from outside the cup.",
 "next_why": "Round 1 repeats with your breath held this time, to take breath out of the picture - still spinning backs up outside air, stopping would bring breath back into play.",
 "closing": "Let's see if it still spins once your breath is held and out of the way."
}

Build the input yourself

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

const K = SpinKit;
const built = K.buildInput(K.form({ setup: "paper square folded into a tent on a needle in a cork", idea: "none",
  trials: [{ jar: "off", hands: "near", breath: "normal", air: "still", spin: 1 },
           { jar: "off", hands: "close", breath: "normal", air: "still", spin: 3 }] }));
built.input.facts;                      // the page's lines: verdict, pairs, suspects, next change
const wire = K.toWire(built.input);     // POST this to /estimate and /run
// then: const reply = K.normalize(K.parseReply(job.output.output));
// built.facts.verdict                  // the verdict the page's rule gives
// K.check(reply, built).notes          // the page's re-check of the reply
// K.md(built, reply, "my session")     // the session 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://who-spun-it.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":"who-spun-it"}' | 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": "spin-check", "setup": "thin card square folded into 4 vanes, on a needle in an eraser", "idea": "none", "rounds": "round 1 | cup: off (...) | hands: close (...) | breath: normal | air: still (...) | spin: steady spin | note: none\nround 2 | cup: on (...) | ...", "facts": "verdict: moving-air\ndeciding pair: round 1 vs round 2 | ...\nnext change: ..."}
# - 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 who-spun-it:<task>:<inputHash>:a<attempt> (the one task is spin-check), 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="who-spun-it:spin-check:$(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: who-spun-it:spin-check:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json