← Turnout Desk
API
Tokens

Run Turnout Desk from your own code

The one metered task, plan, is available over HTTP and returns one JSON object. The division of labour: the page works out the show-up figure with its source, the registration ceiling with its arithmetic, the occupancy gate, the waitlist rule and drain date, the day-of lane and the catering number - all free, in turnkit.js. The run writes the plan around them; the page then holds every one of those numbers to its own (turnreply.js).

Base URL and the response shape

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }

The token is minted for this app (the guest call takes {"slug":"turnout-desk"}), 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 key (task, event, numbers, facts) 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 plan is metered and needs a personal sign-in.
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

One task, plan. A missing or different task is answered as plan.

The input

FieldRequiredWhat
taskyesalways plan. If it is missing or different, still write the plan and set "task": "plan".
eventyesthe event, its format and price posture, the dates (event, headcount lock, today), whether it recurs, the venue's walk-up population, the channel to the waitlist, whether someone can sort the waitlist, and whether the form is open.
numbersyesthe page's five-line ceiling arithmetic, today's registrations and waitlist, the occupancy gate at the top of the band, the catering number and buffer, the waitlist rule with its drain date and claim window, the day-of lane with its expected walk-in volume, last edition's count and comped seats.
factsyesthe ceiling method and every finding tagged [fix], [check] or [info].
retry_notenooptional; if present, your previous reply was unusable - follow it.

The rules the model applies

House rules

