← Almost Human
API

Drive Almost Human from your own code

The one metered task, valley-check, is available over HTTP. It takes one almost-human thing - what it is, where people meet it, how it should feel - and a realism rating from 0 to 10 with a short note for each cue it has (face, eyes, skin or surface, proportions, movement, voice, replies and timing), plus the page's own numbers. It returns the cues that give the thing away, concrete moves each marked less real, more real or keep, the cues already working, a short ride report and a verdict (match-the-cues, stylize, push-real or leave-it).

The division of labour first: the page does the arithmetic and applies a fixed rule; the model explains which cue gives it away and how to move it; the page then re-checks the verdict, the zone, the first tell and first fix, every direction, the cues named and every number against its own. Build cues and page_read the same way (see "Build the input yourself") so the lines match.

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": "...", "status": 402, "details": { ... } } }

The token is minted for this app (the guest call takes {"slug":"almost-human"}), 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, thing, where, goal, count, cues, page_read) 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 check is metered and needs a personal token.
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 numbers and the verdicts

Every run sends task: valley-check (the one task is valley-check); a missing or different one is still answered as valley-check. Human-likeness is the mean rating times 10, rounded (0 to 100); the spread is the highest rating minus the lowest. Zones: under 60 cartoon-side, 60 to 91 valley, 92 and up far-side. The verdict is checked in this order, stopping at the first match: match-the-cues (spread 5 or more), leave-it (outside the valley), push-real (in the valley, goal "passes as real"), stylize (in the valley, any other goal). The first tell and first fix name the outlier (for match-the-cues and leave-it), the highest cue (stylize) or the lowest cue (push-real); every tie goes to the cue listed earlier.

Input (every field is a string)

FieldRequiredWhat it holds
taskyesAlways valley-check.
thingyesWhat it is, at most 80 characters: Robot greeter in the bakery window.
whereyesWhere people meet it, or not given.
goalyesfriendly or cute, passes as real or not sure.
countyesHow many cues were rated (at least 2), as digits.
cuesyesOne line per rated cue: eyes: 9 of 10 | note: glass eyes that seem to follow you (note none when blank).
page_readyesThe page's numbers on one line: human-likeness, spread, zone, verdict by the rule, outlier, highest, lowest, first cue, direction and the cues not rated.
retry_notenoOnly on the page's one reformat retry, after an unusable reply.

Reply (one JSON object)

{"task": "valley-check", "verdict": "match-the-cues", "zone": "cartoon-side", "headline": "...",
 "tells": [{"cue": "eyes", "note": "..."}],
 "fixes": [{"cue": "eyes", "direction": "less-real", "move": "..."}],
 "keep": ["face", "skin"], "ride": "...", "closing": "..."}
FieldWhat it holds
verdict, zoneThe ids above; they equal the page's.
headlineWhat it is and which zone it sits in, at most 16 words.
tellsRated cues that give it away, first cue first: 1 to 3, or 0 to 1 for leave-it. Each note at most 20 words.
fixes1 to 2 for match-the-cues, 2 to 3 for stylize and push-real, exactly 1 for leave-it. Each: a rated cue, a direction (less-real, more-real or keep) and a move of at most 25 words.
keepRated cues that already work and are not tells.
rideWhere the car stops ("H of 100", the zone) and the cue that makes it feel off, at most 35 words.
closingOne sentence of at most 20 words restating the verdict.

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

Worked examples

These are the four free samples on the page: the exact input and a stand-in model's reply, unedited. Together they cover every verdict - the window robot greeter (match the cues: glass eyes on a cartoon screen face), the shop mannequin (pull it toward cartoon), the roast-chicken cake (finish the realism) and the googly-eyed vacuum (leave it be).

Window robot greeter - match the cues

Input:

