← What Was That?
API

Drive What Was That? from your own code

The one metered task, sort-sighting, is available over HTTP. It takes a sky sighting - up to 8 details (when, how long, lights, colour, motion, sound, how it ended, height), each of which may be "not sure", free notes, and the page's own fit table of 9 everyday suspects - and returns a verdict and the top 3 suspects, each explained both ways (what fits, what argues against) with a check for next time, plus a headline, a note and a closing line.

The division of labour first: the page scores and ranks the suspects and a fixed rule decides the verdict; the model explains it. After a run the page checks that the verdict follows the rule, that the suspects are its top 3 in order, that no ruled-out suspect is waved through, 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":"what-was-that"}), 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, sighting, notes, fit_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: a sort 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: sort-sighting (the one task is sort-sighting); a missing or different one is still answered as sort-sighting. The verdict rule, checked in order: the top suspect has a detail against it gives still-open; otherwise a runner-up with nothing against it that fits at most 1 detail fewer gives toss-up; otherwise a top suspect fitting 6 or more details gives solved; otherwise likely.

FieldRequiredWhat it holds
taskyesAlways sort-sighting.
sightingyes8 lines, detail: answer, for example how long: 1 to 5 minutes; an unknown detail reads not sure.
notesyesThe person's own words (up to 400 characters), or none. Read by the run, not scored by the page.
fit_tableyes9 ranked lines: S1 lantern (sky lanterns) | fits 4 of 4 known: when, lights, colour, motion | against: none | open: none.
retry_notenoOnly on the page's single reformat retry.

The reply is one JSON object:

{"task": "sort-sighting", "verdict": "likely", "headline": "...", "suspects": [{"id": "lantern", "fits": "...", "against": "...", "test": "..."}, ...], "note": "...", "closing": "..."}
FieldWhat it holds
verdictstill-open, toss-up, solved or likely, by the rule above.
headlineThe verdict in plain words with the top suspect, at most 16 words.
suspectsExactly 3: the S1, S2 and S3 lines in order. id copied from the line; fits at most 25 words; against at most 20 words; test (a check for next time) at most 20 words.
noteBoth directions, to the person as "you", at most 40 words.
closingOne sentence restating the verdict, at most 20 words.

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

A line of lights after sunset - solved

Input:

{
 "task": "sort-sighting",
 "sighting": "when: at dusk, within about 90 min after sunset\nhow long: 1 to 5 minutes\nlights: a line of 6 or more lights\ncolour: steady white or pale yellow\nmotion: glided steadily in a straight line\nsound: no sound\nending: faded out gradually\nheight: high overhead",
 "notes": "Counted about 20 dots following each other in a row.",
 "fit_table": "S1 sat-train (a string of newly launched satellites) | fits 8 of 8 known: when, how long, lights, colour, motion, sound, ending, height | against: none | open: none\nS2 lantern (sky lanterns) | fits 5 of 8 known: when, how long, lights, sound, ending | against: none | open: colour, motion, height\nS3 aircraft (an aircraft) | fits 4 of 8 known: how long, colour, motion, sound | against: none | open: when, lights, ending, height\nS4 drone (a drone) | fits 3 of 8 known: when, how long, colour | against: none | open: lights, motion, sound, ending, height\nS5 satellite (a single satellite or space station) | fits 7 of 8 known: when, how long, colour, motion, sound, ending, height | against: lights | open: none\nS6 balloon (a high-altitude balloon) | fits 5 of 8 known: when, colour, sound, ending, height | against: lights | open: how long, motion\nS7 reflection (a reflection on glass) | fits 0 of 8 known: none | against: motion | open: when, how long, lights, colour, sound, ending, height\nS8 planet (a bright planet or star) | fits 3 of 8 known: when, colour, sound | against: lights, motion | open: how long, ending, height\nS9 meteor (a meteor or fireball) | fits 3 of 8 known: colour, sound, ending | against: how long, lights | open: when, motion, height"
}

