← Rough Year
API
Tokens

Drive Rough Year from your own code

Both metered steps - reading a benefits summary into a plan card, and the read of a set of plans - are available over HTTP. A natural use is a benefits portal, an HR chatbot or a household-finance tool that already knows the plans on offer: your code sends the plans and the household's year, and gets back a pick, a plain-words read of each plan and a checklist of what to verify before enrolling.

The split first: the page does the arithmetic; the model gives the judgement; the page checks it. The three priced years, the cost curve, the break-even points, the flags and the suggested verdict come from planmath.js, which runs in node as well as in the browser. Its reconcileVerdict then checks the reply: a verdict is never less cautious than the page's suggestion, a clear pick must be the usual-year winner, every plan gets a read, an unconfirmed must-keep doctor or drug appears on the checklist, every cited id is on the page, and every dollar figure is one the page computed. reconcileExtract checks that every number read from a summary appears in the pasted text. The service prices are typical estimates, not any insurer's rates.

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":"rough-year"}), 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 task 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. There are two tasks, in the order a person meets them:

taskWhat it returnsVerdict field
extractplan (the card's fields - name, kind, premium_month, deductible, oop_max, coinsurance, copay_pcp, copay_specialist, copay_urgent, copay_er, rx_generic, rx_brand, employer_hsa - as numbers or null), found [{field, quote}], unclear (fields the text does not state), gotchas [{text, quote}], summary.none
verdictpick (a plan id), title, summary; plans [{id, fit, read, watch_outs[], cite}] with fit best, fine or poor; years [{year, text, cite}] for quiet, usual, rough; before_enroll [{item, why, cite}]; questions; caveats.verdict: clear-pick, close-call, verify-first

clear-pick means one plan is cheapest in the usual and the rough year by at least $400 with nothing open; close-call means the winner flips between those years or the margin is small; verify-first means the cheapest plan has a blank field the price depends on, or a must-keep doctor or drug not confirmed for it. Cites are the page's ids: plans P1…P4, must-keep items K1…, flags X1…, break-evens B1…, years Y-quiet, Y-usual, Y-rough, and N-note.

The declared input schema carries scalars only, so object-valued fields travel JSON-encoded as strings (PlanMath.toWire does this). Only task is required by the schema; each task needs its own fields below.

Input fields

FieldTaskTypeNotes
taskbothstringextract or verdict.
sbc_textextractstringThe pasted benefits summary. Past 9,000 characters the page cuts whole lines from the middle, keeping the top and the end.
plan_labelextractstringOptional, what the person calls the plan (60 characters).
plansverdictstring (JSON)[{id, name, kind, current, premium_month, deductible, oop_max, coinsurance, copay_pcp, copay_specialist, copay_urgent, copay_er, rx_generic, rx_brand, employer_hsa, notes?}]; kind copay or hsa; blank fields are null.
usageverdictstring (JSON){pcp, specialist, urgent, er, generic, brand, labs, planned} - counts for the usual year, planned in dollars billed.
yearsverdictstring (JSON)Three entries {id, name, billed, winner, runner_up, margin, costs[{plan, total, premium, oop, hsa, hit_max}]} from PlanMath.compare.
checksverdictstring (JSON){suggested, why, flags[{id, kind, plan, text}], breakevens[{id, from, to, extra}], prices, rough_stay}.
must_keepverdictstring (JSON)[{id, text, confirmed[]}].
noteverdictstringOptional, the person's own words; over 700 characters keeps the start and the end around a marker.
retry_notebothstringOnly on a reformat retry.

The reply

The job's output.output is a string holding one JSON object. Parse it with PlanMath.parseReply, then normalizeExtract or normalizeVerdict, then check it with reconcileExtract(reply, sbcText) or reconcileVerdict(reply, state). planFromExtract(reply) turns an extract reply into a card.

Worked examples

Replies produced against the app's prompt for the sample plans (made-up plans and people; 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 the long fields folded.

task: extract - Read a plan summary

Input:

{
 "task": "extract",
 "sbc_text": "Summary of Benefits and Coverage: What this Plan Covers & What You Pay for Covered Services\nRiverbend Choice Silver 2500 - Coverage Period: 01/01/2027 - 12/31/2027 - Coverage for: Individual + Family - Plan Type: PPO\n\nImportant Questions / Answers / Why This Matters\nWhat is the overall deductible? $2,500 individual / $5,000 family. Generally, you must pay all of the costs from providers up to the deductible amount before this plan begins to pay.\nAre there services covered before you meet your deductible? Yes. Preventive care, primary care and specialist office visits, urgent care and generic drugs are covered before you meet your deductible.\n... 22 more lines",
 "plan_label": "Riverbend Silver 2500"
}

Reply:

{
 "task": "extract",
 "plan": {
  "name": "Riverbend Silver 2500",
  "kind": "copay",
  "premium_month": null,
  "deductible": 2500,
  "oop_max": 7500,
  "coinsurance": 20,
  "copay_pcp": 35,
  "copay_specialist": 70,
  "copay_urgent": 75,
  "copay_er": 500,
  "rx_generic": 15,
  "rx_brand": 50,
  "employer_hsa": null
 },
 "found": [
  {
   "field": "kind",
   "quote": "Preventive care, primary care and specialist office visits, urgent care and generic drugs are covered before you meet your deductible."
  },
  {
   "field": "deductible",
   "quote": "$2,500 individual / $5,000 family"
  },
  "..."
 ],
 "unclear": [
  "premium_month",
  "employer_hsa"
 ],
 "gotchas": [
  {
   "text": "Brand drugs have their own separate $250 deductible before the brand drug copay applies.",
   "quote": "$250 for preferred and non-preferred brand drugs."
  },
  {
   "text": "Emergency room care is not a flat copay - it is a $500 copay plus 20% coinsurance after that.",
   "quote": "$500 copay, then 20% coinsurance"
  },
  "..."
 ],
 "summary": "This is a copay-style PPO plan: visits, urgent care and generic drugs use copays before the deductible, and coinsurance (20% in-network) applies to tests, imaging, surgery and hospital stays. Used the individual deductible ($2,500) and individual out-of-pocket max ($7,500) rather than the family amounts. The monthly premium and any employer HSA contribution are not stated in this summary of benefits, so both are left blank."
}

task: verdict - Single, rarely sick (clear-pick)

Input:

{
 "task": "verdict",
 "plans": "[{\"id\":\"P1\",\"name\":\"Silver PPO\",\"kind\":\"copay\",\"current\":true,\"premium_month\":145,\"deductible\":1500,\"oop_max\":6000,\"coinsurance\":20,\"copay_pcp\":30,\"copay_specialist\":60,\"copay_urgent\":75,\"copay_er\":400,\"rx_generic\":10,\"rx_brand\":60,\"employer_hsa\":null},\"... 1 more plans\"]",
 "usage": "{\"pcp\":2,\"specialist\":1,\"urgent\":1,\"er\":0,\"generic\":12,\"brand\":0,\"labs\":1,\"planned\":0}",
 "years": "[{\"id\":\"Y-quiet\",\"name\":\"Quiet year\",\"billed\":410,\"winner\":\"P2\",\"runner_up\":\"P1\",\"margin\":1904,\"costs\":[{\"plan\":\"P1\",\"total\":1890,\"premium\":1740,\"oop\":150,\"hsa\":0,\"hit_max\":false},{\"plan\":\"P2\",\"total\":-14,\"premium\":576,\"oop\":410,\"hsa\":1000,\"hit_max\":false}]},\"... quiet and rough years folded\"]",
 "checks": "{\"suggested\":\"clear-pick\",\"why\":\"P2 is cheapest in the usual and the rough year, by $1,429 in the usual year.\",\"flags\":[],\"breakevens\":[],\"prices\":{\"pcp\":170,\"specialist\":260,\"urgent\":210,\"er\":2200,\"generic\":20,\"brand\":480,\"labs\":350},\"rough_stay\":45000}",
 "must_keep": "[]",
 "note": "Just me, 29, healthy. I have been on the PPO by default since I started this job and never looked at the other one. I keep an emergency fund that would cover a big bill."
}

Reply:

{
 "task": "verdict",
 "verdict": "clear-pick",
 "pick": "P2",
 "title": "HSA Saver wins every year",
 "summary": "Pick HSA Saver (P2): it is $1,429 cheaper than Silver PPO in your usual year, and it also wins the quiet year and the rough year with a hospital stay, so this is a clear pick. Nothing missing or must-keep is hanging over it. The call could change if you find doctors you need aren't in its network, or if your real year of care looks very different from the usual year you entered.",
 "plans": [
  {
   "id": "P1",
   "fit": "fine",
   "read": "Silver PPO, your current plan, keeps costs predictable with flat copays ($30 PCP, $60 specialist, $400 ER) instead of a high deductible, but it costs more every year - $2,405 in your usual year versus HSA Saver's $976.",
   "watch_outs": [
    "In a rough year (ER visit plus hospital stay) this plan reaches its $6,000 out-of-pocket max for a $7,740 total, still more than HSA Saver's $5,976.",
    "This plan has no employer HSA money listed, so there's no $1,000 contribution offsetting costs the way HSA Saver gets."
   ],
   "cite": [
    "P1",
    "P2",
    "Y-usual",
    "Y-rough"
   ]
  },
  {
   "id": "P2",
   "fit": "best",
   "read": "HSA Saver pairs a $48 monthly premium with a $1,000 employer contribution; your usual-year total comes to $976, well under Silver PPO's $2,405, because the premium is lower and the employer money offsets the high deductible.",
   "watch_outs": [
    "No flat copay amounts are listed for this plan (PCP, specialist, urgent, ER, prescriptions are all blank) - visits and prescriptions are billed at cost toward the $3,200 deductible, so a run of pricier visits adds up before coinsurance kicks in.",
    "This is a new plan for you - Silver PPO is the one you've actually used - so it's worth checking the network and prescription pricing before switching.",
    "In a rough year this plan still reaches its $6,400 out-of-pocket max for a $5,976 total - cheaper than Silver PPO's $7,740, but not cheap outright."
   ],
   "cite": [
    "P1",
    "P2",
    "Y-usual",
    "Y-rough"
   ]
  }
 ],
 "years": [
  {
   "year": "quiet",
   "text": "HSA Saver wins the quiet year: its $1,000 employer contribution covers more than the year's low bills, for a net total of -$14, versus Silver PPO's $1,890.",
   "cite": [
    "Y-quiet",
    "P1",
    "P2"
   ]
  },
  {
   "year": "usual",
   "text": "HSA Saver stays ahead in the usual year too, beating Silver PPO by $1,429 ($976 versus $2,405).",
   "cite": [
    "Y-usual",
    "P1",
    "P2"
   ]
  },
  {
   "year": "rough",
   "text": "Even with an ER visit and a hospital stay, HSA Saver wins by $1,764 ($5,976 versus $7,740), though both plans hit their out-of-pocket max that year.",
   "cite": [
    "Y-rough",
    "P1",
    "P2"
   ]
  }
 ],
 "before_enroll": [
  {
   "item": "Check that HSA Saver's network includes your usual doctors",
   "why": "No network details are given here, and this would be a new plan for you after defaulting to Silver PPO.",
   "cite": [
    "P2",
    "N-note"
   ]
  },
  {
   "item": "Ask what you'd actually pay per visit and per prescription under HSA Saver",
   "why": "Those fields were left blank in this comparison, so the real per-visit cost before the deductible isn't priced yet.",
   "cite": [
    "P2"
   ]
  },
  "..."
 ],
 "questions": [
  "Does HSA Saver's network include my current doctors and any specialists I see?",
  "What will I actually pay per visit and per prescription under HSA Saver before I hit the deductible?",
  "..."
 ],
 "caveats": [
  "These are the page's typical-cost estimates based on the prices it used for visits and prescriptions, not quotes from either plan.",
  "HSA Saver's visit and prescription costs are modeled through its deductible and coinsurance since no copay amounts were given; your actual bills could differ.",
  "..."
 ]
}

task: verdict - Couple, one brand-name drug (close-call)

Input:

{
 "task": "verdict",
 "plans": "[{\"id\":\"P1\",\"name\":\"Gold HMO\",\"kind\":\"copay\",\"current\":false,\"premium_month\":310,\"deductible\":500,\"oop_max\":4000,\"coinsurance\":10,\"copay_pcp\":20,\"copay_specialist\":40,\"copay_urgent\":50,\"copay_er\":300,\"rx_generic\":5,\"rx_brand\":45,\"employer_hsa\":null},\"... 2 more plans\"]",
 "usage": "{\"pcp\":4,\"specialist\":3,\"urgent\":1,\"er\":0,\"generic\":12,\"brand\":12,\"labs\":2,\"planned\":0}",
 "years": "[{\"id\":\"Y-quiet\",\"name\":\"Quiet year\",\"billed\":6170,\"winner\":\"P3\",\"runner_up\":\"P1\",\"margin\":530,\"costs\":[{\"plan\":\"P1\",\"total\":4340,\"premium\":3720,\"oop\":620,\"hsa\":0,\"hit_max\":false},{\"plan\":\"P2\",\"total\":6981,\"premium\":1680,\"oop\":6051,\"hsa\":750,\"hit_max\":false},{\"plan\":\"P3\",\"total\":3810,\"premium\":2640,\"oop\":1170,\"hsa\":0,\"hit_max\":false}]},\"... quiet and rough years folded\"]",
 "checks": "{\"suggested\":\"close-call\",\"why\":\"P3 wins the usual year but P1 wins the rough year.\",\"flags\":[],\"breakevens\":[{\"id\":\"B1\",\"from\":\"P3\",\"to\":\"P1\",\"extra\":250},{\"id\":\"B2\",\"from\":\"P3\",\"to\":\"P2\",\"extra\":15100}],\"prices\":{\"pcp\":170,\"specialist\":260,\"urgent\":210,\"er\":2200,\"generic\":20,\"brand\":480,\"labs\":350},\"rough_stay\":45000}",
 "must_keep": "[{\"id\":\"K1\",\"text\":\"Monthly brand-name inhaler\",\"confirmed\":[\"P1\",\"P2\",\"P3\"]}]",
 "note": "Two of us. My partner has asthma and fills a brand-name inhaler every month; she ended up in urgent care once this spring. A big surprise bill would really hurt us this year."
}

Reply:

{
 "task": "verdict",
 "verdict": "close-call",
 "pick": "P1",
 "title": "Close call, leaning toward protection",
 "summary": "Silver PPO (your current plan) wins the usual year by just $220, but Gold HMO wins the rough year by $1,210 once bills run $250 past the usual year. Because you said a surprise bill would really hurt this year, and you already have a monthly brand-name drug and one urgent-care visit in the mix, Gold HMO (P1) is the steadier pick. If next year stays quiet instead, Silver PPO would actually cost less.",
 "plans": [
  {
   "id": "P1",
   "fit": "best",
   "read": "Highest monthly premium of the three, but the lowest deductible ($500) and out-of-pocket max ($4,000) cap a bad year early. Usual-year total is $5,090.",
   "watch_outs": [
    "No employer HSA/HRA contribution is shown for this plan, unlike Bronze HSA's $750 - worth asking about.",
    "Still has 10% coinsurance and a $45 brand-drug copay after the deductible, so costs don't stop completely."
   ],
   "cite": [
    "P1",
    "P2",
    "Y-usual",
    "Y-rough"
   ]
  },
  {
   "id": "P2",
   "fit": "poor",
   "read": "Lowest premium ($140/month), but a $6,000 deductible means nearly everything is paid out of pocket until then. Usual-year total is $7,641, the most expensive of the three in a typical year.",
   "watch_outs": [
    "No flat copays on this plan - every visit and prescription counts toward the $6,000 deductible first.",
    "Even in a rough year, with the $750 employer HSA help, this plan still costs more than Gold HMO ($8,930 vs $7,720)."
   ],
   "cite": [
    "P1",
    "P2",
    "Y-usual",
    "Y-rough"
   ]
  },
  "..."
 ],
 "years": [
  {
   "year": "quiet",
   "text": "In a quiet year (one check-up, regular prescriptions), Silver PPO is cheapest at $3,810, $530 less than Gold HMO.",
   "cite": [
    "Y-quiet",
    "P3",
    "P1"
   ]
  },
  {
   "year": "usual",
   "text": "In the usual year you described, Silver PPO still wins, but only by $220 over Gold HMO ($4,870 vs $5,090) - a thin margin.",
   "cite": [
    "Y-usual",
    "P3",
    "P1"
   ]
  },
  {
   "year": "rough",
   "text": "If the year turns rough (the usual year plus an ER visit and a hospital stay), Gold HMO becomes cheapest at $7,720, $1,210 less than the next plan, because its $4,000 out-of-pocket max caps costs sooner than the others.",
   "cite": [
    "Y-rough",
    "P1",
    "P2",
    "B1"
   ]
  }
 ],
 "before_enroll": [
  {
   "item": "Check whether the deductible and out-of-pocket max figures are per person or for your whole household.",
   "why": "You're a household of two; a shared family cap can work very differently than the per-person numbers shown here.",
   "cite": [
    "N-note",
    "Y-usual"
   ]
  },
  {
   "item": "Ask the benefits office whether Gold HMO offers any employer HSA or HRA contribution.",
   "why": "The page shows none for Gold HMO, while Bronze HSA gets $750 - a blank here could mean missing money or just no benefit on this plan.",
   "cite": [
    "P1",
    "P2"
   ]
  },
  "..."
 ],
 "questions": [
  "Does Gold HMO include any employer HSA or HRA contribution?",
  "Are the deductible and out-of-pocket max amounts individual or family totals?",
  "..."
 ],
 "caveats": [
  "These are the page's typical-cost estimates, not quotes from an insurer.",
  "It isn't stated whether the deductible and out-of-pocket figures are per-person or family amounts, which matters for a household of two.",
  "..."
 ]
}

task: verdict - New job, keep my cardiologist (verify-first)

Input:

{
 "task": "verdict",
 "plans": "[{\"id\":\"P1\",\"name\":\"Open PPO\",\"kind\":\"copay\",\"current\":false,\"premium_month\":265,\"deductible\":1000,\"oop_max\":5000,\"coinsurance\":20,\"copay_pcp\":25,\"copay_specialist\":50,\"copay_urgent\":60,\"copay_er\":350,\"rx_generic\":10,\"rx_brand\":50,\"employer_hsa\":null},\"... 2 more plans\"]",
 "usage": "{\"pcp\":2,\"specialist\":6,\"urgent\":0,\"er\":0,\"generic\":0,\"brand\":12,\"labs\":3,\"planned\":0}",
 "years": "[{\"id\":\"Y-quiet\",\"name\":\"Quiet year\",\"billed\":5930,\"winner\":\"P3\",\"runner_up\":\"P2\",\"margin\":654,\"costs\":[{\"plan\":\"P1\",\"total\":3805,\"premium\":3180,\"oop\":625,\"hsa\":0,\"hit_max\":false},{\"plan\":\"P2\",\"total\":2480,\"premium\":1800,\"oop\":680,\"hsa\":0,\"hit_max\":false},{\"plan\":\"P3\",\"total\":1826,\"premium\":1140,\"oop\":1186,\"hsa\":500,\"hit_max\":false}]},\"... quiet and rough years folded\"]",
 "checks": "{\"suggested\":\"verify-first\",\"why\":\"The usual-year pick, P3, has open questions: X1, X3, X4.\",\"flags\":[{\"id\":\"X1\",\"kind\":\"missing\",\"plan\":\"P3\",\"text\":\"HSA Basic: no deductible entered - priced as $0.\"},{\"id\":\"X2\",\"kind\":\"unconfirmed\",\"plan\":\"P2\",\"text\":\"Dr. Okafor (cardiologist) (K1) is not confirmed for Local EPO.\"},{\"id\":\"X3\",\"kind\":\"unconfirmed\",\"plan\":\"P3\",\"text\":\"Dr. Okafor (cardiologist) (K1) is not confirmed for HSA Basic.\"},{\"id\":\"X4\",\"kind\":\"unconfirmed\",\"plan\":\"P3\",\"text\":\"Brand-name blood thinner (K2) is not confirmed for HSA Basic.\"}],\"breakevens\":[{\"id\":\"B1\",\"from\":\"P3\",\"to\":\"P2\",\"extra\":24600}],\"prices\":{\"pcp\":170,\"specialist\":260,\"urgent\":210,\"er\":2200,\"generic\":20,\"brand\":480,\"labs\":350},\"rough_stay\":45000}",
 "must_keep": "[{\"id\":\"K1\",\"text\":\"Dr. Okafor (cardiologist)\",\"confirmed\":[\"P1\"]},{\"id\":\"K2\",\"text\":\"Brand-name blood thinner\",\"confirmed\":[\"P1\",\"P2\"]}]",
 "note": "I start the new job in November and have 30 days to choose. I see my cardiologist every other month and take a brand-name blood thinner. The HSA plan's deductible was not on the one-page flyer HR sent."
}

Reply:

{
 "task": "verdict",
 "verdict": "verify-first",
 "pick": "P3",
 "title": "HSA Basic leads, but verify first",
 "summary": "HSA Basic (P3) is the cheapest plan in the usual year, $1,398 ahead of Local EPO and $2,758 ahead of Open PPO, and it stays cheapest in a quiet year too. But its deductible was left blank on the flyer, and neither your cardiologist nor your brand-name blood thinner is confirmed for it yet, so it only holds up if those checks pass. If your year runs rough, Local EPO overtakes it by $340, so a heavier-than-usual year could change the answer.",
 "plans": [
  {
   "id": "P1",
   "fit": "poor",
   "read": "Open PPO costs more than Local EPO in every year modeled - $5,140 versus $3,780 in the usual year - for similar coverage. Its one advantage is that both your cardiologist and your blood thinner are already confirmed for it.",
   "watch_outs": [
    "Highest total cost of the three plans in the quiet, usual, and rough years"
   ],
   "cite": [
    "P1",
    "P2",
    "Y-quiet",
    "Y-usual",
    "Y-rough"
   ]
  },
  {
   "id": "P2",
   "fit": "fine",
   "read": "Local EPO is the middle-cost plan in a usual year ($3,780) and becomes the cheapest plan in a rough year ($7,300, $340 under HSA Basic). Its own notes flag a smaller network: one hospital system in the county and no out-of-network cover.",
   "watch_outs": [
    "Smaller network, no out-of-network cover (per its own notes)",
    "Your cardiologist is not yet confirmed in-network here"
   ],
   "cite": [
    "P2",
    "Y-usual",
    "Y-rough",
    "X2",
    "K1"
   ]
  },
  "..."
 ],
 "years": [
  {
   "year": "quiet",
   "text": "HSA Basic wins a quiet year by $654 over Local EPO, helped by its low premium and the $500 employer HSA contribution covering most of the light care.",
   "cite": [
    "Y-quiet",
    "P3",
    "P2"
   ]
  },
  {
   "year": "usual",
   "text": "HSA Basic stays cheapest in the usual year, $1,398 ahead of Local EPO, even with six specialist visits and twelve brand-name fills in the mix.",
   "cite": [
    "Y-usual",
    "P3",
    "P2"
   ]
  },
  {
   "year": "rough",
   "text": "A rough year flips the order: Local EPO becomes cheapest by $340 over HSA Basic, since EPO's $5,500 out-of-pocket max is lower than HSA Basic's $7,000 once both plans hit their caps.",
   "cite": [
    "Y-rough",
    "P2",
    "P3"
   ]
  }
 ],
 "before_enroll": [
  {
   "item": "Confirm your cardiologist is in-network for HSA Basic before enrolling.",
   "why": "This must-keep doctor is not yet confirmed for the pick.",
   "cite": [
    "K1",
    "X3"
   ]
  },
  {
   "item": "Confirm the brand-name blood thinner is on HSA Basic's drug list and check its cost.",
   "why": "This must-keep prescription is not yet confirmed for the pick.",
   "cite": [
    "K2",
    "X4"
   ]
  },
  "..."
 ],
 "questions": [
  "What is HSA Basic's actual annual deductible?",
  "Is my cardiologist in-network for HSA Basic?",
  "..."
 ],
 "caveats": [
  "These are typical estimates, not quotes from any insurer.",
  "HSA Basic's deductible and other blank fields were priced using defaults ($0 deductible), not confirmed plan terms.",
  "..."
 ]
}

Build the input yourself

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

const M = PlanMath;
const st = M.state({
  plans: [
    { name: "Silver PPO", kind: "copay", premium_month: 145, deductible: 1500, oop_max: 6000, coinsurance: 20, copay_pcp: 30, rx_generic: 10 },
    { name: "HSA Saver", kind: "hsa", premium_month: 48, deductible: 3200, oop_max: 6400, coinsurance: 20, employer_hsa: 1000 }
  ],
  usage: { pcp: 2, specialist: 1, generic: 12, labs: 1 },
  must: "Dr. Lee (family doctor): in P1, P2",
  note: ""
});
const { input, analysis } = M.buildInput(st, {});   // analysis.years.usual.winner, analysis.breakevens ...
const wire = M.toWire(input);                         // POST this to /estimate and /run
// later: const reply = M.normalizeVerdict(M.parseReply(job.output.output));
// M.reconcileVerdict(reply, st, {}) -> [{level, code, text}]
// extract: M.toWire(M.buildExtract(sbcText, "Silver 2500").input)

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://rough-year.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":"rough-year"}' | 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": "verdict", "plans": "[...]", "usage": "{...}", "years": "[...]", "checks": "{...}", "must_keep": "[...]"} or {"task": "extract", "sbc_text": "..."}
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 rough-year:<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="rough-year:verdict:$(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: rough-year:verdict:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json