← Quiz Night Pack / API
Tokens

Drive Quiz Night Pack from your own code

The page does its free work in the browser: it turns the order slip into a plan (a sport, a format, a question count and a level for each round) and lays out every printable sheet. Its two paid lanes are available over HTTP. You send one JSON object with task set to "pack" or "swap", and the model replies with one JSON object. The easiest way to build the input is the page's own code: QuizKit.buildInput(QuizKit.planFrom(form), "pack") in quizkit.js, which also runs under Node.

Base URL: https://api.skillsafe.ai/v1/app-api. Every call sends Content-Type: application/json and, once you have a token, Authorization: Bearer <token>. Both lanes run on gpt-terra with a 10% publisher markup. A run needs a signed-in (user) token and is metered: the platform holds the estimate's hold_credits while the run is in flight and then charges only what it used (charged_credits). /estimate is free. A plain-text summary is at /llms.txt.

The task field

The response envelope

Every response is {"data": ..., "error": null} on success or {"data": null, "error": {"code", "message"}} on failure.

Error codes

HTTPcodemeaning
400VALIDATION_ERRORThe body is not a JSON object, or a field is the wrong type. Every field of the run input is a string.
401UNAUTHORIZEDNo token, or an expired one: get a new one (step 2).
402INSUFFICIENT_CREDITSThe balance is below the run's minimum. Top up at skillsafe.ai/account/billing/.
403FORBIDDENA guest token tried a metered run: sign in for a personal token.
404NOT_FOUNDUnknown job id.
429RATE_LIMITEDBack off and retry with the same Idempotency-Key.

Input fields (all strings)

fieldwhencontent
taskrequiredpack or swap
titlebothThe quiz's name, up to 80 characters (may be empty).
audiencebothWho is playing, up to 160 characters (may be empty).
regionbothuk, us or intl - spelling and sporting vocabulary.
difficultybothcasual, regular, fanatic or ramp.
erabothall, pre1970, 1970s-1980s, 1990s, 2000s or 2010s-now.
avoidbothComma-separated topics to stay away from, up to 200 characters.
planrequiredA JSON string: {"rounds":[{"id":"R1","name","sport","format":"mc|tf|fill","count":5-12,"difficulty":"easy|medium|hard|mixed"}], "tiebreaker": true|false}, 1-6 rounds, at most 48 questions.
swapswap onlyA JSON string: [{"id":"R2Q3","sport","format","difficulty","reason":"too_hard|too_easy|unsure|duplicate","question":"the old text"}].
existingswap onlyA JSON string: [{"id","question","answer"}] for the questions staying, at most about 9,000 characters; an entry with id "..." says how many more were left out.
retry_noteoptionalOnly on a reformat retry: what was wrong with the previous reply.

The reply contract

A question object is {id: "R2Q5", difficulty: easy|medium|hard, era: "1990s"|"timeless", question, options[4] (multiple choice only; the answer is exactly one of them), answer ("True"/"False" for true or false; at most five words for a fill-in, whose question has one ____ blank), explanation, fun_fact, confidence: sure|check}.

pack: task, title, host_intro, rounds[{id, name, sport, format, questions[]}] (exactly as planned), tiebreaker{question, answer: number, unit, explanation} or null, fact_check[] ("R2Q5: what to verify"), host_notes[].

swap: task, replacements[{replaces: "R2Q5", question}] (one per swap entry, in order), fact_check[], host_notes[].

The page letters the multiple-choice options itself (a seeded shuffle), so the reply carries no letters.

Worked examples

These are the page's own bundled examples, with the input as app.js builds it and the saved model reply the page replays for free - two for pack, one for swap.

pack: a four-round UK pub quiz that ramps from easy to hard

Input:

{
 "task": "pack",
 "title": "The Pellowmarsh Arms Thursday Sports Quiz",
 "audience": "Regulars at a UK pub, mostly football and cricket fans, ages 25 to 70",
 "region": "uk",
 "difficulty": "ramp",
 "era": "all",
 "avoid": "betting scandals, doping",
 "plan": "(a JSON string - decoded below)"
}

plan, decoded:

{
 "rounds": [
  {
   "id": "R1",
   "name": "Round 1: Football",
   "sport": "football",
   "format": "mc",
   "count": 8,
   "difficulty": "easy"
  },
  {
   "id": "R2",
   "name": "Round 2: Cricket",
   "sport": "cricket",
   "format": "fill",
   "count": 8,
   "difficulty": "medium"
  },
  {
   "id": "R3",
   "name": "Round 3: Rugby",
   "sport": "rugby",
   "format": "tf",
   "count": 8,
   "difficulty": "medium"
  },
  {
   "id": "R4",
   "name": "Round 4: Formula 1",
   "sport": "formula1",
   "format": "fill",
   "count": 8,
   "difficulty": "hard"
  }
 ],
 "tiebreaker": true
}