Reply:

{
 "task": "sort-sighting",
 "verdict": "solved",
 "headline": "Solved: this matches a string of newly launched satellites passing overhead.",
 "suspects": [
  {
   "id": "sat-train",
   "fits": "Matches dusk timing, 1 to 5 minutes, 6 or more lights, steady white or pale yellow, straight glide, no sound, gradual fade, high overhead.",
   "against": "Nothing described argues against it.",
   "test": "Check whether the lights blink or flicker; a steady, unblinking line fits better than blinking aircraft or flickering sky lanterns."
  },
  {
   "id": "lantern",
   "fits": "Matches dusk timing, 1 to 5 minutes, a line of 6 or more lights, no sound, and a gradual fade.",
   "against": "Motion is open: it neither supports nor rules out this suspect here.",
   "test": "Watch if it drifts with the wind or flickers like a flame, unlike a steady straight-line glide."
  },
  {
   "id": "aircraft",
   "fits": "Matches 1 to 5 minutes duration, steady white or pale yellow colour, a straight steady glide, and no sound.",
   "against": "Height is open: it neither supports nor rules out this suspect here.",
   "test": "Check for blinking or colored navigation lights and any engine hum, unlike a silent steady line or flame."
  }
 ],
 "note": "You watched a steady line of lights glide quietly before fading, matching a string of newly launched satellites in every detail; your count of about 20 dots following each other also fits this answer.",
 "closing": "This sighting looks solved: a string of newly launched satellites explains what you saw."
}

Orange lights drifting - likely

Input:

{
 "task": "sort-sighting",
 "sighting": "when: at night, well after dusk\nhow long: not sure\nlights: 2 to 5 separate lights\ncolour: orange and flickering, like a flame\nmotion: drifted slowly, as if on the wind\nsound: not sure\nending: not sure\nheight: not sure",
 "notes": "Saw them from the car park; music was playing somewhere nearby.",
 "fit_table": "S1 lantern (sky lanterns) | fits 4 of 4 known: when, lights, colour, motion | against: none | open: none\nS2 drone (a drone) | fits 2 of 4 known: when, lights | against: none | open: colour, motion\nS3 aircraft (an aircraft) | fits 1 of 4 known: lights | against: motion | open: when, colour\nS4 satellite (a single satellite or space station) | fits 1 of 4 known: when | against: motion | open: lights, colour\nS5 planet (a bright planet or star) | fits 1 of 4 known: when | against: motion | open: lights, colour\nS6 meteor (a meteor or fireball) | fits 1 of 4 known: when | against: motion | open: lights, colour\nS7 balloon (a high-altitude balloon) | fits 1 of 4 known: motion | against: colour | open: when, lights\nS8 reflection (a reflection on glass) | fits 0 of 4 known: none | against: motion | open: when, lights, colour\nS9 sat-train (a string of newly launched satellites) | fits 0 of 4 known: none | against: colour, motion | open: when, lights"
}

Reply:

{
 "task": "sort-sighting",
 "verdict": "likely",
 "headline": "Likely sky lanterns drifting with the wind explain what you saw.",
 "suspects": [
  {
   "id": "lantern",
   "fits": "Matches night well after dusk, 2 to 5 separate lights, orange and flickering like a flame, and a slow wind-drift motion.",
   "against": "Nothing described argues against it.",
   "test": "Watch if the lights keep drifting passively with any breeze rather than hovering, darting, or holding a steady path."
  },
  {
   "id": "drone",
   "fits": "Matches night well after dusk and 2 to 5 separate lights.",
   "against": "Motion is open: it neither supports nor rules out this suspect here.",
   "test": "Listen for a faint buzzing motor or sudden hovering and direction changes, unlike steady flight or wind drift."
  },
  {
   "id": "aircraft",
   "fits": "Matches only the 2 to 5 separate lights described.",
   "against": "The slow wind-drift motion rules this out; aircraft move under their own power, not on the wind.",
   "test": "Check for a steady powered flight path, unlike wind-drift or hovering and sudden direction changes."
  }
 ],
 "note": "You watched 2 to 5 orange, flickering lights drift slowly with the wind at night, matching sky lanterns; the nearby music fits a lantern-release setting, and the unclear duration, sound, ending, and height leave gaps.",
 "closing": "This sighting is likely sky lanterns drifting with the wind at night."
}

