← Fold and Look
API

Drive Fold and Look from your own code

The one metered task, read-blots, is available over HTTP. It takes your answers to a set of 5 inkblots - what you saw in each and whether the answer uses the whole blot or a part, with skipped blots allowed - plus the page's own tally (usual or your own, word count, the second-look angle per blot), and returns a verdict, a playful reading and a second-look question for every answered blot, the thread your answers share, a headline and a closing line. It is a game, not a psychological test.

The division of labour first: the page tallies the answers and a fixed rule decides the verdict; the model reads them. After a run the page checks that the verdict follows the rule, that every answered blot is read in order with the page's usual-or-own mark, that each second look is a question, and that no ids or test words 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":"fold-and-look"}), 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, answers, tally) 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: read-blots (the one task is read-blots); a missing or different one is still answered as read-blots. The verdict rule, checked in order: if usual answers are at least half of those answered, crowd-eye; otherwise if more than half use only a part of the blot, detail-hunter; otherwise if at least 3 in 4 are your own and at least half are detailed (4 or more words), far-eye; otherwise fresh-eye.

FieldRequiredWhat it holds
taskyesAlways read-blots.
answersyes5 lines, blot 2 | a part of the blot | a pair of boots at the bottom, or blot 5 | skipped. Answers are up to 80 characters.
tallyyes5 lines, B1 | usual (bat) | whole | 2 words | second look: scale - change its size or B5 | skipped, then the counts: answered 4 | usual 1 | own 3 | part 3 | whole 1 | detailed 3.
retry_notenoOnly on the page's single reformat retry.

The reply is one JSON object:

{"task": "read-blots", "verdict": "fresh-eye", "headline": "...", "blots": [{"n": 1, "kind": "own", "reading": "...", "second_look": "...?"}, ...], "thread": "...", "closing": "..."}
FieldWhat it holds
verdictcrowd-eye, detail-hunter, far-eye or fresh-eye, by the rule above.
headlineThe verdict in plain words, at most 16 words.
blots1 entry per answered blot, in order. n is the blot number; kind is usual or own, copied from the tally; reading at most 25 words; second_look a question from the blot's angle, at most 22 words.
threadWhat the answers share, to the person as "you", at most 40 words.
closingOne sentence restating the verdict and inviting a second look, at most 20 words.

