← Sixty Sleeves
API
Tokens

Drive Sixty Sleeves from your own code

Both metered steps - the tune and the play plan - are available over HTTP. A natural use is a deck-building site or a tournament tool that already holds a player's list: your code sends the parsed list and its odds and shows the cited advice.

The split first: the page does the counting; the model gives the judgement; the page checks it. Parsing, role tags, legality and every probability come from sleevekit.js, which runs in node as well as in the browser. Its reconcile function then checks every cited id, that each cut names a listed card with enough copies, that cuts and adds land exactly on the deck size, that copy-limit problems are cut, that a turn-one plan going first does not attack when the rules forbid it, and that no price slipped in.

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":"sixty-sleeves"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare: never wrap it as {"input": {…}}.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required field 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 comes first

Every run sends task, which routes the one system prompt to one of two contracts:

taskWhat it returnsVerdict field
tuneA title, a summary, a ratio line; 1–4 strengths and 0–4 problems lines; cuts [{card_id, name, qty, why}]; adds [{cat, role, qty, why, card_id?}] - by role, or more copies of a listed card, never a new card name; 2–4 tips; 1–2 caveats. total - cuts + adds must equal the deck size.verdict: ready, tune, rebuild
planA title, a summary, a win_con line; exactly three turns [{turn, goal, plays[]}]; mulligan {keep[], ship[]}; 1–3 prize_checks [{card_id, name, why, cite}]; 1–3 matchup_tips; caveats.archetype: aggro, setup, control

A line is always {text, cite}: one or two plain sentences, and the ids they rest on - cards C1…, odds S-open S-mull S-mull2 S-draw S-draw2 S-out S-energy S-switch S-prize1, category counts N-creature N-trainer N-energy, key cards K1… and their prize odds K1-prize…, legality issues L1…, flags F-mull F-draw-low F-line F-energy-high F-energy-low.

The declared input schema carries scalars only, so object-valued fields travel JSON-encoded as strings (SleeveKit.toWire does this). task, deck and stats are required.

Input fields

FieldTypeNotes
taskstringtune or plan.
deckstring (JSON)[{id, qty, name, cat, role, key?}] from SleeveKit.parseList; at most 80 distinct cards.
statsstring (JSON){total, rules, going, counts, count_ids, roles, odds, keys, legal, flags, lines} from SleeveKit.analyze.
goalstringcasual, locals or tournament.
turn_orderstringplan only: first or second.
tunestring (JSON)plan only, optional: {verdict, summary, cuts, adds} from an earlier tune.
notestringOptional; over 800 characters keeps the start and the end around a marker.
retry_notestringOnly on a reformat retry.

The reply

The job's output.output is a string holding one JSON object. Parse it with SleeveKit.parseReply, then normalize, then check it with reconcile(reply, {cards, stats}): it flags a cited id that is not on the page, an uncited line, a cut of an unknown card or of more copies than the list has, cuts and adds that miss the deck size, a copy-limit problem left uncut, a ready verdict on an illegal list or with a mulligan rate over 22%, a plan for an illegal list that never says so, a turn-one attack going first, and any price. project applies a tune to the list and diff compares the odds before and after.

Worked examples

Replies produced against the app's prompt for three made-up decks with invented card names (long lists shortened; the full replies ship in example-result.js). The page replays each of them free from its sample buttons. Inputs are shown with most cards and odds folded.

task: tune - Ember rush, a tournament list (sample deck) (ready)

Input:

{
 "task": "tune",
 "deck": "[{\"id\":\"C1\",\"qty\":4,\"name\":\"Emberkit\",\"cat\":\"creature\",\"role\":\"basic\",\"key\":true},{\"id\":\"C2\",\"qty\":4,\"name\":\"Flarepup\",\"cat\":\"creature\",\"role\":\"basic\"},{\"id\":\"C3\",\"qty\":3,\"name\":\"Ashling\",\"cat\":\"creature\",\"role\":\"basic\"},\"... 15 more cards in the same shape\"]",
 "stats": "{\"total\":60,\"rules\":{\"deck_size\":60,\"hand\":7,\"prizes\":6,\"max_copies\":4,\"first_draw\":true,\"first_no_attack\":true},\"going\":\"first\",\"counts\":{\"creature\":18,\"trainer\":32,\"energy\":10},\"count_ids\":[{\"id\":\"N-creature\",\"text\":\"18 creatures (30%)\"},{\"id\":\"N-trainer\",\"text\":\"32 trainers (53%)\"},{\"id\":\"N-energy\",\"text\":\"10 energy (17%)\"}],\"roles\":\"{...}\",\"odds\":[{\"id\":\"S-open\",\"label\":\"Opening 7 holds a basic creature\",\"p\":0.809},{\"id\":\"S-mull\",\"label\":\"Mulligan chance (no basic in 7)\",\"p\":0.191},\"...\"],\"keys\":[{\"id\":\"K1\",\"card\":\"C1\",\"name\":\"Emberkit\",\"qty\":4,\"open\":0.399,\"by_turn\":[0.445,0.488,0.528,0.566],\"prize_any\":0.351,\"prize_all\":0},{\"id\":\"K2\",\"card\":\"C6\",\"name\":\"Pyrewyrm\",\"qty\":3,\"open\":0.315,\"by_turn\":[0.354,0.391,0.427,0.462],\"prize_any\":0.275,\"prize_all\":0.001}],\"legal\":[],\"flags\":[],\"lines\":\"[...]\"}",
 "goal": "tournament",
 "note": "Regional next month. I mostly worry about bricking on turn 1."
}

Reply:

{
 "task": "tune",
 "verdict": "ready",
 "title": "Ember Rush - ready to sleeve up",
 "summary": "This 60-card Ember list is legal with only a 19% mulligan rate and a draw/search engine deep enough for tournament play. The Cindercub-into-Pyrewyrm line has enough support behind it to come online without crowding the early curve of basics.",
 "ratio": {
  "text": "18 creatures, 32 trainers and 10 energy gives a trainer-heavy 30/53/17 split - typical for a list that leans on draw and search rather than raw energy count to function.",
  "cite": [
   "N-creature",
   "N-trainer",
   "N-energy"
  ]
 },
 "strengths": [
  {
   "text": "12 basic creatures keep the mulligan chance to just 19%, comfortably under the usual 20% target, so turn-1 bricks are rare.",
   "cite": [
    "S-mull",
    "C1",
    "C2",
    "C3",
    "C4"
   ]
  },
  {
   "text": "11 draw and 8 search trainers mean you see a draw or search card in your opening hand 94% of the time, so dead hands are uncommon.",
   "cite": [
    "S-out"
   ]
  },
  "..."
 ],
 "problems": [],
 "cuts": [],
 "adds": [],
 "tips": [
  "Watch for hands with a basic creature but no energy - you see energy in the opening 7 only 74% of the time, which bricks a turn more often than the creature count alone suggests.",
  "Keep Swap Step and Slip Cord in mind for retreating a damaged attacker, but don't plan around always having one - access to a switch effect by turn 2 is only 39%."
 ],
 "caveats": [
  "Odds assume a fair shuffle and don't know this game's actual card text, costs or abilities beyond name and role.",
  "These are independent per-hand probabilities; a single game can still run hot or cold against them."
 ]
}

task: plan - Ember rush, a tournament list (sample deck) (aggro)

Input:

{
 "task": "plan",
 "deck": "[{\"id\":\"C1\",\"qty\":4,\"name\":\"Emberkit\",\"cat\":\"creature\",\"role\":\"basic\",\"key\":true},{\"id\":\"C2\",\"qty\":4,\"name\":\"Flarepup\",\"cat\":\"creature\",\"role\":\"basic\"},{\"id\":\"C3\",\"qty\":3,\"name\":\"Ashling\",\"cat\":\"creature\",\"role\":\"basic\"},\"... 15 more cards in the same shape\"]",
 "stats": "{\"total\":60,\"rules\":{\"deck_size\":60,\"hand\":7,\"prizes\":6,\"max_copies\":4,\"first_draw\":true,\"first_no_attack\":true},\"going\":\"first\",\"counts\":{\"creature\":18,\"trainer\":32,\"energy\":10},\"count_ids\":[{\"id\":\"N-creature\",\"text\":\"18 creatures (30%)\"},{\"id\":\"N-trainer\",\"text\":\"32 trainers (53%)\"},{\"id\":\"N-energy\",\"text\":\"10 energy (17%)\"}],\"roles\":\"{...}\",\"odds\":[{\"id\":\"S-open\",\"label\":\"Opening 7 holds a basic creature\",\"p\":0.809},{\"id\":\"S-mull\",\"label\":\"Mulligan chance (no basic in 7)\",\"p\":0.191},\"...\"],\"keys\":[{\"id\":\"K1\",\"card\":\"C1\",\"name\":\"Emberkit\",\"qty\":4,\"open\":0.399,\"by_turn\":[0.445,0.488,0.528,0.566],\"prize_any\":0.351,\"prize_all\":0},{\"id\":\"K2\",\"card\":\"C6\",\"name\":\"Pyrewyrm\",\"qty\":3,\"open\":0.315,\"by_turn\":[0.354,0.391,0.427,0.462],\"prize_any\":0.275,\"prize_all\":0.001}],\"legal\":[],\"flags\":[],\"lines\":\"[...]\"}",
 "goal": "tournament",
 "turn_order": "first",
 "note": "Regional next month. I mostly worry about bricking on turn 1."
}

Reply:

{
 "task": "plan",
 "archetype": "aggro",
 "title": "Ember Rush - first three turns",
 "summary": "This is an aggro list: a wide basic-creature base backed by heavy draw and search, with Cindercub into Pyrewyrm as a stage-2 finisher rather than the main plan. Going first, turn 1 is pure setup since the first attack is skipped, then turns 2-3 are about attacking every turn while digging for the evolution line.",
 "win_con": {
  "text": "Twelve basic creatures let this deck start attacking on turn 2 while draw and search refill the hand - Emberkit leads the early offense, and Pyrewyrm closes games on the turns it shows up.",
  "cite": [
   "N-creature",
   "K1",
   "K2"
  ]
 },
 "turns": [
  {
   "turn": 1,
   "goal": "Set up your board; no attack yet going first.",
   "plays": [
    {
     "text": "Play a basic creature - Emberkit (C1), Flarepup (C2), Ashling (C3) or Smoldertail (C4), whichever you drew - and attach a Basic Ember Energy (C18) if you have one.",
     "cite": [
      "C1",
      "C2",
      "C3",
      "C4",
      "C18"
     ]
    },
    {
     "text": "No attack yet since you're going first; instead use a draw trainer like Field Journal (C7) or Night Study (C8) to dig for a second attacker or more energy.",
     "cite": [
      "C7",
      "C8"
     ]
    }
   ]
  },
  "... turns 2 and 3 in the same shape"
 ],
 "mulligan": {
  "keep": [
   "One basic creature with a Basic Ember Energy, even with no trainer - that is already a playable turn 1.",
   "Two basic creatures plus any draw or search trainer, especially one with Cindercub for the follow-up line."
  ],
  "ship": [
   "No basic creature at all - this is a guaranteed mulligan under these rules anyway.",
   "All trainers and energy with nothing to attach to or attack with."
  ]
 },
 "prize_checks": [
  {
   "card_id": "C6",
   "name": "Pyrewyrm",
   "why": "Your only copies of the stage-2 finisher - about a 28% chance at least one of the three is sitting in the prizes.",
   "cite": [
    "K2-prize"
   ]
  },
  {
   "card_id": "C4",
   "name": "Smoldertail",
   "why": "Single copy in the list - roughly a 10% chance it's prized and unavailable this game.",
   "cite": [
    "S-prize1"
   ]
  },
  "..."
 ],
 "matchup_tips": [
  "Against faster decks, keep attacking with whatever basic you have rather than waiting on Pyrewyrm to show up.",
  "Against slower decks, spend a turn searching up Cindercub before committing energy you might need later.",
  "..."
 ],
 "caveats": [
  "This plan assumes no disruption from the opponent and a fair shuffle.",
  "Odds are exact for a random draw; they don't know this game's real card text, costs or abilities."
 ]
}

task: tune - Tidal engine, a slow setup list (sample deck) (tune)

Input:

{
 "task": "tune",
 "deck": "[{\"id\":\"C1\",\"qty\":3,\"name\":\"Driplet\",\"cat\":\"creature\",\"role\":\"basic\"},{\"id\":\"C2\",\"qty\":2,\"name\":\"Puddlefin\",\"cat\":\"creature\",\"role\":\"basic\"},{\"id\":\"C3\",\"qty\":2,\"name\":\"Mistral\",\"cat\":\"creature\",\"role\":\"basic\"},\"... 15 more cards in the same shape\"]",
 "stats": "{\"total\":60,\"rules\":{\"deck_size\":60,\"hand\":7,\"prizes\":6,\"max_copies\":4,\"first_draw\":true,\"first_no_attack\":true},\"going\":\"second\",\"counts\":{\"creature\":14,\"trainer\":34,\"energy\":12},\"count_ids\":[{\"id\":\"N-creature\",\"text\":\"14 creatures (23%)\"},{\"id\":\"N-trainer\",\"text\":\"34 trainers (57%)\"},{\"id\":\"N-energy\",\"text\":\"12 energy (20%)\"}],\"roles\":\"{...}\",\"odds\":[{\"id\":\"S-open\",\"label\":\"Opening 7 holds a basic creature\",\"p\":0.654},{\"id\":\"S-mull\",\"label\":\"Mulligan chance (no basic in 7)\",\"p\":0.346},\"...\"],\"keys\":[{\"id\":\"K1\",\"card\":\"C5\",\"name\":\"Wavecaller\",\"qty\":3,\"open\":0.315,\"by_turn\":[0.354,0.391,0.427,0.462],\"prize_any\":0.275,\"prize_all\":0.001},{\"id\":\"K2\",\"card\":\"C6\",\"name\":\"Tidemother\",\"qty\":3,\"open\":0.315,\"by_turn\":[0.354,0.391,0.427,0.462],\"prize_any\":0.275,\"prize_all\":0.001}],\"legal\":[],\"flags\":[{\"id\":\"F-draw-low\",\"text\":\"Only 5 cards tagged as draw.\"},{\"id\":\"F-mull\",\"text\":\"A 35% mulligan rate - most lists aim under 20%.\"}],\"lines\":\"[...]\"}",
 "goal": "locals",
 "note": "My setup turns feel slow - I often miss the stage 2 by turn 3."
}

Reply:

{
 "task": "tune",
 "verdict": "tune",
 "title": "Tidal Engine: smooth out the stall",
 "summary": "This list is legal and its evolution line is balanced at three Wavecaller to three Tidemother, so the core plan is sound. A 35% mulligan rate and only 5 draw cards are the real drag - trimming some of the heavier trainer slots for more draw and basics should speed up exactly the slow starts you're feeling.",
 "ratio": {
  "text": "Trainers make up 57% of the deck against 23% creatures and 20% energy, which is trainer-heavy for how few threats and basics back it up.",
  "cite": [
   "N-creature",
   "N-trainer",
   "N-energy"
  ]
 },
 "strengths": [
  {
   "text": "Your evolution line is even - three Wavecaller into three Tidemother - so you are not short on the stage 1 that gets you there.",
   "cite": [
    "C5",
    "C6"
   ]
  },
  {
   "text": "A full search suite gives you a draw or search card in the opening hand 70% of the time.",
   "cite": [
    "S-out"
   ]
  }
 ],
 "problems": [
  {
   "text": "At 35% you'll mulligan more than one hand in three, well above the 20% most lists aim for.",
   "cite": [
    "S-mull",
    "F-mull"
   ]
  },
  {
   "text": "Only 5 cards are tagged as draw, so refilling after the opening hand is slow.",
   "cite": [
    "F-draw-low",
    "S-draw2"
   ]
  },
  "..."
 ],
 "cuts": [
  {
   "card_id": "C16",
   "name": "Riptide Gate",
   "qty": 2,
   "why": "Four copies of a slower trainer is a lot; two frees room for more draw and basics."
  },
  {
   "card_id": "C13",
   "name": "Undertow",
   "qty": 2,
   "why": "Same idea - two copies keep the effect without crowding out consistency."
  },
  "..."
 ],
 "adds": [
  {
   "cat": "creature",
   "role": "basic",
   "qty": 2,
   "why": "More basics push your 35% mulligan chance down and give you more turn-1 options."
  },
  {
   "cat": "trainer",
   "role": "draw",
   "qty": 1,
   "card_id": "C7",
   "why": "A fourth Field Journal adds to a thin 5-card draw suite."
  },
  "..."
 ],
 "tips": [
  "Play a Wavecaller before a Tidemother whenever both are in hand.",
  "Keep the 8-to-4 basic-to-special energy split once you add more basics; do not add more special without basic support."
 ],
 "caveats": [
  "These odds assume a fair shuffle and do not know what any card's text does.",
  "Look at the actual text of the trainers you cut - role tags alone do not capture everything a card does."
 ]
}

