← Mirror Reps
API
Tokens

Drive Mirror Reps from your own code

Two metered tasks are available over HTTP. ladder takes the steps of one magic trick, with the page's own reading of every step and its practice-time figures, and returns a practice ladder: 3 to 8 rungs, each with its steps, a drill, an observable pass check and practice minutes; one angle fix per hidden move that shows from the side; one line to say over each hidden move; a day-by-day plan; and a verdict (ready_in_time, tight or swap_trick). debrief takes the same trick, the ladder being practised and what happened in a run-through, and returns findings quoted from the notes, the rung to go back to, fixes, the next run, what to keep, and a verdict (perform_again, drop_back or rebuild).

The division of labour first: the page owns the reading; the model owns the judgement and the words; the page re-checks the reply. Sorting the steps, the practice estimate, the angle flags, the draft rungs and the run-note cues come from repkit.js, which runs in node as well as in the browser: RepKit.buildInput(form, task, ctx) gives you the exact input the page sends, and RepKit.check(reply, built) re-checks a reply. Neither the page nor the coach ever explains a secret method beyond the steps you send.

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":"mirror-reps"}), 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, 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: both tasks are metered and need 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 verdict rules

Every run sends task first: ladder or debrief (a missing or unknown one is treated as ladder). Each step arrives sorted by the page into a kind - setup, sleight (a hidden move), handling, patter or reveal - with angle = yes for a hidden move that can be seen from the side. The practice estimate (per hidden move 90 / 60 / 30 minutes for first / few / regular; 15 / 10 / 5 per open move or reveal; 10 / 8 / 5 per line; 5 / 5 / 3 per set-up; +15 per angle move with watchers wide, +30 surrounded; plus 3 minutes a step for full run-throughs) is the page's rule of thumb, not a measurement.

ladder, checked in this order - the first that applies wins:

  1. swap_trick - fit is short (your days x minutes is under 60% of the estimate).
  2. tight - fit is tight (under the estimate), or angle_risk is high.
  3. ready_in_time - everything else.

debrief, from the reply's own findings, checked in this order:

  1. rebuild - two or more findings of kind caught.
  2. drop_back - at least one caught, flash or fumble.
  3. perform_again - everything else.

The declared input schema carries scalars only, so the array- and object-valued fields are JSON-encoded strings.

Input fields

FieldTypeNotes
taskstringladder or debrief. Required.
trickstringWhat you call the trick, up to 80 characters. May be empty.
level, level_labelstringfirst, few or regular, and its plain label.
audience, audience_labelstringfamily_table, friends_standing, kids_group or stage, and its plain label.
angles, angles_labelstringfront, wide or surrounded, and its plain label.
stepsstring (JSON)[{id, text, kind, angle}] - ids S1.., up to 16 steps of up to 160 characters. Required.
figuresstring (JSON){steps, sleights, angle_moves, days, minutes_per_day, budget_minutes, need_minutes, plan_minutes, fit, angle_risk}, every value a string. Required.
notesstringFree text, up to 300 characters.
rungsstring (JSON)debrief only: [{id, name, steps}], ids R1.. - the ladder being practised (a ladder reply's rungs, or the page's draft).
passedstring (JSON)debrief only: the rung ids ticked as passed.
run_no, run_notesstringdebrief only: which run-through, and what happened (up to 1,500 characters; a longer note loses its middle, marked [...]).
cuesstring (JSON)debrief only: {caught, flash, fumble, pace, reaction} - counts of cue words the page spotted in run_notes.
clip_note, retry_notestringWhat the page cut, when it cut something; and only on a reformat retry, what was wrong.

The replies

The job's output.output is a string holding one JSON object. ladder: task, verdict, title, summary, rungs ([{id, name, steps, drill, pass_check, minutes}]), sightlines ([{ref, risk, fix}]), cover_lines ([{ref, line}]), schedule ([{days, rungs, focus}]) and swap_hint. debrief: task, verdict, title, summary, findings ([{quote, kind, ref, meaning}]), back_to, fixes ([{ref, change, drill}]), next_run ({who, focus, check}) and keep. Parse with RepKit.normalize(RepKit.parseReply(text), task).

Worked examples

Replies produced against the app's prompt for the six samples - three made-up tricks and three made-up run-throughs of one of them (the full replies ship in example-result.js). Together they cover every verdict of both tasks.

ladder: a trick that works itself, ten days, everyone in front - ready_in_time

Input:

{
 "task": "ladder",
 "trick": "Four piles, four aces",
 "level": "first",
 "level_label": "this is my first trick",
 "audience": "family_table",
 "audience_label": "family around a table",
 "angles": "front",
 "angles_label": "everyone in front of me",
 "steps": "[{\"id\":\"S1\",\"text\":\"Before the show, put the four aces on top of the deck\",\"kind\":\"setup\",\"angle\":\"no\"},{\"id\":\"S2\",\"text\":\"Ask someone to cut the deck into four piles\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S3\",\"text\":\"Have them pick up each pile in turn, move three cards from the top to the bottom, then deal one card onto each of the other three piles\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S4\",\"text\":\"Say: you did all the mixing, I never touched the cards\",\"kind\":\"patter\",\"angle\":\"no\"},{\"id\":\"S5\",\"text\":\"Ask them to turn over the top card of each pile to reveal the four aces\",\"kind\":\"reveal\",\"angle\":\"no\"}]",
 "figures": "{\"steps\":\"5\",\"sleights\":\"0\",\"angle_moves\":\"\",\"days\":\"10\",\"minutes_per_day\":\"20\",\"budget_minutes\":\"200\",\"need_minutes\":\"75\",\"plan_minutes\":\"75\",\"fit\":\"fits\",\"angle_risk\":\"none\"}",
 "notes": ""
}

Reply:

{
 "task": "ladder",
 "verdict": "ready_in_time",
 "title": "75 minutes by the page's estimate, 200 minutes to work with",
 "summary": "Ready in time - you have 200 minutes over 10 days, about 75 minutes by the page's estimate for this first trick, with family around a table in front of you. Start with the setup and the cut, then drill the pile move until it is automatic.",
 "rungs": [
  {
   "id": "R1",
   "name": "Setup and the cut",
   "steps": [
    "S1",
    "S2"
   ],
   "drill": "Practice placing the aces on top before you start, then have someone cut the deck into four piles.",
   "pass_check": "Ten times in a row, the aces land on top and the cut makes four even piles.",
   "minutes": 5
  },
  {
   "id": "R2",
   "name": "Moving three cards",
   "steps": [
    "S3"
   ],
   "drill": "Pick up each pile, move three cards to the bottom, then deal one card onto each other pile, slowly, then at speed.",
   "pass_check": "Ten run-throughs with no stray cards and no hesitation moving between piles.",
   "minutes": 25
  },
  {
   "id": "R3",
   "name": "Adding the line",
   "steps": [
    "S3",
    "S4"
   ],
   "drill": "Repeat the pile move while saying the line about doing all the mixing, keeping your pace even.",
   "pass_check": "Ten in a row saying the line smoothly with no pause in the move.",
   "minutes": 20
  },
  {
   "id": "R4",
   "name": "The reveal",
   "steps": [
    "S5"
   ],
   "drill": "Practice asking them to turn over the top card of each pile, pausing just before they do.",
   "pass_check": "Five run-throughs where the pause before the reveal feels the same length each time.",
   "minutes": 10
  },
  {
   "id": "R5",
   "name": "Full run in the mirror",
   "steps": [
    "S1",
    "S2",
    "S3",
    "S4",
    "S5"
   ],
   "drill": "Run the whole trick alone, with a phone propped at the eye level of family around a table.",
   "pass_check": "Three full run-throughs on camera with no stray cards and the line landing every time.",
   "minutes": 10
  },
  {
   "id": "R6",
   "name": "Full performance",
   "steps": [
    "S1",
    "S2",
    "S3",
    "S4",
    "S5"
   ],
   "drill": "Perform the whole trick for one person, start to finish, no stopping.",
   "pass_check": "One full performance for one person with the reveal landing and no hesitation.",
   "minutes": 5
  }
 ],
 "sightlines": [],
 "cover_lines": [],
 "schedule": [
  {
   "days": "1",
   "rungs": [
    "R1"
   ],
   "focus": "learn the setup and the cut"
  },
  {
   "days": "2-4",
   "rungs": [
    "R2"
   ],
   "focus": "practice the three-card move until it is automatic"
  },
  {
   "days": "5-6",
   "rungs": [
    "R3"
   ],
   "focus": "add the line while doing the move"
  },
  {
   "days": "7",
   "rungs": [
    "R4"
   ],
   "focus": "practice the pause before the reveal"
  },
  {
   "days": "8-9",
   "rungs": [
    "R5"
   ],
   "focus": "run the whole trick alone in the mirror or on camera"
  },
  {
   "days": "10",
   "rungs": [
    "R6"
   ],
   "focus": "perform the whole trick for one person"
  }
 ],
 "swap_hint": ""
}

