← Choice Grid / API
Tokens

Drive Choice Grid from your own code

Everything the page does is available over HTTP. A natural use is a comparison you keep in a file - flats you are viewing this month, quotes for a boiler, three job offers - where a script recomputes the matrix whenever the file changes and asks for a fresh evidence reading only for the options that are new.

One thing to be clear about first: the model never does the arithmetic. The caller computes the weighted matrix, the ranks and the sensitivity analysis, and sends those numbers as facts with the options and criteria. The model returns evidence, challenges and deal-breakers; the caller reconciles them against its own numbers. The page's own matrix and reconciler ship as plain scripts you can load in node — see the end of the examples.

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":"choice-grid"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object: never wrap it as {"input": {…}} — that returns 200, warns unknown field 'input', and hides every field from the model.

Errors:

StatusCodeMeaning
400validation_errorThe body is not a JSON object.
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: readings 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

One system prompt serves two tasks. task picks the contract; anything else is read as assess and the reply says so in notes.

taskWhenCells in the reply
assessThe first reading of a decision, or a full re-read.One per option × criterion, every pair.
reassessThe grid changed since a reading: an option added or edited, a criterion added or renamed, weights moved, the brief edited.Only the pairs in changes.stale_pairs, one each. Merge them into the previous cells yourself (Verdict.merge).

Every run body is a JSON object of string fields (the declared input schema; task, decision, options, criteria and matrix are required, and /estimate warns when one is missing):