task: plan - Tidal engine, a slow setup list (sample deck) (setup)

Input:

{
 "task": "plan",
 "deck": "[{\"id\":\"C1\",\"qty\":3,\"name\":\"Driplet\",\"cat\":\"creature\",\"role\":\"basic\"},{\"id\":\"C2\",\"qty\":2,\"name\":\"Puddlefin\",\"cat\":\"creature\",\"role\":\"basic\"},{\"id\":\"C3\",\"qty\":2,\"name\":\"Mistral\",\"cat\":\"creature\",\"role\":\"basic\"},\"... 15 more cards in the same shape\"]",
 "stats": "{\"total\":60,\"rules\":{\"deck_size\":60,\"hand\":7,\"prizes\":6,\"max_copies\":4,\"first_draw\":true,\"first_no_attack\":true},\"going\":\"second\",\"counts\":{\"creature\":14,\"trainer\":34,\"energy\":12},\"count_ids\":[{\"id\":\"N-creature\",\"text\":\"14 creatures (23%)\"},{\"id\":\"N-trainer\",\"text\":\"34 trainers (57%)\"},{\"id\":\"N-energy\",\"text\":\"12 energy (20%)\"}],\"roles\":\"{...}\",\"odds\":[{\"id\":\"S-open\",\"label\":\"Opening 7 holds a basic creature\",\"p\":0.654},{\"id\":\"S-mull\",\"label\":\"Mulligan chance (no basic in 7)\",\"p\":0.346},\"...\"],\"keys\":[{\"id\":\"K1\",\"card\":\"C5\",\"name\":\"Wavecaller\",\"qty\":3,\"open\":0.315,\"by_turn\":[0.354,0.391,0.427,0.462],\"prize_any\":0.275,\"prize_all\":0.001},{\"id\":\"K2\",\"card\":\"C6\",\"name\":\"Tidemother\",\"qty\":3,\"open\":0.315,\"by_turn\":[0.354,0.391,0.427,0.462],\"prize_any\":0.275,\"prize_all\":0.001}],\"legal\":[],\"flags\":[{\"id\":\"F-draw-low\",\"text\":\"Only 5 cards tagged as draw.\"},{\"id\":\"F-mull\",\"text\":\"A 35% mulligan rate - most lists aim under 20%.\"}],\"lines\":\"[...]\"}",
 "goal": "locals",
 "turn_order": "second",
 "note": "My setup turns feel slow - I often miss the stage 2 by turn 3."
}

Reply:

{
 "task": "plan",
 "archetype": "setup",
 "title": "Tidal Engine: build to Tidemother",
 "summary": "This list is a setup deck - it spends its first turns finding a basic and digging for Wavecaller before turning on Tidemother as its real engine. Going second, you get an extra card before you even start, so use it to find a basic and start searching.",
 "win_con": {
  "text": "You win by evolving into Tidemother and riding its stage-2 payoff with Surge Energy once the early turns are secured.",
  "cite": [
   "C6",
   "K2",
   "C18"
  ]
 },
 "turns": [
  {
   "turn": 1,
   "goal": "Land a basic and start digging.",
   "plays": [
    {
     "text": "Put a basic creature in the active spot - you'll have one in your opening hand 65% of the time (otherwise it's a mulligan).",
     "cite": [
      "S-open",
      "S-mull"
     ]
    },
    {
     "text": "Attach your first energy - 81% of your going-second opening hands have at least one.",
     "cite": [
      "S-energy"
     ]
    }
   ]
  },
  "... turns 2 and 3 in the same shape"
 ],
 "mulligan": {
  "keep": [
   "Any hand with a basic creature and at least one Tide Energy"
  ],
  "ship": [
   "A hand with no basic creature at all"
  ]
 },
 "prize_checks": [
  {
   "card_id": "C4",
   "name": "Brineguard",
   "why": "Your only copy - 10% chance it's sitting face-down in the prizes.",
   "cite": [
    "S-prize1"
   ]
  },
  {
   "card_id": "C10",
   "name": "Swap Step",
   "why": "Also a single copy, so there's a 10% chance you won't see it unless you take a prize.",
   "cite": [
    "S-prize1"
   ]
  },
  "..."
 ],
 "matchup_tips": [
  "Against fast decks, get a basic attacking as soon as you can rather than waiting for the full Tidemother plan.",
  "Against slow decks, take the extra turn or two to evolve properly before pushing damage.",
  "..."
 ],
 "caveats": [
  "These odds assume a fair shuffle and don't account for what any card's text actually does.",
  "This plan does not assume any tune changes - it's built on the list exactly as submitted."
 ]
}