{
 "task": "valley-check",
 "thing": "Robot greeter in the bakery window",
 "where": "waves at people walking past the bakery",
 "goal": "friendly or cute",
 "count": "5",
 "cues": "face: 3 of 10 | note: flat screen with a drawn-on smile\neyes: 9 of 10 | note: glass eyes that seem to follow you\nskin: 4 of 10 | note: shiny white plastic shell\nmovement: 5 of 10 | note: waves its arm in a slow loop\nvoice: 8 of 10 | note: warm recorded voice says good morning",
 "page_read": "human-likeness: 58 of 100 | spread: 6 | zone: cartoon-side | verdict by the rule: match-the-cues | outlier: eyes (9, above the mean of 5.8) | highest: eyes (9) | lowest: face (3) | first cue: eyes | direction: less-real | not rated: proportions, replies"
}

Reply:

{
 "task": "valley-check",
 "verdict": "match-the-cues",
 "zone": "cartoon-side",
 "headline": "Robot greeter in the bakery window sitting on the cartoon side.",
 "tells": [
  {
   "cue": "eyes",
   "note": "Glass eyes that seem to follow you look far too real for the rest of the robot."
  }
 ],
 "fixes": [
  {
   "cue": "eyes",
   "direction": "less-real",
   "move": "Swap the glass eyes for flat painted or printed dot eyes with no glassy shine or pupils that follow."
  }
 ],
 "keep": [
  "face",
  "skin",
  "movement",
  "voice"
 ],
 "ride": "The car stops at 58 of 100 on the cartoon side, where eyes rated 9 of 10 feel the most out of place.",
 "closing": "The cues don't agree with each other yet, so bring the odd one into line with the rest."
}

Shop mannequin - pull it toward cartoon

Input:

{
 "task": "valley-check",
 "thing": "Life-size mannequin in the coat display",
 "where": "stands by the front door of a clothes shop",
 "goal": "friendly or cute",
 "count": "4",
 "cues": "face: 8 of 10 | note: painted lips and lashes\neyes: 7 of 10 | note: painted irises that never blink\nskin: 6 of 10 | note: matte peach finish with no pores\nproportions: 9 of 10 | note: exactly adult height and build",
 "page_read": "human-likeness: 75 of 100 | spread: 3 | zone: valley | verdict by the rule: stylize | outlier: skin (6, below the mean of 7.5) | highest: proportions (9) | lowest: skin (6) | first cue: proportions | direction: less-real | not rated: movement, voice, replies"
}

Reply:

{
 "task": "valley-check",
 "verdict": "stylize",
 "zone": "valley",
 "headline": "A life-size mannequin in the coat display, sitting in the valley.",
 "tells": [
  {
   "cue": "proportions",
   "note": "Exactly adult height and build reads as a real body, not a stylised one."
  },
  {
   "cue": "face",
   "note": "Painted lips and lashes read as a fully formed, realistic face."
  },
  {
   "cue": "eyes",
   "note": "Painted irises that never blink read as lifelike rather than drawn-on."
  }
 ],
 "fixes": [
  {
   "cue": "proportions",
   "direction": "less-real",
   "move": "Resize the body with a slightly larger head and shorter limbs for a toy-like build."
  },
  {
   "cue": "face",
   "direction": "less-real",
   "move": "Simplify the painted lips and lashes into bold, flat shapes instead of fine realistic detail."
  },
  {
   "cue": "eyes",
   "direction": "less-real",
   "move": "Paint the irises as larger, simplified circles instead of fine lifelike detail."
  }
 ],
 "keep": [
  "skin"
 ],
 "ride": "The car stops at 75 of 100, in the valley, where exactly adult proportions make it feel too real for a friendly, cute read.",
 "closing": "Overall, the mannequin reads best once its realistic details are simplified toward a friendlier, more cartoon-like look."
}

Roast-chicken cake - finish the realism

Input:

{
 "task": "valley-check",
 "thing": "Cake made to look like a roast chicken",
 "where": "centrepiece for a birthday table",
 "goal": "passes as real",
 "count": "2",
 "cues": "skin: 8 of 10 | note: glossy browned icing like roasted skin\nproportions: 6 of 10 | note: drumsticks look a bit stubby",
 "page_read": "human-likeness: 70 of 100 | spread: 2 | zone: valley | verdict by the rule: push-real | outlier: skin (8, above the mean of 7) | highest: skin (8) | lowest: proportions (6) | first cue: proportions | direction: more-real | not rated: face, eyes, movement, voice, replies"
}

Reply:

{
 "task": "valley-check",
 "verdict": "push-real",
 "zone": "valley",
 "headline": "This roast-chicken cake sits in the valley.",
 "tells": [
  {
   "cue": "proportions",
   "note": "drumsticks look a bit stubby"
  }
 ],
 "fixes": [
  {
   "cue": "proportions",
   "direction": "more-real",
   "move": "Reshape the drumsticks fuller and rounder at the ends instead of stubby."
  },
  {
   "cue": "skin",
   "direction": "more-real",
   "move": "Vary the icing's browning and shine unevenly instead of smooth and uniform."
  }
 ],
 "keep": [],
 "ride": "The car stops at 70 of 100, in the valley, where the stubby drumsticks make it feel off.",
 "closing": "Overall, this cake needs to move further toward realism to pass as the real thing."
}

Googly-eyed vacuum - leave it be

Input:

{
 "task": "valley-check",
 "thing": "Robot vacuum with stick-on googly eyes",
 "where": "cleans the living room floor",
 "goal": "friendly or cute",
 "count": "3",
 "cues": "eyes: 2 of 10 | note: big wobbly googly eyes\nmovement: 3 of 10 | note: bumps into chair legs and spins\nvoice: 1 of 10 | note: beeps",
 "page_read": "human-likeness: 20 of 100 | spread: 2 | zone: cartoon-side | verdict by the rule: leave-it | outlier: movement (3, above the mean of 2) | highest: movement (3) | lowest: voice (1) | first cue: movement | direction: keep | not rated: face, skin, proportions, replies"
}

Reply:

{
 "task": "valley-check",
 "verdict": "leave-it",
 "zone": "cartoon-side",
 "headline": "A robot vacuum with googly eyes sits clearly in the cartoon side.",
 "tells": [],
 "fixes": [
  {
   "cue": "movement",
   "direction": "keep",
   "move": "Keep the bump-and-spin motion exactly as it is; skip adding smoother obstacle-avoidance navigation."
  }
 ],
 "keep": [
  "eyes",
  "voice"
 ],
 "ride": "The car parks at 20 of 100, resting firmly on the cartoon side. The bumping, spinning movement is what keeps it happily there.",
 "closing": "This googly-eyed vacuum already fits its playful cartoon look, so there's nothing here to change."
}

Build the input yourself

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

const K = ValleyKit;
const built = K.buildInput({ thing: "Talking doll", where: "", goal: "friendly", cues: {
  face: { r: 6, note: "soft painted cheeks" },
  eyes: { r: 8, note: "glossy eyes that blink" },
  voice: { r: 7, note: "giggles on cue" } } });
built.facts.h;                           // 70 - human-likeness
built.facts.verdict;                     // "stylize" - the page's own rule
const wire = K.toWire(built.input);      // POST this to /estimate and /run
// then: const reply = K.normalize(K.parseReply(job.output.output));
// K.check(reply, built).notes           // the page's re-check of the reply
// K.md(built, reply)                    // the report 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://almost-human.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":"almost-human"}' | 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": "valley-check", "thing": "Robot greeter in the bakery window", "goal": "friendly or cute", "count": "5", "cues": "face: 3 of 10 | note: flat screen with a drawn-on smile ...
2. ..."} - 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 almost-human:<task>:<inputHash>:a<attempt> (the one task is valley-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="almost-human:valley-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: almost-human:valley-check:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json