FieldWhat it holds
task"assess" or "reassess".
decisionThe decision in one line, e.g. Which flat to rent from September.
contextOptional. Who it is for and what must be true (budget, a dog, a commute). Deal-breakers are found against it. The page cuts the middle past 1,500 characters.
optionsJSON string: [{"id":"o1","name":"...","price":1450,"facts":["..."]}], 2 to 6 options, at most 8 facts of 160 characters each.
criteriaJSON string: [{"id":"c1","name":"...","kind":"rate"|"cost","weight":0-10,"weight_share":percent,"floor":1-9}], 3 to 8 criteria.
matrixJSON string with your computed numbers: scores (the person's 0-10 scores, null when unrated), totals with ranks, leader, runner_up, tie, margin, floors_failed, sensitivity (plain sentences), most_fragile, decider.
changesreassess only. JSON string: {"changes":[{"type":"option_added","id":"o4",...}],"stale_pairs":["o4|c1",...]}.
previousreassess only. JSON string: the earlier reading, compacted (cells with evidence cut to 160 characters, missing criteria, deal-breakers, leaning).

The reply text is one JSON object (the same envelope for both tasks):

{
  "task": "assess | reassess", "title": "...", "headline": "...",
  "cells":             [{ "option": "o1", "criterion": "c2", "evidence", "suggested_score": 0-10 or null, "confidence": "high|medium|low", "basis": "fact|inference|missing" }],
  "missing_criteria":  [{ "name", "why", "suggested_weight": 1-10 }],
  "weight_challenges": [{ "criterion": "c1", "issue", "direction": "raise|lower|split|merge", "suggested_weight": 0-10 }],
  "deal_breakers":     [{ "option": "o2", "criterion": "c3" or null, "reason", "severity": "hard|soft" }],
  "bias_checks":       [{ "bias", "signal", "question" }],
  "leaning":           { "option": "o1" or null, "why", "agrees_with_matrix": true|false },
  "questions": [...], "next_steps": [...], "notes": [...],
  "changed": "reassess only; empty for assess"
}

suggested_score is null for cost criteria (scored from prices: cheapest 10, dearest 0) and where the facts are silent. Totals are never in the reply: compute them as 10 × Σ (weight / Σweights) × score.

Worked examples

These are the bundled examples on the page: the exact run body the page builds and a reply produced against the shipped prompt (the page replays the same replies for free).

assess: a flat where the matrix leader has a hard deal-breaker

Input:

{
 "task": "assess",
 "decision": "Which flat to rent from September",
 "context": "Two of us plus Biscuit, a 30 kg Labrador. One of us commutes to the hospital on Elm Road every day (shifts start 7am); the other works from home three days a week and needs a room with a door for calls. Budget ceiling 1,700 a month all in. 12-month lease minimum.",
 "options": "(a JSON string - decoded below)",
 "criteria": "(a JSON string - decoded below)",
 "matrix": "(a JSON string - decoded below)"
}

options, decoded:

[
 {
  "id": "o1",
  "name": "Canal Street 2-bed",
  "facts": [
   "Second floor, no lift",
   "Two bedrooms, second one is 2.4 x 2.6 m",
   "18 min cycle to Elm Road, towpath route",
   "Pets considered with 300 deposit",
   "South-facing balcony over the canal",
   "Gas central heating, EPC C"
  ],
  "price": 1550
 },
 {
  "id": "o2",
  "name": "Albert Road conversion",
  "facts": [
   "Ground floor of a Victorian terrace",
   "One bedroom plus a box room with no window",
   "35 min by two buses to Elm Road; no bus before 6:20",
   "Shared garden, landlord happy with a dog",
   "Single glazing at the front, on a bus route"
  ],
  "price": 1420
 },
 {
  "id": "o3",
  "name": "Harbour View new-build",
  "facts": [
   "Two bedrooms, both doubles",
   "Concierge, gym, bike store",
   "12 min walk to Elm Road",
   "Building rules: no pets of any kind",
   "Floor-to-ceiling windows, quiet courtyard side",
   "Service charge included in rent"
  ],
  "price": 1380
 }
]

criteria, decoded:

[
 {
  "id": "c1",
  "name": "Rent",
  "kind": "cost",
  "weight": 7,
  "weight_share": 25
 },
 {
  "id": "c2",
  "name": "Commute to Elm Road",
  "kind": "rate",
  "weight": 8,
  "weight_share": 28.6,
  "floor": 4
 },
 {
  "id": "c3",
  "name": "Work-from-home room",
  "kind": "rate",
  "weight": 6,
  "weight_share": 21.4
 },
 {
  "id": "c4",
  "name": "Light and quiet",
  "kind": "rate",
  "weight": 4,
  "weight_share": 14.3
 },
 {
  "id": "c5",
  "name": "Outdoor space",
  "kind": "rate",
  "weight": 3,
  "weight_share": 10.7
 }
]

matrix, decoded:

{
 "scale": "scores 0-10 per cell, weights 0-10, totals 0-100 points; computed in the browser",
 "price_label": "Rent / month",
 "scores": {
  "o1": {
   "c1": 0,
   "c2": 7,
   "c3": 6,
   "c4": 7,
   "c5": 8
  },
  "o2": {
   "c1": 7.6,
   "c2": 3,
   "c3": 3,
   "c4": 4,
   "c5": 7
  },
  "o3": {
   "c1": 10,
   "c2": 9,
   "c3": 9,
   "c4": 8,
   "c5": 2
  }
 },
 "totals": [
  {
   "option": "o3",
   "total": 83.6,
   "rank": 1
  },
  {
   "option": "o1",
   "total": 51.4,
   "rank": 2
  },
  {
   "option": "o2",
   "total": 47.3,
   "rank": 3
  }
 ],
 "leader": "o3",
 "runner_up": "o1",
 "tie": [],
 "margin": 32.1,
 "floors_failed": [
  {
   "option": "o2",
   "criteria": [
    "c2"
   ]
  }
 ],
 "unrated_cells": 0,
 "sensitivity": [
  "No single weight, anywhere from 0 to 10, takes the lead from Harbour View new-build.",
  "Rent decides it: on its own it puts Harbour View new-build 25 points ahead of Canal Street 2-bed, against a 32.1-point lead overall."
 ],
 "most_fragile": null,
 "decider": {
  "criterion": "c1",
  "points_of_lead": 25
 }
}

Reply:

{
 "task": "assess",
 "title": "Flat to rent from September",
 "headline": "Harbour View leads the matrix but bans all pets, so with Biscuit the evidence points to Canal Street, if the landlord confirms the dog.",
 "cells": [
  {
   "option": "o1",
   "criterion": "c1",
   "evidence": "1,550 is under the 1,700 all-in ceiling, but gas heating bills sit on top and pets need a 300 deposit; check the all-in monthly figure.",
   "suggested_score": null,
   "confidence": "medium",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c2",
   "evidence": "18 min cycle to Elm Road on the towpath is workable for 7am shifts; check the towpath is lit and passable on dark winter mornings.",
   "suggested_score": 7,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c3",
   "evidence": "The second bedroom (2.4 x 2.6 m) gives a room with a door for calls; small, but enough for a desk.",
   "suggested_score": 6,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c4",
   "evidence": "South-facing balcony over the canal suggests good light; nothing is said about noise.",
   "suggested_score": 6,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o1",
   "criterion": "c5",
   "evidence": "A private south-facing balcony over the canal; good for people, but no garden for a 30 kg Labrador.",
   "suggested_score": 6,
   "confidence": "medium",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c1",
   "evidence": "1,420 is within budget, but single glazing at the front points to higher heating bills on top of rent.",
   "suggested_score": null,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o2",
   "criterion": "c2",
   "evidence": "35 min on two buses with no bus before 6:20 leaves almost no slack for a 7am shift; one missed connection means arriving late.",
   "suggested_score": 2,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c3",
   "evidence": "The box room has no window, so the only usable room with a door is the bedroom; poor for three days of calls.",
   "suggested_score": 2,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c4",
   "evidence": "Single glazing at the front on a bus route implies traffic noise; a ground-floor terrace flat is likely darker.",
   "suggested_score": 3,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o2",
   "criterion": "c5",
   "evidence": "Shared garden and a landlord happy with a dog give Biscuit outdoor space, though shared with other residents.",
   "suggested_score": 6,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c1",
   "evidence": "1,380 with the service charge included is the closest to an all-in figure of the three; utilities still need checking.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c2",
   "evidence": "12 min walk to Elm Road: the shortest commute and no dependence on early buses or a bike.",
   "suggested_score": 9,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c3",
   "evidence": "Two double bedrooms, so the second is a full-size room with a door for calls.",
   "suggested_score": 9,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c4",
   "evidence": "Floor-to-ceiling windows and a quiet courtyard side; confirm this flat faces the courtyard.",
   "suggested_score": 8,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c5",
   "evidence": "The facts list no balcony, garden or terrace; outdoor space is not mentioned.",
   "suggested_score": null,
   "confidence": "low",
   "basis": "missing"
  }
 ],
 "missing_criteria": [
  {
   "name": "Dog-friendliness",
   "why": "The brief includes Biscuit, a 30 kg Labrador, and the options range from no pets (o3) to pets considered (o1) to dog welcome (o2); no criterion captures this.",
   "suggested_weight": 9
  },
  {
   "name": "Stairs and access",
   "why": 
... (shortened here; the page shows it whole)

assess: a family car, close call, the evidence agrees with the matrix

Input:

{
 "task": "assess",
 "decision": "Which car to replace the Fiesta with",
 "context": "Family of four, kids aged 3 and 7 in car seats. Mostly school run and supermarket, 9,000 miles a year, one 300-mile trip to the grandparents each month. Home charger possible on the drive. Want to keep it eight years.",
 "options": "(a JSON string - decoded below)",
 "criteria": "(a JSON string - decoded below)",
 "matrix": "(a JSON string - decoded below)"
}

options, decoded:

[
 {
  "id": "o1",
  "name": "Used estate, 3 years old",
  "facts": [
   "Diesel, 52 mpg combined",
   "Boot 600 litres",
   "Three ISOFIX points across the rear",
   "38,000 miles, full dealer history",
   "Insurance group 18"
  ],
  "price": 17500
 },
 {
  "id": "o2",
  "name": "New compact EV",
  "facts": [
   "Real-world range about 230 miles in winter",
   "Boot 380 litres",
   "Two ISOFIX points",
   "8-year battery warranty",
   "Charging cost about 4p a mile at home overnight"
  ],
  "price": 31000
 },
 {
  "id": "o3",
  "name": "Nearly-new hybrid SUV",
  "facts": [
   "Self-charging hybrid, 48 mpg",
   "Boot 520 litres",
   "Two ISOFIX points plus front passenger ISOFIX",
   "7-year manufacturer warranty transfers",
   "Higher seating, easy for car seats"
  ],
  "price": 26500
 },
 {
  "id": "o4",
  "name": "Keep the Fiesta",
  "facts": [
   "11 years old, 96,000 miles",
   "Needs a clutch soon, quoted 900",
   "Boot 290 litres",
   "Two ISOFIX points, tight with two seats"
  ],
  "price": 0
 }
]

criteria, decoded:

[
 {
  "id": "c1",
  "name": "Upfront cost",
  "kind": "cost",
  "weight": 5,
  "weight_share": 16.1
 },
 {
  "id": "c2",
  "name": "Space for kids and luggage",
  "kind": "rate",
  "weight": 8,
  "weight_share": 25.8,
  "floor": 4
 },
 {
  "id": "c3",
  "name": "Running costs over 8 years",
  "kind": "rate",
  "weight": 7,
  "weight_share": 22.6
 },
 {
  "id": "c4",
  "name": "Long-trip comfort",
  "kind": "rate",
  "weight": 5,
  "weight_share": 16.1
 },
 {
  "id": "c5",
  "name": "Reliability",
  "kind": "rate",
  "weight": 6,
  "weight_share": 19.4
 }
]

matrix, decoded:

{
 "scale": "scores 0-10 per cell, weights 0-10, totals 0-100 points; computed in the browser",
 "price_label": "Price",
 "scores": {
  "o1": {
   "c1": 4.4,
   "c2": 9,
   "c3": 6,
   "c4": 8,
   "c5": 7
  },
  "o2": {
   "c1": 0,
   "c2": 5,
   "c3": 9,
   "c4": 5,
   "c5": 8
  },
  "o3": {
   "c1": 1.5,
   "c2": 8,
   "c3": 7,
   "c4": 8,
   "c5": 9
  },
  "o4": {
   "c1": 10,
   "c2": 2,
   "c3": 5,
   "c4": 3,
   "c5": 2
  }
 },
 "totals": [
  {
   "option": "o1",
   "total": 70.2,
   "rank": 1
  },
  {
   "option": "o3",
   "total": 69.1,
   "rank": 2
  },
  {
   "option": "o2",
   "total": 56.8,
   "rank": 3
  },
  {
   "option": "o4",
   "total": 41.3,
   "rank": 4
  }
 ],
 "leader": "o1",
 "runner_up": "o3",
 "tie": [],
 "margin": 1.1,
 "floors_failed": [
  {
   "option": "o4",
   "criteria": [
    "c2"
   ]
  }
 ],
 "unrated_cells": 0,
 "sensitivity": [
  "Used estate, 3 years old leads unless Upfront cost moves from weight 5 to 3.8 (-1.2), where Nearly-new hybrid SUV draws level.",
  "Upfront cost decides it: on its own it puts Used estate, 3 years old 4.7 points ahead of Nearly-new hybrid SUV, against a 1.1-point lead overall.",
  "Dropping Upfront cost or Space for kids and luggage (weight 0) changes the leader."
 ],
 "most_fragile": {
  "criterion": "c1",
  "from": 5,
  "flips_at": 3.8,
  "to": "o3"
 },
 "decider": {
  "criterion": "c1",
  "points_of_lead": 4.7
 }
}

Reply:

{
 "task": "assess",
 "title": "Replacing the Fiesta for a family of four",
 "headline": "The used estate and hybrid SUV both carry two car seats and the monthly trip well; the estate has more room, the SUV more warranty, and the EV's winter range falls short of 300 miles.",
 "cells": [
  {
   "option": "o1",
   "criterion": "c1",
   "evidence": "17,500 for a 3-year-old car with 38,000 miles and full dealer history; no warranty is listed, so any repairs over eight years are on you.",
   "suggested_score": null,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o1",
   "criterion": "c2",
   "evidence": "600-litre boot, the largest here, and three ISOFIX points across the rear, so two car seats fit with room to spare.",
   "suggested_score": 9,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c3",
   "evidence": "Diesel at 52 mpg over 9,000 miles a year is economical, but insurance group 18 and no listed warranty add cost across eight years.",
   "suggested_score": 6,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o1",
   "criterion": "c4",
   "evidence": "52 mpg diesel covers the 300-mile monthly trip on one tank and the 600-litre boot takes luggage; ride comfort itself is not in the facts.",
   "suggested_score": 7,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o1",
   "criterion": "c5",
   "evidence": "38,000 miles with full dealer history is a good sign, but no warranty is listed and it will be 11 years old at the end of eight years.",
   "suggested_score": 7,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o2",
   "criterion": "c1",
   "evidence": "31,000 is the highest price; set against it, home charging at about 4p a mile and an 8-year battery warranty lower later costs.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c2",
   "evidence": "380-litre boot and two ISOFIX points: both car seats fit, but luggage for four on the monthly trip will be tight.",
   "suggested_score": 5,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c3",
   "evidence": "About 4p a mile charging at home overnight is the cheapest running cost here, and the 8-year battery warranty matches the keep period.",
   "suggested_score": 9,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c4",
   "evidence": "About 230 miles of winter range means at least one charging stop on every 300-mile trip, with two children aged 3 and 7.",
   "suggested_score": 4,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c5",
   "evidence": "New car with an 8-year battery warranty covering the whole keep period; cover for the rest of the car is not stated.",
   "suggested_score": 8,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o3",
   "criterion": "c1",
   "evidence": "26,500, the second dearest, but the 7-year manufacturer warranty transfers, which offsets some of the premium over the estate.",
   "suggested_score": null,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o3",
   "criterion": "c2",
   "evidence": "520-litre boot, two rear ISOFIX plus a front one, and higher seating that makes fitting car seats easier.",
   "suggested_score": 8,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c3",
   "evidence": "Self-charging hybrid at 48 mpg, slightly thirstier than the diesel, but the 7-year warranty limits repair bills over most of the eight years.",
   "suggested_score": 7,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o3",
   "criterion": "c4",
   "evidence": "Higher seating and a 520-litre boot suit the monthly trip, and 48 mpg needs no charging stops; ride comfort is not stated.",
   "suggested_score": 7,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o3",
   "criterion": "c5",
   "evidence": "Nearly new with a 7-year manufacturer warranty that transfers, the longest whole-car cover listed
... (shortened here; the page shows it whole)

assess: a student laptop, an exact tie at the top

Input:

{
 "task": "assess",
 "decision": "Laptop for Maya starting a design degree",
 "context": "Maya starts a graphic design degree in October. The course uses Adobe Illustrator and Figma; first-year brief says 16 GB RAM minimum. She carries it to campus by bike every day. Budget up to 1,300 including any case.",
 "options": "(a JSON string - decoded below)",
 "criteria": "(a JSON string - decoded below)",
 "matrix": "(a JSON string - decoded below)"
}

options, decoded:

[
 {
  "id": "o1",
  "name": "13-inch ultralight",
  "facts": [
   "Education-discount price",
   "16 GB RAM, 512 GB SSD",
   "1.2 kg",
   "18 hours claimed battery",
   "Colour-accurate display, 100% sRGB",
   "Two USB-C ports only"
  ],
  "price": 1194
 },
 {
  "id": "o2",
  "name": "15-inch creator laptop",
  "facts": [
   "32 GB RAM, 1 TB SSD",
   "2.1 kg",
   "8 hours claimed battery",
   "OLED display, 100% DCI-P3",
   "Dedicated graphics card",
   "Fan noise under load reported in reviews"
  ],
  "price": 1299
 },
 {
  "id": "o3",
  "name": "2-in-1 with stylus",
  "facts": [
   "8 GB RAM soldered, not upgradeable",
   "1.4 kg",
   "Stylus included",
   "Display 72% sRGB",
   "12 hours claimed battery"
  ],
  "price": 949
 }
]

criteria, decoded:

[
 {
  "id": "c1",
  "name": "Price",
  "kind": "cost",
  "weight": 4,
  "weight_share": 16.7
 },
 {
  "id": "c2",
  "name": "Runs the design software",
  "kind": "rate",
  "weight": 8,
  "weight_share": 33.3,
  "floor": 5
 },
 {
  "id": "c3",
  "name": "Portable for a daily bike ride",
  "kind": "rate",
  "weight": 6,
  "weight_share": 25
 },
 {
  "id": "c4",
  "name": "Screen for colour work",
  "kind": "rate",
  "weight": 6,
  "weight_share": 25
 }
]

matrix, decoded:

{
 "scale": "scores 0-10 per cell, weights 0-10, totals 0-100 points; computed in the browser",
 "price_label": "Price",
 "scores": {
  "o1": {
   "c1": 3,
   "c2": 7,
   "c3": 9,
   "c4": 7
  },
  "o2": {
   "c1": 0,
   "c2": 10,
   "c3": 4,
   "c4": 10
  },
  "o3": {
   "c1": 10,
   "c2": 3,
   "c3": 7,
   "c4": 4
  }
 },
 "totals": [
  {
   "option": "o1",
   "total": 68.3,
   "rank": 1
  },
  {
   "option": "o2",
   "total": 68.3,
   "rank": 1
  },
  {
   "option": "o3",
   "total": 54.2,
   "rank": 3
  }
 ],
 "leader": null,
 "runner_up": null,
 "tie": [
  "o1",
  "o2"
 ],
 "margin": 0,
 "floors_failed": [
  {
   "option": "o3",
   "criteria": [
    "c2"
   ]
  }
 ],
 "unrated_cells": 0,
 "sensitivity": [
  "Tie: 13-inch ultralight and 15-inch creator laptop both score 68.3.",
  "Raising Price breaks the tie toward 13-inch ultralight.",
  "Raising Runs the design software breaks the tie toward 15-inch creator laptop.",
  "Raising Portable for a daily bike ride breaks the tie toward 13-inch ultralight.",
  "Raising Screen for colour work breaks the tie toward 15-inch creator laptop."
 ],
 "most_fragile": null,
 "decider": null
}

Reply:

{
 "task": "assess",
 "title": "Laptop for Maya's design degree",
 "headline": "The ultralight and the creator laptop trade portability against power and screen; the 2-in-1's 8 GB RAM misses the course's 16 GB minimum.",
 "cells": [
  {
   "option": "o1",
   "criterion": "c1",
   "evidence": "Education-discount price leaves room under the 1,300 budget for a case; two USB-C ports only may mean buying adapters for a mouse, tablet or drive.",
   "suggested_score": null,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o1",
   "criterion": "c2",
   "evidence": "16 GB RAM meets the first-year brief's minimum exactly and 512 GB SSD is workable; no dedicated graphics listed, so heavy Illustrator files have less headroom.",
   "suggested_score": 7,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o1",
   "criterion": "c3",
   "evidence": "1.2 kg is the lightest option and 18 hours claimed battery should cover a full campus day without a charger in the bike bag.",
   "suggested_score": 9,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c4",
   "evidence": "Colour-accurate display covering 100% sRGB suits screen and web design work, though it lacks the wider DCI-P3 gamut of o2.",
   "suggested_score": 7,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c1",
   "evidence": "At 1,299 it sits at the 1,300 ceiling, leaving essentially nothing for the case the budget must include.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c2",
   "evidence": "32 GB RAM, 1 TB SSD and a dedicated graphics card comfortably exceed the 16 GB minimum for Illustrator and Figma; reviews report fan noise under load.",
   "suggested_score": 10,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c3",
   "evidence": "2.1 kg is the heaviest option for a daily bike ride, and 8 hours claimed battery may not last a long campus day.",
   "suggested_score": 3,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c4",
   "evidence": "OLED display covering 100% DCI-P3 is the widest gamut on offer, the strongest screen for colour work.",
   "suggested_score": 10,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c1",
   "evidence": "Cheapest at 949, but 8 GB soldered RAM cannot be upgraded later, so the saving cannot buy its way out of the RAM shortfall.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c2",
   "evidence": "8 GB RAM soldered, not upgradeable, is half the 16 GB minimum the first-year brief sets for the design software.",
   "suggested_score": 2,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c3",
   "evidence": "1.4 kg and 12 hours claimed battery make it reasonably easy to carry by bike each day, a little heavier than o1.",
   "suggested_score": 7,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c4",
   "evidence": "Display covers 72% sRGB, so colours will look noticeably off for print and screen design work.",
   "suggested_score": 3,
   "confidence": "high",
   "basis": "fact"
  }
 ],
 "missing_criteria": [
  {
   "name": "Ports and connectivity",
   "why": "o1 has two USB-C ports only; a design student may plug in a drawing tablet, mouse, drive and charger at once.",
   "suggested_weight": 3
  },
  {
   "name": "Pen input",
   "why": "o3 includes a stylus, which can matter for sketching on a graphic design course; no criterion credits it.",
   "suggested_weight": 2
  }
 ],
 "weight_challenges": [
  {
   "criterion": "c3",
   "issue": "The brief says she carries it to campus by bike every day, which also means durability and battery for the day; one portability criterion may hide weight and battery life.",
   "direction": "split",
   "suggested_weight": 6
  }
 ],
 "deal_breakers": [
  {
   "option": "o3",
   "criterion": "c2",
   "reason": "8 GB RAM soldered, not upgradeable, is below the fir
... (shortened here; the page shows it whole)

assess: a summer holiday, the first reading

Input:

{
 "task": "assess",
 "decision": "Where to go for two weeks in August",
 "context": "Couple in our 40s, one of us gets very unwell in heat above 32C. We want walking, good food and some swimming. Budget about 4,000 for the two of us including flights. Flying from Manchester; would rather avoid early-morning flights.",
 "options": "(a JSON string - decoded below)",
 "criteria": "(a JSON string - decoded below)",
 "matrix": "(a JSON string - decoded below)"
}

options, decoded:

[
 {
  "id": "o1",
  "name": "Slovenian lakes and Alps",
  "facts": [
   "Bled and Bohinj; August highs around 26C",
   "Hut-to-hut walks, lake swimming",
   "Flight to Ljubljana via a connection, about 6 hours",
   "Busy in August, book guesthouses early"
  ],
  "price": 3600
 },
 {
  "id": "o2",
  "name": "Crete, south coast",
  "facts": [
   "August highs 31-35C",
   "Samaria gorge walk, best started at 7am",
   "Direct flights from Manchester, 6:05am departure",
   "Seafood tavernas, beaches"
  ],
  "price": 3200
 },
 {
  "id": "o3",
  "name": "Norwegian fjords",
  "facts": [
   "August highs 18-22C",
   "Trolltunga and Preikestolen hikes",
   "Direct flight to Bergen, 11:40",
   "Eating out expensive, fjord swimming cold (16C)"
  ],
  "price": 4600
 }
]

criteria, decoded:

[
 {
  "id": "c1",
  "name": "Trip cost",
  "kind": "cost",
  "weight": 5,
  "weight_share": 16.7
 },
 {
  "id": "c2",
  "name": "Bearable heat",
  "kind": "rate",
  "weight": 9,
  "weight_share": 30,
  "floor": 5
 },
 {
  "id": "c3",
  "name": "Walking",
  "kind": "rate",
  "weight": 7,
  "weight_share": 23.3
 },
 {
  "id": "c4",
  "name": "Food",
  "kind": "rate",
  "weight": 5,
  "weight_share": 16.7
 },
 {
  "id": "c5",
  "name": "Swimming",
  "kind": "rate",
  "weight": 4,
  "weight_share": 13.3
 }
]

matrix, decoded:

{
 "scale": "scores 0-10 per cell, weights 0-10, totals 0-100 points; computed in the browser",
 "price_label": "Trip cost",
 "scores": {
  "o1": {
   "c1": 7.1,
   "c2": 8,
   "c3": 8,
   "c4": 6,
   "c5": 8
  },
  "o2": {
   "c1": 10,
   "c2": 3,
   "c3": 7,
   "c4": 9,
   "c5": 9
  },
  "o3": {
   "c1": 0,
   "c2": 10,
   "c3": 10,
   "c4": 5,
   "c5": 3
  }
 },
 "totals": [
  {
   "option": "o1",
   "total": 75.2,
   "rank": 1
  },
  {
   "option": "o2",
   "total": 69,
   "rank": 2
  },
  {
   "option": "o3",
   "total": 65.7,
   "rank": 3
  }
 ],
 "leader": "o1",
 "runner_up": "o2",
 "tie": [],
 "margin": 6.2,
 "floors_failed": [
  {
   "option": "o2",
   "criteria": [
    "c2"
   ]
  }
 ],
 "unrated_cells": 0,
 "sensitivity": [
  "Slovenian lakes and Alps leads unless Bearable heat moves from weight 9 to 5.3 (-3.7), where Crete, south coast draws level.",
  "Bearable heat decides it: on its own it puts Slovenian lakes and Alps 15 points ahead of Crete, south coast, against a 6.2-point lead overall.",
  "Dropping Trip cost or Bearable heat (weight 0) changes the leader."
 ],
 "most_fragile": {
  "criterion": "c2",
  "from": 9,
  "flips_at": 5.3,
  "to": "o2"
 },
 "decider": {
  "criterion": "c2",
  "points_of_lead": 15
 }
}

Reply:

{
 "task": "assess",
 "title": "Two weeks away in August",
 "headline": "Slovenia is the only option that clears both the heat limit and the budget; Crete runs too hot and Norway costs more than the brief allows.",
 "cells": [
  {
   "option": "o1",
   "criterion": "c1",
   "evidence": "Priced at 3,600, inside the stated budget of about 4,000. The connecting flight and early guesthouse booking may add costs to check.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c2",
   "evidence": "August highs around 26C, well under the 32C level where one of you gets unwell.",
   "suggested_score": 9,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c3",
   "evidence": "Hut-to-hut walks in the Alps are listed, which suits a walking holiday directly.",
   "suggested_score": 8,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c4",
   "evidence": "None of the facts mention food or places to eat, so your 6 is not backed by anything listed here.",
   "suggested_score": null,
   "confidence": "low",
   "basis": "missing"
  },
  {
   "option": "o1",
   "criterion": "c5",
   "evidence": "Lake swimming at Bled and Bohinj is listed, and the 26C highs make it pleasant.",
   "suggested_score": 8,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c1",
   "evidence": "Cheapest at 3,200 and inside the budget; the direct flight avoids connection costs, though it leaves at 6:05am.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c2",
   "evidence": "August highs of 31-35C go past the 32C level where one of you gets very unwell, so most days risk being too hot.",
   "suggested_score": 2,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c3",
   "evidence": "Samaria gorge is a big walk, but it is best started at 7am to beat the heat, and 31-35C highs limit walking for the heat-sensitive one.",
   "suggested_score": 5,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o2",
   "criterion": "c4",
   "evidence": "Seafood tavernas are listed, a direct match for wanting good food.",
   "suggested_score": 8,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c5",
   "evidence": "Beaches are listed and the sea will be warm, though midday swims overlap the hottest hours.",
   "suggested_score": 8,
   "confidence": "medium",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c1",
   "evidence": "At 4,600 it is over the budget of about 4,000, and eating out is listed as expensive, so day-to-day costs add more on top.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c2",
   "evidence": "August highs of 18-22C, far below 32C; no heat risk at all.",
   "suggested_score": 10,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c3",
   "evidence": "Trolltunga and Preikestolen hikes are listed, both major day walks.",
   "suggested_score": 9,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c4",
   "evidence": "The only food fact is that eating out is expensive, which is about cost, not how good the food is.",
   "suggested_score": null,
   "confidence": "low",
   "basis": "missing"
  },
  {
   "option": "o3",
   "criterion": "c5",
   "evidence": "Fjord swimming is listed as cold at 16C, so short dips only.",
   "suggested_score": 3,
   "confidence": "high",
   "basis": "fact"
  }
 ],
 "missing_criteria": [
  {
   "name": "Flight times and travel",
   "why": "The brief says you would rather avoid early-morning flights; Crete leaves at 6:05am and Slovenia needs a connection of about 6 hours.",
   "suggested_weight": 4
  }
 ],
 "weight_challenges": [],
 "deal_breakers": [
  {
   "option": "o2",
   "criterion": "c2",
   "reason": "Bearable heat is below your floor of 5: August highs of 31-35C pass the 32C level where one of you gets very unwell.",
   "severity":
... (shortened here; the page shows it whole)

reassess: the same holiday after a fourth option was added

Input (only the five cells of the new option are stale):

{
 "task": "reassess",
 "decision": "Where to go for two weeks in August",
 "context": "Couple in our 40s, one of us gets very unwell in heat above 32C. We want walking, good food and some swimming. Budget about 4,000 for the two of us including flights. Flying from Manchester; would rather avoid early-morning flights.",
 "options": "(a JSON string - decoded below)",
 "criteria": "(a JSON string - decoded below)",
 "matrix": "(a JSON string - decoded below)",
 "changes": "(a JSON string - decoded below)",
 "previous": "(a JSON string - decoded below)"
}

options, decoded:

[
 {
  "id": "o1",
  "name": "Slovenian lakes and Alps",
  "facts": [
   "Bled and Bohinj; August highs around 26C",
   "Hut-to-hut walks, lake swimming",
   "Flight to Ljubljana via a connection, about 6 hours",
   "Busy in August, book guesthouses early"
  ],
  "price": 3600
 },
 {
  "id": "o2",
  "name": "Crete, south coast",
  "facts": [
   "August highs 31-35C",
   "Samaria gorge walk, best started at 7am",
   "Direct flights from Manchester, 6:05am departure",
   "Seafood tavernas, beaches"
  ],
  "price": 3200
 },
 {
  "id": "o3",
  "name": "Norwegian fjords",
  "facts": [
   "August highs 18-22C",
   "Trolltunga and Preikestolen hikes",
   "Direct flight to Bergen, 11:40",
   "Eating out expensive, fjord swimming cold (16C)"
  ],
  "price": 4600
 },
 {
  "id": "o4",
  "name": "Northern Portugal, Peneda-Geres",
  "facts": [
   "August highs 27-30C inland, cooler on the coast",
   "Walking in Peneda-Geres national park",
   "Direct flight Manchester to Porto, 13:15",
   "Vinho verde country, river beaches"
  ],
  "price": 3400
 }
]

criteria, decoded:

[
 {
  "id": "c1",
  "name": "Trip cost",
  "kind": "cost",
  "weight": 5,
  "weight_share": 16.7
 },
 {
  "id": "c2",
  "name": "Bearable heat",
  "kind": "rate",
  "weight": 9,
  "weight_share": 30,
  "floor": 5
 },
 {
  "id": "c3",
  "name": "Walking",
  "kind": "rate",
  "weight": 7,
  "weight_share": 23.3
 },
 {
  "id": "c4",
  "name": "Food",
  "kind": "rate",
  "weight": 5,
  "weight_share": 16.7
 },
 {
  "id": "c5",
  "name": "Swimming",
  "kind": "rate",
  "weight": 4,
  "weight_share": 13.3
 }
]

matrix, decoded:

{
 "scale": "scores 0-10 per cell, weights 0-10, totals 0-100 points; computed in the browser",
 "price_label": "Trip cost",
 "scores": {
  "o1": {
   "c1": 7.1,
   "c2": 8,
   "c3": 8,
   "c4": 6,
   "c5": 8
  },
  "o2": {
   "c1": 10,
   "c2": 3,
   "c3": 7,
   "c4": 9,
   "c5": 9
  },
  "o3": {
   "c1": 0,
   "c2": 10,
   "c3": 10,
   "c4": 5,
   "c5": 3
  },
  "o4": {
   "c1": 8.6,
   "c2": 6,
   "c3": 8,
   "c4": 8,
   "c5": 7
  }
 },
 "totals": [
  {
   "option": "o1",
   "total": 75.2,
   "rank": 1
  },
  {
   "option": "o4",
   "total": 73.6,
   "rank": 2
  },
  {
   "option": "o2",
   "total": 69,
   "rank": 3
  },
  {
   "option": "o3",
   "total": 65.7,
   "rank": 4
  }
 ],
 "leader": "o1",
 "runner_up": "o4",
 "tie": [],
 "margin": 1.6,
 "floors_failed": [
  {
   "option": "o2",
   "criteria": [
    "c2"
   ]
  }
 ],
 "unrated_cells": 0,
 "sensitivity": [
  "Slovenian lakes and Alps leads unless Bearable heat moves from weight 9 to 6.6 (-2.4), where Northern Portugal, Peneda-Geres draws level.",
  "Bearable heat decides it: on its own it puts Slovenian lakes and Alps 6 points ahead of Northern Portugal, Peneda-Geres, against a 1.6-point lead overall.",
  "Dropping Trip cost or Bearable heat (weight 0) changes the leader."
 ],
 "most_fragile": {
  "criterion": "c2",
  "from": 9,
  "flips_at": 6.6,
  "to": "o4"
 },
 "decider": {
  "criterion": "c2",
  "points_of_lead": 6
 }
}

changes, decoded:

{
 "changes": [
  {
   "type": "option_added",
   "id": "o4",
   "to": "Northern Portugal, Peneda-Geres"
  }
 ],
 "stale_pairs": [
  "o4|c1",
  "o4|c2",
  "o4|c3",
  "o4|c4",
  "o4|c5"
 ]
}

previous, decoded:

{
 "title": "Two weeks away in August",
 "headline": "Slovenia is the only option that clears both the heat limit and the budget; Crete runs too hot and Norway costs more than the brief allows.",
 "cells": [
  {
   "option": "o1",
   "criterion": "c1",
   "evidence": "Priced at 3,600, inside the stated budget of about 4,000. The connecting flight and early guesthouse booking may add costs to check.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c2",
   "evidence": "August highs around 26C, well under the 32C level where one of you gets unwell.",
   "suggested_score": 9,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c3",
   "evidence": "Hut-to-hut walks in the Alps are listed, which suits a walking holiday directly.",
   "suggested_score": 8,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o1",
   "criterion": "c4",
   "evidence": "None of the facts mention food or places to eat, so your 6 is not backed by anything listed here.",
   "suggested_score": null,
   "confidence": "low",
   "basis": "missing"
  },
  {
   "option": "o1",
   "criterion": "c5",
   "evidence": "Lake swimming at Bled and Bohinj is listed, and the 26C highs make it pleasant.",
   "suggested_score": 8,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c1",
   "evidence": "Cheapest at 3,200 and inside the budget; the direct flight avoids connection costs, though it leaves at 6:05am.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c2",
   "evidence": "August highs of 31-35C go past the 32C level where one of you gets very unwell, so most days risk being too hot.",
   "suggested_score": 2,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c3",
   "evidence": "Samaria gorge is a big walk, but it is best started at 7am to beat the heat, and 31-35C highs limit walking for the heat-sensitive one.",
   "suggested_score": 5,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o2",
   "criterion": "c4",
   "evidence": "Seafood tavernas are listed, a direct match for wanting good food.",
   "suggested_score": 8,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o2",
   "criterion": "c5",
   "evidence": "Beaches are listed and the sea will be warm, though midday swims overlap the hottest hours.",
   "suggested_score": 8,
   "confidence": "medium",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c1",
   "evidence": "At 4,600 it is over the budget of about 4,000, and eating out is listed as expensive, so day-to-day costs add more on top.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o3",
   "criterion": "c2",
   "evidence": "August highs of 18-22C, far below 32C; no heat risk at all.",
   "suggested_score": 10
... (shortened here)

Reply:

{
 "task": "reassess",
 "title": "Two weeks away in August",
 "headline": "Slovenia and northern Portugal both clear the heat limit and the budget; Portugal runs warmer inland but has the easier flight, while Crete is too hot and Norway over budget.",
 "cells": [
  {
   "option": "o4",
   "criterion": "c1",
   "evidence": "Priced at 3,400, inside the budget of about 4,000, and the direct Porto flight avoids connection costs. No running costs are listed.",
   "suggested_score": null,
   "confidence": "high",
   "basis": "fact"
  },
  {
   "option": "o4",
   "criterion": "c2",
   "evidence": "Inland highs of 27-30C sit under the 32C level but close to it; the cooler coast gives a fallback on hot days.",
   "suggested_score": 6,
   "confidence": "medium",
   "basis": "fact"
  },
  {
   "option": "o4",
   "criterion": "c3",
   "evidence": "Walking in Peneda-Geres national park is listed, though with less detail than named routes elsewhere; inland heat of up to 30C may push walks early.",
   "suggested_score": 7,
   "confidence": "medium",
   "basis": "fact"
  },
  {
   "option": "o4",
   "criterion": "c4",
   "evidence": "Vinho verde country points to a food and wine region, but no restaurants or dishes are listed, so this is one step of inference.",
   "suggested_score": 7,
   "confidence": "medium",
   "basis": "inference"
  },
  {
   "option": "o4",
   "criterion": "c5",
   "evidence": "River beaches are listed, which gives swimming near the walking area; sea swimming on the coast is not mentioned.",
   "suggested_score": 7,
   "confidence": "high",
   "basis": "fact"
  }
 ],
 "missing_criteria": [
  {
   "name": "Flight times and travel",
   "why": "The brief says you would rather avoid early-morning flights, and the options range from a 6:05am departure to a 6-hour connection to a 13:15 direct flight.",
   "suggested_weight": 4
  }
 ],
 "weight_challenges": [],
 "deal_breakers": [
  {
   "option": "o2",
   "criterion": "c2",
   "reason": "Bearable heat is below your floor of 5: August highs of 31-35C pass the 32C level where one of you gets very unwell.",
   "severity": "hard"
  },
  {
   "option": "o3",
   "criterion": "c1",
   "reason": "The price of 4,600 is over the budget of about 4,000 for both of you, flights included.",
   "severity": "hard"
  },
  {
   "option": "o2",
   "criterion": null,
   "reason": "The direct flight leaves Manchester at 6:05am, the early-morning flight the brief says you would rather avoid.",
   "severity": "soft"
  },
  {
   "option": "o4",
   "criterion": "c2",
   "reason": "Inland highs of 27-30C leave little margin below the 32C level; worth checking how often inland days go past it in August.",
   "severity": "soft"
  }
 ],
 "bias_checks": [
  {
   "bias": "Shiny object",
   "signal": "Portugal was added after the first reading and went straight in close behind the leader.",
   "question": "Would you rate Portugal the same if it had been on the list from the start?"
  },
  {
   "bias": "Confirmation",
   "signal": "Slovenia's food is scored 6 although none of its facts mention food.",
   "question": "What have you actually read about eating in Bled and Bohinj?"
  }
 ],
 "leaning": {
  "option": "o1",
  "why": "Slovenia has the most heat margin with 26C highs, plus hut-to-hut walks and lake swimming inside the budget. Portugal is close behind with a better flight and food signal but warmer inland days.",
  "agrees_with_matrix": true
 },
 "questions": [
  "How often do inland Peneda-Geres days go above 32C in August, and could you base yourselves nearer the coast?",
  "What is the food like around Bled and Bohinj?",
  "What time does the Ljubljana connection leave Manchester?",
  "Are guesthouses in Bled and Bohinj still available for your dates?"
 ],
 "next_steps": [
  "Look up past August daily highs for a Peneda-Geres base and for the coast.",
  "Check the departure time of the Slovenia flight connection.",
  "Ask Slovenian guesthouses about availability before deciding, since August books early.",
  "Add a flight-times criterion if the early-morning departure matters to you."
 ],
 "notes": [],
 "changed": "The new Portugal option has no hard deal-breaker: it is inside the budget, has 
... (shortened here; the page shows it whole)

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

Over the API you get the reading as JSON. The numbers in matrix are yours to compute, and the page's own code is plain scripts you can load in node after global.window = {}: gridmath.js (GridMath.compute(decision) for the totals, ranks, floors and sensitivity; GridMath.buildInput(computed, {task}) for this exact run body; GridMath.diff(before, after) for a re-assessment's changes and stale_pairs) and verdict.js (Verdict.normalize(reply, decision), Verdict.merge(previous, reply, decision, stale_pairs) and Verdict.reconcile(result, computed, opts) for the mismatches the page lists under "Checked against the matrix").

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://choice-grid.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":"choice-grid"}' | 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": "assess", "decision": "...", "context": "...", "options": "[...]", "criteria": "[...]", "matrix": "{...}"}
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 choice-grid:assess:<inputHash>:a<attempt>. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="choice-grid:assess:$(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: choice-grid:assess:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json