The reply

  1. summary - one sentence, at most 35 words: the ceiling, the catering number and the one thing to act on first.
  2. ceiling - registrations (the page's ceiling, a number), arithmetic (the five lines, each at most 30 words, in order: capacity, aimed attendance, show-up rate with its source, the ceiling, the catering number with its buffer), closes (one sentence: which role closes the form - the event organizer unless the input names another - and when; when a [fix] finding says the form is past the ceiling, say it closes now and carry that finding's stated cost).
  3. gate - one or two sentences, at most 50 words: expected attendance against the occupancy limit, the arrival assumption, and what happens at the top of the band.
  4. waitlist - rule (fifo, proximity or none, the page's pick), drain_date (the page's date, or "none"), claim_window (the page's window), publish (one sentence, at most 40 words: what is published when the list opens).
  5. walk_in - lane (walkin, fed or none, the page's pick), volume (the page's expected walk-ins, a number), opens (one sentence: when it opens and, for a walk-in lane, that the full form is still completed).
  6. catering - number (the page's catering number), buffer (the page's buffer), note (one sentence).
  7. deposit - one or two sentences, at most 50 words: no deposit recommended, its three costs, the price posture as the route.
  8. handoffs - 3 to 5 entries: to (a role: catering, check-in desk, reminder-email owner, venue contract signer, ticket pricing), what (one sentence with the number they need).
  9. measure - 3 to 5 short strings: what to count on the day.
  10. caveats - at most 4 short strings, carrying the stated cost of any [fix] finding not already in the plan: anything the organizer should know before trusting the plan (a borrowed ratio, an unapproved overflow, a lock date that has passed). [] when none.
{
  "task": "plan",
  "summary": "one sentence",
  "ceiling": { "registrations": 400, "arithmetic": [ "line 1", "line 2", "line 3", "line 4", "line 5" ], "closes": "one sentence" },
  "gate": "one or two sentences",
  "waitlist": { "rule": "fifo", "drain_date": "YYYY-MM-DD", "claim_window": "48 hours (self-set)", "publish": "one sentence" },
  "walk_in": { "lane": "walkin", "volume": 30, "opens": "one sentence" },
  "catering": { "number": 200, "buffer": 200, "note": "one sentence" },
  "deposit": "one or two sentences",
  "handoffs": [ { "to": "catering", "what": "one sentence" } ],
  "measure": [ "what to count" ],
  "caveats": [ "a caveat" ]
}

The page holds it to its own numbers: the ceiling, the catering number and its buffer, the waitlist rule and drain date, the day-of lane and its volume must be the page's; the deposit answer must decline the mechanic; no number may appear that the input does not hold.

Worked examples - every sample

These are the page's three free samples. Each input is exactly what the page sends; each reply is the saved plan the sample replays, shipped as the validating model wrote it; the last line is the page's own check of it.

A free meetup, 250 seats, 410 signed up

Ceiling 500 by the sourced double; catering 250.

Input:

{
 "task": "plan",
 "event": "Event: Rust Meetup - winter edition - Meetup; Free registration\nEvent date: 2026-11-19; headcount lock (catering or venue guarantee): 2026-11-12; today: 2026-11-02\nRecurring: no; walk-up population at the venue: yes; channel that reaches the waitlist within a day: yes; someone can sort and check the waitlist: no\nRegistration form: open",
 "numbers": "1. Hard capacity: 250 (the occupancy limit)\n2. Aimed attendance: 250\n3. Expected show-up rate: 50% - MLH hackathon organizer guide: about 50% of people who register for a free event show up (trendline across 323 events) - aim for double, and do not go over double; free band Event Tech Live, free events see 40-60% no-shows\n4. Registration ceiling: 250 / 50% = 500 registrations, and not one more\n5. Expected show-ups handed to catering at the ceiling: 250, with the 250-registration buffer named separately\nRegistrations today: 410; waitlist (including anyone past the ceiling): 0\nExpected show-ups on today's registrations: 205\nOccupancy gate: at 60% show-up the ceiling brings 300 against a limit of 250 (50 over); chance of exceeding the limit at exactly 50%: 48.2%\nCatering number: 250 expected show-ups at the ceiling (buffer 250 registrations) - never the registration count\nWaitlist rule: fifo - Signup order (FIFO), drained in one batch on a named date; drain date: 2026-11-12 - Drain on or before 2026-11-12, the headcount lock, so promotions still move the catering number.; claim window: 48 hours (self-set)\nDay-of lane: walkin - Walk-in lane (separate late form, opens after pre-registered check-in closes, full form for everyone); expected walk-in volume (empty places at the expected show-up): 45",
 "facts": "Ceiling method: sourced 2x ceiling\nFindings (4):\n[info] no_prior (ratio): No prior registered-versus-attended count, so the ratio is borrowed. Count registered, attended, walk-ins and promoted arrivals this time - that count is what turns a borrowed ratio into your own.\n[check] gate_exposure (gate): At the top of the band (60% show up) the ceiling of 500 brings 300 people to a limit of 250 - 50 over. The walk-in lane closes and registered people are turned away; name this exposure to whoever signs the venue contract before setting the number. Even at exactly 50%, chance alone puts attendance over the limit 48.2% of the time.\n[info] ceiling_room (ceiling): 90 registrations left under the ceiling. Write the ceiling into the form's configuration and name who closes it.\n[info] format (ratio): The 2x ceiling was written for free hackathons; for a meetup treat it as an upper bound, not a target."
}

Reply:

{
 "task": "plan",
 "summary": "The sourced ceiling is 500 registrations with 250 expected show-ups handed to catering; act first by flagging the 50-over exposure at the band's top to whoever signs the venue contract before finalizing the number.",
 "ceiling": {
  "registrations": 500,
  "arithmetic": [
   "Hard capacity is 250, the occupancy limit (sourced).",
   "Aimed attendance is 250 (sourced).",
   "Expected show-up rate is 50%, sourced from the MLH hackathon organizer guide's trendline across 323 free events.",
   "Registration ceiling is 250 divided by 50%, equaling 500 registrations, and not one more (sourced).",
   "Catering gets 250 expected show-ups at the ceiling, with a 250-registration buffer named separately (sourced)."
  ],
  "closes": "The event organizer closes the registration form at the 500-registration ceiling (sourced), no later than the 2026-11-12 headcount lock (sourced)."
 },
 "gate": "Expected attendance at the 50% show-up rate (sourced) sits at 250, exactly at the 250-person limit. At the band's 60% top (sourced) it reaches 300, 50 over, closing the walk-in lane and turning away registered people, with a 48.2% chance of exceeding the limit even at 50% (sourced).",
 "waitlist": {
  "rule": "fifo",
  "drain_date": "2026-11-12",
  "claim_window": "48 hours (self-set)",
  "publish": "When the list opens, publish the FIFO order, the 2026-11-12 drain date (sourced), the 48-hour claim window (self-set), and that the event organizer decides promotions."
 },
 "walk_in": {
  "lane": "walkin",
  "volume": 45,
  "opens": "The walk-in lane opens once the pre-registered check-in window closes, and everyone completes the full late-registration form (sourced)."
 },
 "catering": {
  "number": 250,
  "buffer": 250,
  "note": "250 expected show-ups at the ceiling, never the registration count, with the 250-registration buffer named separately (sourced)."
 },
 "deposit": "No deposit is recommended; its costs are refund handling for every attendee, the accounting and tax burden of moving money, and a barrier that falls hardest on scholarship seats. Route this concern instead to the ticket-price posture, which here is free registration (sourced).",
 "handoffs": [
  {
   "to": "catering",
   "what": "Catering gets 250 expected show-ups at the ceiling, with a 250-registration buffer (sourced)."
  },
  {
   "to": "check-in desk",
   "what": "The check-in desk should expect up to 45 walk-ins once the pre-registered window closes (sourced)."
  },
  {
   "to": "venue contract signer",
   "what": "The venue contract signer should see that the band's 60% top brings 300 against the 250 limit, 50 over, before the ceiling is finalized (sourced)."
  },
  {
   "to": "reminder-email owner",
   "what": "The reminder-email owner gets today's 410 registrations against the 500 ceiling, 90 remaining, to target reminders (sourced)."
  }
 ],
 "measure": [
  "registered count",
  "attended count",
  "walk-ins",
  "waitlist promotions",
  "how many promoted arrived"
 ],
 "caveats": [
  "The 50% show-up ratio is borrowed - no prior registered-vs-attended count for this event (sourced).",
  "The sourced 2x ceiling was written for free hackathons; here it is an upper bound, not a target.",
  "At the band's 60% top the ceiling brings 50 more attendees than the 250 limit allows (sourced).",
  "Only 90 registrations remain before the 500 ceiling closes the form (sourced)."
 ]
}

The page's check: kept - nothing to flag

A free hackathon already past double

Ceiling 600 by the own-ratio method; catering 275.

Input:

{
 "task": "plan",
 "event": "Event: Fernhollow Hack 2026 - Hackathon; Free registration\nEvent date: 2026-11-28; headcount lock (catering or venue guarantee): 2026-11-14; today: 2026-11-02\nRecurring: yes; walk-up population at the venue: no; channel that reaches the waitlist within a day: yes; someone can sort and check the waitlist: yes\nRegistration form: open",
 "numbers": "1. Hard capacity: 300 (the occupancy limit)\n2. Aimed attendance: 300\n3. Expected show-up rate: 45.9% - your own last edition: 280 of 610 registered attended (45.9%; 95% interval 42%-49.9%)\n4. Registration ceiling: 300 / 45.9% = 653, held at the sourced double: 600\n5. Expected show-ups handed to catering at the ceiling: 275, with the 325-registration buffer named separately\nRegistrations today: 720; waitlist (including anyone past the ceiling): 180\nExpected show-ups on today's registrations: 275\nOccupancy gate: at 49.9% show-up the ceiling brings 299 against a limit of 300 (under); chance of exceeding the limit at exactly 45.9%: 2%\nCatering number: 275 expected show-ups at the ceiling (buffer 325 registrations) - never the registration count\nWaitlist rule: proximity - Proximity to the venue, published when the list opens; drain date: 2026-11-14 - Drain on or before 2026-11-14, the headcount lock, so promotions still move the catering number.; claim window: 48 hours (self-set)\nDay-of lane: fed - Waitlist-fed lane (the same lane, the waitlist told the night before); expected walk-in volume (empty places at the expected show-up): 25\nLast edition: 280 attended of 610 registered = 45.9% (95% interval 42%-49.9%)\nComped or scholarship seats: 25",
 "facts": "Ceiling method: own-ratio ceiling (its promotion condition holds)\nFindings (4):\n[check] overflow_unapproved (gate): The 40 standing or overflow places carry no written venue approval, so they are not counted - get approval from whoever the venue names before registrations are accepted against them, never on the day at the door.\n[fix] over_ceiling (ceiling): 720 registrations are past the ceiling of 600 by 120 - past double is the one posture the guide forbids; close the form and move everyone past the line to the waitlist.\n[info] proximity (waitlist): Proximity ordering is on the table: someone can sort and check the list and 180 wait past a ceiling of 600 (more than a quarter of it, a self-set line). Publish the rule when the list opens; it is an allocation on registrants' location data and disadvantages people who travel.\n[info] comped (ratio): 25 comped or scholarship seats: plan them at the free-seat rate and count them separately so next edition has their own rate."
}

Reply:

{
 "task": "plan",
 "summary": "The ceiling is 600 registrations (sourced) with 275 expected show-ups to catering (sourced); act first by closing the form now, since 720 registrations already sit 120 past the ceiling.",
 "ceiling": {
  "registrations": 600,
  "arithmetic": [
   "Hard capacity is 300, the occupancy limit (sourced).",
   "Aimed attendance is 300 (sourced).",
   "Expected show-up rate is 45.9%, sourced from last edition's own ratio: 280 of 610 registered attended (95% interval 42%-49.9%).",
   "Registration ceiling is 300 divided by 45.9%, equaling 653, held at the sourced double of 600 (sourced).",
   "Catering gets 275 expected show-ups at the ceiling, with a 325-registration buffer named separately (sourced)."
  ],
  "closes": "The event organizer closes the registration form now, since 720 registrations already exceed the 600 ceiling by 120 (sourced), moving everyone past the line to the waitlist."
 },
 "gate": "At the sourced top of the interval (49.9%), the 600 ceiling brings 299 against the 300 limit - under it, with a 2% chance of exceeding it at the 45.9% rate (sourced). The 40 standing or overflow places lack written venue approval and do not count until approved (sourced).",
 "waitlist": {
  "rule": "proximity",
  "drain_date": "2026-11-14",
  "claim_window": "48 hours (self-set)",
  "publish": "When the list opens, publish the proximity-to-venue order, the 2026-11-14 drain date (sourced), the 48-hour claim window (self-set), that the event organizer decides promotions, and that the ordering disadvantages people who travel."
 },
 "walk_in": {
  "lane": "fed",
  "volume": 25,
  "opens": "The waitlist-fed lane opens when waitlisted registrants are told the night before that their unclaimed seats are available, using the same lane they already registered through (sourced)."
 },
 "catering": {
  "number": 275,
  "buffer": 325,
  "note": "275 expected show-ups at the ceiling, never the registration count, with the 325-registration buffer named separately (sourced)."
 },
 "deposit": "No deposit is recommended; its costs are refund handling for every attendee, the accounting and tax burden of moving money, and a barrier that falls hardest on comped and scholarship seats. Route this concern instead to the ticket-price posture, which here is free registration (sourced).",
 "handoffs": [
  {
   "to": "catering",
   "what": "Catering gets 275 expected show-ups at the ceiling, with a 325-registration buffer (sourced)."
  },
  {
   "to": "venue contract signer",
   "what": "The venue contract signer needs written approval before the 40 standing or overflow places count toward capacity (sourced)."
  },
  {
   "to": "reminder-email owner",
   "what": "The reminder-email owner gets the 180 waitlisted registrants so reminders are not sent as if they are confirmed attendees (sourced)."
  },
  {
   "to": "check-in desk",
   "what": "The check-in desk should expect about 25 fed-lane arrivals from the waitlist the night before (sourced)."
  }
 ],
 "measure": [
  "registered count",
  "attended count",
  "waitlist promotions and arrivals",
  "fed-lane arrivals from unclaimed seats",
  "comped seat attendance, counted separately"
 ],
 "caveats": [
  "720 registrations already sit 120 past the 600 ceiling (sourced) - close the form now.",
  "The 40 standing or overflow places have no written venue approval and do not count (sourced).",
  "The 45.9% own-ratio comes from a single prior edition (sourced) - one edition is a sample of one.",
  "Proximity ordering is an allocation on location data and disadvantages people who travel (sourced)."
 ]
}

The page's check: kept - nothing to flag

A paid workshop sold past its room

Ceiling 80 by the no-overbooking rule; catering 75.

Input:

{
 "task": "plan",
 "event": "Event: Kubernetes in a Day (paid workshop) - Workshop day; Paid ticket\nEvent date: 2026-11-10; headcount lock (catering or venue guarantee): 2026-11-05; today: 2026-11-02\nRecurring: no; walk-up population at the venue: yes; channel that reaches the waitlist within a day: yes; someone can sort and check the waitlist: no\nRegistration form: open",
 "numbers": "1. Hard capacity: 80 (the occupancy limit)\n2. Aimed attendance: 80\n3. Expected show-up rate: 93.5% - Event Tech Live: paid events convert 90-97% of tickets to attendance (3-10% no-shows)\n4. Registration ceiling: 80 - no overbooking on a paid event\n5. Expected show-ups handed to catering at the ceiling: 75, with the 5-registration buffer named separately\nRegistrations today: 86; waitlist (including anyone past the ceiling): 20\nExpected show-ups on today's registrations: 75\nOccupancy gate: at 97% show-up the ceiling brings 78 against a limit of 80 (under); chance of exceeding the limit at exactly 93.5%: 0%\nCatering number: 75 expected show-ups at the ceiling (buffer 5 registrations) - never the registration count\nWaitlist rule: fifo - Signup order (FIFO), drained in one batch on a named date; drain date: 2026-11-05 - Drain on or before 2026-11-05, the headcount lock, so promotions still move the catering number.; claim window: 48 hours (self-set)\nDay-of lane: walkin - Walk-in lane (separate late form, opens after pre-registered check-in closes, full form for everyone); expected walk-in volume (empty places at the expected show-up): 5",
 "facts": "Ceiling method: no overbooking (paid or nominal fee)\nFindings (3):\n[info] no_prior (ratio): No prior registered-versus-attended count, so the ratio is borrowed. Count registered, attended, walk-ins and promoted arrivals this time - that count is what turns a borrowed ratio into your own.\n[fix] over_ceiling (ceiling): 86 registrations are past the ceiling of 80 by 6 - a paid event sold past the room: stop sales; refunds and the public account of turning away someone who paid are the cost.\n[check] lock_soon (waitlist): The headcount guarantee locks in 3 days - drain the waitlist before then or it stops moving the catering number."
}

Reply:

{
 "task": "plan",
 "summary": "The ceiling is 80 registrations (sourced) with 75 expected show-ups to catering (sourced); act first by stopping ticket sales now, since 86 registrations already exceed the ceiling by 6.",
 "ceiling": {
  "registrations": 80,
  "arithmetic": [
   "Hard capacity is 80, the occupancy limit (sourced).",
   "Aimed attendance is 80 (sourced).",
   "Expected show-up rate is 93.5%, sourced from Event Tech Live: paid events convert 90-97% of tickets (3-10% no-shows).",
   "Registration ceiling is 80, with no overbooking on this paid event (sourced).",
   "Catering gets 75 expected show-ups at the ceiling, with a 5-registration buffer named separately (sourced)."
  ],
  "closes": "The event organizer stops ticket sales now, since 86 registrations already exceed the 80 ceiling by 6 (sourced)."
 },
 "gate": "At the sourced 93.5% show-up rate, the 80-seat ceiling brings about 75 expected attendees, well under the 80 limit, with 0% chance of exceeding it even at the band's 97% top (sourced), since this paid ceiling is capped at capacity with no overbooking.",
 "waitlist": {
  "rule": "fifo",
  "drain_date": "2026-11-05",
  "claim_window": "48 hours (self-set)",
  "publish": "When the list opens, publish the FIFO order, the 2026-11-05 drain date (sourced), the 48-hour claim window (self-set), and that the event organizer decides promotions."
 },
 "walk_in": {
  "lane": "walkin",
  "volume": 5,
  "opens": "The walk-in lane opens once the pre-registered check-in window closes, and everyone completes the full late-registration form (sourced)."
 },
 "catering": {
  "number": 75,
  "buffer": 5,
  "note": "75 expected show-ups at the ceiling, never the registration count, with the 5-registration buffer named separately (sourced)."
 },
 "deposit": "No deposit is recommended; its costs are refund handling for every attendee, the accounting and tax burden of moving money, and a barrier that falls hardest on scholarship seats. This event already uses the ticket-price posture as its lever, since it is a paid ticket (sourced).",
 "handoffs": [
  {
   "to": "catering",
   "what": "Catering gets 75 expected show-ups at the ceiling, with a 5-registration buffer (sourced)."
  },
  {
   "to": "check-in desk",
   "what": "The check-in desk should expect up to 5 walk-ins once the pre-registered window closes (sourced)."
  },
  {
   "to": "ticket pricing",
   "what": "Ticket pricing stops sales now, since 86 registrations already exceed the 80 ceiling by 6 (sourced)."
  },
  {
   "to": "reminder-email owner",
   "what": "The reminder-email owner gets the 6-oversold count so no confirmation goes to registrants past the ceiling (sourced)."
  }
 ],
 "measure": [
  "registered count",
  "attended count",
  "walk-ins",
  "waitlist promotions and arrivals"
 ],
 "caveats": [
  "86 registrations already exceed the 80 ceiling by 6 (sourced) - a paid event sold past the room; stop sales now.",
  "The headcount lock is 3 days away (2026-11-05, sourced) - drain the waitlist before then or it stops moving the catering number.",
  "The 93.5% show-up rate is borrowed from Event Tech Live's general paid-event band, not this event's own history (sourced)."
 ]
}

The page's check: kept - nothing to flag

The numbers CSV for the first sample

figure,value,source
occupancy limit,250,venue
aimed attendance,250,organizer
show-up rate,50%,"MLH hackathon organizer guide: about 50% of people who register for a free event show up (trendline across 323 events) - aim for double, and do not go over double; free band Event Tech Live, free events see 40-60% no-shows"
registration ceiling,500,sourced
expected show-ups (catering),250,ceiling x rate
buffer,250,ceiling - catering
attendance at top of band,300,60%
waitlist rule,fifo,page
drain date,2026-11-12,"Drain on or before 2026-11-12, the headcount lock, so promotions still move the catering number."
day-of lane,walkin,page
expected walk-ins,45,limit - expected show-ups

Build the input yourself

Load the engine in node (global.window = global; require("./turnkit.js"); require("./turnreply.js")), then:

const K = TurnKit, R = TurnReply;
const f = K.form({ name: "Rust Meetup", format: "meetup", posture: "free", capacity: "250", regs: "410", waitlist: "0", status: "open",
  today: "2026-11-02", eventDate: "2026-11-19", lockDate: "2026-11-12", recurring: "no", walkup: "yes", channel: "yes", sorter: "no" });
// f.ceiling, f.catering, f.buffer, f.limit, f.top (attendance at the top of the band), f.tail (chance over the limit at the planned rate)
// f.method: sourced | own | none   f.wlRule: fifo | proximity   f.drain (ms)   f.lane: walkin | fed | none   f.walkins   f.findings
K.csv(f)                                       // figure, value, source
const b = K.buildInput(f);                     // b.ok is false, with b.note, until the capacity is given
const wire = K.toWire(b.input);                // POST this to /estimate and /run
// then: const plan = R.normalize(R.parseReply(job.output.output));
// R.check(plan, b)                            // {status: kept or flagged, notes}
// R.md("Rust Meetup", b, plan)                // the plan as Markdown

Get a token for the steps below from /tokens.html.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the response. 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://turnout-desk.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":"turnout-desk"}' | 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": "plan", "event": "Event: ... - Meetup; Free registration ...", "numbers": "1. Hard capacity: 250 ...", "facts": "Ceiling method: sourced 2x ceiling ..."}
# - see the input table
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 task and the input: a retry with the same key never bills twice. The page uses turnout-desk:plan:<inputHash>:a<attempt>, and its one automatic reformat retry is a new attempt number, named in the page as one extra run. The plan carries summary, ceiling, gate, waitlist, walk_in, catering, deposit and handoffs; the code below prints the headline parts.

KEY="turnout-desk:plan:$(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}'
jq -r '.summary // .format, .gate // .deposit, ((.handoffs // .handoffs)[] | "\(.to // .to): \(.status // .what)")' reply.json
jq -r '.ceiling.registrations, .catering.number' reply.json   # anything to know before trusting the plan

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: turnout-desk:plan:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json