Bright light low at dusk - toss-up

Input:

{
 "task": "sort-sighting",
 "sighting": "when: at dusk, within about 90 min after sunset\nhow long: 5 to 30 minutes\nlights: a single light\ncolour: steady white or pale yellow\nmotion: hovered, darted, stopped and started\nsound: no sound\nending: was still there when the watcher stopped looking\nheight: low, near the horizon",
 "notes": "It seemed to wobble a little when I stared at it.",
 "fit_table": "S1 planet (a bright planet or star) | fits 7 of 8 known: when, how long, lights, colour, sound, ending, height | against: none | open: motion\nS2 drone (a drone) | fits 6 of 8 known: when, how long, lights, colour, motion, height | against: none | open: sound, ending\nS3 balloon (a high-altitude balloon) | fits 6 of 8 known: when, how long, lights, colour, sound, ending | against: none | open: motion, height\nS4 aircraft (an aircraft) | fits 4 of 8 known: how long, lights, colour, sound | against: none | open: when, motion, ending, height\nS5 reflection (a reflection on glass) | fits 0 of 8 known: none | against: motion | open: when, how long, lights, colour, sound, ending, height\nS6 lantern (sky lanterns) | fits 5 of 8 known: when, how long, lights, sound, height | against: motion, ending | open: colour\nS7 satellite (a single satellite or space station) | fits 4 of 8 known: when, lights, colour, sound | against: motion, ending | open: how long, height\nS8 sat-train (a string of newly launched satellites) | fits 4 of 8 known: when, how long, colour, sound | against: lights, motion, ending | open: height\nS9 meteor (a meteor or fireball) | fits 3 of 8 known: lights, colour, sound | against: how long, motion, ending | open: when, height"
}

Reply:

{
 "task": "sort-sighting",
 "verdict": "toss-up",
 "headline": "This could be either a bright planet or star, or a drone.",
 "suspects": [
  {
   "id": "planet",
   "fits": "Matches dusk timing, 5 to 30 minutes, a single light, steady white or pale yellow, no sound, still there at the end, and low height.",
   "against": "Motion is open: it neither supports nor rules out this suspect here.",
   "test": "Watch closely; a planet stays fixed against the background, unlike a hovering, darting drone or a drifting balloon."
  },
  {
   "id": "drone",
   "fits": "Matches dusk timing, 5 to 30 minutes, a single light, steady white or pale yellow, the hovering and darting motion, and low height.",
   "against": "Sound is open: it neither supports nor rules out this suspect here.",
   "test": "Listen for a faint motor hum and watch for sudden stops or darts, unlike a steady planet or slow balloon."
  },
  {
   "id": "balloon",
   "fits": "Matches dusk timing, 5 to 30 minutes, a single light, steady white or pale yellow, no sound, and still there at the end.",
   "against": "Motion is open: it neither supports nor rules out this suspect here.",
   "test": "Watch if it drifts slowly with any breeze rather than darting abruptly or staying perfectly fixed in place."
  }
 ],
 "note": "You watched a steady light low near the horizon, hovering and darting for 5 to 30 minutes, still there when you stopped looking; the wobble you noticed fits a twinkling planet or star, but the motion also fits a drone.",
 "closing": "It's a close call between a bright planet or star and a drone, with evidence split between them."
}

A shape that hung there - still open

Input:

{
 "task": "sort-sighting",
 "sighting": "when: at night, well after dusk\nhow long: over 30 minutes\nlights: lights held in a fixed shape\ncolour: steady white or pale yellow\nmotion: hung still or barely moved\nsound: no sound\nending: blinked out suddenly\nheight: high overhead",
 "notes": "none",
 "fit_table": "S1 planet (a bright planet or star) | fits 5 of 8 known: when, how long, colour, motion, sound | against: lights | open: ending, height\nS2 balloon (a high-altitude balloon) | fits 5 of 8 known: how long, colour, motion, sound, height | against: lights | open: when, ending\nS3 drone (a drone) | fits 4 of 8 known: when, lights, colour, ending | against: how long | open: motion, sound, height\nS4 aircraft (an aircraft) | fits 2 of 8 known: colour, sound | against: how long | open: when, lights, motion, ending, height\nS5 reflection (a reflection on glass) | fits 1 of 8 known: ending | against: motion | open: when, how long, lights, colour, sound, height\nS6 lantern (sky lanterns) | fits 3 of 8 known: when, sound, ending | against: how long, lights | open: colour, motion, height\nS7 satellite (a single satellite or space station) | fits 4 of 8 known: when, colour, sound, height | against: how long, lights, motion | open: ending\nS8 meteor (a meteor or fireball) | fits 4 of 8 known: when, colour, sound, ending | against: how long, lights, motion | open: height\nS9 sat-train (a string of newly launched satellites) | fits 3 of 8 known: colour, sound, height | against: how long, lights, motion | open: when, ending"
}

Reply:

{
 "task": "sort-sighting",
 "verdict": "still-open",
 "headline": "No everyday suspect fits every detail; a bright planet or star fits best.",
 "suspects": [
  {
   "id": "planet",
   "fits": "Matches night well after dusk, over 30 minutes, steady white or pale yellow, hanging still or barely moving, and no sound.",
   "against": "The lights held in a fixed shape rule this out; a bright planet or star is a single point.",
   "test": "Look closely for whether it is a single point of light or several lights arranged in a shape."
  },
  {
   "id": "balloon",
   "fits": "Matches over 30 minutes, steady white or pale yellow, hanging still or barely moving, no sound, and high overhead.",
   "against": "The lights held in a fixed shape rule this out; a high-altitude balloon typically shows as a single light.",
   "test": "Note whether its position slowly drifts over time, unlike a fixed star-like point or a shaped light cluster."
  },
  {
   "id": "drone",
   "fits": "Matches night well after dusk, lights held in a fixed shape, steady white or pale yellow, and a sudden blink-out ending.",
   "against": "The over 30 minute duration rules this out; drones typically can't fly that long.",
   "test": "Watch for the light shape shifting or moving, and listen for a motor hum, unlike a fixed point or balloon."
  }
 ],
 "note": "You watched lights held in a fixed shape high overhead for over 30 minutes before they blinked out suddenly; that fixed shape is what rules out a bright planet or star, which would show as a single point of light.",
 "closing": "No everyday suspect fits every detail you described, so this sighting remains unexplained for now."
}

Build the input yourself

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

const K = SkyKit;
const built = K.buildInput(K.form({ when: "night", duration: "minutes", lights: "one", colour: "white",
  motion: "straight", sound: "none", ending: "fade", height: "high", notes: "" }));
built.input.fit_table;                  // the page's 9 ranked suspect lines
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 sighting")    // the sighting 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://what-was-that.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":"what-was-that"}' | 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": "sort-sighting", "sighting": "when: at night, well after dusk\nhow long: not sure\n...", "notes": "none", "fit_table": "S1 lantern (sky lanterns) | fits 4 of 4 known: ..."}
# - 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 what-was-that:<task>:<inputHash>:a<attempt> (the one task is sort-sighting), 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="what-was-that:sort-sighting:$(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: what-was-that:sort-sighting:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json