← Proof Rack
API
Tokens

Drive Proof Rack from your own code

The one metered lane, audit, is available over HTTP. It takes the claims a launch page makes and returns a verdict - walk-away, ask-first, back-it (shown on the page as "Holds up") or needs-claims - with one audit per claim: its rung on the evidence ladder (asserted, shown, measured, checked), the one flaw that weakens it most (phantom, strawman, cherry-pick, thin-test or none), a reason and a question to send the creator. A natural use is a reading-list script that audits every campaign you bookmarked before the early-bird price ends.

The division of labour first: the page finds the claims; the model audits them; the page re-checks the reply. The claim finder and the clipping come from rackkit.js, which runs in node as well as in the browser. RackKit.buildInput splits the page into sentences, keeps up to 12 that make a claim, and marks what each one shows. RackKit.check holds each reply to the claims: one audit per claim, the verdict recomputed from the reply's own rungs and flaws, top-rung claims that name no outside checker, renders called "shown", numbers the claims never state, and accusing words.

Get a token on the token page (step 2 below also shows how to mint a guest token in code).

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":"proof-rack"}), 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 such as task or claims 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: the audit lane 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 and the verdicts

Every run sends task: audit, the only lane; a missing or unknown task is treated as audit. Each claim gets a rung and a flaw first (the flaws are checked in the order listed above, the first that applies wins); the verdict then follows from them by four ordered, non-overlapping rules:

OrderVerdictWhen
1needs-claimsFewer than two claims were sent. claims is empty; asks holds two or three questions about what to look for.
2walk-awayTwo or more claims have flaw phantom (they rest on a render, a projection or an unbuilt product, or are health, accuracy or breakthrough claims that are only asserted).
3back-itEvery claim has flaw none and at least one is checked by an outside party. It means the claims hold up as written - not that the product is good.
4ask-firstEverything else.

The declared input schema carries scalars only, so the array-valued claims field travels JSON-encoded as a string (RackKit.toWire does this). Every value is a string.

Input fields

FieldTypeNotes
taskstringaudit. Required.
claimsstring (JSON)[{id, text, marks}] - ids C1.., up to 12 sentences of up to 300 characters; marks are the page's hints: number, up-to, relative, vs-unnamed, proof-outside, proof-own, shown, render, sample:N, hype, health. Required.
product, askingstringThe product's name and what the launch wants from the reader (a pledge, a preorder), as typed. Optional.
clip_note, retry_notestringWhat the page cut, when it cut something; and only on a reformat retry, what was wrong.

The reply

The job's output.output is a string holding one JSON object: task, verdict, title, summary, claims ([{id, rung, flaw, why, ask}] - one per input claim, in order; ask is empty only for a checked claim with flaw none) and asks (only for needs-claims). Parse with RackKit.parseReply, then normalize(reply), then check(reply, form), which returns the page's notes.

Worked examples

Replies produced against the app's prompt for the four sample pages (made-up products; long fields and lists shortened; the full replies ship in example-result.js). Together they cover every verdict, every rung and every flaw.

audit - a smart bottle that claims to read your health, six claims: walk-away

Input:

{
 "task": "audit",
 "product": "Vellisip smart bottle",
 "asking": "a $149 early-bird pledge",
 "claims": "[{\"id\":\"C1\",\"text\":\"Meet Vellisip, the revolutionary bottle that changes how you look after your health.\",\"marks\":[\"hype\",\"health\"]},{\"id\":\"C2\",\"text\":\"It is the world's first bottle that tests 120 health markers from a single sip of water in under 60 seconds.\",\"marks\":[\"number\",\"hype\",\"health\"]},{\"id\":\"C3\",\"text\":\"Our AI is 99.9% accurate at spotting vitamin deficiencies, according to our projected results.\",\"marks\":[\"number\",\"render\",\"health\"]},{\"id\":\"C4\",\"text\":\"The renders above show the final design, which wil ... (808 characters)"
}

Reply:

{
 "task": "audit",
 "verdict": "walk-away",
 "title": "Vellisip: renders and projected results",
 "summary": "Six claims describe Vellisip, and five of them rest on renders, projected results, or broad health and breakthrough language rather than anything built or tested. The remaining claim compares its price to traditional blood tests without naming which ones. Independent test results, footage of a working unit, and a named point of comparison would change this picture before any pledge.",
 "claims": [
  {
   "id": "C1",
   "rung": "asserted",
   "flaw": "phantom",
   "why": "This claim says Vellisip changes how you look after your health, but it names no test, study, or result behind that change.",
   "ask": "What evidence or result backs the claim that Vellisip changes how you look after your health?"
  },
  {
   "id": "C2",
   "rung": "asserted",
   "flaw": "phantom",
   "why": "This claim says the bottle is the world's first to test 120 health markers from a single sip of water in under 60 seconds, but it gives no test or result showing it does this.",
   "ask": "What test or result shows the bottle testing 120 health markers from a single sip of water in under 60 seconds?"
  },
  {
   "id": "C3",
   "rung": "asserted",
   "flaw": "phantom",
   "why": "This claim gives a 99.9% accuracy figure for spotting vitamin deficiencies, but says it comes from projected results rather than a result from a built product.",
   "ask": "What actual test result, rather than a projection, shows the AI's accuracy at spotting vitamin deficiencies?"
  },
  "... (6 in all)"
 ],
 "asks": []
}

audit - sleep earbuds with mixed evidence, five claims: ask-first

Input:

{
 "task": "audit",
 "product": "Dozuvo sleep earbuds",
 "asking": "a $89 preorder",
 "claims": "[{\"id\":\"C1\",\"text\":\"Each bud weighs 4 g and sits flush inside the ear.\",\"marks\":[\"number\"]},{\"id\":\"C2\",\"text\":\"Get up to 40 hours of battery life from the charging case.\",\"marks\":[\"number\",\"up-to\"]},{\"id\":\"C3\",\"text\":\"Dozuvo blocks 3x more noise than ordinary foam earplugs.\",\"marks\":[\"number\",\"relative\",\"vs-unnamed\"]},{\"id\":\"C4\",\"text\":\"In our own tests with 18 beta testers, 16 said they fell asleep faster.\",\"marks\":[\"number\",\"proof-own\",\"sample:18\"]},{\"id\":\"C5\",\"text\":\"Watch our working prototype in the demo video ... (585 characters)"
}

Reply:

{
 "task": "audit",
 "verdict": "ask-first",
 "title": "Dozuvo sleep earbuds battery and noise",
 "summary": "Five claims, three resting on a best case, an unnamed baseline or a small in-house test; the weight and fit claim and the demo video claim stand on their own. Naming the compared earplugs, sharing a typical battery figure, and testing a larger group would make the picture clearer before backing it.",
 "claims": [
  {
   "id": "C1",
   "rung": "asserted",
   "flaw": "none",
   "why": "This states the bud's weight and fit, which the buyer can check once it arrives.",
   "ask": "Will an outside reviewer confirm the weight and fit?"
  },
  {
   "id": "C2",
   "rung": "asserted",
   "flaw": "cherry-pick",
   "why": "This gives the top end of the battery life range, not the figure a typical user would get.",
   "ask": "What is the typical battery life for most users?"
  },
  {
   "id": "C3",
   "rung": "asserted",
   "flaw": "strawman",
   "why": "This compares the noise blocked to ordinary foam earplugs, which is not a named product, so the comparison cannot be checked.",
   "ask": "Which specific earplug products were compared to get that figure?"
  },
  "... (5 in all)"
 ],
 "asks": []
}

audit - a lab-tested, already-delivered pocket stove, three claims: back-it

Input:

{
 "task": "audit",
 "product": "Flarnick pocket stove",
 "asking": "a $59 pledge",
 "claims": "[{\"id\":\"C1\",\"text\":\"The Flarnick pocket stove is made of titanium and weighs 86 g.\",\"marks\":[\"number\"]},{\"id\":\"C2\",\"text\":\"In tests by an independent outdoor-gear lab, it boiled 1 litre of water in 4 minutes 10 seconds; the lab's full report is posted in our updates.\",\"marks\":[\"number\",\"proof-outside\",\"proof-own\"]},{\"id\":\"C3\",\"text\":\"1,200 stoves from our first campaign were delivered to backers in March.\",\"marks\":[\"number\",\"proof-outside\"]}]"
}

Reply:

{
 "task": "audit",
 "verdict": "back-it",
 "title": "Titanium stove, independent tests, delivered",
 "summary": "Three claims: a titanium build and weight you can check on delivery, a boil-time test run by an independent outdoor-gear lab with the report posted, and a report that stoves from an earlier campaign already reached backers. All three hold up as written, with two backed by an outside check, so the claims back the pledge. That means the claims stand as written, not that the stove is worth the money.",
 "claims": [
  {
   "id": "C1",
   "rung": "asserted",
   "flaw": "none",
   "why": "This states the stove's material and weight, a plain spec the buyer can confirm when it arrives.",
   "ask": "Will you publish a measured weight from a finished production unit?"
  },
  {
   "id": "C2",
   "rung": "checked",
   "flaw": "none",
   "why": "An independent outdoor-gear lab measured the boil time and the full report is posted for anyone to read.",
   "ask": ""
  },
  {
   "id": "C3",
   "rung": "checked",
   "flaw": "none",
   "why": "Stoves from the earlier campaign have already been delivered to backers, which backers themselves can confirm.",
   "ask": ""
  }
 ],
 "asks": []
}

audit - a lamp page with one mood claim: needs-claims

Input:

{
 "task": "audit",
 "product": "Glimmowa lamp",
 "asking": "",
 "claims": "[{\"id\":\"C1\",\"text\":\"Glimmowa is the first lamp designed by people who love mornings.\",\"marks\":[\"hype\"]}]"
}

Reply:

{
 "task": "audit",
 "verdict": "needs-claims",
 "title": "Glimmowa lamp designed by people",
 "summary": "Only one claim came through, and a rack needs at least two before it can be compared on rungs and flaws. The one claim says Glimmowa is the first lamp designed by people who love mornings, which describes the product rather than stating anything that can be checked. A few more claims, especially ones with numbers or tests behind them, would make a real audit possible.",
 "claims": [],
 "asks": [
  "What does the lamp do, in numbers?",
  "What tests, if any, were run, and by whom?",
  "Has anything already been delivered to customers or backers?"
 ]
}

Build the input yourself

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

const K = RackKit;
const form = K.form({ product: "Dozuvo sleep earbuds", asking: "an $89 preorder",
  page: "Get up to 40 hours of battery life.\nBlocks 3x more noise than ordinary foam earplugs.\nIn our own tests with 18 beta testers, 16 slept better." });
console.log(K.flags(form), K.guess(form), K.lean(form));   // the flags, the page's reading per claim, its lean
const wire = K.toWire(K.buildInput("audit", form).input);  // POST this to /estimate and /run
// then: const audit = K.normalize(K.parseReply(job.output.output));
// const { notes } = K.check(audit, form);                   // the page's re-check of the reply
// K.letter(audit, form)                                     // the questions for the creator, as a message

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://proof-rack.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":"proof-rack"}' | 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": "audit", "product": "Dozuvo sleep earbuds", "asking": "a $89 preorder", "claims": "[{\"id\":\"C1\",\"text\":\"Each bud weighs 4 g and sits flush inside the ear.\",\"marks\":[\"number\"]},{\"id\":\"C2\",\"text\":\"Get up to 40 hours of battery life from the charging case.\",\"marks\":[\"number\",\"up-to\"]}, ...]"}
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 proof-rack:<task>:<inputHash>:a<attempt> (the one lane is audit), 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="proof-rack:audit:$(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: proof-rack:audit:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json