ladder: two hidden moves, seven days, a wide half-circle - tight

Input:

{
 "task": "ladder",
 "trick": "The card that jumps back",
 "level": "few",
 "level_label": "I have learned a few tricks",
 "audience": "friends_standing",
 "audience_label": "a few friends standing",
 "angles": "wide",
 "angles_label": "a wide half-circle",
 "steps": "[{\"id\":\"S1\",\"text\":\"Spread the cards and have one chosen and signed with a marker\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S2\",\"text\":\"Take it back and secretly bring it to the top of the deck with a pinky break\",\"kind\":\"sleight\",\"angle\":\"yes\"},{\"id\":\"S3\",\"text\":\"Double lift to show a different card on top, then turn the two back face down as one\",\"kind\":\"sleight\",\"angle\":\"yes\"},{\"id\":\"S4\",\"text\":\"Take the top card and push it into the middle of the deck\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S5\",\"text\":\"Say: watch, it jumps back up\",\"kind\":\"patter\",\"angle\":\"no\"},{\"id\":\"S6\",\"text\":\"Snap your fingers and turn over the top card to reveal the signed card\",\"kind\":\"reveal\",\"angle\":\"no\"}]",
 "figures": "{\"steps\":\"6\",\"sleights\":\"2\",\"angle_moves\":\"S2,S3\",\"days\":\"7\",\"minutes_per_day\":\"20\",\"budget_minutes\":\"140\",\"need_minutes\":\"206\",\"plan_minutes\":\"140\",\"fit\":\"tight\",\"angle_risk\":\"medium\"}",
 "notes": "I can do the double lift slowly but it looks stiff."
}

Reply:

{
 "task": "ladder",
 "verdict": "tight",
 "title": "Tight but doable: two hidden moves in seven days",
 "summary": "This is tight: you have 140 minutes over 7 days, but the page's estimate for this trick at your level is about 206 minutes, and you will show it to a few friends standing in a wide half-circle. Start by drilling the pinky break until it is silent and automatic.",
 "rungs": [
  {
   "id": "R1",
   "name": "Secret move alone",
   "steps": [
    "S2"
   ],
   "drill": "Repeat the secret move slowly, eyes on your friends' faces, not your hands, until it feels invisible.",
   "pass_check": "Ten in a row with no glance down at your hands.",
   "minutes": 20
  },
  {
   "id": "R2",
   "name": "Double lift alone",
   "steps": [
    "S3"
   ],
   "drill": "Repeat the double lift slowly, then faster, softening the motion until it looks like turning over one card, not two.",
   "pass_check": "Ten in a row at the audience's eye level with no flash of the second card.",
   "minutes": 25
  },
  {
   "id": "R3",
   "name": "Open to the switch",
   "steps": [
    "S1",
    "S2",
    "S3"
   ],
   "drill": "Run the spread, the secret move and the double lift together while talking, keeping your eyes up throughout.",
   "pass_check": "Five in a row with no pause and no hesitation before the double lift.",
   "minutes": 25
  },
  {
   "id": "R4",
   "name": "Finish and reveal",
   "steps": [
    "S4",
    "S5",
    "S6"
   ],
   "drill": "Practice pushing the card in, saying the line, then snapping and revealing it, keeping the pace steady.",
   "pass_check": "Ten in a row with the line landing just before the reveal.",
   "minutes": 20
  },
  {
   "id": "R5",
   "name": "Full chain, check the angles",
   "steps": [
    "S1",
    "S2",
    "S3",
    "S4",
    "S5",
    "S6"
   ],
   "drill": "Run every step in order at a normal pace, then again with your friends' half-circle in mind.",
   "pass_check": "Five in a row with the secret move and double lift hidden from both ends of the half-circle.",
   "minutes": 25
  },
  {
   "id": "R6",
   "name": "Mirror or camera run",
   "steps": [
    "S1",
    "S2",
    "S3",
    "S4",
    "S5",
    "S6"
   ],
   "drill": "Run the whole trick alone in front of a mirror or phone camera set at the half-circle's eye level.",
   "pass_check": "The camera shows nothing at the side angle on three run-throughs in a row.",
   "minutes": 20
  },
  {
   "id": "R7",
   "name": "Full performance, one person",
   "steps": [
    "S1",
    "S2",
    "S3",
    "S4",
    "S5",
    "S6"
   ],
   "drill": "Perform the whole trick for one person start to finish, at full pace, with no second attempt.",
   "pass_check": "The person watching reacts to the reveal and you say the line without hesitating once.",
   "minutes": 15
  }
 ],
 "sightlines": [
  {
   "ref": "S2",
   "risk": "medium",
   "fix": "Angle your body so the pinky break faces away from both ends of the half-circle, not straight at the middle."
  },
  {
   "ref": "S3",
   "risk": "medium",
   "fix": "Turn slightly so the double lift's face stays angled toward the middle, away from the half-circle's open sides."
  }
 ],
 "cover_lines": [
  {
   "ref": "S2",
   "line": "So, got it memorized?"
  },
  {
   "ref": "S3",
   "line": "Still with me?"
  }
 ],
 "schedule": [
  {
   "days": "1",
   "rungs": [
    "R1"
   ],
   "focus": "Make the secret move invisible and automatic before adding anything else."
  },
  {
   "days": "2",
   "rungs": [
    "R2"
   ],
   "focus": "Smooth out the double lift so it never looks stiff."
  },
  {
   "days": "3-4",
   "rungs": [
    "R3"
   ],
   "focus": "Join the opening, the secret move and the double lift into one flow."
  },
  {
   "days": "5",
   "rungs": [
    "R4"
   ],
   "focus": "Lock in the push, the line and the reveal together."
  },
  {
   "days": "6",
   "rungs": [
    "R5"
   ],
   "focus": "Run the whole trick and check every angle in the half-circle."
  },
  {
   "days": "7",
   "rungs": [
    "R6",
    "R7"
   ],
   "focus": "Mirror run, then one full performance for one person."
  }
 ],
 "swap_hint": ""
}

