← Booth Math
Booth Math API
Tokens

Drive Booth Math from your own code

The one metered step - the price check of one maker's table at one craft fair - is available over HTTP. A natural use is an inventory spreadsheet or a shop back office: your code sends the stock list with the page's arithmetic, and gets back a status, quote, reason, suggested whole-dollar price and fix for every item, up to three bundles, the signs for the table, next steps and a ready / reprice / rethink verdict.

The split first: the page does the arithmetic; the model judges; the page holds the model to the arithmetic. Reading the list, the floor per item, the headroom, the tiers and the three sell-through gauges come from boothkit.js, which runs in node as well as in the browser. Its check function then compares the reply with those numbers: a status is never shown better than the page counted, a suggested price under its floor is flagged, every quote must be in the list, bundles must name listed items and clear their floors, and every price on a sign must be one the reply gave.

Get a token on the token page (step 1 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":"booth-math"}), 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: the price check is metered and needs a personal token.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field comes first

Every run sends task. There is one task, check. It returns title, verdict, summary; items [{item, status, says, why, price, fix}], one per item in the page's order; bundles [{name, items[], price, why}] (0-3); signs (2-4 lines); next_steps (3-5); caveats (0-3).

VerdictMeaning
readyEvery item is healthy, something sells under $15, and 35% or less of the stock covers the booth plus your days at the fair.
repriceThe booth can pay for itself (60% or less), but some prices are thin or underwater, nothing is under $15, or it takes over 35%.
rethinkOver 60% of the stock must sell to cover the booth and your days, or no amount of selling covers it: weigh the fair and the stock before repricing.
Item statusMeaning
healthy30% or more of the current price sits above the floor. fix is empty when the price stays.
thinAbove the floor, but under 30% headroom. The suggested price is at least the floor.
underwaterUnder the floor: every sale loses money on your time. The suggested price is never under the floor.

The declared input schema carries scalars only, so object-valued fields travel JSON-encoded as strings (BoothKit.toWire does this). The schema requires task, today, fair_date, stock, facts, items, page_reads and page_verdict; notes and retry_note are optional.

Input fields

FieldTypeNotes
taskstringAlways check.
todaystringYYYY-MM-DD.
fair_datestringThe fair's (first) day, YYYY-MM-DD, or (not given).
stockstringThe stock list as pasted. Past 6,000 characters the middle is cut on whole lines around a marker.
factsstring (JSON){fair, date, days_to_fair, days, hours_per_day, hours_assumed, booth_fee, other_costs, fixed_costs, wage, wage_assumed, card_fee_pct, card_fee_assumed, items_n, units, stock_value, materials_spent, making_hours, fair_day_cost, cash_at_sellout, net_at_sellout, needed_pct_booth, needed_pct_day, needed_pct_all, units_for_booth, sales_per_hour_for_booth, underwater_n, thin_n, healthy_n, tiers{anchor, core, impulse}}. A needed_pct_* of null means no amount of selling covers it.
itemsstring (JSON)[{item, materials, minutes, price, qty, floor, target, headroom_pct, page_status, tier, cash_per_sale}].
page_readsstring (JSON)[{level, code, text}]; level is fix or info. At most 40; past that the per-item price flags are cut and a closing reads_cut flag says how many.
page_verdictstringThe page's own reading: ready, reprice or rethink.
notesstringOptional, the maker's own comments; past 600 characters the middle is cut.
retry_notestringOnly on a reformat retry.

The reply

The job's output.output is a string holding one JSON object. Parse it with BoothKit.parseReply, then normalize, then check it with check(reply, state), which returns {rows, notes}: one row per item (the reply's status and price, or the page's when the reply skipped the item or called it better than the numbers allow) and the notes (ok, info, warn, bad) the page shows under "Checked against your list".

Worked examples

Replies produced against the app's prompt for the sample tables (made-up fairs and prices; 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: check - Candles and soap - ready

Input:

{
 "task": "check",
 "today": "2026-10-19",
 "fair_date": "2026-12-05",
 "stock": "Beeswax votives (set of 3) | 2.10 | 6 min | $12 | 40\nLavender soap bar | 1.60 | 5 min | $8 | 60\nBeeswax pillar candle | 6.50 | 20 min | $28 | 24\nSoap gift box (4 bars) | 9.00 | 25 min | $38 | 15\nCandle in a ceramic bowl  ... (246 characters)",
 "facts": "{\"fair\":\"Quillby Hall Holiday Market\",\"date\":\"2026-12-05\",\"days_to_fair\":47,\"days\":1,\"hours_per_day\":7,\"hours_assumed\":false,\"booth_fee\":85,\"other_costs\":15,\"fixed_costs\":100,\"wage\":18,\"wage_assumed\":false,\"card_fee_pct\":2.9,\"card_fee_assumed\":false,\"items_n\":5,\"units\":145,\"stock_value\":2610,\"materials_spent\":555,\"making_hours\":27.75,\"fair_day_cost\":126,\"cash_at_sellout\":1979.31,\"net_at_sellout\":1479.81,\"needed_pct_booth\":5,\"needed_pct_day\":11,\"needed_pct_all\":15,\"units_for_booth\":8,\"sales_per_hour_for_booth\":1.1,\"underwater_n\":0,\"thin_n\":0,\"healthy_n\":5,\"tiers\":{\"anchor\":{\"items\":1,\"units\":6},\"core\":{\"items\":2,\"units\":39},\"impulse\":{\"items\":2,\"units\":100}}}",
 "items": "[{\"item\":\"Beeswax votives (set of 3)\",\"materials\":2.1,\"minutes\":6,\"price\":12,\"qty\":40,\"floor\":4.02,\"target\":6,\"headroom_pct\":67,\"page_status\":\"healthy\",\"tier\":\"impulse\",\"cash_per_sale\":9.55},{\"item\":\"Lavender soap bar\",\"materials\":1.6,\"minutes\":5,\"price\":8,\"qty\":60,\"floor\":3.19,\"target\":5,\"headroom_pct\":60,\"page_status\":\"healthy\",\"tier\":\"impulse\",\"cash_per_sale\":6.17},\"... 3 more\"]",
 "page_reads": "[]",
 "page_verdict": "ready",
 "notes": ""
}

Reply:

{
 "task": "check",
 "title": "Beeswax Candles & Soap Table",
 "verdict": "ready",
 "summary": "This table is ready: all five items are healthy, none underwater or thin, and the booth needs just 5% of stock sold to clear its $85 fee, or 11% to cover the whole fair day. The one soft spot is the anchor tier - a single $68 ceramic-bowl candle is the only showpiece - so the bundles below help lift the average sale without touching a price. Keep the current prices and spend the weeks before December 5 on signs and on cutting the soap-bar and pillar-candle bundles.",
 "items": [
  {
   "item": "Beeswax votives (set of 3)",
   "status": "healthy",
   "says": "Beeswax votives (set of 3) | 2.10 | 6 min | $12 | 40",
   "why": "At $12 against a $4.02 floor, this set carries 67% headroom - a strong impulse-tier seller and the table's cheapest way in.",
   "price": 12,
   "fix": ""
  },
  {
   "item": "Lavender soap bar",
   "status": "healthy",
   "says": "Lavender soap bar | 1.60 | 5 min | $8 | 60",
   "why": "At $8 against a $3.19 floor, this bar carries 60% headroom and is the table's other impulse-tier piece, with 60 on the list - the biggest count on the table.",
   "price": 8,
   "fix": ""
  },
  {
   "item": "Beeswax pillar candle",
   "status": "healthy",
   "says": "Beeswax pillar candle | 6.50 | 20 min | $28 | 24",
   "why": "At $28 against a $12.87 floor, this candle carries 54% headroom, a solid core-tier price for the 24 on the list.",
   "price": 28,
   "fix": ""
  },
  "..."
 ],
 "bundles": [
  {
   "name": "Votive & Soap Sampler",
   "items": [
    "Beeswax votives (set of 3)",
    "Lavender soap bar"
   ],
   "price": 18,
   "why": "Pairs the two impulse pieces for $2 under buying them separately ($20), still well clear of their combined $7.21 floor."
  },
  {
   "name": "Pillar & Gift Box Pairing",
   "items": [
    "Beeswax pillar candle",
    "Soap gift box (4 bars)"
   ],
   "price": 64,
   "why": "Combines the two core pieces for $2 under the $66 separate total, comfortably above their combined $29.86 floor."
  },
  "..."
 ],
 "signs": [
  "Beeswax votives (set of 3) - $12",
  "Lavender soap bars - $8, 2 for $15",
  "Soap gift box (4 bars) - $38",
  "Candle in a ceramic bowl - $68"
 ],
 "next_steps": [
  "Keep all five prices as listed - every piece already clears 30%+ headroom.",
  "Letter and print the signs below, including the soap-bar 2-pack and the pillar-candle/gift-box pairing.",
  "Arrange the table so the 100 impulse-tier units (votives, soap bars) are front and easy to grab.",
  "..."
 ],
 "caveats": [
  "These numbers only reflect the materials, minutes and costs entered - anything left off the list isn't priced in.",
  "The 30% headroom gauge is a rule of thumb, not a guarantee of what sells at this particular fair."
 ]
}

task: check - Small-batch ceramics - reprice

Input:

{
 "task": "check",
 "today": "2026-10-19",
 "fair_date": "2026-11-22",
 "stock": "Stoneware mug | 7.00 | 50 min | $28 | 24\nRing dish | 2.50 | 15 min | $14 | 30\nServing bowl | 16.00 | 2 h | $45 | 8\nBud vase | 4.00 | 30 min | $22 | 18\nSpoon rest | 2.00 | 12 min | $12 | 20 ... (188 characters)",
 "facts": "{\"fair\":\"Wrenmoor Makers Holiday Fair\",\"date\":\"2026-11-22\",\"days_to_fair\":34,\"days\":2,\"hours_per_day\":7,\"hours_assumed\":false,\"booth_fee\":175,\"other_costs\":40,\"fixed_costs\":215,\"wage\":20,\"wage_assumed\":false,\"card_fee_pct\":2.6,\"card_fee_assumed\":false,\"items_n\":5,\"units\":100,\"stock_value\":2088,\"materials_spent\":483,\"making_hours\":56.5,\"fair_day_cost\":280,\"cash_at_sellout\":1550.71,\"net_at_sellout\":420.71,\"needed_pct_booth\":14,\"needed_pct_day\":32,\"needed_pct_all\":118,\"units_for_booth\":14,\"sales_per_hour_for_booth\":1,\"underwater_n\":1,\"thin_n\":1,\"healthy_n\":3,\"tiers\":{\"anchor\":{\"items\":0,\"units\":0},\"core\":{\"items\":3,\"units\":50},\"impulse\":{\"items\":2,\"units\":50}}}",
 "items": "[{\"item\":\"Stoneware mug\",\"materials\":7,\"minutes\":50,\"price\":28,\"qty\":24,\"floor\":24.3,\"target\":35,\"headroom_pct\":13,\"page_status\":\"thin\",\"tier\":\"core\",\"cash_per_sale\":20.27},{\"item\":\"Ring dish\",\"materials\":2.5,\"minutes\":15,\"price\":14,\"qty\":30,\"floor\":7.7,\"target\":12,\"headroom_pct\":45,\"page_status\":\"healthy\",\"tier\":\"impulse\",\"cash_per_sale\":11.14},\"... 3 more\"]",
 "page_reads": "[{\"level\":\"fix\",\"code\":\"underwater\",\"text\":\"Serving bowl sells at $45 but costs $57.49 in materials, your 120 minutes and the card fee - every sale loses money on your time. $83 gives 30% headroom.\"},{\"level\":\"fix\",\"code\":\"thin\",\"text\":\"Stoneware mug at $28 clears its $24.30 floor by only 13% - $35 gives 30% headroom.\"},\"... 1 more\"]",
 "page_verdict": "reprice",
 "notes": "Mugs sold out by 1pm at this fair last year; the serving bowls barely moved."
}

Reply:

{
 "task": "check",
 "title": "Reprice Two Pieces Before Wrenmoor Fair",
 "verdict": "reprice",
 "summary": "This booth easily covers its costs - just 14% of stock sold pays the $175 booth fee, and 32% covers the full two days of selling time - so reprice is the right call rather than rethinking the fair. One item, the serving bowl, is underwater at $45 against a $57.49 floor, and the stoneware mug is thin, clearing its floor by only 13%. The single biggest fix is raising the serving bowl toward its $83 target, which also gives the table the $50+ anchor it currently lacks; the mug, which sold out by 1pm last year, can safely move up to $35.",
 "items": [
  {
   "item": "Stoneware mug",
   "status": "thin",
   "says": "Stoneware mug | 7.00 | 50 min | $28 | 24",
   "why": "At $28 the mug clears its $24.30 floor by only 13% headroom, leaving little buffer for 50 minutes of work. It sold out by 1pm at this fair last year, so there's demand to support a higher price.",
   "price": 35,
   "fix": "Raise to $35 - last year's early sellout shows room to move it up."
  },
  {
   "item": "Ring dish",
   "status": "healthy",
   "says": "Ring dish | 2.50 | 15 min | $14 | 30",
   "why": "$14 sits 45% above the $7.70 floor, a healthy margin, and it's one of the two pieces that give browsers a way in under $15.",
   "price": 14,
   "fix": ""
  },
  {
   "item": "Serving bowl",
   "status": "underwater",
   "says": "Serving bowl | 16.00 | 2 h | $45 | 8",
   "why": "At $45 the bowl sits 28% under its $57.49 floor - 120 minutes of work plus materials and the card fee mean every sale loses money on your time, and the notes say it barely moved even at this price last year.",
   "price": 83,
   "fix": "Raise to $83 - at that price it also doubles as the table's $50+ anchor."
  },
  "..."
 ],
 "bundles": [
  {
   "name": "Browsers' two-for",
   "items": [
    "Ring dish",
    "Spoon rest"
   ],
   "price": 24,
   "why": "Pairs the table's two under-$15 pieces at a small saving ($2 off the $26 suggested total) to turn a browser into a two-item sale."
  },
  {
   "name": "Mug and vase pairing",
   "items": [
    "Stoneware mug",
    "Bud vase"
   ],
   "price": 54,
   "why": "Pairs the two strongest core pieces just under their $57 combined suggested price, rewarding a buyer who picks up both."
  },
  "..."
 ],
 "signs": [
  "Stoneware mugs - $35",
  "Ring dish & spoon rest, 2 for $24",
  "Bud vase - $22",
  "Serving bowl - $83"
 ],
 "next_steps": [
  "Reprice the serving bowl to $83 and the stoneware mug to $35 before the fair - the repriced bowl also becomes the table's missing $50+ anchor.",
  "Letter new signs for the mug, bowl, vase and the two bundles.",
  "Cut the serving bowl quantity below 8 - the notes say it barely moved even at $45, and each one takes 2 hours you could put toward mugs and spoon rests.",
  "..."
 ],
 "caveats": [
  "These floors only count the materials, minutes and card fee entered here - packaging, travel and setup time aren't in the numbers.",
  "The 30% headroom target and the healthy/thin/underwater gauges are rules of thumb, not data about how this particular fair will sell."
 ]
}

task: check - Hand-knit winter wear - rethink

Input:

{
 "task": "check",
 "today": "2026-10-19",
 "fair_date": "2026-12-12",
 "stock": "Hand-knit beanie | 9.00 | 4 h | $35 | 10\nChunky scarf | 22.00 | 6 h | $60 | 6\nFingerless mitts | 6.00 | 2.5 h | $25 | 8\nPom-pom keychain | 1.00 | 10 min | $6 | 25 ... (162 characters)",
 "facts": "{\"fair\":\"Thistlecombe Mill Winter Craft Show\",\"date\":\"2026-12-12\",\"days_to_fair\":54,\"days\":2,\"hours_per_day\":8,\"hours_assumed\":false,\"booth_fee\":325,\"other_costs\":60,\"fixed_costs\":385,\"wage\":15,\"wage_assumed\":false,\"card_fee_pct\":3,\"card_fee_assumed\":true,\"items_n\":4,\"units\":49,\"stock_value\":1060,\"materials_spent\":295,\"making_hours\":100.17,\"fair_day_cost\":240,\"cash_at_sellout\":733.2,\"net_at_sellout\":-769.3,\"needed_pct_booth\":53,\"needed_pct_day\":85,\"needed_pct_all\":null,\"units_for_booth\":26,\"sales_per_hour_for_booth\":1.6,\"underwater_n\":3,\"thin_n\":0,\"healthy_n\":1,\"tiers\":{\"anchor\":{\"items\":1,\"units\":6},\"core\":{\"items\":2,\"units\":18},\"impulse\":{\"items\":1,\"units\":25}}}",
 "items": "[{\"item\":\"Hand-knit beanie\",\"materials\":9,\"minutes\":240,\"price\":35,\"qty\":10,\"floor\":71.13,\"target\":102,\"headroom_pct\":-103,\"page_status\":\"underwater\",\"tier\":\"core\",\"cash_per_sale\":24.95},{\"item\":\"Chunky scarf\",\"materials\":22,\"minutes\":360,\"price\":60,\"qty\":6,\"floor\":115.46,\"target\":165,\"headroom_pct\":-92,\"page_status\":\"underwater\",\"tier\":\"anchor\",\"cash_per_sale\":36.2},\"... 2 more\"]",
 "page_reads": "[{\"level\":\"fix\",\"code\":\"underwater\",\"text\":\"Hand-knit beanie sells at $35 but costs $71.13 in materials, your 240 minutes and the card fee - every sale loses money on your time. $102 gives 30% headroom.\"},{\"level\":\"fix\",\"code\":\"underwater\",\"text\":\"Chunky scarf sells at $60 but costs $115.46 in materials, your 360 minutes and the card fee - every sale loses money on your time. $165 gives 30% headroom.\"},\"... 3 more\"]",
 "page_verdict": "rethink",
 "notes": ""
}

Reply:

{
 "task": "check",
 "title": "Hand-Knit Winter Accessories Table",
 "verdict": "rethink",
 "summary": "This needs a rethink, not just new tags: you'd have to sell 53% of stock to cover the $325 booth and 85% to cover the booth plus your two days here, and 3 of the 4 pieces (beanie, scarf, mitts) are already underwater. Sold out, the $733.20 in cash clears the $385 booth and other costs, but that cash still has to pay for the 100+ hours behind this stock, and at this fair this mix doesn't support it. Reaching 30%-headroom prices means roughly 2.6 to 2.9 times today's tags ($35 to $102, $60 to $165, $25 to $65) - a jump that size may not sell here - so the bigger lever is thinning out the slowest pieces (the 6-hour scarf and 4-hour beanie are 76 of your 100 hours) rather than pricing alone.",
 "items": [
  {
   "item": "Hand-knit beanie",
   "status": "underwater",
   "says": "Hand-knit beanie | 9.00 | 4 h | $35 | 10",
   "why": "Keeping the beanie in your core range is the right call, but at $35 it sits about $36 under its $71.13 floor (materials, your 4 hours of knitting at $15/hr, and the card fee) - headroom of -103%, so every one you sell currently loses money against your own time.",
   "price": 102,
   "fix": "Raise to $102, the whole-dollar price that gives 30% headroom over the $71.13 floor."
  },
  {
   "item": "Chunky scarf",
   "status": "underwater",
   "says": "Chunky scarf | 22.00 | 6 h | $60 | 6",
   "why": "Pricing the scarf as your one anchor piece is the right instinct, but at $60 it's about $55 under its $115.46 floor (materials, your 6 hours of knitting at $15/hr, and the card fee) - headroom of -92%, and it's also the slowest piece on the table to make.",
   "price": 165,
   "fix": "Raise to $165, the whole-dollar price that gives 30% headroom over the $115.46 floor."
  },
  {
   "item": "Fingerless mitts",
   "status": "underwater",
   "says": "Fingerless mitts | 6.00 | 2.5 h | $25 | 8",
   "why": "The mitts round out the core range well, but at $25 they fall about $20 short of their $44.85 floor (materials, 2.5 hours at $15/hr, and the card fee) - headroom of -79%, so this price also loses money on your time.",
   "price": 65,
   "fix": "Raise to $65, the whole-dollar price that gives 30% headroom over the $44.85 floor."
  },
  "..."
 ],
 "bundles": [
  {
   "name": "Hat & Mitts Set",
   "items": [
    "Hand-knit beanie",
    "Fingerless mitts"
   ],
   "price": 159,
   "why": "Pairs the beanie and mitts as a warm starter set for a few dollars under buying both at their new prices, while still clearing both floors."
  },
  {
   "name": "Scarf & Mitts Set",
   "items": [
    "Chunky scarf",
    "Fingerless mitts"
   ],
   "price": 219,
   "why": "Bundles the anchor scarf with the mitts for a small saving off buying both separately, giving browsers a reason to add the core piece."
  },
  "..."
 ],
 "signs": [
  "Hand-knit beanies - $102",
  "Chunky scarves - $165",
  "Fingerless mitts - $65",
  "Pom-pom keychains - $6, 2 for $11"
 ],
 "next_steps": [
  "Decide if this fair is worth its $325 fee and $385 total fixed cost for this stock mix before you put in more knitting time.",
  "Reprice the beanie, scarf and mitts to at least their floors ($71.13 / $115.46 / $44.85), using $102 / $165 / $65 if the room will bear it.",
  "Cut back on the slowest pieces to make - the 6-hour scarf and 4-hour beanie eat 76 of your 100 making hours - or find a faster version of each.",
  "..."
 ],
 "caveats": [
  "These floors only count the materials and minutes you entered - other costs (travel, packaging, a helper) would push them higher.",
  "The 3% card fee is assumed, not entered - check your actual processor rate."
 ]
}

Build the input yourself

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

const K = BoothKit;
const st = K.state({
  today: "2026-10-19", fair: "Winter makers market", date: "2026-12-05", hours: "10-4", days: "1",
  fee: "120", other: "20", wage: "18", cardfee: "2.9", notes: "",
  items: "Stoneware mug | 7.00 | 50 min | $28 | 24\nRing dish | 2.50 | 15 min | $14 | 30"
});
console.log(st.gauges, st.items.map(it => it.name + " " + it.status + " floor " + it.floor.toFixed(2)));
const { input } = K.buildInput(st, { today: "2026-10-19" });
const wire = K.toWire(input);           // POST this to /estimate and /run
// later: const reply = K.normalize(K.parseReply(job.output.output));
// const { rows, notes } = K.check(reply, st);

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://booth-math.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":"booth-math"}' | 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": "check", "today": "2026-10-19", "fair_date": "2026-11-22", "stock": "Stoneware mug | 7.00 | 50 min | $28 | 24 ...", "facts": "{...}", "items": "[...]", "page_reads": "[...]", "page_verdict": "reprice"}
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 booth-math:check:<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="booth-math:check:$(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: booth-math:check:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json