task: tune - Stone wall, an illegal kitchen-table list (sample deck) (rebuild)

Input:

{
 "task": "tune",
 "deck": "[{\"id\":\"C1\",\"qty\":3,\"name\":\"Pebblet\",\"cat\":\"creature\",\"role\":\"basic\"},{\"id\":\"C2\",\"qty\":2,\"name\":\"Shalehorn\",\"cat\":\"creature\",\"role\":\"basic\"},{\"id\":\"C3\",\"qty\":1,\"name\":\"Ironmoss\",\"cat\":\"creature\",\"role\":\"basic\"},\"... 14 more cards in the same shape\"]",
 "stats": "{\"total\":62,\"rules\":{\"deck_size\":60,\"hand\":7,\"prizes\":6,\"max_copies\":4,\"first_draw\":true,\"first_no_attack\":true},\"going\":\"first\",\"counts\":{\"creature\":10,\"trainer\":40,\"energy\":12},\"count_ids\":[{\"id\":\"N-creature\",\"text\":\"10 creatures (16%)\"},{\"id\":\"N-trainer\",\"text\":\"40 trainers (65%)\"},{\"id\":\"N-energy\",\"text\":\"12 energy (19%)\"}],\"roles\":\"{...}\",\"odds\":[{\"id\":\"S-open\",\"label\":\"Opening 7 holds a basic creature\",\"p\":0.528},{\"id\":\"S-mull\",\"label\":\"Mulligan chance (no basic in 7)\",\"p\":0.472},\"...\"],\"keys\":[{\"id\":\"K1\",\"card\":\"C5\",\"name\":\"Bastion\",\"qty\":2,\"open\":0.215,\"by_turn\":[0.243,0.271,0.299,0.326],\"prize_any\":0.186,\"prize_all\":0.008},{\"id\":\"K2\",\"card\":\"C6\",\"name\":\"Wall Repair\",\"qty\":5,\"open\":0.462,\"by_turn\":[0.511,0.557,0.598,0.637],\"prize_any\":0.41,\"prize_all\":0}],\"legal\":[{\"id\":\"L1\",\"text\":\"The deck has 62 cards; it must have exactly 60 (cut 2).\"},{\"id\":\"L2\",\"text\":\"5 copies of Wall Repair; the limit is 4 (basic energy is exempt).\"}],\"flags\":[{\"id\":\"F-mull\",\"text\":\"A 47% mulligan rate - most lists aim under 20%.\"}],\"lines\":\"[...]\"}",
 "goal": "casual",
 "note": "Kitchen-table deck I play with my kids. I want it to actually work."
}

Reply:

{
 "task": "tune",
 "verdict": "rebuild",
 "title": "Fix the legal count, then tighten the consistency",
 "summary": "Two trims make this a legal 60-card deck: Wall Repair is one over the copy limit and the list runs 2 cards heavy. The bones of a kitchen-table stone wall are here, but the deck still mulligans close to half the time, so there's useful tightening to do after the fix.",
 "ratio": {
  "text": "10 creatures (16%), 40 trainers (65%) and 12 energy (19%) - very trainer-heavy for a deck that only has 10 things that can actually attack or block.",
  "cite": [
   "N-creature",
   "N-trainer",
   "N-energy"
  ]
 },
 "strengths": [
  {
   "text": "A draw trainer shows up in the opening hand about half the time, and close to two-thirds of the time by turn 2, so you're rarely starved for cards.",
   "cite": [
    "S-draw",
    "S-draw2"
   ]
  },
  {
   "text": "Wall Repair is a real engine behind Bastion - you'll see at least one copy about half the time by turn 2.",
   "cite": [
    "K2"
   ]
  }
 ],
 "problems": [
  {
   "text": "The list is 62 cards (2 over the 60-card limit) and runs 5 copies of Wall Repair (1 over the 4-copy limit) - both make it illegal to play as-is.",
   "cite": [
    "L1",
    "L2"
   ]
  },
  {
   "text": "Only 6 basic creatures back the whole deck, so the opening hand misses one almost half the time - a steep mulligan rate for a deck you want to just work.",
   "cite": [
    "N-creature",
    "S-mull",
    "F-mull"
   ]
  },
  "..."
 ],
 "cuts": [
  {
   "card_id": "C6",
   "name": "Wall Repair",
   "qty": 2,
   "why": "Drops Wall Repair from 5 to 3 copies, which clears the 4-copy limit and trims the deck's 2 extra cards in the same move, landing exactly on 60."
  }
 ],
 "adds": [],
 "tips": [
  "Once it's legal, a couple more basic creatures (Pebblet or Shalehorn both have room under the 4-copy limit) would chip away at that mulligan rate.",
  "If you want a sturdier bridge to Bastion, trimming one of the many 4-of 'other' trainers to add a third Cragmaw is worth trying."
 ],
 "caveats": [
  "Odds assume a fair shuffle and don't account for what any of these cards actually do beyond their tagged role."
 ]
}

task: plan - Stone wall, an illegal kitchen-table list (sample deck) (control)

Input:

{
 "task": "plan",
 "deck": "[{\"id\":\"C1\",\"qty\":3,\"name\":\"Pebblet\",\"cat\":\"creature\",\"role\":\"basic\"},{\"id\":\"C2\",\"qty\":2,\"name\":\"Shalehorn\",\"cat\":\"creature\",\"role\":\"basic\"},{\"id\":\"C3\",\"qty\":1,\"name\":\"Ironmoss\",\"cat\":\"creature\",\"role\":\"basic\"},\"... 14 more cards in the same shape\"]",
 "stats": "{\"total\":62,\"rules\":{\"deck_size\":60,\"hand\":7,\"prizes\":6,\"max_copies\":4,\"first_draw\":true,\"first_no_attack\":true},\"going\":\"first\",\"counts\":{\"creature\":10,\"trainer\":40,\"energy\":12},\"count_ids\":[{\"id\":\"N-creature\",\"text\":\"10 creatures (16%)\"},{\"id\":\"N-trainer\",\"text\":\"40 trainers (65%)\"},{\"id\":\"N-energy\",\"text\":\"12 energy (19%)\"}],\"roles\":\"{...}\",\"odds\":[{\"id\":\"S-open\",\"label\":\"Opening 7 holds a basic creature\",\"p\":0.528},{\"id\":\"S-mull\",\"label\":\"Mulligan chance (no basic in 7)\",\"p\":0.472},\"...\"],\"keys\":[{\"id\":\"K1\",\"card\":\"C5\",\"name\":\"Bastion\",\"qty\":2,\"open\":0.215,\"by_turn\":[0.243,0.271,0.299,0.326],\"prize_any\":0.186,\"prize_all\":0.008},{\"id\":\"K2\",\"card\":\"C6\",\"name\":\"Wall Repair\",\"qty\":5,\"open\":0.462,\"by_turn\":[0.511,0.557,0.598,0.637],\"prize_any\":0.41,\"prize_all\":0}],\"legal\":[{\"id\":\"L1\",\"text\":\"The deck has 62 cards; it must have exactly 60 (cut 2).\"},{\"id\":\"L2\",\"text\":\"5 copies of Wall Repair; the limit is 4 (basic energy is exempt).\"}],\"flags\":[{\"id\":\"F-mull\",\"text\":\"A 47% mulligan rate - most lists aim under 20%.\"}],\"lines\":\"[...]\"}",
 "goal": "casual",
 "turn_order": "first",
 "note": "Kitchen-table deck I play with my kids. I want it to actually work."
}

