Drive Rule of Odds from your own code
Both metered lanes are available over HTTP. A natural use is a collection or home-inventory tool: your code sends a shelf and its pieces with the page's measurements, and gets back a read of where the eye lands and how it travels, or a re-arrangement of the same pieces in groups.
The division of labour first: the page measures and flags; the model reads and arranges; the page checks the model against its own measurements. Parsing the list, the estimated widths, how full each shelf is, the tallest piece against its opening, flat height runs, the balance sum and every flag (too tall, wider than the board, crowded, leaning, an empty shelf, uneven shelves, a busy palette) come from shelfkit.js, which runs in node as well as in the browser. Its checkRead and checkArrange functions then hold each reply to the shelf: every shelf once, every piece named exactly as listed, nothing invented, a plan that places every piece once and fits every opening and width, and a plan verdict that matches its own count of moves.
Get a token on the token page (step 2 below also shows how to mint a guest token in code).
Base URL and the envelope
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }
The token is minted for this app (the guest call takes {"slug":"rule-of-odds"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare: never wrap it as {"input": {…}}.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or task is missing. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal token. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field comes first
Every run sends task, one of two lanes over the same shelf. Run read first when you can: arrange answers its findings through read_findings.
| task | Returns |
|---|---|
read | title, verdict (balanced / tune / rework), summary; entry {item, why}; eye_path (3-7 piece names, starting with the entry); rows [{row, status, weight, note, fix}] - every shelf, top first, status settled / uneasy / broken, weight left / right / even; findings [{principle, items, problem, fix}] (1-5, principle one of entry, flow, weight, emphasis, spacing, colour). |
arrange | title, verdict (light-touch / reshuffle / edit-down), summary; rows [{row, groups, note}] - every shelf, groups an array of arrays of piece names, left to right; retire [{item, why}]; spacing (2-4 tips). |
| Verdict | Meaning |
|---|---|
balanced | Every shelf settled and the page flagged nothing at the warn or fix level. |
tune | No blocker, but at least one uneasy shelf or a warn flag: crowded, a flat run of equal heights, a lean, an empty shelf, one shelf far fuller than another, a busy palette. |
rework | A blocker: a piece taller than its opening or a shelf wider than its board - the shelf cannot stand as it is. |
light-touch | The plan moves at most one piece to another shelf and sends nothing on display to the box (re-ordering within a shelf still counts as a light touch). |
reshuffle | Two or more pieces change shelf (a piece coming out of the box counts); nothing on display goes to the box. |
edit-down | At least one piece that was on display goes to the box - the shelf cannot hold everything with breathing room. |
The declared input schema carries scalars only, so object-valued fields travel JSON-encoded as strings (ShelfKit.toWire does this). Only task is required by the schema.
Input fields
| Field | Type | Notes |
|---|---|---|
task | string | read or arrange. |
today | string | YYYY-MM-DD. |
shelf | string (JSON) | {name, shelves, width_cm, openings_cm}; shelves are numbered 1 at the top, openings_cm lists each shelf's clear height, top first. |
goal | string | The owner's words about the feel they want; clipped to 600 characters on a word boundary. |
items | string (JSON) | [{name, row, h_cm, w_cm, size, colour, colour_family, material, kind, hero}]; row 0 = in the box; on a shelf, listed left to right; size is measured or estimated. At most 40 pieces; when the list is longer, page_reads carries an info flag list_capped saying how many were left out. |
row_scan | string (JSON) | [{row, opening_cm, pieces, fill_pct, tallest_pct, rhythm, balance}]; rhythm is flat, varied or few; balance is even, left-heavy, right-heavy or single (an estimate). |
page_reads | string (JSON) | [{level, code, rows, text}]; level is fix, warn or info; rows lists shelf numbers. |
page_verdict | string | The page's own reading: balanced, tune or rework. |
read_findings | string (JSON or "") | arrange only: the read's {verdict, entry, rows: [{row, status, fix}], findings: [{principle, items, fix}]}, or "" when no read was run. |
retry_note | string | Only on a reformat retry. |
The reply
The job's output.output is a string holding one JSON object. Parse it with ShelfKit.parseReply, then normalize(reply, task), then check it: checkRead(reply, state) returns {rows, counts, path, entry, notes} (every shelf with the read's status, the settled / uneasy / broken counts, the eye path resolved to pieces, and the notes shown under "Checked by the page"); checkArrange(reply, state) returns {rows, retired, missing, moves, reordered, expected, counts, notes} (every shelf re-measured as planned, the pieces sent to the box, any left out, each change of shelf, and the verdict the moves imply).
Worked examples
Replies produced against the app's prompt for the sample shelves (made-up shelves, pieces and sizes; lists shortened; the full replies ship in example-result.js). The page replays each of them free from its sample buttons. Inputs are shown with the long fields folded.
task: read - Teacup dresser - balanced
Input:
{
"task": "read",
"today": "2026-10-20",
"shelf": "{\"name\":\"Kitchen dresser\",\"shelves\":3,\"width_cm\":90,\"openings_cm\":[32,32,34]}",
"goal": "Keep it calm - Nan's blue and white china is the point.",
"items": "[{\"name\":\"blue and white teapot\",\"row\":1,\"h_cm\":18,\"w_cm\":24,\"size\":\"measured\",\"colour\":\"blue\",\"colour_family\":\"blue\",\"material\":\"ceramic\",\"kind\":\"vase\",\"hero\":true},{\"name\":\"white jug\",\"row\":1,\"h_cm\":22,\"w_cm\":14,\"size\":\"measured\",\"colour\":\"white\",\"colour_family\":\"neutral\",\"material\":\"ceramic\",\"kind\":\"vase\",\"hero\":false},\"... 7 more\"]",
"row_scan": "[{\"row\":1,\"opening_cm\":32,\"pieces\":[\"blue and white teapot\",\"white jug\",\"brass candlestick\"],\"fill_pct\":51,\"tallest_pct\":81,\"rhythm\":\"varied\",\"balance\":\"even\"},{\"row\":2,\"opening_cm\":32,\"pieces\":[\"blue and white plate on a stand\",\"stacked teacups\",\"small fern in a blue pot\"],\"fill_pct\":58,\"tallest_pct\":81,\"rhythm\":\"varied\",\"balance\":\"even\"},\"... 1 more\"]",
"page_reads": "[]",
"page_verdict": "balanced"
}
Reply:
{
"task": "read",
"title": "The Teapot Opens a Calm Dresser",
"verdict": "balanced",
"summary": "This dresser reads as balanced, with every shelf settled and nothing flagged as a problem. The eye opens on the blue and white teapot, moves past the white jug to the taller brass candlestick, then drops to the blue and white plate on a stand and across to the small fern in a blue pot, following the blue that repeats down all three shelves. The one thing worth watching is the brass candlestick on the top shelf, the tallest piece there, which could start to pull attention from the teapot if it ever grew taller or sat further forward.",
"entry": {
"item": "blue and white teapot",
"why": "It is the widest piece on the top shelf and the one the owner most wants seen first, and nothing beside it is strong enough to take that place."
},
"eye_path": [
"blue and white teapot",
"white jug",
"brass candlestick",
"blue and white plate on a stand",
"..."
],
"rows": [
{
"row": 1,
"status": "settled",
"weight": "even",
"note": "The eye opens on the blue and white teapot, crosses the plain white jug, and the brass candlestick pulls it up and to the right before it moves on.",
"fix": ""
},
{
"row": 2,
"status": "settled",
"weight": "even",
"note": "The blue and white plate on its stand holds the eye first, then it dips to the small stacked teacups and lifts again with the small fern in a blue pot.",
"fix": ""
},
"..."
],
"findings": [
{
"principle": "colour",
"items": [
"blue and white teapot",
"blue and white plate on a stand",
"small fern in a blue pot",
"sugar tin"
],
"problem": "Blue repeats down all three shelves, from the teapot through the plate on its stand to the fern in the blue pot and the sugar tin, so the eye keeps finding a reason to travel downward instead of jumping around.",
"fix": ""
},
{
"principle": "emphasis",
"items": [
"blue and white teapot",
"brass candlestick"
],
"problem": "The brass candlestick is the tallest piece on the top shelf and its colour sets it apart from everything else there, so it competes for a second look right after the teapot.",
"fix": "Leave the candlestick where it is for now, but if it is ever swapped for anything taller, give the teapot more room so it keeps the first look."
},
"..."
]
}
task: read - Thrifted brass and glass - tune
Input:
{
"task": "read",
"today": "2026-10-20",
"shelf": "{\"name\":\"Living room wall shelf\",\"shelves\":3,\"width_cm\":100,\"openings_cm\":[34,34,34]}",
"goal": "Thrift finds - warm and a bit eclectic, but not messy.",
"items": "[{\"name\":\"brass lamp\",\"row\":1,\"h_cm\":32,\"w_cm\":14,\"size\":\"measured\",\"colour\":\"brass\",\"colour_family\":\"metal\",\"material\":\"metal\",\"kind\":\"lamp\",\"hero\":false},{\"name\":\"amber glass vase\",\"row\":1,\"h_cm\":28,\"w_cm\":12,\"size\":\"measured\",\"colour\":\"amber\",\"colour_family\":\"orange\",\"material\":\"glass\",\"kind\":\"vase\",\"hero\":false},\"... 8 more\"]",
"row_scan": "[{\"row\":1,\"opening_cm\":34,\"pieces\":[\"brass lamp\",\"amber glass vase\",\"tall green bottle\",\"carved wooden horse\",\"framed botanical print\",\"brass bell\"],\"fill_pct\":87,\"tallest_pct\":94,\"rhythm\":\"varied\",\"balance\":\"even\"},{\"row\":2,\"opening_cm\":34,\"pieces\":[\"blue glass bottle\",\"purple glass bottle\",\"clear glass bottle\",\"pink glass bottle\"],\"fill_pct\":28,\"tallest_pct\":56,\"rhythm\":\"flat\",\"balance\":\"even\"},\"... 1 more\"]",
"page_reads": "[{\"level\":\"warn\",\"code\":\"crowded\",\"rows\":[1],\"text\":\"Shelf 1 is 87% full - nothing has room to breathe.\"},{\"level\":\"info\",\"code\":\"even_count\",\"rows\":[1],\"text\":\"Shelf 1 holds 6 - an even count tends to pair off; groups of three or five usually settle easier.\"},\"... 5 more\"]",
"page_verdict": "tune"
}
Reply:
{
"task": "read",
"title": "Crowded top, flat middle, empty bottom",
"verdict": "tune",
"summary": "This shelf needs tuning, not a full rework. The eye lands first on the brass lamp, the tallest and most eye-catching piece, rather than the framed botanical print the owner wanted seen first, then has to pick its way through five more pieces packed onto a crowded top shelf before it reaches the print; the middle shelf's four bottles sit in a flat, quiet row, and the bottom shelf is left empty. The one change that would help most is easing the crowding at the top - taking the brass bell off that shelf - so the print, not the lamp, leads, while bringing a piece down to even out the weight across the display.",
"entry": {
"item": "brass lamp",
"why": "It is the tallest piece on the shelf, reaching about 94% of the opening's height, and its brass finish stands out against the glass, wood and paper pieces beside it, so it is seen before the shorter framed botanical print."
},
"eye_path": [
"brass lamp",
"amber glass vase",
"tall green bottle",
"carved wooden horse",
"..."
],
"rows": [
{
"row": 1,
"status": "uneasy",
"weight": "even",
"note": "The top shelf is 87% full, so the eye starts on the brass lamp and then has to work past five more pieces packed close together before it reaches the botanical print.",
"fix": "Take the brass bell off this shelf and give the framed botanical print clear space on both sides, so its height and colour lead instead of the brass lamp's."
},
{
"row": 2,
"status": "uneasy",
"weight": "even",
"note": "The four bottles sit at nearly the same height in an even row with space between them, so the eye slides along the middle shelf without pausing.",
"fix": "Group the blue glass bottle and the purple glass bottle together and set the clear glass bottle and the pink glass bottle apart, so the row breaks into two shorter beats instead of one flat line."
},
"..."
],
"findings": [
{
"principle": "entry",
"items": [
"brass lamp",
"framed botanical print"
],
"problem": "The brass lamp, not the framed botanical print the owner marked to be seen first, is what the eye meets first - it is the tallest piece on the shelf and its brass finish stands out against the glass, wood and paper around it.",
"fix": "Give the botanical print clear space of its own, for instance by moving the brass bell elsewhere on the shelf, so its height and colour win the first look instead of the lamp's."
},
{
"principle": "spacing",
"items": [
"brass lamp",
"amber glass vase",
"tall green bottle",
"carved wooden horse",
"brass bell"
],
"problem": "The top shelf is 87% full with six pieces packed edge to edge, so nothing has room to be seen as its own moment.",
"fix": "Take one piece off this shelf - the brass bell is the easiest, since it already gets lost beside the taller brass lamp - and let the rest spread out."
},
"..."
]
}
task: read - Figure shelf - rework
Input:
{
"task": "read",
"today": "2026-10-20",
"shelf": "{\"name\":\"Collector cabinet\",\"shelves\":3,\"width_cm\":70,\"openings_cm\":[30,26,26]}",
"goal": "Show the collection without it looking like a toy shop.",
"items": "[{\"name\":\"tin robot\",\"row\":1,\"h_cm\":29,\"w_cm\":14,\"size\":\"measured\",\"colour\":\"silver\",\"colour_family\":\"metal\",\"material\":\"metal\",\"kind\":\"figure\",\"hero\":true},{\"name\":\"vinyl figure fox\",\"row\":1,\"h_cm\":18,\"w_cm\":10,\"size\":\"measured\",\"colour\":\"orange\",\"colour_family\":\"orange\",\"material\":\"plastic\",\"kind\":\"figure\",\"hero\":false},\"... 13 more\"]",
"row_scan": "[{\"row\":1,\"opening_cm\":30,\"pieces\":[\"tin robot\",\"vinyl figure fox\",\"vinyl figure astronaut\",\"boxed figure\",\"space shuttle model\"],\"fill_pct\":106,\"tallest_pct\":97,\"rhythm\":\"varied\",\"balance\":\"even\"},{\"row\":2,\"opening_cm\":26,\"pieces\":[\"resin dragon\",\"wooden peg dolls\",\"blind box mini cat\",\"blind box mini frog\",\"blind box mini owl\",\"enamel pin board\"],\"fill_pct\":97,\"tallest_pct\":77,\"rhythm\":\"varied\",\"balance\":\"even\"},\"... 1 more\"]",
"page_reads": "[{\"level\":\"fix\",\"code\":\"overfull\",\"rows\":[1],\"text\":\"Shelf 1 is 106% full - the pieces are wider than the shelf.\"},{\"level\":\"warn\",\"code\":\"crowded\",\"rows\":[2],\"text\":\"Shelf 2 is 97% full - nothing has room to breathe.\"},\"... 4 more\"]",
"page_verdict": "rework"
}
Reply:
{
"task": "read",
"title": "Cabinet reads strong but overcrowded",
"verdict": "rework",
"summary": "This reads as a rework: both the top and bottom shelves hold a piece that cannot stand as placed, so nothing here settles on its own. The eye opens on the tin robot, whose height and silver metal stand apart from the colour around it, then crosses the packed top shelf past the pale space shuttle, drops to the dark pin board anchoring the crowded middle shelf, and catches on the trading card binder, which stands taller than the bottom shelf allows. The one change that matters most is getting that binder off the bottom shelf, since no shelf here can be called settled while a piece stands somewhere it does not fit.",
"entry": {
"item": "tin robot",
"why": "It is the tallest piece on its shelf, filling nearly the whole opening, and its silver metal stands out sharply against the coloured plastic nearby - it is also the piece the owner wants seen first."
},
"eye_path": [
"tin robot",
"space shuttle model",
"enamel pin board",
"resin dragon",
"..."
],
"rows": [
{
"row": 1,
"status": "broken",
"weight": "even",
"note": "The eye opens on the tin robot, then has to pick through four more pieces that run past the shelf's own width before reaching the shuttle at the far end.",
"fix": "Move the vinyl figure fox or the vinyl figure astronaut off this shelf, or into the box, so the remaining pieces fit within the width."
},
{
"row": 2,
"status": "uneasy",
"weight": "even",
"note": "Six pieces run edge to edge with almost no gap, and the three matching minis in the middle read as one flat block rather than a group of three.",
"fix": "Give the three minis a little more room, or move one of them, such as the blind box mini frog, into the box so the row can breathe."
},
"..."
],
"findings": [
{
"principle": "spacing",
"items": [
"tin robot",
"vinyl figure fox",
"vinyl figure astronaut",
"boxed figure",
"space shuttle model"
],
"problem": "The top shelf's five pieces add up to more width than the shelf has, so the row cannot stand as drawn even though every piece is short enough for the opening.",
"fix": "Move the vinyl figure fox or the vinyl figure astronaut off the top shelf, or into the box, to bring the row back within the shelf's width."
},
{
"principle": "spacing",
"items": [
"trading card binder"
],
"problem": "The trading card binder is taller than the bottom shelf's opening, so it cannot physically stand there.",
"fix": "Move the trading card binder into the box until it has a shelf tall enough to hold it."
},
"..."
]
}
task: arrange - Teacup dresser - light touch
Input:
{
"task": "arrange",
"today": "2026-10-20",
"shelf": "{\"name\":\"Kitchen dresser\",\"shelves\":3,\"width_cm\":90,\"openings_cm\":[32,32,34]}",
"goal": "Keep it calm - Nan's blue and white china is the point.",
"items": "[{\"name\":\"blue and white teapot\",\"row\":1,\"h_cm\":18,\"w_cm\":24,\"size\":\"measured\",\"colour\":\"blue\",\"colour_family\":\"blue\",\"material\":\"ceramic\",\"kind\":\"vase\",\"hero\":true},{\"name\":\"white jug\",\"row\":1,\"h_cm\":22,\"w_cm\":14,\"size\":\"measured\",\"colour\":\"white\",\"colour_family\":\"neutral\",\"material\":\"ceramic\",\"kind\":\"vase\",\"hero\":false},\"... 7 more\"]",
"row_scan": "[{\"row\":1,\"opening_cm\":32,\"pieces\":[\"blue and white teapot\",\"white jug\",\"brass candlestick\"],\"fill_pct\":51,\"tallest_pct\":81,\"rhythm\":\"varied\",\"balance\":\"even\"},{\"row\":2,\"opening_cm\":32,\"pieces\":[\"blue and white plate on a stand\",\"stacked teacups\",\"small fern in a blue pot\"],\"fill_pct\":58,\"tallest_pct\":81,\"rhythm\":\"varied\",\"balance\":\"even\"},\"... 1 more\"]",
"page_reads": "[]",
"page_verdict": "balanced",
"read_findings": "{\"verdict\":\"balanced\",\"entry\":\"blue and white teapot\",\"rows\":[{\"row\":1,\"status\":\"settled\",\"fix\":\"\"},\"...\"],\"findings\":[{\"principle\":\"colour\",\"items\":[\"blue and white teapot\",\"blue and white plate on a stand\",\"small fern in a blue pot\",\"sugar tin\"],\"fix\":\"\"},\"...\"]}"
}
Reply:
{
"task": "arrange",
"title": "Letting the blue and white lead",
"verdict": "light-touch",
"summary": "This is a light touch: every piece stays on the shelf it is already on, simply grouped with a bit more air so the teapot keeps the first look. The blue and white pieces carry the eye down through the dresser, while the brass candlestick and the brass scale weights echo each other top and bottom. Nothing needed to move because the shelves already read calmly; this plan just gives the hero piece room and keeps the quieter pieces from crowding it.",
"rows": [
{
"row": 1,
"groups": [
[
"blue and white teapot"
],
[
"white jug"
],
[
"brass candlestick"
]
],
"note": "The teapot stands alone on the left with room to breathe, so the blue and white is the first thing seen before the eye steps up through the white jug to the brass candlestick."
},
{
"row": 2,
"groups": [
[
"blue and white plate on a stand"
],
[
"stacked teacups",
"small fern in a blue pot"
]
],
"note": "The plate on a stand gets its own space to echo the teapot above it, while the stacked teacups and the small fern in a blue pot settle together on the right for a quieter pairing."
},
"..."
],
"retire": [],
"spacing": [
"Give the blue and white teapot clear air on both sides so it keeps the first look.",
"Leave a visible gap between the white jug and the brass candlestick so they do not blur together.",
"..."
]
}
task: arrange - Thrifted brass and glass - reshuffle
Input:
{
"task": "arrange",
"today": "2026-10-20",
"shelf": "{\"name\":\"Living room wall shelf\",\"shelves\":3,\"width_cm\":100,\"openings_cm\":[34,34,34]}",
"goal": "Thrift finds - warm and a bit eclectic, but not messy.",
"items": "[{\"name\":\"brass lamp\",\"row\":1,\"h_cm\":32,\"w_cm\":14,\"size\":\"measured\",\"colour\":\"brass\",\"colour_family\":\"metal\",\"material\":\"metal\",\"kind\":\"lamp\",\"hero\":false},{\"name\":\"amber glass vase\",\"row\":1,\"h_cm\":28,\"w_cm\":12,\"size\":\"measured\",\"colour\":\"amber\",\"colour_family\":\"orange\",\"material\":\"glass\",\"kind\":\"vase\",\"hero\":false},\"... 8 more\"]",
"row_scan": "[{\"row\":1,\"opening_cm\":34,\"pieces\":[\"brass lamp\",\"amber glass vase\",\"tall green bottle\",\"carved wooden horse\",\"framed botanical print\",\"brass bell\"],\"fill_pct\":87,\"tallest_pct\":94,\"rhythm\":\"varied\",\"balance\":\"even\"},{\"row\":2,\"opening_cm\":34,\"pieces\":[\"blue glass bottle\",\"purple glass bottle\",\"clear glass bottle\",\"pink glass bottle\"],\"fill_pct\":28,\"tallest_pct\":56,\"rhythm\":\"flat\",\"balance\":\"even\"},\"... 1 more\"]",
"page_reads": "[{\"level\":\"warn\",\"code\":\"crowded\",\"rows\":[1],\"text\":\"Shelf 1 is 87% full - nothing has room to breathe.\"},{\"level\":\"info\",\"code\":\"even_count\",\"rows\":[1],\"text\":\"Shelf 1 holds 6 - an even count tends to pair off; groups of three or five usually settle easier.\"},\"... 5 more\"]",
"page_verdict": "tune",
"read_findings": "{\"verdict\":\"tune\",\"entry\":\"brass lamp\",\"rows\":[{\"row\":1,\"status\":\"uneasy\",\"fix\":\"Take the brass bell off this shelf and give the framed botanical print clear space on both sides, so its height and colour lead instead of the brass lamp's.\"},\"...\"],\"findings\":[{\"principle\":\"entry\",\"items\":[\"brass lamp\",\"framed botanical print\"],\"fix\":\"Give the botanical print clear space of its own, for instance by moving the brass bell elsewhere on the shelf, so its height and colour win the first look instead of the lamp's.\"},\"...\"]}"
}
Reply:
{
"task": "arrange",
"title": "Room to breathe up top, pieces below",
"verdict": "reshuffle",
"summary": "This is a reshuffle: three pieces move to a different shelf so every part of the display gets room to breathe. The framed botanical print now stands alone on the top shelf so its height and colour lead the eye instead of the brass lamp, the tall green bottle moves down to break the four short bottles into two shorter beats, and the carved wooden horse and the brass bell move down again to fill out the shelf that was empty.",
"rows": [
{
"row": 1,
"groups": [
[
"framed botanical print"
],
[
"brass lamp",
"amber glass vase"
]
],
"note": "The framed botanical print stands alone with clear space on each side so its height and colour lead the eye first, while the brass lamp and the amber glass vase are paired a comfortable distance away."
},
{
"row": 2,
"groups": [
[
"blue glass bottle",
"purple glass bottle"
],
[
"tall green bottle"
],
[
"clear glass bottle",
"pink glass bottle"
]
],
"note": "The tall green bottle breaks the four short bottles into two pairs, trading one flat line for a rise and fall, with the blue glass bottle and the purple glass bottle together and the clear glass bottle and the pink glass bottle echoing that pairing further along."
},
"..."
],
"retire": [],
"spacing": [
"Leave clear air on both sides of the framed botanical print on the top shelf so it reads before the brass lamp and the amber glass vase.",
"Stand the carved wooden horse toward the centre of the bottom shelf so it reads as an anchor rather than an afterthought.",
"..."
]
}
task: arrange - Figure shelf - edit down
Input:
{
"task": "arrange",
"today": "2026-10-20",
"shelf": "{\"name\":\"Collector cabinet\",\"shelves\":3,\"width_cm\":70,\"openings_cm\":[30,26,26]}",
"goal": "Show the collection without it looking like a toy shop.",
"items": "[{\"name\":\"tin robot\",\"row\":1,\"h_cm\":29,\"w_cm\":14,\"size\":\"measured\",\"colour\":\"silver\",\"colour_family\":\"metal\",\"material\":\"metal\",\"kind\":\"figure\",\"hero\":true},{\"name\":\"vinyl figure fox\",\"row\":1,\"h_cm\":18,\"w_cm\":10,\"size\":\"measured\",\"colour\":\"orange\",\"colour_family\":\"orange\",\"material\":\"plastic\",\"kind\":\"figure\",\"hero\":false},\"... 13 more\"]",
"row_scan": "[{\"row\":1,\"opening_cm\":30,\"pieces\":[\"tin robot\",\"vinyl figure fox\",\"vinyl figure astronaut\",\"boxed figure\",\"space shuttle model\"],\"fill_pct\":106,\"tallest_pct\":97,\"rhythm\":\"varied\",\"balance\":\"even\"},{\"row\":2,\"opening_cm\":26,\"pieces\":[\"resin dragon\",\"wooden peg dolls\",\"blind box mini cat\",\"blind box mini frog\",\"blind box mini owl\",\"enamel pin board\"],\"fill_pct\":97,\"tallest_pct\":77,\"rhythm\":\"varied\",\"balance\":\"even\"},\"... 1 more\"]",
"page_reads": "[{\"level\":\"fix\",\"code\":\"overfull\",\"rows\":[1],\"text\":\"Shelf 1 is 106% full - the pieces are wider than the shelf.\"},{\"level\":\"warn\",\"code\":\"crowded\",\"rows\":[2],\"text\":\"Shelf 2 is 97% full - nothing has room to breathe.\"},\"... 4 more\"]",
"page_verdict": "rework",
"read_findings": "{\"verdict\":\"rework\",\"entry\":\"tin robot\",\"rows\":[{\"row\":1,\"status\":\"broken\",\"fix\":\"Move the vinyl figure fox or the vinyl figure astronaut off this shelf, or into the box, so the remaining pieces fit within the width.\"},\"...\"],\"findings\":[{\"principle\":\"spacing\",\"items\":[\"tin robot\",\"vinyl figure fox\",\"vinyl figure astronaut\",\"boxed figure\",\"space shuttle model\"],\"fix\":\"Move the vinyl figure fox or the vinyl figure astronaut off the top shelf, or into the box, to bring the row back within the shelf's width.\"},\"...\"]}"
}
Reply:
{
"task": "arrange",
"title": "A calmer cabinet, one star per shelf",
"verdict": "edit-down",
"summary": "This is an edit-down: the trading card binder is too tall for any shelf here, and the enamel pin board leaves the middle shelf too crowded, so both go back in the box along with the spare display risers, which are not needed. The tin robot now stands alone on the top shelf with the boxed figure and the space shuttle model stepping down in height beside it, the resin dragon anchors the open end of the middle shelf while the three minis keep their own huddle alongside the wooden peg dolls and the vinyl figure astronaut, and the brick-built car and brick-built house pair up on the bottom shelf next to the vinyl figure fox. The boxed figure's red now echoes the brick-built car's red, and the blind box mini owl's yellow echoes the brick-built house's yellow, giving the eye places to land.",
"rows": [
{
"row": 1,
"groups": [
[
"tin robot"
],
[
"boxed figure"
],
[
"space shuttle model"
]
],
"note": "The tin robot stands alone so it is the first thing seen, with the boxed figure and the space shuttle model stepping down in height beside it, each with room of its own."
},
{
"row": 2,
"groups": [
[
"blind box mini cat",
"blind box mini frog",
"blind box mini owl"
],
[
"wooden peg dolls",
"vinyl figure astronaut"
],
[
"resin dragon"
]
],
"note": "The three minis hold together as one set, the wooden peg dolls sit with the vinyl figure astronaut in the middle, and the resin dragon stands alone at the open end to anchor the row."
},
"..."
],
"retire": [
{
"item": "trading card binder",
"why": "It is 30 cm tall, and no shelf here has that much clear height to spare, so it stays in the box."
},
{
"item": "enamel pin board",
"why": "The middle shelf only has room to breathe for the resin dragon, the wooden peg dolls and the three minis once this comes off, so it stays in the box for now."
},
"..."
],
"spacing": [
"Leave a clear gap on both sides of the tin robot on the top shelf so nothing crowds it.",
"Keep the blind box mini cat, blind box mini frog and blind box mini owl touching as one huddle, with a wider gap before the resin dragon.",
"..."
]
}
Build the input yourself
Load the script in node (global.window = {}; require("./shelfkit.js")), then:
const K = ShelfKit;
const st = K.state({
today: "2026-10-20", name: "Hall shelf", rows: "2", width: "60", openings: "28, 32",
goal: "Calm, with the green vase as the star.",
items: "1 | *green vase | 24 x 12 | green | glass\n1 | brass candlestick | 22 x 7 | brass | metal\n2 | stack of books | 9 x 22 | cream | paper"
});
console.log(st.rows, st.reads, st.verdict);
const readWire = K.toWire(K.buildInput(st, "read", { today: "2026-10-20" }).input); // POST to /estimate and /run
// later: const read = K.normalize(K.parseReply(job.output.output), "read"); K.checkRead(read, st);
const planWire = K.toWire(K.buildInput(st, "arrange", { today: "2026-10-20", read }).input); // carries read_findings
// later: const plan = K.normalize(K.parseReply(job2.output.output), "arrange"); K.checkArrange(plan, st);
Step 1 — a tiny client helper
One function that sends JSON and unwraps the envelope. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here" # from https://rule-of-odds.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"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://rule-of-odds.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// rule-of-odds.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://rule-of-odds.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "paste-your-token-here" // from https://rule-of-odds.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// RuleOfOdds.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class RuleOfOdds {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://rule-of-odds.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://rule-of-odds.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "paste-your-token-here"; // from https://rule-of-odds.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "paste-your-token-here"; // from https://rule-of-odds.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
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":"rule-of-odds"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "rule-of-odds"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "rule-of-odds" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "rule-of-odds"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "rule-of-odds"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "rule-of-odds" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "rule-of-odds"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "rule-of-odds" }))!["token"]!.GetValue<string>();
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}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["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": "read", "today": "2026-10-20", "shelf": "{\"name\":\"Living room wall shelf\",\"shelves\":3,...}", "goal": "Thrift finds - warm and a bit eclectic, but not messy.", "items": "[...]", "row_scan": "[...]", "page_reads": "[...]", "page_verdict": "tune"}
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "read", "today": "2026-10-20", "shelf": "{\"name\":\"Living room wall shelf\",\"shelves\":3,...}", "goal": "Thrift finds - warm and a bit eclectic, but not messy.", "items": "[...]", "row_scan": "[...]", "page_reads": "[...]", "page_verdict": "tune"}
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "read" | "arrange", today, shelf, goal, items, row_scan, page_reads, page_verdict, read_findings?}
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
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 rule-of-odds:<lane>:<inputHash>:a<attempt> (lane = read or arrange), and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.
KEY="rule-of-odds:read:$(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}'
import hashlib, time
key = "rule-of-odds:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the lane's JSON object
print(j["charged_credits"], j.get("truncated"), reply.get("verdict"))
import { createHash } from "node:crypto";
const key = `rule-of-odds:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.verdict);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "rule-of-odds:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
fmt.Println(j["charged_credits"], j["truncated"], j["output"].(map[string]any)["output"])
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "rule-of-odds:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply);
require "digest"
key = "rule-of-odds:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["title"]].inspect
$key = "rule-of-odds:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", $reply["title"], "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"rule-of-odds:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>());
Console.WriteLine($"{j["charged_credits"]} {reply}");
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: rule-of-odds:read:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "read", "today": "2026-10-20", "shelf": "{\"name\":\"Living room wall shelf\",\"shelves\":3,...}", "goal": "Thrift finds - warm and a bit eclectic, but not messy.", "items": "[...]", "row_scan": "[...]", "page_reads": "[...]", "page_verdict": "tune"}
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "rule-of-odds:read:" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `rule-of-odds:read:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "rule-of-odds:read:"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "rule-of-odds:read:" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "rule-of-odds:read:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: rule-of-odds:read:" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"rule-of-odds:read:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);