The page then checks the reply (BlotKit.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.

Mostly the usual answers - crowd's eye

Input:

{
 "task": "read-blots",
 "answers": "blot 1 | the whole blot | a bat\nblot 2 | the whole blot | two people clapping hands\nblot 3 | the whole blot | a butterfly\nblot 4 | the whole blot | a crab\nblot 5 | the whole blot | a lighthouse in fog",
 "tally": "B1 | usual (bat) | whole | 2 words | second look: scale - change its size\nB2 | usual (people) | whole | 4 words, detailed | second look: who - change who or what it is\nB3 | usual (butterfly) | whole | 2 words | second look: when - change when it happens\nB4 | usual (crab) | whole | 2 words | second look: doing - change what it is doing\nB5 | own | whole | 4 words, detailed | second look: borrow - borrow it from a different world\nanswered 5 | usual 4 | own 1 | part 0 | whole 5 | detailed 2"
}

Reply:

{
 "task": "read-blots",
 "verdict": "crowd-eye",
 "headline": "You mostly saw the same things many people see.",
 "blots": [
  {
   "n": 1,
   "kind": "usual",
   "reading": "You took in the whole blot and named a bat - many people land on this same answer.",
   "second_look": "What if that bat were a different size?"
  },
  {
   "n": 2,
   "kind": "usual",
   "reading": "Taking in the entire blot, you named 2 people clapping hands - many people arrive at this same notion.",
   "second_look": "What if those people were someone or something else?"
  },
  {
   "n": 3,
   "kind": "usual",
   "reading": "You saw the full blot as a butterfly - many people reach this same idea.",
   "second_look": "What if this butterfly appeared at a different time?"
  },
  {
   "n": 4,
   "kind": "usual",
   "reading": "You took in all of the blot and named a crab - many people share this same thought.",
   "second_look": "What if this crab were doing something else?"
  },
  {
   "n": 5,
   "kind": "own",
   "reading": "You saw the blot entirely as a lighthouse in fog - a take that is your own.",
   "second_look": "What if this lighthouse in fog belonged to a different world entirely?"
  }
 ],
 "thread": "Across 5 blots you named bat, people, butterfly and crab - 4 defaults to look past next - alongside your own lighthouse in fog.",
 "closing": "Most of what you named matches what many people spot - a fresh look could turn up something only yours."
}

Zoomed in on parts - detail hunter

Input:

{
 "task": "read-blots",
 "answers": "blot 1 | a part of the blot | a keyhole in the middle\nblot 2 | a part of the blot | a pair of boots at the bottom\nblot 3 | the whole blot | a bat\nblot 4 | a part of the blot | a little teapot spout on the left edge\nblot 5 | skipped",
 "tally": "B1 | own | part | 5 words, detailed | second look: scale - change its size\nB2 | own | part | 7 words, detailed | second look: who - change who or what it is\nB3 | usual (bat) | whole | 2 words | second look: when - change when it happens\nB4 | own | part | 8 words, detailed | second look: doing - change what it is doing\nB5 | skipped\nanswered 4 | usual 1 | own 3 | part 3 | whole 1 | detailed 3"
}

Reply:

{
 "task": "read-blots",
 "verdict": "detail-hunter",
 "headline": "You mostly zoomed in on parts of these blots.",
 "blots": [
  {
   "n": 1,
   "kind": "own",
   "reading": "You zoomed into the middle and named a keyhole, a part only you picked out of the whole blot.",
   "second_look": "What different size might this keyhole be?"
  },
  {
   "n": 2,
   "kind": "own",
   "reading": "You looked to the bottom and named boots there, a close part that is yours alone.",
   "second_look": "Who or what else might these boots be?"
  },
  {
   "n": 3,
   "kind": "usual",
   "reading": "You took in the whole blot and saw a bat, something many people land on too, named simply and warmly.",
   "second_look": "What different time might this bat belong to?"
  },
  {
   "n": 4,
   "kind": "own",
   "reading": "You picked the left edge of this blot and named a little teapot spout - a precise detail only you reached for.",
   "second_look": "What might this teapot spout be doing instead?"
  }
 ],
 "thread": "You picked out 3 parts - a keyhole, boots, a teapot spout - across your 4 answers, taking in the whole blot only 1 time, for a bat.",
 "closing": "This set leans toward parts over the whole blot - worth a second look at what else those pieces could be."
}

Long answers of my own - far eye

Input:

{
 "task": "read-blots",
 "answers": "blot 1 | the whole blot | a chandelier dripping wax onto a ballroom floor\nblot 2 | the whole blot | an accordion folding shut with a sigh\nblot 3 | the whole blot | a cathedral doorway seen from below\nblot 4 | the whole blot | a moth\nblot 5 | the whole blot | a tired knight leaning on a shield",
 "tally": "B1 | own | whole | 8 words, detailed | second look: scale - change its size\nB2 | own | whole | 7 words, detailed | second look: who - change who or what it is\nB3 | own | whole | 6 words, detailed | second look: when - change when it happens\nB4 | usual (moth) | whole | 2 words | second look: doing - change what it is doing\nB5 | own | whole | 7 words, detailed | second look: borrow - borrow it from a different world\nanswered 5 | usual 1 | own 4 | part 0 | whole 5 | detailed 4"
}

Reply:

{
 "task": "read-blots",
 "verdict": "far-eye",
 "headline": "Most of your answers were your own, written out in detail.",
 "blots": [
  {
   "n": 1,
   "kind": "own",
   "reading": "You took in the whole blot and named a chandelier dripping wax onto a ballroom floor - a vivid picture, fully your own.",
   "second_look": "If that chandelier were far smaller, what would you notice about the wax and the floor?"
  },
  {
   "n": 2,
   "kind": "own",
   "reading": "You saw the whole blot as an accordion folding shut with a sigh - an image that's entirely yours.",
   "second_look": "If something other than an accordion were folding shut with a sigh, what might it be?"
  },
  {
   "n": 3,
   "kind": "own",
   "reading": "You took in the whole shape and saw a cathedral doorway seen from below - your own view, imagined in detail.",
   "second_look": "If this cathedral doorway appeared at another time, what might look different?"
  },
  {
   "n": 4,
   "kind": "usual",
   "reading": "You saw all of it as a moth. Many people land on that same picture too - a fine, simple thing to notice.",
   "second_look": "That moth - what might it be doing right now?"
  },
  {
   "n": 5,
   "kind": "own",
   "reading": "You saw the whole image as a tired knight leaning on a shield - a detailed scene fully your own.",
   "second_look": "That knight - if it belonged to a different world, what might change about it?"
  }
 ],
 "thread": "You answered all 5 blots, 4 in your own words and 4 with detail - a chandelier, an accordion, a cathedral doorway, a moth, and a knight, each read your way.",
 "closing": "These 5 answers were mostly your own and detailed - worth a closer second look at each blot."
}

Short answers of my own - fresh eye

Input:

{
 "task": "read-blots",
 "answers": "blot 1 | the whole blot | a teapot\nblot 2 | the whole blot | an anchor\nblot 3 | the whole blot | a spider\nblot 4 | the whole blot | a jellyfish\nblot 5 | a part of the blot | a bow tie",
 "tally": "B1 | own | whole | 2 words | second look: scale - change its size\nB2 | own | whole | 2 words | second look: who - change who or what it is\nB3 | usual (spider) | whole | 2 words | second look: when - change when it happens\nB4 | own | whole | 2 words | second look: doing - change what it is doing\nB5 | own | part | 3 words | second look: borrow - borrow it from a different world\nanswered 5 | usual 1 | own 4 | part 1 | whole 4 | detailed 0"
}

Reply:

{
 "task": "read-blots",
 "verdict": "fresh-eye",
 "headline": "Most of your answers were your own, not borrowed ones.",
 "blots": [
  {
   "n": 1,
   "kind": "own",
   "reading": "You took in the whole blot and named a teapot, a reading entirely your own.",
   "second_look": "If that teapot were far smaller, what would you call it?"
  },
  {
   "n": 2,
   "kind": "own",
   "reading": "You took in the whole blot and called it an anchor, a choice entirely your own.",
   "second_look": "If that anchor were someone or something entirely different, who or what might it be?"
  },
  {
   "n": 3,
   "kind": "usual",
   "reading": "Taking the blot as a whole, you saw a spider, the same thing plenty of people notice too - good company.",
   "second_look": "If that spider were from a different time, when would it be?"
  },
  {
   "n": 4,
   "kind": "own",
   "reading": "You looked at the full shape and named a jellyfish, a pick that's yours alone.",
   "second_look": "If that jellyfish were doing something else entirely, what might it be doing?"
  },
  {
   "n": 5,
   "kind": "own",
   "reading": "You zoomed in on a part of the blot and saw a bow tie, a detail that's all yours.",
   "second_look": "If that bow tie came from a completely different world, what might it be there?"
  }
 ],
 "thread": "You brought 4 of your 5 answers in your own words - a teapot, an anchor, a jellyfish and a bow tie, alongside a spider - mostly taking in the whole blot, with just a part for the bow tie.",
 "closing": "Most of what you saw was your own - take another look and see what else these blots might hold."
}

Build the input yourself

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

const K = BlotKit;
const built = K.buildInput(K.form({ seed: 7, answers: [
  { text: "a kite caught in a tree", where: "whole" }, { text: "a comb", where: "part" },
  { text: "a tuba" }, { text: "" }, { text: "two foxes arguing" } ] }));
built.input.tally;                      // the page's 5 tally lines + the counts line
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 set")         // the set 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://fold-and-look.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":"fold-and-look"}' | 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-blots", "answers": "blot 1 | the whole blot | a bat
...", "tally": "B1 | usual (bat) | whole | 2 words | second look: scale - change its size
..."}
# - 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 fold-and-look:<task>:<inputHash>:a<attempt> (the one task is read-blots), 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="fold-and-look:read-blots:$(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: fold-and-look:read-blots:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json