Reply:

{
 "task": "pack",
 "title": "The Pellowmarsh Arms Thursday Sports Quiz",
 "host_intro": "Welcome to the Pellowmarsh Arms Thursday Sports Quiz: four rounds of football, cricket, rugby and Formula 1 that start gentle and finish fiendish. Phones away, one answer per question, and the host's word is final.",
 "rounds": [
  {
   "id": "R1",
   "name": "Round 1: Back of the Net",
   "sport": "football",
   "format": "mc",
   "questions": [
    {
     "id": "R1Q1",
     "difficulty": "easy",
     "era": "1960s",
     "question": "Which country won the 1966 FIFA World Cup?",
     "options": [
      "England",
      "West Germany",
      "Brazil",
      "Portugal"
     ],
     "answer": "England",
     "explanation": "England beat West Germany 4-2 after extra time in the final at Wembley.",
     "fun_fact": "Geoff Hurst is still the only man to score a hat-trick in a men's World Cup final.",
     "confidence": "sure"
    },
    {
     "id": "R1Q2",
     "difficulty": "easy",
     "era": "1890s",
     "question": "Anfield is the home ground of which club?",
     "options": [
      "Liverpool",
      "Everton",
      "Manchester United",
      "Newcastle United"
     ],
     "answer": "Liverpool",
     "explanation": "Liverpool have played at Anfield since the club was founded in 1892.",
     "fun_fact": "Everton were Anfield's first tenants before moving across Stanley Park to Goodison Park.",
     "confidence": "sure"
    },
    {
     "id": "R1Q3",
     "difficulty": "easy",
     "era": "2000s",
     "question": "Which club went the whole 2003-04 Premier League season unbeaten?",
     "options": [
      "Arsenal",
      "Chelsea",
      "Manchester United",
      "Liverpool"
     ],
     "answer": "Arsenal",
     "explanation": "Arsene Wenger's 'Invincibles' won 26 and drew 12 of their 38 league games.",
     "fun_fact": "A special gold Premier League trophy was made to mark the feat.",
     "confidence": "sure"
    },
    {
     "id": "R1Q4",
     "difficulty": "easy",
     "era": "1980s",
     "question": "Who managed Manchester United from 1986 to 2013?",
     "options": [
      "Alex Ferguson",
      "Matt Busby",
      "Ron Atkinson",
      "Arsene Wenger"
     ],
     "answer": "Alex Ferguson",
     "explanation": "Sir Alex Ferguson arrived from Aberdeen in November 1986 and retired in May 2013.",
     "fun_fact": "He won 13 Premier League titles with the club.",
     "confidence": "sure"
    },
    {
     "id": "R1Q5",
     "difficulty": "easy",
     "era": "2010s",
     "question": "Which country hosted the 2014 FIFA World Cup?",
     "options": [
      "Brazil",
      "Argentina",
      "Germany",
      "South Africa"
     ],
     "answer": "Brazil",
     "explanation": "Brazil hosted in 2014 and Germany won the tournament.",
     "fun_fact": "Germany beat the hosts 7-1 in the semi-final in Belo Horizonte.",
     "confidence": "sure"
    },
    {
     "id": "R1Q6",
     "difficulty": "easy",
     "era": "2010s",
     "question": "Which club won the 2015-16 Premier League title after starting the season as 5000-1 outsiders?",
     "options": [
      "Leicester City",
      "Tottenham Hotspur",
      "West Ham United",
      "Southampton"
     ],
     "answer": "Leicester City",
     "explanation": "Claudio Ranieri's Leicester finished ten points clear of Arsenal.",
     "fun_fact": "Jamie Vardy scored in a Premier League record 11 consecutive matches that season.",
     "confidence": "sure"
    },
    {
     "id": "R1Q7",
     "difficulty": "easy",
     "era": "2020s",
     "question": "In 2023, which player overtook Wayne Rooney to become the England men's team's record goalscorer?",
     "options": [
      "Harry Kane",
      "Raheem Sterling",
      "Marcus Rashford",
      "Jamie Vardy"
     ],
     "answer": "Harry Kane",
     "explanation": "Kane scored his 54th England goal, a penalty against Italy in March 2023, passing Rooney's 53.",
     "fun_fact": "",
     "confidence": "sure"
    },
    {
     "id": "R1Q8",
     "difficulty": "easy",
     "era": "2020s",
     "question": "Which country won the 2022 FIFA World Cup in Qatar?",
     "options": [
      "Argentina",
      "France",
      "Croatia",
      "Morocco"
     ],
     "a
... (shortened here; the page shows it whole)

pack: a casual US family night on the 1990s, no tie-breaker

Input:

{
 "task": "pack",
 "title": "Nineties Night: Family Sports Showdown",
 "audience": "Three generations of a US family at Thanksgiving; kids aged 10 to 14 play too",
 "region": "us",
 "difficulty": "casual",
 "era": "1990s",
 "avoid": "",
 "plan": "(a JSON string - decoded below)"
}

plan, decoded:

{
 "rounds": [
  {
   "id": "R1",
   "name": "Round 1: Basketball",
   "sport": "basketball",
   "format": "mc",
   "count": 6,
   "difficulty": "easy"
  },
  {
   "id": "R2",
   "name": "Round 2: Baseball",
   "sport": "baseball",
   "format": "fill",
   "count": 6,
   "difficulty": "easy"
  },
  {
   "id": "R3",
   "name": "Round 3: Football (NFL and college)",
   "sport": "american_football",
   "format": "tf",
   "count": 6,
   "difficulty": "easy"
  }
 ],
 "tiebreaker": false
}

Reply:

{
 "task": "pack",
 "title": "Nineties Night: Family Sports Showdown",
 "host_intro": "Welcome to Nineties Night, where we head back to the decade of Air Jordans, home run races and Super Bowl dynasties. Three rounds, basketball, baseball and football, and every generation at the table has a shot at the win.",
 "rounds": [
  {
   "id": "R1",
   "name": "Round 1: Nothing but Net",
   "sport": "basketball",
   "format": "mc",
   "questions": [
    {
     "id": "R1Q1",
     "difficulty": "easy",
     "era": "1990s",
     "question": "Michael Jordan led which team to six NBA championships in the 1990s?",
     "options": [
      "Chicago Bulls",
      "Los Angeles Lakers",
      "New York Knicks",
      "Detroit Pistons"
     ],
     "answer": "Chicago Bulls",
     "explanation": "The Bulls won in 1991, 1992, 1993, 1996, 1997 and 1998, with Jordan Finals MVP every time.",
     "fun_fact": "The 1995-96 Bulls went 72-10, a record at the time.",
     "confidence": "sure"
    },
    {
     "id": "R1Q2",
     "difficulty": "easy",
     "era": "1990s",
     "question": "What nickname was given to the 1992 US Olympic men's basketball team?",
     "options": [
      "Dream Team",
      "Redeem Team",
      "Fab Five",
      "Showtime"
     ],
     "answer": "Dream Team",
     "explanation": "It was the first US Olympic team to include NBA stars, and it won gold in Barcelona.",
     "fun_fact": "The team won its games by an average of almost 44 points.",
     "confidence": "sure"
    },
    {
     "id": "R1Q3",
     "difficulty": "easy",
     "era": "1990s",
     "question": "Which 1996 movie starred Michael Jordan playing basketball alongside Bugs Bunny?",
     "options": [
      "Space Jam",
      "Air Bud",
      "Like Mike",
      "Eddie"
     ],
     "answer": "Space Jam",
     "explanation": "Jordan and the Looney Tunes take on alien basketball players in Space Jam.",
     "fun_fact": "",
     "confidence": "sure"
    },
    {
     "id": "R1Q4",
     "difficulty": "easy",
     "era": "1990s",
     "question": "Which team won back-to-back NBA titles in 1994 and 1995?",
     "options": [
      "Houston Rockets",
      "Orlando Magic",
      "Utah Jazz",
      "New York Knicks"
     ],
     "answer": "Houston Rockets",
     "explanation": "Hakeem Olajuwon led Houston to both titles while Jordan was away from the Bulls.",
     "fun_fact": "",
     "confidence": "sure"
    },
    {
     "id": "R1Q5",
     "difficulty": "easy",
     "era": "1990s",
     "question": "In 1996 Shaquille O'Neal left the Orlando Magic to sign with which team?",
     "options": [
      "Los Angeles Lakers",
      "Miami Heat",
      "Boston Celtics",
      "Phoenix Suns"
     ],
     "answer": "Los Angeles Lakers",
     "explanation": "Shaq signed with the Lakers as a free agent in July 1996.",
     "fun_fact": "He went on to win three straight titles with the Lakers from 2000 to 2002.",
     "confidence": "sure"
    },
    {
     "id": "R1Q6",
     "difficulty": "easy",
     "era": "1990s",
     "question": "In what year did the WNBA play its first season?",
     "options": [
      "1997",
      "1992",
      "1995",
      "1999"
     ],
     "answer": "1997",
     "explanation": "The WNBA tipped off in June 1997.",
     "fun_fact": "The Houston Comets won the first four WNBA championships.",
     "confidence": "sure"
    }
   ]
  },
  {
   "id": "R2",
   "name": "Round 2: Take Me Out to the Ballgame",
   "sport": "baseball",
   "format": "fill",
   "questions": [
    {
     "id": "R2Q1",
     "difficulty": "easy",
     "era": "1990s",
     "question": "In 1998 Mark McGwire and Sammy ____ raced each other to break the single-season home run record.",
     "answer": "Sosa",
     "explanation": "McGwire finished with 70 home runs and Sosa with 66, both past Roger Maris's 61.",
     "fun_fact": "",
     "confidence": "sure"
    },
    {
     "id": "R2Q2",
     "difficulty": "easy",
     "era": "1990s",
     "question": "In 1995 Cal Ripken Jr. broke Lou ____'s record for the most consecutive games played.",
     "answer": "Gehrig",
     "explanation": "Ripken played his 2,131st straight game on September 6, 1995.",
     "fun_fact": "Ripken's streak ended at 2,63
... (shortened here; the page shows it whole)

swap: three questions in the pub quiz marked too hard, unsure and too easy

Input (swap and existing shown decoded):

{
 "task": "swap",
 "title": "The Pellowmarsh Arms Thursday Sports Quiz",
 "audience": "Regulars at a UK pub, mostly football and cricket fans, ages 25 to 70",
 "region": "uk",
 "difficulty": "ramp",
 "era": "all",
 "avoid": "betting scandals, doping",
 "plan": "(a JSON string - decoded below)",
 "swap": "[{\"id\":\"R2Q3\",\"sport\":\"cricket\",\"format\":\"fill\",\"difficulty\":\"easy\",\"reason\":\"too_hard\",\"question\":\"Shane Warne's 'Ball of the Century' at Old Trafford in 1993 bowled England's Mike ____.\"},{\"id\":\"R3Q5\",\"sport\":\"rugby\",\"format\":\"tf\",\"difficulty\":\"medium\",\"reason\":\"unsure\",\"question\":\"The Calcutta Cup is contested between England and Scotland.\"},{\"id\":\"R4Q1\",\"sport\":\"formula1\",\"format\":\"fill\",\"difficulty\":\"hard\",\"reason\":\"too_easy\",\"question\":\"The first ever Formula One World Championship race was held in May 1950 at ____.\"}]",
 "existing": "[{\"id\":\"R2Q1\",\"question\":\"Jim Laker took 19 wickets in the 1956 Old Trafford Test against ____.\",\"answer\":\"Australia\"},{\"id\":\"R2Q2\",\"question\":\"In 1968 Garfield Sobers hit six sixes in an over, the first time it was done in first-class cricket, off Glamorgan's Malcolm ____.\",\"answer\":\"Nash\"},{\"id\":\"R2Q4\",\"question\":\"Brian Lara's record individual Test score of ____ not out came against England in Antigua in 2004.\",\"answer\":\"400\"},{\"id\":\"R2Q5\",\"question\":\"In 2012 Sachin Tendulkar became the first player to score ____ international centuries.\",\"answer\":\"100\"},{\"id\":\"R2Q6\",\"question\":\"England won their first men's Cricket World Cup in 2019, beating ____ on boundary count after a tied final and Super Over at Lord's.\",\"answer\":\"New Zealand\"},{\"id\":\"R2Q7\",\"question\":\"Ben Stokes made 135 not out to win the 2019 Headingley Ashes Test, sharing a last-wicket stand with Jack ____.\",\"answer\":\"Leach\"},{\"id\":\"R2Q8\",\"question\":\"James Anderson retired from Test cricket in 2024 with ____ wickets, the most by any fast bowler.\",\"answer\":\"704\"},{\"id\":\"R3Q1\",\"question\":\"England beat New Zealand in the final of the 2003 Rugby World Cup.\",\"answer\":\"False\"},{\"id\":\"R3Q2\",\"question\":\"South Africa won the 1995 Rugby World Cup as the host nation.\",\"answer\":\"True\"},{\"id\":\"R3Q3\",\"question\":\"A try in rugby union is worth five points.\",\"answer\":\"True\"},{\"id\":\"R3Q4\",\"question\":\"Ireland's 2018 Grand Slam was their first Grand Slam of the Six Nations era.\",\"answer\":\"False\"},{\"id\":\"R3Q6\",\"question\":\"The British and Irish Lions won their 2021 Test series in South Africa.\",\"answer\":\"False\"},{\"id\":\"R3Q7\",\"question\":\"Wales won the Six Nations Grand Slam in 2019.\",\"answer\":\"True\"},{\"id\":\"R3Q8\",\"question\":\"Richie McCaw captained New Zealand to just one Rugby World Cup title.\",\"answer\":\"False\"},{\"id\":\"R4Q2\",\"question\":\"The first Formula One drivers' world champion, in 1950, was Alfa Romeo's Giuseppe ____.\",\"answer\":\"Farina\"},{\"id\":\"R4Q3\",\"question\":\"The only driver to be crowned Formula One world champion after his death was Jochen ____, in 1970.\",\"answer\":\"Rindt\"},{\"id\":\"R4Q4\",\"question\":\"James Hunt won the 1976 drivers' title by a single point from Niki ____.\",\"answer\":\"Lauda\"},{\"id\":\"R4Q5\",\"question\":\"In 1988 McLaren won ____ of the season's 16 Grands Prix.\",\"answer\":\"15\"},{\"id\":\"R4Q6\",\"question\":\"Michael Schumacher won his first two drivers' titles, in 1994 and 1995, with the ____ team.\",\"answer\":\"Benetton\"},{\"id\":\"R4Q7\",\"question\":\"Jenson Button won the 2009 drivers' title for ____, a team that raced for just that one season.\",\"answer\":\"Brawn GP\"},{\"id\":\"R4Q8\",\"question\":\"Max Verstappen became the youngest ever Formula One race winner, aged 18, at the 2016 ____ Grand Prix.\",\"answer\":\"Spanish\"},{\"id\":\"R1Q1\",\"question\":\"Which country won the 1966 FIFA World Cup?\",\"answer\":\"England\"},{\"id\":\"R1Q2\",\"question\":\"Anfield is the home ground of which club?\",\"answer\":\"Liverpool\"},{\"id\":\"R1Q3\",\"question\":\"Which club went the whole 2003-04 Premier League season unbeaten?\",\"answer\":\"Arsenal\"},{\"id\":\"R1Q4\",\"question\":\"Who managed Manchester United from 1986 to 2013?\",\"answer\":\"Alex Ferguson\"},{\"id\":\"R1Q5\",\"question\":\"Which country hosted the 2014 FIFA World Cup?\",\"answer\":\"Brazil\"},{\"id\":\"R1Q6\",\"question\":\"Which club won the 2015-16 Premier League title after starting the season as 5000-1 outsiders?\",\"answer\":\"Leicester City\"},{\"id\":\"R1Q7\",\"question\":\"In 2023, which player overtook Wayne Rooney to become the England men's team's record goalscorer?\",\"answer\":\"Harry Kane\"},{\"id\":\"R1Q8\",\"question\":\"Which country won the 2022 FIFA World Cup in Qatar?\",\"answer\":\"Argentina\"}]"
}

plan, decoded:

{
 "rounds": [
  {
   "id": "R1",
   "name": "Round 1: Football",
   "sport": "football",
   "format": "mc",
   "count": 8,
   "difficulty": "easy"
  },
  {
   "id": "R2",
   "name": "Round 2: Cricket",
   "sport": "cricket",
   "format": "fill",
   "count": 8,
   "difficulty": "medium"
  },
  {
   "id": "R3",
   "name": "Round 3: Rugby",
   "sport": "rugby",
   "format": "tf",
   "count": 8,
   "difficulty": "medium"
  },
  {
   "id": "R4",
   "name": "Round 4: Formula 1",
   "sport": "formula1",
   "format": "fill",
   "count": 8,
   "difficulty": "hard"
  }
 ],
 "tiebreaker": true
}

swap, decoded:

[
 {
  "id": "R2Q3",
  "sport": "cricket",
  "format": "fill",
  "difficulty": "easy",
  "reason": "too_hard",
  "question": "Shane Warne's 'Ball of the Century' at Old Trafford in 1993 bowled England's Mike ____."
 },
 {
  "id": "R3Q5",
  "sport": "rugby",
  "format": "tf",
  "difficulty": "medium",
  "reason": "unsure",
  "question": "The Calcutta Cup is contested between England and Scotland."
 },
 {
  "id": "R4Q1",
  "sport": "formula1",
  "format": "fill",
  "difficulty": "hard",
  "reason": "too_easy",
  "question": "The first ever Formula One World Championship race was held in May 1950 at ____."
 }
]

existing, decoded:

[
 {
  "id": "R2Q1",
  "question": "Jim Laker took 19 wickets in the 1956 Old Trafford Test against ____.",
  "answer": "Australia"
 },
 {
  "id": "R2Q2",
  "question": "In 1968 Garfield Sobers hit six sixes in an over, the first time it was done in first-class cricket, off Glamorgan's Malcolm ____.",
  "answer": "Nash"
 },
 {
  "id": "R2Q4",
  "question": "Brian Lara's record individual Test score of ____ not out came against England in Antigua in 2004.",
  "answer": "400"
 },
 {
  "id": "R2Q5",
  "question": "In 2012 Sachin Tendulkar became the first player to score ____ international centuries.",
  "answer": "100"
 },
 {
  "id": "...",
  "question": "(25 more entries in the real input)"
 }
]

Reply:

{
 "task": "swap",
 "replacements": [
  {
   "replaces": "R2Q3",
   "question": {
    "id": "R2Q3",
    "difficulty": "easy",
    "era": "1880s",
    "question": "England and Australia play their Test series for a tiny urn known as The ____.",
    "answer": "Ashes",
    "explanation": "The name comes from a mock obituary of English cricket after Australia won at The Oval in 1882.",
    "fun_fact": "The original urn rarely leaves the MCC Museum at Lord's, whoever holds the series.",
    "confidence": "sure"
   }
  },
  {
   "replaces": "R3Q5",
   "question": {
    "id": "R3Q5",
    "difficulty": "medium",
    "era": "1980s",
    "question": "New Zealand won the first Rugby World Cup, in 1987.",
    "answer": "True",
    "explanation": "The All Blacks beat France 29-9 in the final at Eden Park, Auckland.",
    "fun_fact": "The 1987 tournament was co-hosted by New Zealand and Australia.",
    "confidence": "sure"
   }
  },
  {
   "replaces": "R4Q1",
   "question": {
    "id": "R4Q1",
    "difficulty": "hard",
    "era": "1980s",
    "question": "Keke Rosberg won the 1982 drivers' title despite winning just ____ race that season.",
    "answer": "one",
    "explanation": "His only win came at the Swiss Grand Prix at Dijon, in a season with 11 different race winners.",
    "fun_fact": "His son Nico Rosberg won the title in 2016, making them the second father and son champions.",
    "confidence": "sure"
   }
  }
 ],
 "fact_check": [],
 "host_notes": [
  "R4Q1 is a number written as a word: accept 'one' or '1'."
 ]
}

The page re-checks the reply; the API does not

The page holds every reply to the plan it sent (recon.js): the same rounds in the same order with the planned sport, format and count; four distinct options with the answer among them exactly once; True or False answers, not all one way in a round; exactly one ____ blank per fill-in; no answer given away in its own question; no question asked twice; years inside the chosen era and none in the future; nothing from the avoid list; difficulty labels on target; every check question listed in fact_check; a numeric tie-breaker exactly when one was planned. A swap must give one replacement per marked question, in its format, about a different fact from the old question and from every question in existing. Over the API you get the model's reply as it is; run the same checks yourself with Recon.reconcile(Recon.normalize(reply, task), {plan, K: QuizKit, lane: task, sent: input}) from recon.js and quizkit.js, which also run under Node.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://quiz-night-pack.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":"quiz-night-pack"}' | 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 (not wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model reads gpt-5.6-terra and model_alias gpt-terra.

# input.json: the run input object - {"task": "pack", "title": "...", "region": "uk", "plan": "{...rounds as a JSON string...}", ...}
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 quiz-night-pack:<task>:<inputHash>:a<attempt>. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="quiz-night-pack:pack:$(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 lane's contract
echo "$J" | jq '.data | {charged_credits, truncated}' 

Step 6 — POST /run-stream

The same run as Server-Sent Events. swap.json is a swap-lane run input: the pack fields plus swap and existing.

# 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: quiz-night-pack:swap:$(shasum -a 256 swap.json | cut -c1-16):a1" --data @swap.json