← Hic Est
Saga API
Tokens

Drive Hic Est from your own code

The one metered task, stitch, is available over HTTP. It takes one to three accounts of the same small saga (each headed [A] told by <name>) and returns a verdict on the record (the teller's version, a contested thread, or a fair chronicle), four to eight tapestry scenes - a "Here ..." caption, what happened, a motif, the people in it and which tellers describe it - plus any disputed points with each teller's version side by side, a historian's reading in eight short lines, up to three border notes and one question for the record.

The division of labour first: the page owns the sentence split, the rough stitch, the motif keywords and the source rule; the model owns the stitching and the reading; the page re-checks the reply. All of it comes from hickit.js, which you can run yourself (see Build the input yourself). Hic Est is a keepsake: it never decides who is right in a dispute.

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":"hic-est"}), 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, tellers, accounts, draft) 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: stitching a saga 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 motifs and the verdict

Every run sends task: stitch; a missing or different one is still answered as stitch. Each scene gets one motif: feast, journey, council, labour, struggle, message, omen or triumph.

The verdict is checked in order, first match wins: commissioned (shown as "the teller's version") if tellers is 1; otherwise contested if disputes has at least one entry - a point of fact two tellers cannot both be right about; otherwise chronicle.

The input

Every value is a string (objects are never sent). app.js builds it with HicKit.buildInput(form).input.

FieldRequiredWhat it holds
taskyesAlways stitch.
tellersyes1, 2 or 3 - how many accounts follow.
accountsyesEach account headed [A] told by <name> (then [B], [C]), a blank line between accounts. An account over 1,800 characters keeps its opening and its ending, with a marker where the middle was cut.
draftyesThe page's rough stitch, one line per scene: <n> | <teller letter> | <motif> | <words>. Only a guess - the model stitches its own scenes.
clip_notenoWhat the page shortened, if anything.
retry_notenoSent only on the page's single reformat retry.

The reply

The reply is one JSON object in job.output.output. The page parses it with HicKit.parseReply and HicKit.normalize (a cut-off stream is closed by HicKit.closeJson and whatever parsed is shown).

{"task": "stitch", "verdict": "commissioned | contested | chronicle", "verdict_reason": "...", "title": "...",
 "scenes": [{"n": 1, "caption": "Here ...", "gloss": "...",
             "motif": "feast | journey | council | labour | struggle | message | omen | triumph",
             "figures": ["..."], "source": "A | A+B | ...", "disputed": false}],
 "disputes": [{"point": "...", "versions": [{"teller": "A", "says": "..."}, {"teller": "B", "says": "..."}]}],
 "reading": {"slant": "...", "silence": "...", "long_cause": "...", "immediate_cause": "...", "trigger": "...",
             "changed": "...", "stayed": "...", "parallel": "..."},
 "border": ["..."], "question": "..."}
FieldWhat it holds
scenesFour to eight scenes in the order events happened. caption at most 9 words starting "Here"; gloss at most 25 words; figures zero to three names from the accounts; source the teller letters; disputed true only for a scene holding a disputed point.
disputesPoints of fact two tellers cannot both be right about, each with one version per teller. Empty for one teller or full agreement.
readingEight lines of at most 30 words: whose version it is, what the record leaves out, the slow, direct and sudden causes, what changed, what stayed, and a pattern historians know. A cause or change the accounts do not give reads "The accounts do not say."
borderZero to three small details the accounts mention in passing, at most 14 words each.
questionOne question to a named teller, or to someone the accounts never hear from.

The page then checks the reply against the accounts (HicKit.check): the verdict against the number of tellers and the disputes, four to eight scenes with known motifs, every figure named in the accounts, every scene citing a teller on screen, captions starting "Here", every dispute setting two tellers side by side, disputed scenes matching the disputes, border notes sharing words with the accounts, no he or she for anyone, and no internal field name or teller letter in the prose.

Worked examples, one per verdict

These are the three free samples on the page: the exact input and a stand-in model's reply, unedited.

The leaning cake - the teller's version (commissioned)

Input:

{
 "task": "stitch",
 "tellers": "1",
 "accounts": "[A] told by Ines\nThe bakery rang at ten to say the cake for Olu's leaving do was ready. Dev offered to collect it on his bike, and I said that was a bad idea. Forty minutes later Dev came back with the box strapped to the rack and the icing slid to one side. Hana fixed the lettering with a butter knife while I kept Olu busy in the kitchen. At three we all sang, Olu laughed at the leaning cake, and Dev took a bow. Someone had put a paper crown on the office plant. Next time I am collecting the cake myself.",
 "draft": "1 | A | message | The bakery rang at ten to say the cake for Olu's leaving do was ready.\n2 | A | journey | Dev offered to collect it on his bike, and I said that was a bad idea.\n3 | A | struggle | Forty minutes later Dev came back with the box strapped to the rack and the icing slid to \n4 | A | labour | Hana fixed the lettering with a butter knife while I kept Olu busy in the kitchen.\n5 | A | feast | At three we all sang, Olu laughed at the leaning cake, and Dev took a bow.\n6 | A | council | Someone had put a paper crown on the office plant.\n7 | A | feast | Next time I am collecting the cake myself."
}

Reply:

{
 "task": "stitch",
 "verdict": "commissioned",
 "verdict_reason": "Only Ines tells this story, with no other teller to check it against.",
 "title": "The Tapestry of the Leaning Cake",
 "scenes": [
  {
   "n": 1,
   "caption": "Here the bakery rings to announce the cake",
   "gloss": "The bakery rang at ten to say the cake for Olu's leaving do was ready.",
   "motif": "message",
   "figures": [
    "Olu"
   ],
   "source": "A",
   "disputed": false
  },
  {
   "n": 2,
   "caption": "Here Dev offers to fetch the cake by bike",
   "gloss": "Dev offered to collect the cake by bike, but Ines said that was a bad idea.",
   "motif": "council",
   "figures": [
    "Dev",
    "Ines"
   ],
   "source": "A",
   "disputed": false
  },
  {
   "n": 3,
   "caption": "Here Dev returns as the icing slides",
   "gloss": "Forty minutes later Dev came back with the box strapped to the rack and the icing had slid to one side.",
   "motif": "journey",
   "figures": [
    "Dev"
   ],
   "source": "A",
   "disputed": false
  },
  {
   "n": 4,
   "caption": "Here Hana mends the lettering with a butter knife",
   "gloss": "Hana fixed the lettering with a butter knife while Ines kept Olu busy in the kitchen.",
   "motif": "labour",
   "figures": [
    "Hana",
    "Ines",
    "Olu"
   ],
   "source": "A",
   "disputed": false
  },
  {
   "n": 5,
   "caption": "Here Olu laughs as all sing and Dev bows",
   "gloss": "At three everyone sang, Olu laughed at the leaning cake, and Dev took a bow.",
   "motif": "triumph",
   "figures": [
    "Olu",
    "Dev",
    "Ines"
   ],
   "source": "A",
   "disputed": false
  },
  {
   "n": 6,
   "caption": "Here Ines vows to collect the cake next time",
   "gloss": "Ines decided that next time Ines would collect the cake in person.",
   "motif": "council",
   "figures": [
    "Ines"
   ],
   "source": "A",
   "disputed": false
  }
 ],
 "disputes": [],
 "reading": {
  "slant": "The account favours Ines, who warned against the bike, kept Olu occupied during the repair, and resolved to collect the cake directly next time.",
  "silence": "The account never says who put the paper crown on the office plant or why.",
  "long_cause": "Olu's leaving do meant a cake had to be collected from the bakery.",
  "immediate_cause": "Dev chose to collect the cake by bike, which Ines had called a bad idea.",
  "trigger": "The accounts do not say.",
  "changed": "Ines now intends to collect the cake in person next time.",
  "stayed": "Olu's leaving do still went ahead with singing and a cake despite the icing sliding to one side.",
  "parallel": "This resembles a chronicle written for the winner, since Ines, whose warning proved right, is the only one telling the tale."
 },
 "border": [
  "Someone had put a paper crown on the office plant"
 ],
 "question": "Who put the paper crown on the office plant, and why was it there?"
}

The night the lights went out - a fair chronicle

Input:

{
 "task": "stitch",
 "tellers": "2",
 "accounts": "[A] told by Sam\nAt about eight the lights on our street went out with a bang from the substation. Rhea knocked on my door with a torch and we checked on Mr Okafor at number nine, who was fine and already lighting candles. Rhea's phone still had signal, so we called the power company and they said two hours. We carried my camping stove out to the front steps and made tea for whoever came by. The lights came back just after ten and everyone on the steps cheered.\n\n[B] told by Rhea\nWhen the power went out around eight I grabbed my torch and went to Sam's. We knocked at number nine first, where Mr Okafor had candles going and a cat on his lap. I rang the power company on my phone and they promised about two hours. Sam's camping stove came out onto the steps and half the street had tea. When the lights came back a little after ten, the steps cheered.",
 "draft": "1 | A | omen | At about eight the lights on our street went out with a bang from the substation.\n2 | A | council | Rhea knocked on my door with a torch and we checked on Mr Okafor at number nine, who was f\n3 | A | message | Rhea's phone still had signal, so we called the power company and they said two hours.\n4 | A | feast | We carried my camping stove out to the front steps and made tea for whoever came by.\n5 | A | triumph | The lights came back just after ten and everyone on the steps cheered."
}

Reply:

{
 "task": "stitch",
 "verdict": "chronicle",
 "verdict_reason": "Sam and Rhea agree on every point, from the bang that cut the lights to the cheer when they came back.",
 "title": "The Tapestry of the Steps and the Stove",
 "scenes": [
  {
   "n": 1,
   "caption": "Here the street's lights go out with a bang",
   "gloss": "At about eight the lights went out on the street with a bang from the substation.",
   "motif": "omen",
   "figures": [],
   "source": "A+B",
   "disputed": false
  },
  {
   "n": 2,
   "caption": "Here Rhea and Sam check on Mr Okafor",
   "gloss": "Rhea came to Sam's with a torch and they found Mr Okafor fine, already lighting candles.",
   "motif": "council",
   "figures": [
    "Rhea",
    "Sam",
    "Mr Okafor"
   ],
   "source": "A+B",
   "disputed": false
  },
  {
   "n": 3,
   "caption": "Here Rhea and Sam call the power company",
   "gloss": "Rhea's phone still had signal, so the power company was called and said two hours.",
   "motif": "message",
   "figures": [
    "Rhea",
    "Sam"
   ],
   "source": "A+B",
   "disputed": false
  },
  {
   "n": 4,
   "caption": "Here the camping stove comes out for tea",
   "gloss": "Sam's camping stove came out onto the front steps, and tea was made for whoever came by, half the street joining in.",
   "motif": "feast",
   "figures": [
    "Sam",
    "Rhea"
   ],
   "source": "A+B",
   "disputed": false
  },
  {
   "n": 5,
   "caption": "Here the lights return and the steps cheer",
   "gloss": "Just after ten the lights came back and everyone on the steps cheered.",
   "motif": "triumph",
   "figures": [],
   "source": "A+B",
   "disputed": false
  }
 ],
 "disputes": [],
 "reading": {
  "slant": "Sam and Rhea both tell the outage as a smooth joint effort, leaving little room for how other neighbours beyond Mr Okafor experienced it.",
  "silence": "Mr Okafor is checked on but never gives an account, and the two hours between the call and the lights returning go undescribed.",
  "long_cause": "Sam and Rhea live on the same street, close enough to look in on each other.",
  "immediate_cause": "The power went out on the street, leaving everyone without lights.",
  "trigger": "A bang from the substation at about eight set off the power cut.",
  "changed": "The lights are back on, ending the outage that had left the street without power.",
  "stayed": "The accounts do not say.",
  "parallel": "This resembles two companions recounting a small rescue they carried out together, their matching memories reflecting a shared vantage rather than independent proof."
 },
 "border": [
  "A cat sat on Mr Okafor's lap.",
  "Mr Okafor lives at number nine."
 ],
 "question": "What was it like at number nine during the two hours before the lights came back, Mr Okafor?"
}

The missing music folder - a contested thread

Input:

{
 "task": "stitch",
 "tellers": "2",
 "accounts": "[A] told by Nell\nAfter choir practice on Thursday, Bram and I took the bus home together. Bram was carrying the folder with everyone's sheet music for Sunday's concert. I got off at Mill Road and Bram stayed on. At nine Bram texted asking if I had the folder, and I said no, Bram had it. We spent Friday morning phoning around. The bus depot's lost property had it, and Bram collected it at lunchtime. The concert went fine.\n\n[B] told by Bram\nNell and I caught the bus after choir practice. I handed Nell the folder at the Mill Road stop because Nell's bag was bigger, and Nell got off there. At nine I texted to check Nell had it, and the answer was that I did. On Friday morning we rang everywhere we could think of. It was at the depot's lost property, so I went over at lunch and brought it back. Sunday's concert went off without a hitch.",
 "draft": "1 | A | journey | After choir practice on Thursday, Bram and I took the bus home together.\n2 | A | labour | Bram was carrying the folder with everyone's sheet music for Sunday's concert.\n3 | A | journey | I got off at Mill Road and Bram stayed on.\n4 | A | message | At nine Bram texted asking if I had the folder, and I said no, Bram had it.\n5 | A | message | We spent Friday morning phoning around.\n6 | A | journey | The bus depot's lost property had it, and Bram collected it at lunchtime.\n7 | A | council | The concert went fine."
}

Reply:

{
 "task": "stitch",
 "verdict": "contested",
 "verdict_reason": "Nell and Bram disagree about whether Bram handed Nell the folder at Mill Road.",
 "title": "The Tapestry of the Wandering Folder",
 "scenes": [
  {
   "n": 1,
   "caption": "Here Bram and Nell ride the bus from choir",
   "gloss": "After choir practice on Thursday, Bram and Nell take the bus home together.",
   "motif": "journey",
   "figures": [
    "Nell",
    "Bram"
   ],
   "source": "A+B",
   "disputed": false
  },
  {
   "n": 2,
   "caption": "Here Nell steps off the bus at Mill Road",
   "gloss": "Bram was carrying the folder; Nell says Bram kept it, but Bram says it was handed to Nell at the Mill Road stop.",
   "motif": "journey",
   "figures": [
    "Nell",
    "Bram"
   ],
   "source": "A+B",
   "disputed": true
  },
  {
   "n": 3,
   "caption": "Here Bram texts to ask who has the folder",
   "gloss": "At nine Bram texts to ask if Nell has the folder, and the answer is that Bram has it.",
   "motif": "message",
   "figures": [
    "Nell",
    "Bram"
   ],
   "source": "A+B",
   "disputed": false
  },
  {
   "n": 4,
   "caption": "Here Nell and Bram ring around on Friday morning",
   "gloss": "On Friday morning, Nell and Bram phone around everywhere they can think of.",
   "motif": "labour",
   "figures": [
    "Nell",
    "Bram"
   ],
   "source": "A+B",
   "disputed": false
  },
  {
   "n": 5,
   "caption": "Here Bram collects the folder from lost property",
   "gloss": "The bus depot's lost property has the folder, and Bram collects it at lunchtime.",
   "motif": "journey",
   "figures": [
    "Bram"
   ],
   "source": "A+B",
   "disputed": false
  },
  {
   "n": 6,
   "caption": "Here Sunday's concert goes off without a hitch",
   "gloss": "Sunday's concert goes fine, with nothing going wrong.",
   "motif": "triumph",
   "figures": [],
   "source": "A+B",
   "disputed": false
  }
 ],
 "disputes": [
  {
   "point": "Whether Bram handed the folder to Nell at Mill Road",
   "versions": [
    {
     "teller": "A",
     "says": "Bram was carrying the folder, and I got off at Mill Road while Bram stayed on."
    },
    {
     "teller": "B",
     "says": "I handed Nell the folder at Mill Road because Nell's bag was bigger, and Nell got off there."
    }
   ]
  }
 ],
 "reading": {
  "slant": "Each account places the folder with the other person after Mill Road, which quietly keeps blame for the mix-up away from its own teller.",
  "silence": "The record never explains how the folder ended up at the depot's lost property between Thursday and Friday.",
  "long_cause": "The choir's sheet music travels in a single shared folder instead of separate copies for each person.",
  "immediate_cause": "Bram and Nell parted ways at Mill Road without being sure which of them was keeping the folder.",
  "trigger": "Bram's text at nine, checking who has the folder, is the spark that leads to the Friday morning search.",
  "changed": "The folder goes from lost to recovered, safely back in hand before Sunday's concert.",
  "stayed": "The concert still goes ahead successfully despite the folder having gone missing.",
  "parallel": "This resembles a quarrel over a missing record, where each side's telling quietly shifts the blame for its disappearance onto the other."
 },
 "border": [
  "Nell's bag was bigger"
 ],
 "question": "Bram, did you hand the folder to Nell at Mill Road, or did you keep carrying it yourself?"
}

Build the input yourself

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

const K = HicKit;
const form = K.form({ accounts: [
  { name: "Ana", text: "The gate blew open at dawn and the goats got out. Jo chased them down the lane while I phoned the farm. By breakfast every goat was back." },
  { name: "Jo", text: "Ana left the gate unlatched, so the goats got out at dawn. I chased them down the lane. They were all back by breakfast." } ] });
K.tellers(form);                                 // [{letter: "A", name: "Ana", ...}, {letter: "B", name: "Jo", ...}]
K.roughScenes(K.tellers(form));                  // the page's rough stitch: one scene per sentence of account A
const built = K.buildInput(form);
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 tapestry and the reading 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://hic-est.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":"hic-est"}' | 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": "stitch", "tellers": "2", "accounts": "[A] told by Sam\n...\n\n[B] told by Rhea\n...", "draft": "1 | A | omen | ..."}
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 hic-est:<task>:<inputHash>:a<attempt> (the one task is stitch), 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="hic-est:stitch:$(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: hic-est:stitch:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json