Drive Slice for Slice from your own code
The one metered task, compare, is available over HTTP. It takes 2 to 5 festive cakes or sweet breads from the person's own shortlist - the page's shelf lines (price, weight, slices, price per slice and per 100 g, whether each fits the budget per cake and the slices needed, the fitting order, the verdict, the pick, the runner-up and the closest), the person's note on each cake, up to 3 wishes and who or what it is for - and returns the verdict, the pick, a headline, 1 comparison row per cake (taste, a mark for each wish, best for, watch out), a note on the pick and a closing line.
The division of labour first: the page does the arithmetic and a fixed rule decides the verdict and the pick; the model fills in the comparison from the person's notes and explains the pick. After a run the page checks that the verdict and the pick follow the rule, that every cake has a row in shelf order with 1 mark per wish, that every number in the prose is on the shelf or in the notes, that word limits hold, and that no ids leak into the prose.
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": "..." } }
The token is minted for this app (the guest call takes {"slug":"slice-for-slice"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required field (task, occasion, wishes, shelf, notes) 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: a comparison is metered and needs a personal sign-in. |
| 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, the input and the verdicts
Every run sends task: compare (the one task is compare); a missing or different one is still answered as compare. Slices come from the box, or 1 per 80 g when left blank; prices per slice and per 100 g are rounded to the cent. A cake fits when it is within the budget per cake and has at least the slices needed. The verdict rule, checked in order: no cake fits, nothing-fits (the closest has the fewest misses, then the lowest price per slice); otherwise 2 or more fit and the 2nd cheapest per slice costs at most 10% more per slice than the cheapest, close-call; otherwise clear-pick. Fitting cakes rank by price per slice, then the heavier, then the one higher on the list.
| Field | Required | What it holds |
|---|---|---|
task | yes | Always compare. |
occasion | yes | Who or what the cake is for, up to 100 characters, or none. |
wishes | yes | Up to 3 lines wish 1 | text, or none. |
shelf | yes | The page's lines: currency: $, budget per cake: 30.00, slices needed: 16, 1 line per cake such as option 1 | name | price 24.00 | 1500 g | 18 slices (given) | 1.33 per slice | 1.60 per 100 g | fits, then fitting order (...), verdict:, pick:, runner-up: and closest:. |
notes | yes | 1 line per cake, option 1 | text or option 1 | none. |
retry_note | no | Only on the page's single reformat retry. |
The reply is one JSON object:
{"task": "compare", "verdict": "clear-pick", "pick": "...", "headline": "...", "rows": [{"option": "...", "taste": "...", "wishes": ["meets"], "best_for": "...", "watch_out": "..."}, ...], "pick_note": "...", "closing": "..."}
| Field | What it holds |
|---|---|
verdict | nothing-fits, close-call or clear-pick, copied from the shelf. |
pick | The pick's name from the shelf, or empty when nothing fits. |
headline | The verdict in plain words, at most 16 words. |
rows | 1 per cake in shelf order: option (the name), taste (from the note, at most 14 words), wishes (1 of meets, misses, unknown per wish), best_for (at most 10 words) and watch_out (at most 14 words). |
pick_note | Why the pick comes first, or what the closest cake misses, at most 45 words. |
closing | One light next step, at most 20 words. |
The page then checks the reply (SliceKit.check) and lists any disagreement under "The page's check".
Worked examples - every verdict
These are the four free samples on the page: the exact input the page sends and the reply that replays for free.
Christmas Eve, 4 cakes - clear-pick
Input:
{
"task": "compare",
"occasion": "Christmas Eve at my in-laws",
"wishes": "wish 1 | no marzipan\nwish 2 | something chocolatey",
"shelf": "currency: £\nbudget per cake: 25.00\nslices needed: 12\noption 1 | Classic panettone | price 12.00 | 1000 g | 12 slices (given) | 1.00 per slice | 1.20 per 100 g | fits\noption 2 | Pandoro star | price 9.00 | 750 g | 8 slices (given) | 1.13 per slice | 1.20 per 100 g | does not fit: too few slices\noption 3 | Stollen loaf | price 10.00 | 750 g | 9 slices (estimated at 1 per 80 g) | 1.11 per slice | 1.33 per 100 g | does not fit: too few slices\noption 4 | Chocolate panettone | price 16.00 | 1000 g | 12 slices (given) | 1.33 per slice | 1.60 per 100 g | fits\nfitting order (cheapest per slice first): option 1, option 4\nverdict: clear-pick\npick: option 1 | Classic panettone\nrunner-up: none\nclosest: none",
"notes": "option 1 | candied orange and raisins, tall dome\noption 2 | plain buttery sponge, comes with icing sugar\noption 3 | marzipan centre, dusted with icing sugar\noption 4 | chocolate chips, no candied peel"
}
Reply:
{
"task": "compare",
"verdict": "clear-pick",
"pick": "Classic panettone",
"headline": "Classic panettone is the cheapest per slice of the cakes that fit.",
"rows": [
{
"option": "Classic panettone",
"taste": "Candied orange and raisins, with a tall dome.",
"wishes": [
"unknown",
"unknown"
],
"best_for": "Cheapest per slice of the cakes that fit, £1.00.",
"watch_out": "Its note doesn't mention marzipan or anything chocolatey."
},
{
"option": "Pandoro star",
"taste": "Plain buttery sponge that comes with icing sugar.",
"wishes": [
"unknown",
"misses"
],
"best_for": "Lowest price on the shelf, £9.00.",
"watch_out": "Just 8 slices, short of the 12 needed."
},
{
"option": "Stollen loaf",
"taste": "Marzipan centre, dusted with icing sugar.",
"wishes": [
"misses",
"unknown"
],
"best_for": "Comes dusted with icing sugar.",
"watch_out": "Only 9 slices, below the 12 needed."
},
{
"option": "Chocolate panettone",
"taste": "Chocolate chips, with no candied peel.",
"wishes": [
"unknown",
"meets"
],
"best_for": "Ties for the most slices on the shelf, 12.",
"watch_out": "Its note says nothing about marzipan."
}
],
"pick_note": "Classic panettone comes first for you because it costs £1.00 per slice, against £1.33 for Chocolate panettone, the next cake that fits.",
"closing": "Check the label for marzipan before you decide."
}
Office treat, nearly level - close-call
Input:
{
"task": "compare",
"occasion": "Friday treat for the office team",
"wishes": "wish 1 | no raisins",
"shelf": "currency: $\nbudget per cake: 30.00\nslices needed: 16\noption 1 | Big classic panettone | price 24.00 | 1500 g | 18 slices (given) | 1.33 per slice | 1.60 per 100 g | fits\noption 2 | Twin 750 g pack | price 22.00 | 1500 g | 16 slices (given) | 1.38 per slice | 1.47 per 100 g | fits\noption 3 | Fruitcake slab | price 19.00 | 1200 g | 15 slices (estimated at 1 per 80 g) | 1.27 per slice | 1.58 per 100 g | does not fit: too few slices\nfitting order (cheapest per slice first): option 1, option 2\nverdict: close-call\npick: option 1 | Big classic panettone\nrunner-up: option 2 | Twin 750 g pack\nclosest: none",
"notes": "option 1 | raisins and candied peel\noption 2 | plain, no fruit, individually wrapped\noption 3 | dark fruitcake with brandy"
}
Reply:
{
"task": "compare",
"verdict": "close-call",
"pick": "Big classic panettone",
"headline": "Big classic panettone and Twin 750 g pack are nearly level on price per slice.",
"rows": [
{
"option": "Big classic panettone",
"taste": "Raisins and candied peel.",
"wishes": [
"misses"
],
"best_for": "Lowest price per slice among the fitting cakes, $1.33.",
"watch_out": "It misses the wish to avoid raisins, since it has raisins and candied peel."
},
{
"option": "Twin 750 g pack",
"taste": "Plain, no fruit, individually wrapped.",
"wishes": [
"meets"
],
"best_for": "Meets the wish against raisins, since it has no fruit.",
"watch_out": "Costs $1.38 per slice, more than Big classic panettone's $1.33."
},
{
"option": "Fruitcake slab",
"taste": "Dark fruitcake with brandy.",
"wishes": [
"unknown"
],
"best_for": "Lowest price on the shelf, at $19.00.",
"watch_out": "Only has 15 slices, short of the 16 needed."
}
],
"pick_note": "Big classic panettone costs $1.33 per slice, Twin 750 g pack $1.38 - close enough for taste to decide, since their notes differ: raisins and candied peel against no fruit. Big classic panettone misses your wish for no raisins; Twin 750 g pack marks no misses.",
"closing": "Check the label for raisins before choosing between Big classic panettone and Twin 750 g pack."
}
Dinner for 10 - nothing-fits
Input:
{
"task": "compare",
"occasion": "dessert after a dinner for 10",
"wishes": "wish 1 | not too sweet\nwish 2 | under 15",
"shelf": "currency: €\nbudget per cake: 15.00\nslices needed: 10\noption 1 | Bakery panettone | price 28.00 | 1000 g | 12 slices (given) | 2.33 per slice | 2.80 per 100 g | does not fit: over budget\noption 2 | Mini pandoro | price 6.00 | 400 g | 5 slices (estimated at 1 per 80 g) | 1.20 per slice | 1.50 per 100 g | does not fit: too few slices\noption 3 | Gingerbread loaf | price 8.00 | 600 g | 8 slices (given) | 1.00 per slice | 1.33 per 100 g | does not fit: too few slices\nfitting order (cheapest per slice first): none\nverdict: nothing-fits\npick: none\nrunner-up: none\nclosest: option 3 | Gingerbread loaf",
"notes": "option 1 | slow-proofed, lemon zest, sold at the market stall\noption 2 | none\noption 3 | spiced, sticky top"
}
Reply:
{
"task": "compare",
"verdict": "nothing-fits",
"pick": "",
"headline": "Gingerbread loaf comes closest but has only 8 slices, short of the 10 needed.",
"rows": [
{
"option": "Bakery panettone",
"taste": "Slow-proofed with lemon zest, sold at the market stall.",
"wishes": [
"unknown",
"misses"
],
"best_for": "Has the most slices on the shelf, 12.",
"watch_out": "Costs €28.00, well over the €15.00 budget."
},
{
"option": "Mini pandoro",
"taste": "No notes given.",
"wishes": [
"unknown",
"meets"
],
"best_for": "Priced at €6.00, the lowest on the shelf.",
"watch_out": "Only 5 slices, short of the 10 needed."
},
{
"option": "Gingerbread loaf",
"taste": "Spiced, with a sticky top.",
"wishes": [
"unknown",
"meets"
],
"best_for": "Priced at €8.00, well under 15.",
"watch_out": "Only 8 slices, short of the 10 needed."
}
],
"pick_note": "Gingerbread loaf comes closest, with 8 slices against the 10 you need.",
"closing": "You might look for a cake with closer to 10 slices."
}
Just me, no budget - clear-pick
Input:
{
"task": "compare",
"occasion": "just me and a pot of tea",
"wishes": "none",
"shelf": "currency: $\nbudget per cake: none\nslices needed: none\noption 1 | Panforte wedge | price 14.00 | 400 g | 5 slices (estimated at 1 per 80 g) | 2.80 per slice | 3.50 per 100 g | fits\noption 2 | Lebkuchen tin | price 11.00 | 500 g | 20 slices (given) | 0.55 per slice | 2.20 per 100 g | fits\nfitting order (cheapest per slice first): option 2, option 1\nverdict: clear-pick\npick: option 2 | Lebkuchen tin\nrunner-up: none\nclosest: none",
"notes": "option 1 | dense, honey and almonds\noption 2 | soft spiced biscuits, some chocolate-coated"
}
Reply:
{
"task": "compare",
"verdict": "clear-pick",
"pick": "Lebkuchen tin",
"headline": "Lebkuchen tin costs the least per slice among the cakes that fit.",
"rows": [
{
"option": "Panforte wedge",
"taste": "Dense, with honey and almonds.",
"wishes": [],
"best_for": "Dense cake with honey and almonds.",
"watch_out": "Costs $2.80 per slice, more than Lebkuchen tin's $0.55."
},
{
"option": "Lebkuchen tin",
"taste": "Soft spiced biscuits, some chocolate-coated.",
"wishes": [],
"best_for": "Lowest price per slice of the fitting cakes, $0.55.",
"watch_out": "No mark against this cake on the shelf."
}
],
"pick_note": "Lebkuchen tin costs $0.55 per slice, far less than Panforte wedge's $2.80, so it gives you the best value per slice for your tea.",
"closing": "Peek at the label for almonds before you cut in."
}
Build the input yourself
Load the engine in node (global.window = global; require("./slicekit.js")), then:
const K = SliceKit;
const built = K.buildInput(K.form({ occasion: "a winter picnic", currency: "$", budget: 20, need: 8, wishes: ["no peel"],
options: [{ name: "Tall panettone", price: 18, grams: 1000, slices: 12, notes: "raisins, no peel" },
{ name: "Spiced loaf", price: 9, grams: 700, slices: 0, notes: "" }] }));
built.input.shelf; // the page's lines: prices, fits, verdict and pick
const wire = K.toWire(built.input); // POST this to /estimate and /run
// then: const reply = K.normalize(K.parseReply(job.output.output));
// built.facts.verdict // the verdict the page's rule gives
// K.check(reply, built).notes // the page's re-check of the reply
// K.md(built, reply, "my shortlist") // the comparison as Markdown
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://slice-for-slice.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://slice-for-slice.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"]
// slice-for-slice.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://slice-for-slice.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://slice-for-slice.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
}
// WhatWasThat.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 WhatWasThat {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://slice-for-slice.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://slice-for-slice.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://slice-for-slice.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://slice-for-slice.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":"slice-for-slice"}' | 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": "slice-for-slice"})["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: "slice-for-slice" })).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": "slice-for-slice"}, 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", "slice-for-slice"), 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: "slice-for-slice" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "slice-for-slice"])["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 = "slice-for-slice" }))!["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": "compare", "occasion": "just me and a pot of tea", "wishes": "none", "shelf": "currency: $\nbudget per cake: none\n...\nverdict: clear-pick\npick: option 2 | Lebkuchen tin\n...", "notes": "option 1 | dense, honey and almonds\noption 2 | soft spiced biscuits, some chocolate-coated"}
# - see the field table
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": "compare", "occasion": "just me and a pot of tea", "wishes": "none", "shelf": "currency: $\nbudget per cake: none\n...\nverdict: clear-pick\npick: option 2 | Lebkuchen tin\n...", "notes": "option 1 | dense, honey and almonds\noption 2 | soft spiced biscuits, some chocolate-coated"}
# - see the field table
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: "compare", occasion, wishes, shelf, notes} (see the field table)
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 slice-for-slice:<task>:<inputHash>:a<attempt> (the one task is compare), 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="slice-for-slice:compare:$(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 = "slice-for-slice:" + 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 reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply.get("verdict"))
import { createHash } from "node:crypto";
const key = `slice-for-slice:${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 := "slice-for-slice:" + 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 = "slice-for-slice:" + 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 = "slice-for-slice:#{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["verdict"]].inspect
$key = "slice-for-slice:" . $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["verdict"], "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"slice-for-slice:{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: slice-for-slice:compare:$(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": "compare", "occasion": "just me and a pot of tea", "wishes": "none", "shelf": "currency: $\nbudget per cake: none\n...\nverdict: clear-pick\npick: option 2 | Lebkuchen tin\n...", "notes": "option 1 | dense, honey and almonds\noption 2 | soft spiced biscuits, some chocolate-coated"}
# - see the field table
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "slice-for-slice:compare:" + 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": `slice-for-slice:compare:${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", "slice-for-slice:compare:"+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", "slice-for-slice:compare:" + 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" => "slice-for-slice:compare:#{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: slice-for-slice:compare:" . 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", $"slice-for-slice:compare:{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);