ladder: two hidden coin moves, two days, children all round - swap_trick

Input:

{
 "task": "ladder",
 "trick": "Coin from behind the ear",
 "level": "first",
 "level_label": "this is my first trick",
 "audience": "kids_group",
 "audience_label": "a group of children",
 "angles": "surrounded",
 "angles_label": "some at my sides or behind me",
 "steps": "[{\"id\":\"S1\",\"text\":\"Show a coin in the right hand\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S2\",\"text\":\"Pretend to take it in the left hand with a French drop, keeping it in the right\",\"kind\":\"sleight\",\"angle\":\"yes\"},{\"id\":\"S3\",\"text\":\"Hold the coin in finger palm while pointing at the left fist\",\"kind\":\"sleight\",\"angle\":\"yes\"},{\"id\":\"S4\",\"text\":\"Open the left hand to show it empty\",\"kind\":\"reveal\",\"angle\":\"no\"},{\"id\":\"S5\",\"text\":\"Reach behind someone's ear and produce the coin\",\"kind\":\"reveal\",\"angle\":\"no\"}]",
 "figures": "{\"steps\":\"5\",\"sleights\":\"2\",\"angle_moves\":\"S2,S3\",\"days\":\"2\",\"minutes_per_day\":\"10\",\"budget_minutes\":\"20\",\"need_minutes\":\"300\",\"plan_minutes\":\"20\",\"fit\":\"short\",\"angle_risk\":\"high\"}",
 "notes": "It is for a class visit on Friday."
}

Reply:

{
 "task": "ladder",
 "verdict": "swap_trick",
 "title": "2 hidden moves, 2 days: time for an easier trick",
 "summary": "With 20 minutes over 2 days against about 300 minutes by the page's estimate at this level, and watchers also to the sides or behind you, it will not be ready in time. Look for an easier trick with fewer hidden moves before building a new practice ladder.",
 "rungs": [
  {
   "id": "R1",
   "name": "Practice the pretend take",
   "steps": [
    "S2"
   ],
   "drill": "Repeat step 2 slowly in a mirror, watching your own eyes and the speed of both hands until the fake pass looks just like the real pass.",
   "pass_check": "The move looks identical to a real hand to hand pass when you watch the recording back.",
   "minutes": 4
  },
  {
   "id": "R2",
   "name": "Practice the hidden hold",
   "steps": [
    "S3"
   ],
   "drill": "Repeat step 3 while pointing at the other fist and speaking normally, keeping your shoulders and hand relaxed and still.",
   "pass_check": "You can point and talk for a full run without glancing down at the hidden hand.",
   "minutes": 4
  },
  {
   "id": "R3",
   "name": "Link the vanish sequence",
   "steps": [
    "S1",
    "S2",
    "S3",
    "S4"
   ],
   "drill": "Run steps 1 through 4 back to back without stopping, saying your line each time and checking your hand positions feel natural.",
   "pass_check": "You move from showing the coin to the empty hand without a pause or a visible fumble.",
   "minutes": 4
  },
  {
   "id": "R4",
   "name": "Full run in the mirror",
   "steps": [
    "S1",
    "S2",
    "S3",
    "S4",
    "S5"
   ],
   "drill": "Film the whole trick on a phone held at the children's eye level, then watch it back for anything visible from the side or behind.",
   "pass_check": "The playback shows nothing from the side or behind through the whole run.",
   "minutes": 4
  },
  {
   "id": "R5",
   "name": "Full performance for someone",
   "steps": [
    "S1",
    "S2",
    "S3",
    "S4",
    "S5"
   ],
   "drill": "Perform the whole trick for someone from start to finish, keeping your pace steady and your line timed to the hidden moves.",
   "pass_check": "The coin appears behind their ear and they do not ask to see your hands again.",
   "minutes": 4
  }
 ],
 "sightlines": [
  {
   "ref": "S2",
   "risk": "high",
   "fix": "Turn so your back is toward open space and keep both hands low and in front of you, never near your sides or behind you."
  },
  {
   "ref": "S3",
   "risk": "high",
   "fix": "Keep pointing at the other fist with your free hand so eyes stay to the front, away from children at your sides or behind you."
  }
 ],
 "cover_lines": [
  {
   "ref": "S2",
   "line": "Abracadabra, everyone watch my face, not my hands!"
  },
  {
   "ref": "S3",
   "line": "Keep your eyes on this fist, something sneaky is about to happen in here!"
  }
 ],
 "schedule": [
  {
   "days": "1",
   "rungs": [
    "R1",
    "R2",
    "R3"
   ],
   "focus": "Drill the two hidden moves alone, then link them with the open steps."
  },
  {
   "days": "2",
   "rungs": [
    "R4",
    "R5"
   ],
   "focus": "Film the full run, then perform the whole trick for someone."
  }
 ],
 "swap_hint": "Look for a trick with fewer hidden moves, something that works itself, with nothing to hide from children at your sides or behind you, and short enough to learn well inside 20 minutes."
}

debrief: it went well, a little fast - perform_again

Input:

{
 "task": "debrief",
 "trick": "The card that jumps back",
 "level": "few",
 "level_label": "I have learned a few tricks",
 "audience": "friends_standing",
 "audience_label": "a few friends standing",
 "angles": "wide",
 "angles_label": "a wide half-circle",
 "steps": "[{\"id\":\"S1\",\"text\":\"Spread the cards and have one chosen and signed with a marker\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S2\",\"text\":\"Take it back and secretly bring it to the top of the deck with a pinky break\",\"kind\":\"sleight\",\"angle\":\"yes\"},{\"id\":\"S3\",\"text\":\"Double lift to show a different card on top, then turn the two back face down as one\",\"kind\":\"sleight\",\"angle\":\"yes\"},{\"id\":\"S4\",\"text\":\"Take the top card and push it into the middle of the deck\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S5\",\"text\":\"Say: watch, it jumps back up\",\"kind\":\"patter\",\"angle\":\"no\"},{\"id\":\"S6\",\"text\":\"Snap your fingers and turn over the top card to reveal the signed card\",\"kind\":\"reveal\",\"angle\":\"no\"}]",
 "figures": "{\"steps\":\"6\",\"sleights\":\"2\",\"angle_moves\":\"S2,S3\",\"days\":\"7\",\"minutes_per_day\":\"20\",\"budget_minutes\":\"140\",\"need_minutes\":\"206\",\"plan_minutes\":\"140\",\"fit\":\"tight\",\"angle_risk\":\"medium\"}",
 "notes": "I can do the double lift slowly but it looks stiff.",
 "rungs": "[{\"id\":\"R1\",\"name\":\"Secret move alone\",\"steps\":[\"S2\"]},{\"id\":\"R2\",\"name\":\"Double lift alone\",\"steps\":[\"S3\"]},{\"id\":\"R3\",\"name\":\"Open to the switch\",\"steps\":[\"S1\",\"S2\",\"S3\"]},{\"id\":\"R4\",\"name\":\"Finish and reveal\",\"steps\":[\"S4\",\"S5\",\"S6\"]},{\"id\":\"R5\",\"name\":\"Full chain, check the angles\",\"steps\":[\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\"]},{\"id\":\"R6\",\"name\":\"Mirror or camera run\",\"steps\":[\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\"]},{\"id\":\"R7\",\"name\":\"Full performance, one person\",\"steps\":[\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\"]}]",
 "passed": "[\"R1\",\"R2\",\"R3\",\"R4\",\"R5\",\"R6\"]",
 "run_no": "2",
 "run_notes": "Showed my partner after dinner, standing in the kitchen. Went smoothly both times and my partner gasped when the signed card came back. I talked a bit too fast at the start, and the jump line got no laugh.",
 "cues": "{\"caught\":\"0\",\"flash\":\"0\",\"fumble\":\"0\",\"pace\":\"1\",\"reaction\":\"1\"}"
}

