← First Slip
API
Tokens

Drive First Slip from your own code

The one metered task, mark, is available over HTTP. It takes a math problem and the working, one step per line, together with the page's own check of every line, and returns a mark on every line (tick, slip, follow or note), a verdict (full_marks, one_slip, rework or unfinished), for each slip what went wrong, why and a nudge, the corrected first slipped line, done-well notes and one practice problem.

The division of labour first: the page owns the checking; the model owns the judgement and the words; the page re-checks the reply. Reading the lines and checking each against the one before it come from slipkit.js, which runs in node as well as in the browser: SlipKit.buildInput(form) gives you the exact input the page sends, and SlipKit.check(reply, built) re-checks a reply - the verdict against the rule, the marks against the page's own check, the corrected line and the practice answer solved again.

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":"first-slip"}), 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, kind, steps, figures) 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: marking 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 marks and the verdict rule

Every run sends task: mark (the only task; a missing or unknown one is treated as mark). Each line arrives with the page's check: ok (follows from the line before), slip (does not), start (first line, nothing to compare), unchecked (the page could not read it) or note (words). The marks follow, in order: a note line is marked note; a slip line is marked slip; an ok line is follow after an earlier slip, else tick; start and unchecked lines are judged by the model. The verdict, checked in this order - the first that applies wins:

  1. rework - 2 or more lines marked slip.
  2. one_slip - exactly 1.
  3. unfinished - none, and answer_reached is no.
  4. full_marks - everything else.

In solve mode two equations match when they have the same solutions (found numerically between -10000 and 10000); in simplify mode two expressions match when they agree at ten values of the letter. The declared input schema carries scalars only, so the array- and object-valued fields (steps, figures) are JSON-encoded strings.

Input fields

FieldTypeNotes
taskstringmark. Required.
kind, kind_labelstringsolve or simplify, and its plain label. kind is required.
level, level_labelstringprimary, middle, high or college, and its plain label.
problemstringThe problem as typed, up to 300 characters. May be empty.
stepsstring (JSON)[{id, text, check, detail}] - ids L1.., up to 30 lines of up to 120 characters; detail is the page's reason. Required.
figuresstring (JSON){lines, math_lines, checked, slips, first_slip, unchecked, answer_reached, answer_line, answer_text, answer_fits_problem, problem_solutions, letter}, every value a string. Required.
notesstringFree text from the learner, up to 300 characters.
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, marks (one {ref, mark, comment} per line, in order), slips (one {ref, what, why, nudge} per line marked slip), fixed_line (the first slipped line corrected, or ""), practice ({problem, answer}) and well_done (0-3 strings). Parse with SlipKit.normalize(SlipKit.parseReply(text)).

Worked examples

Replies produced against the app's prompt for the four sample pieces of working (made up; the full replies ship in example-result.js). Together they cover every verdict.

a bracket equation where every step holds - full_marks

Input:

{
 "task": "mark",
 "kind": "solve",
 "kind_label": "solve an equation",
 "level": "middle",
 "level_label": "middle school",
 "problem": "Solve 3(x - 2) + 4 = 19",
 "steps": "[{\"id\":\"L1\",\"text\":\"3x - 6 + 4 = 19\",\"check\":\"ok\",\"detail\":\"same solutions as the problem: x = 7\"},{\"id\":\"L2\",\"text\":\"3x - 2 = 19\",\"check\":\"ok\",\"detail\":\"same solutions as the line before: x = 7\"},{\"id\":\"L3\",\"text\":\"3x = 21\",\"check\":\"ok\",\"detail\":\"same solutions as the line before: x = 7\"},{\"id\":\"L4\",\"text\":\"x = 7\",\"check\":\"ok\",\"detail\":\"same solutions as the line before: x = 7\"}]",
 "figures": "{\"lines\":\"4\",\"math_lines\":\"4\",\"checked\":\"4\",\"slips\":\"0\",\"first_slip\":\"\",\"unchecked\":\"0\",\"answer_reached\":\"yes\",\"answer_line\":\"L4\",\"answer_text\":\"x = 7\",\"answer_fits_problem\":\"yes\",\"problem_solutions\":\"x = 7\",\"letter\":\"x\"}",
 "notes": "",
 "clip_note": ""
}

Reply:

{
 "task": "mark",
 "verdict": "full_marks",
 "title": "Full marks - every step holds",
 "summary": "Full marks - the working holds all the way from the problem to x = 7. Try the practice problem to practice the same skill.",
 "marks": [
  {
   "ref": "L1",
   "mark": "tick",
   "comment": "Expands the bracket correctly: 3 times (x - 2) gives 3x - 6."
  },
  {
   "ref": "L2",
   "mark": "tick",
   "comment": "Combines -6 and 4 to get -2 on the left side."
  },
  {
   "ref": "L3",
   "mark": "tick",
   "comment": "Adds 2 to both sides: 19 + 2 = 21."
  },
  {
   "ref": "L4",
   "mark": "tick",
   "comment": "Divides both sides by 3 to reach x = 7."
  }
 ],
 "slips": [],
 "fixed_line": "",
 "practice": {
  "problem": "Solve 4(x - 1) + 3 = 23",
  "answer": "x = 6"
 },
 "well_done": [
  "Line 1 expands 3 times the bracket to 3x - 6.",
  "Line 3 adds 2 to both sides to reach 3x = 21.",
  "Line 4 divides by 3 to find x = 7."
 ]
}

moving terms across with one sign slip, carried through - one_slip

Input:

{
 "task": "mark",
 "kind": "solve",
 "kind_label": "solve an equation",
 "level": "middle",
 "level_label": "middle school",
 "problem": "Solve 5x - 3 = 2x + 9",
 "steps": "[{\"id\":\"L1\",\"text\":\"5x - 2x = 9 - 3\",\"check\":\"slip\",\"detail\":\"solutions change: the problem has x = 4, this line has x = 2\"},{\"id\":\"L2\",\"text\":\"3x = 6\",\"check\":\"ok\",\"detail\":\"same solutions as the line before: x = 2\"},{\"id\":\"L3\",\"text\":\"x = 2\",\"check\":\"ok\",\"detail\":\"same solutions as the line before: x = 2\"}]",
 "figures": "{\"lines\":\"3\",\"math_lines\":\"3\",\"checked\":\"3\",\"slips\":\"1\",\"first_slip\":\"L1\",\"unchecked\":\"0\",\"answer_reached\":\"yes\",\"answer_line\":\"L3\",\"answer_text\":\"x = 2\",\"answer_fits_problem\":\"no\",\"problem_solutions\":\"x = 4\",\"letter\":\"x\"}",
 "notes": "I always get mixed up moving numbers across the equals sign.",
 "clip_note": ""
}

Reply:

{
 "task": "mark",
 "verdict": "one_slip",
 "title": "One sign slip when moving the 3.",
 "summary": "This has one slip: line 1 did not flip the sign of the 3 when moving it across the equals sign. Fix line 1, then carry the correction through the rest of the working.",
 "marks": [
  {
   "ref": "L1",
   "mark": "slip",
   "comment": "Did not flip the sign of the 3 across the equals sign."
  },
  {
   "ref": "L2",
   "mark": "follow",
   "comment": "Correctly combines 5x - 2x and 9 - 3 into 3x = 6, following the line before."
  },
  {
   "ref": "L3",
   "mark": "follow",
   "comment": "Correctly solves 3x = 6 to reach x = 2, following the line before."
  }
 ],
 "slips": [
  {
   "ref": "L1",
   "what": "The 3 did not flip sign when it moved across the equals sign, so the line shows 9 - 3.",
   "why": "A term's sign must flip when it crosses the equals sign; this term kept its original sign instead.",
   "nudge": "Look at the sign of the 3 when it crosses the equals sign."
  }
 ],
 "fixed_line": "5x - 2x = 9 + 3",
 "practice": {
  "problem": "Solve 4x - 5 = x + 13",
  "answer": "x = 6"
 },
 "well_done": [
  "Line 2 correctly simplifies the line before to 3x = 6.",
  "Line 3 correctly solves 3x = 6 to reach x = 2."
 ]
}

expanding a square with two separate slips - rework

Input:

{
 "task": "mark",
 "kind": "simplify",
 "kind_label": "simplify, expand or work out",
 "level": "high",
 "level_label": "high school",
 "problem": "Expand and simplify (x + 3)^2 - 2(x - 1)",
 "steps": "[{\"id\":\"L1\",\"text\":\"= x^2 + 9 - 2(x - 1)\",\"check\":\"slip\",\"detail\":\"not equal to the problem: at x = 2 this gives 11 but the problem gives 23\"},{\"id\":\"L2\",\"text\":\"= x^2 + 9 - 2x - 2\",\"check\":\"slip\",\"detail\":\"not equal to the line before: at x = 2 this gives 7 but the line before gives 11\"},{\"id\":\"L3\",\"text\":\"= x^2 - 2x + 7\",\"check\":\"ok\",\"detail\":\"equal to the line before\"}]",
 "figures": "{\"lines\":\"3\",\"math_lines\":\"3\",\"checked\":\"3\",\"slips\":\"2\",\"first_slip\":\"L1\",\"unchecked\":\"0\",\"answer_reached\":\"yes\",\"answer_line\":\"L3\",\"answer_text\":\"= x^2 - 2x + 7\",\"answer_fits_problem\":\"no\",\"problem_solutions\":\"\",\"letter\":\"x\"}",
 "notes": "",
 "clip_note": ""
}