Reply:

{
 "task": "plan",
 "archetype": "control",
 "title": "Stall behind the wall until Bastion lands",
 "summary": "This is a defensive stone-wall list: Wall Repair and Stone Mender patch up your board while Iron Vest, Hold Fast, Grind Down, Tar Pit Gate and Deep Roots slow the opponent down, buying time for Bastion. Note the list is currently 2 cards over the limit and runs one too many Wall Repair, so this plan assumes those are fixed before you play it.",
 "win_con": {
  "text": "This list wins by outlasting rather than racing: healing from Wall Repair and Stone Mender keeps your board alive while the slow-down trainers deny the opponent tempo, so you grind the game long past the point a faster deck expects to win.",
  "cite": [
   "C6",
   "C11",
   "C16",
   "C14",
   "C13",
   "C15"
  ]
 },
 "turns": [
  {
   "turn": 1,
   "goal": "Get a basic creature down and start digging",
   "plays": [
    {
     "text": "Play a basic creature - Pebblet, Shalehorn or Ironmoss - and attach energy if you drew one; no attack yet since you're going first and skip your first attack.",
     "cite": [
      "C1",
      "C2",
      "C3",
      "S-energy"
     ]
    },
    {
     "text": "If Field Journal, Old Notes or Scout Lantern is in hand, use it to dig toward another basic or your first Wall Repair.",
     "cite": [
      "C7",
      "C8",
      "C9"
     ]
    }
   ]
  },
  "... turns 2 and 3 in the same shape"
 ],
 "mulligan": {
  "keep": [
   "A hand with a basic creature (Pebblet, Shalehorn or Ironmoss) plus a draw or search trainer like Field Journal or Scout Lantern.",
   "Two basics and a couple of energy, even with no trainer - you can dig for one next turn."
  ],
  "ship": [
   "A hand with no basic creature at all - the rules force a mulligan on that anyway.",
   "A hand that's all energy and 'other' trainers with no way to find or draw into a creature."
  ]
 },
 "prize_checks": [
  {
   "card_id": "C5",
   "name": "Bastion",
   "why": "At least one Bastion ends up prized about 19% of the time, and with only two copies that hurts.",
   "cite": [
    "K1-prize"
   ]
  },
  {
   "card_id": "C6",
   "name": "Wall Repair",
   "why": "With 5 copies you're unlikely to lose them all, but at least one being prized happens about 41% of the time.",
   "cite": [
    "K2-prize"
   ]
  },
  "..."
 ],
 "matchup_tips": [
  "Against fast decks, the slow-down trainers (Hold Fast, Grind Down, Tar Pit Gate) buy the time Wall Repair needs to matter.",
  "Against slow decks, just keep healing and stalling - a control list like this usually wins the long game if nothing forces a mistake.",
  "..."
 ],
 "caveats": [
  "This list is 2 cards over the limit and runs one too many Wall Repair - this plan assumes those fixes are made before you sit down to play it.",
  "Odds here assume a fair shuffle and don't account for what any of these cards' actual text does beyond their tagged role."
 ]
}

Build the input yourself

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

const K = SleeveKit;
const { cards, warnings } = K.parseList(listText);          // "4 Card Name SET 12" lines, section headers, [tags]
const stats = K.analyze(cards, { deck_size: 60, hand: 7, prizes: 6 }, "first");
const { input } = K.buildInput("tune", { cards, stats, goal: "locals", note: "" });
const wire = K.toWire(input);                               // POST this to /estimate and /run
// later: const reply = K.normalize(K.parseReply(job.output.output));
// K.reconcile(reply, { cards, stats }) -> [{level, code, text}]
// K.diff(stats, K.analyze(K.project(cards, reply), stats.rules, "first")) -> before / after odds
// plan: K.buildInput("plan", { cards, stats, goal, tune: reply })

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://sixty-sleeves.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":"sixty-sleeves"}' | 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": "tune", "deck": "[...]", "stats": "{...}", "goal": "locals"}
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 sixty-sleeves:<task>:<inputHash>:a<attempt>, 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="sixty-sleeves:tune:$(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: sixty-sleeves:tune:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json