Reply:

{
 "task": "debrief",
 "verdict": "perform_again",
 "title": "Run 2: mostly smooth - tidy the pace and the patter line",
 "summary": "This run went well enough to carry on - no one caught on and nothing was fumbled, just a rushed start and a line that did not land. Next time, slow the opening and let the pause before the reveal line breathe.",
 "findings": [
  {
   "quote": "Went smoothly both times",
   "kind": "praise",
   "ref": "",
   "meaning": "The whole routine ran cleanly through both performances in that run."
  },
  {
   "quote": "my partner gasped when the signed card came back",
   "kind": "reaction",
   "ref": "S6",
   "meaning": "Your partner's gasp when the card came back shows the reveal landed."
  },
  {
   "quote": "I talked a bit too fast at the start",
   "kind": "pace",
   "ref": "",
   "meaning": "The pace at the start was too quick, by your own account."
  },
  {
   "quote": "the jump line got no laugh",
   "kind": "patter",
   "ref": "S5",
   "meaning": "The line before the reveal did not land as a laugh line."
  }
 ],
 "back_to": "",
 "fixes": [
  {
   "ref": "R6",
   "change": "Start at a slower pace, pausing a beat before speaking, through the whole run.",
   "drill": "Run the full routine in front of a mirror, narrating the opening slower than feels natural."
  },
  {
   "ref": "R4",
   "change": "Change the delivery of the line before the reveal, adding a pause for suspense.",
   "drill": "Say the line several times with a short pause, then the reveal, and note any laugh."
  }
 ],
 "next_run": {
  "who": "Your partner again, or try the friends standing in a wide half-circle next.",
  "focus": "A calmer pace at the start and a stronger pause before the line lands.",
  "check": "The start feels unhurried and the line before the reveal gets a response."
 },
 "keep": [
  "The whole run went smoothly across both times, by your own account.",
  "Your partner gasped right when the signed card came back."
 ]
}

debrief: a watcher saw two cards move - drop_back

Input:

{
 "task": "debrief",
 "trick": "The card that jumps back",
 "level": "few",
 "level_label": "I have learned a few tricks",
 "audience": "friends_standing",
 "audience_label": "a few friends standing",
 "angles": "wide",
 "angles_label": "a wide half-circle",
 "steps": "[{\"id\":\"S1\",\"text\":\"Spread the cards and have one chosen and signed with a marker\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S2\",\"text\":\"Take it back and secretly bring it to the top of the deck with a pinky break\",\"kind\":\"sleight\",\"angle\":\"yes\"},{\"id\":\"S3\",\"text\":\"Double lift to show a different card on top, then turn the two back face down as one\",\"kind\":\"sleight\",\"angle\":\"yes\"},{\"id\":\"S4\",\"text\":\"Take the top card and push it into the middle of the deck\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S5\",\"text\":\"Say: watch, it jumps back up\",\"kind\":\"patter\",\"angle\":\"no\"},{\"id\":\"S6\",\"text\":\"Snap your fingers and turn over the top card to reveal the signed card\",\"kind\":\"reveal\",\"angle\":\"no\"}]",
 "figures": "{\"steps\":\"6\",\"sleights\":\"2\",\"angle_moves\":\"S2,S3\",\"days\":\"7\",\"minutes_per_day\":\"20\",\"budget_minutes\":\"140\",\"need_minutes\":\"206\",\"plan_minutes\":\"140\",\"fit\":\"tight\",\"angle_risk\":\"medium\"}",
 "notes": "I can do the double lift slowly but it looks stiff.",
 "rungs": "[{\"id\":\"R1\",\"name\":\"Secret move alone\",\"steps\":[\"S2\"]},{\"id\":\"R2\",\"name\":\"Double lift alone\",\"steps\":[\"S3\"]},{\"id\":\"R3\",\"name\":\"Open to the switch\",\"steps\":[\"S1\",\"S2\",\"S3\"]},{\"id\":\"R4\",\"name\":\"Finish and reveal\",\"steps\":[\"S4\",\"S5\",\"S6\"]},{\"id\":\"R5\",\"name\":\"Full chain, check the angles\",\"steps\":[\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\"]},{\"id\":\"R6\",\"name\":\"Mirror or camera run\",\"steps\":[\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\"]},{\"id\":\"R7\",\"name\":\"Full performance, one person\",\"steps\":[\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\"]}]",
 "passed": "[\"R1\",\"R2\",\"R3\",\"R4\",\"R5\",\"R6\"]",
 "run_no": "3",
 "run_notes": "Did it for my two flatmates standing by the sofa. On the first go one of them said wait, I saw two cards move when I turned the top card over. The second go was clean and they laughed at the jump line.",
 "cues": "{\"caught\":\"0\",\"flash\":\"1\",\"fumble\":\"0\",\"pace\":\"0\",\"reaction\":\"1\"}"
}