Reply:

{
 "task": "mark",
 "verdict": "rework",
 "title": "Two slips before the correct-looking line",
 "summary": "This needs reworking: two lines don't follow, starting at line 1. Fix line 1, then carry the correction through to finish the problem.",
 "marks": [
  {
   "ref": "L1",
   "mark": "slip",
   "comment": "Dropped the middle term when squaring (x + 3)."
  },
  {
   "ref": "L2",
   "mark": "slip",
   "comment": "Wrong sign when distributing -2 across (x - 1)."
  },
  {
   "ref": "L3",
   "mark": "follow",
   "comment": "Correctly simplifies the terms carried from line 2."
  }
 ],
 "slips": [
  {
   "ref": "L1",
   "what": "The middle term from squaring (x + 3) is missing from this line.",
   "why": "Squaring (x + 3) gives x^2 plus a middle term plus 3^2; the middle term is missing here.",
   "nudge": "When you multiply (x + 3) by (x + 3), how many terms do you get before combining them?"
  },
  {
   "ref": "L2",
   "what": "Distributing -2 over (x - 1) put the wrong sign on the second term of that bracket.",
   "why": "Multiplying -2 by -1 should give a positive result, since a negative number times a negative number is positive.",
   "nudge": "What is -2 times -1?"
  }
 ],
 "fixed_line": "= x^2 + 6x + 9 - 2(x - 1)",
 "practice": {
  "problem": "Expand and simplify (x + 4)^2 - 3(x - 2)",
  "answer": "x^2 + 5x + 22"
 },
 "well_done": [
  "Line 3 correctly combines the terms carried from line 2."
 ]
}

a quadratic factored but never solved - unfinished

Input:

{
 "task": "mark",
 "kind": "solve",
 "kind_label": "solve an equation",
 "level": "high",
 "level_label": "high school",
 "problem": "Solve x^2 - 5x + 6 = 0",
 "steps": "[{\"id\":\"L1\",\"text\":\"(x - 2)(x - 3) = 0\",\"check\":\"ok\",\"detail\":\"same solutions as the problem: x = 2 or x = 3\"},{\"id\":\"L2\",\"text\":\"Now I am not sure what to do next\",\"check\":\"note\",\"detail\":\"words - read as a note\"}]",
 "figures": "{\"lines\":\"2\",\"math_lines\":\"1\",\"checked\":\"1\",\"slips\":\"0\",\"first_slip\":\"\",\"unchecked\":\"0\",\"answer_reached\":\"no\",\"answer_line\":\"\",\"answer_text\":\"\",\"answer_fits_problem\":\"unknown\",\"problem_solutions\":\"x = 2 or x = 3\",\"letter\":\"x\"}",
 "notes": "Got stuck after factoring.",
 "clip_note": ""
}

Reply:

{
 "task": "mark",
 "verdict": "unfinished",
 "title": "Good factoring, but not finished yet",
 "summary": "The working holds all the way through line 1, but is not finished. Finish the last step to find x from (x - 2)(x - 3) = 0.",
 "marks": [
  {
   "ref": "L1",
   "mark": "tick",
   "comment": "Correctly factored into (x - 2)(x - 3)."
  },
  {
   "ref": "L2",
   "mark": "note",
   "comment": ""
  }
 ],
 "slips": [],
 "fixed_line": "",
 "practice": {
  "problem": "Solve x^2 - 7x + 12 = 0",
  "answer": "x = 3 or x = 4"
 },
 "well_done": [
  "Line 1 factors the quadratic correctly as (x - 2)(x - 3)."
 ]
}

Build the input yourself

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

const K = SlipKit;
const form = K.form({ problem: "Solve 2(x + 3) = 14", kind: "solve", level: "middle",
  working: "2x + 6 = 14\n2x = 8\nx = 4" });
const r = K.read(form);                            // r.steps: each line with its check; r.fig: the figures
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.csv(built, reply)                            // the lines, one row each, with the marks

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://first-slip.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":"first-slip"}' | 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": "mark", "kind": "solve", "level": "middle", "problem": "Solve 5x - 3 = 2x + 9", "steps": "[{\"id\":\"L1\",\"text\":\"5x - 2x = 9 - 3\",\"check\":\"slip\",\"detail\":\"solutions change: the problem has x = 4, this line has x = 2\"},{\"id\":\"L2\",\"text\":\"3x = 6\",\"check\":\"ok\",\"detail\":\"same solutions as the line before: x = 2\"}, ...]", "figures": "{...}", "notes": "I always get mixed up moving numbers across the equals sign."}
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 first-slip:<task>:<inputHash>:a<attempt> (the one task is mark), 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="first-slip:mark:$(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: first-slip:mark:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json