Reply:

{
 "task": "debrief",
 "verdict": "drop_back",
 "title": "Clean second run, but one flash on the double lift",
 "summary": "One flatmate noticed two cards move during the double lift, so you drop back to that rung before trying it on anyone new. Turn it flatter and slower at a hidden angle, then run the whole trick again.",
 "findings": [
  {
   "quote": "I saw two cards move when I turned the top card over",
   "kind": "flash",
   "ref": "S3",
   "meaning": "one flatmate briefly saw two cards instead of one as you turned the top card over"
  },
  {
   "quote": "The second go was clean",
   "kind": "praise",
   "ref": "",
   "meaning": "the second run went well, with nothing to flag"
  },
  {
   "quote": "they laughed at the jump line",
   "kind": "reaction",
   "ref": "S5",
   "meaning": "the flatmates reacted well to the line right before the reveal"
  }
 ],
 "back_to": "R2",
 "fixes": [
  {
   "ref": "R2",
   "change": "turn the double over flatter and slower, angled away from the half-circle, eyes up on the watchers' faces.",
   "drill": "run the turn ten times at that slower pace, checking from the side that nothing extra shows."
  }
 ],
 "next_run": {
  "who": "no new audience until the double lift alone rung passes again",
  "focus": "keeping the double flat and the edge hidden from the half-circle",
  "check": "the turn draws no comment about seeing two cards from any angle"
 },
 "keep": [
  "the second run was clean",
  "the flatmates laughed at the jump line"
 ]
}

debrief: two watchers worked it out - rebuild

Input:

{
 "task": "debrief",
 "trick": "The card that jumps back",
 "level": "few",
 "level_label": "I have learned a few tricks",
 "audience": "friends_standing",
 "audience_label": "a few friends standing",
 "angles": "wide",
 "angles_label": "a wide half-circle",
 "steps": "[{\"id\":\"S1\",\"text\":\"Spread the cards and have one chosen and signed with a marker\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S2\",\"text\":\"Take it back and secretly bring it to the top of the deck with a pinky break\",\"kind\":\"sleight\",\"angle\":\"yes\"},{\"id\":\"S3\",\"text\":\"Double lift to show a different card on top, then turn the two back face down as one\",\"kind\":\"sleight\",\"angle\":\"yes\"},{\"id\":\"S4\",\"text\":\"Take the top card and push it into the middle of the deck\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S5\",\"text\":\"Say: watch, it jumps back up\",\"kind\":\"patter\",\"angle\":\"no\"},{\"id\":\"S6\",\"text\":\"Snap your fingers and turn over the top card to reveal the signed card\",\"kind\":\"reveal\",\"angle\":\"no\"}]",
 "figures": "{\"steps\":\"6\",\"sleights\":\"2\",\"angle_moves\":\"S2,S3\",\"days\":\"7\",\"minutes_per_day\":\"20\",\"budget_minutes\":\"140\",\"need_minutes\":\"206\",\"plan_minutes\":\"140\",\"fit\":\"tight\",\"angle_risk\":\"medium\"}",
 "notes": "I can do the double lift slowly but it looks stiff.",
 "rungs": "[{\"id\":\"R1\",\"name\":\"Secret move alone\",\"steps\":[\"S2\"]},{\"id\":\"R2\",\"name\":\"Double lift alone\",\"steps\":[\"S3\"]},{\"id\":\"R3\",\"name\":\"Open to the switch\",\"steps\":[\"S1\",\"S2\",\"S3\"]},{\"id\":\"R4\",\"name\":\"Finish and reveal\",\"steps\":[\"S4\",\"S5\",\"S6\"]},{\"id\":\"R5\",\"name\":\"Full chain, check the angles\",\"steps\":[\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\"]},{\"id\":\"R6\",\"name\":\"Mirror or camera run\",\"steps\":[\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\"]},{\"id\":\"R7\",\"name\":\"Full performance, one person\",\"steps\":[\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\"]}]",
 "passed": "[\"R1\",\"R2\",\"R3\",\"R4\",\"R5\",\"R6\"]",
 "run_no": "4",
 "run_notes": "Tried it on my cousins at the table, three of them. After the first go my older cousin said you just turned two cards over together. Then the youngest said it was the same card on top the whole time. I also dropped the break once while spreading.",
 "cues": "{\"caught\":\"2\",\"flash\":\"0\",\"fumble\":\"1\",\"pace\":\"0\",\"reaction\":\"0\"}"
}

Reply:

{
 "task": "debrief",
 "verdict": "rebuild",
 "title": "Two cousins caught the double lift: back to rung 1",
 "summary": "Two cousins worked out the double lift and you dropped the break once, so this needs a rebuild starting at rung 1. First, get the break solid again before reselling the double lift.",
 "findings": [
  {
   "quote": "you just turned two cards over together",
   "kind": "caught",
   "ref": "S3",
   "meaning": "The older cousin spotted that both cards were turned over together instead of only one."
  },
  {
   "quote": "it was the same card on top the whole time",
   "kind": "caught",
   "ref": "S3",
   "meaning": "The youngest realized the top card never actually changed during the double lift."
  },
  {
   "quote": "dropped the break once while spreading",
   "kind": "fumble",
   "ref": "S2",
   "meaning": "You lost your place in the cards for a moment while spreading them."
  }
 ],
 "back_to": "R1",
 "fixes": [
  {
   "ref": "R1",
   "change": "Keep talking as you take the card back so the break happens while attention is on your words, not your hands.",
   "drill": "Run the move ten times while speaking a line aloud each time, with no silent pause before or after."
  },
  {
   "ref": "R2",
   "change": "Slow the turn and look up at your audience as you turn the cards, so their eyes follow your face.",
   "drill": "Practice the turn slowly, then at speed, saying your line each time and checking your eyes lift up."
  }
 ],
 "next_run": {
  "who": "Nobody new until rung 1 passes clean again.",
  "focus": "Get the break solid in rung 1, then resell the double lift with your eyes up.",
  "check": "The mirror or camera run shows no dropped break and nobody names the double lift."
 },
 "keep": []
}

Build the input yourself

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

const K = RepKit;
const form = K.form({ trick: "The card that comes back", level: "few", days: "7", minutes: "20",
  audience: "friends_standing", angles: "wide",
  steps: "Have a card chosen and signed\nDouble lift to show a different card\nTurn over the top card to reveal it" });
const r = K.read(form);                               // r.steps: each step with its kind and angle; r.fig: the figures
const ladderIn = K.toWire(K.buildInput(form, "ladder").input);     // POST this to /estimate and /run
// then: const ladder = K.normalize(K.parseReply(job.output.output), "ladder");
form.run_notes = "Showed my partner. Smooth, and my partner gasped at the end.";
const built = K.buildInput(form, "debrief", { rungs: ladder.rungs, passed: ["R1", "R2"] });
const debriefIn = K.toWire(built.input);
// K.check(debriefReply, built).notes                // the page's re-check of a reply
// K.csv(ladder.rungs, ["R1"], r)                    // the practice log, one row per rung

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://mirror-reps.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":"mirror-reps"}' | 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": "ladder", "trick": "The card that jumps back", "level": "few", "audience": "friends_standing", "angles": "wide", "steps": "[{\"id\":\"S1\",\"text\":\"Spread the cards and have one chosen and signed with a marker\",\"kind\":\"handling\",\"angle\":\"no\"},{\"id\":\"S2\",\"text\":\"Take it back and secretly bring it to the top of the deck with a pinky break\",\"kind\":\"sleight\",\"angle\":\"yes\"}, ...]", "figures": "{...}", "notes": "I can do the double lift slowly but it looks stiff."}
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 mirror-reps:<task>:<inputHash>:a<attempt> (the tasks are ladder and debrief), 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="mirror-reps:ladder:$(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: mirror-reps:ladder:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json