Drive Treat It Like from your own code
The one metered task, collide, is available over HTTP. It takes an everyday thing, its snags and a far-off system with its parts, together with the page's pairing, and returns one collision per snag (the idea that carries over, a first small try and where the comparison breaks, with a verdict), the overall contact, the best bump and what the collision taught.
The division of labour first: the page pairs; the model writes. The page gives every snag and part a job (sort, find, wait, remember, share, fit, reset, check), aims each snag at a part and decides whether their jobs match. It sends all of that as facts. The model judges one thing only, whether what is on hand meets a part's need, and applies fixed rules for the verdicts, the contact and the best bump; the page re-checks them.
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":"treat-it-like"}), 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, thing, system, snags, parts, on_hand, facts) 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: the reasons are metered and need 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: collide (the one task is collide); a missing or different one is still answered as collide. All fields are strings.
| Field | Required | What it holds |
|---|---|---|
task | yes | Always collide. |
thing | yes | The everyday thing, in your words. |
system | yes | The far-off system to treat it like. |
snags | yes | One line per snag, s1: text to s5: text. |
parts | yes | One line per part, p1: name to p6: name. |
on_hand | yes | What is already nearby, or nothing listed. |
facts | yes | The page's lines: every part with its job and need, every snag with its job, the part it collides with and whether the jobs match (same job: yes or no), then the deck systems with a part for every job the snags need. |
retry_note | no | Only on a reformat retry: why the previous reply was unusable. |
Verdicts, first match wins: jobs differ, fizzles; the part needs something not on hand, sparks; otherwise sticks. Contact: every collision fizzles, miss; none fizzles and at least one sticks, solid; otherwise glancing. The best bump is the first collision that sticks, else the first that sparks, else none.
The reply
One JSON object, no prose around it:
{"task": "collide", "headline": "...", "collisions": [{"snag": "s1", "part": "p3", "same_job": true, "needs_met": true, "verdict": "sticks", "idea": "...", "first_try": "...", "breaks": "..."}, ...], "contact": "solid", "best": "s1", "insight": "..."}
| Field | What it holds |
|---|---|
headline | At most 20 words: what treating the thing like the system came to. |
collisions[] | One per snag in order, with snag and part ids from facts, same_job (copied), needs_met (judged), verdict, idea (what carries over, or why the jobs differ), first_try (empty when it fizzles) and breaks (where the comparison stops working). |
contact | solid, glancing or miss. |
best | The snag id of the best bump, or an empty string. |
insight | What the whole collision taught; on a miss, the deck system to try next. |
The page then checks the reply (BumpKit.check) - one collision for every snag and none for snags not on the list, every pairing and job match its own, every verdict, the contact and the best bump against the rules, a part that needs nothing counted as met and a need with nothing on hand counted as missing, no numbers, times or days you did not give, no contact details, no internal ids and nothing described as already done - and lists anything it finds under "The page's check".
Worked examples - every contact and every verdict
These are the 4 free samples on the page: the exact input the page sends and the reply that replays for free. Together they cover every contact and every verdict.
The shared fridge, like a library - solid hit (sticks, sticks, sparks)
Input:
{
"task": "collide",
"thing": "the shared fridge",
"system": "a library",
"snags": "s1: Leftovers get pushed to the back and forgotten until they go off\ns2: Nobody knows whose yoghurt is whose\ns3: Jars and tubs never go back to the same shelf",
"parts": "p1: Shelf marks\np2: The returns trolley\np3: Due dates\np4: Library cards\np5: Weeding the shelves",
"on_hand": "nothing listed",
"facts": "thing: the shared fridge\nsystem: a library (from the page's deck)\non hand: nothing listed\nparts:\np1: Shelf marks - job: find - needs: stickers or tape to mark the shelves\np2: The returns trolley - job: reset - needs: a box or basket\np3: Due dates - job: remember - needs: nothing\np4: Library cards - job: share - needs: nothing\np5: Weeding the shelves - job: fit - needs: nothing\nsnags, in order:\ns1: Leftovers get pushed to the back and forgotten until they go off - job: remember (picked by the person) - collides with p3 Due dates (job: remember) - same job: yes - the part needs: nothing\ns2: Nobody knows whose yoghurt is whose - job: share (picked by the person) - collides with p4 Library cards (job: share) - same job: yes - the part needs: nothing\ns3: Jars and tubs never go back to the same shelf - job: reset (picked by the person) - collides with p2 The returns trolley (job: reset) - same job: yes - the part needs: a box or basket\ndeck systems whose parts do every job these snags need: a lighthouse; a fire station; a hotel front desk"
}
Reply:
{
"task": "collide",
"headline": "Treating the shared fridge like a library carries over well, giving each snag a matching part to learn from.",
"collisions": [
{
"snag": "s1",
"part": "p3",
"same_job": true,
"needs_met": true,
"verdict": "sticks",
"idea": "Due dates give library items a point when they must be noticed again. The same move could give leftovers a point to be noticed again, before they are pushed to the back and forgotten.",
"first_try": "Try checking the back of the fridge, so a leftover is noticed again before it is forgotten.",
"breaks": "Unlike due dates, nothing in the fridge flags a leftover to be noticed again once it is pushed to the back."
},
{
"snag": "s2",
"part": "p4",
"same_job": true,
"needs_met": true,
"verdict": "sticks",
"idea": "Library cards tie each item to whoever holds it, so nobody has to guess who it belongs to. The same move could tie each yoghurt to whoever it belongs to.",
"first_try": "Try marking each yoghurt so it is clear whose it is.",
"breaks": "Unlike library cards, a mark on the yoghurt does not by itself say who it belongs to."
},
{
"snag": "s3",
"part": "p2",
"same_job": true,
"needs_met": false,
"verdict": "sparks",
"idea": "The returns trolley gathers items left out of place, so they can be sorted onto their shelf. The same move could gather jars and tubs left on the wrong shelf, ready to be put right.",
"first_try": "Once you have a box or basket, use it to gather jars and tubs that land on the wrong shelf.",
"breaks": "Unlike a returns trolley, a box does not sort jars and tubs onto their own shelf by itself."
}
],
"contact": "solid",
"best": "s1",
"insight": "Due dates suggest the fridge could use a point to notice leftovers again, before they are pushed to the back and forgotten."
}
The laundry pile, like air-traffic control - glancing blow (fizzles, sparks, sticks)
Input:
{
"task": "collide",
"thing": "the laundry pile",
"system": "air-traffic control",
"snags": "s1: Clean and dirty clothes end up in the same heap on the bedroom chair\ns2: Wet washing sits in the machine because nobody remembers it is there\ns3: Everyone tries to use the machine at once",
"parts": "p1: Runway slots\np2: Call signs\np3: The departures board\np4: The holding pattern\np5: The handover checklist",
"on_hand": "two laundry baskets",
"facts": "thing: the laundry pile\nsystem: air-traffic control (from the page's deck)\non hand: two laundry baskets\nparts:\np1: Runway slots - job: wait - needs: nothing\np2: Call signs - job: share - needs: nothing\np3: The departures board - job: remember - needs: a board everyone can see\np4: The holding pattern - job: fit - needs: nothing\np5: The handover checklist - job: check - needs: nothing\nsnags, in order:\ns1: Clean and dirty clothes end up in the same heap on the bedroom chair - job: sort (picked by the person) - collides with p2 Call signs (job: share) - same job: no - the part needs: nothing\ns2: Wet washing sits in the machine because nobody remembers it is there - job: remember (picked by the person) - collides with p3 The departures board (job: remember) - same job: yes - the part needs: a board everyone can see\ns3: Everyone tries to use the machine at once - job: wait (picked by the person) - collides with p1 Runway slots (job: wait) - same job: yes - the part needs: nothing\ndeck systems whose parts do every job these snags need: none"
}
Reply:
{
"task": "collide",
"headline": "Treating the laundry pile like air-traffic control carries over in places but not everywhere.",
"collisions": [
{
"snag": "s1",
"part": "p2",
"same_job": false,
"needs_met": true,
"verdict": "fizzles",
"idea": "Call signs give each aircraft its own identity, which is a job about sharing. Keeping clean and dirty clothes from piling into one heap on the bedroom chair is about sorting, not sharing.",
"first_try": "",
"breaks": "Call signs identify who things belong to, but they do not separate one group of clothes from another the way sorting needs."
},
{
"snag": "s2",
"part": "p3",
"same_job": true,
"needs_met": false,
"verdict": "sparks",
"idea": "The departures board keeps flights visible so nobody forgets which ones are still due to leave. The same move could keep wet washing visible so nobody forgets it is still in the machine.",
"first_try": "Once you have a board everyone can see, note on it when wet washing goes into the machine so it is not forgotten.",
"breaks": "A departures board only works if it gets checked, while the machine's wash can stay unseen and forgotten even with a board nearby."
},
{
"snag": "s3",
"part": "p1",
"same_job": true,
"needs_met": true,
"verdict": "sticks",
"idea": "Runway slots give each aircraft its own turn to take off, one after another. The same move could let everyone take a turn with the machine instead of all trying at once.",
"first_try": "Try taking turns at the machine, using the two laundry baskets to show whose wash is queued and going in next.",
"breaks": "Runway slots work because air traffic control assigns each turn, whereas nothing here assigns the order in which everyone uses the machine."
}
],
"contact": "glancing",
"best": "s3",
"insight": "Runway slots suggest that giving the machine a clear turn order could ease the clash of everyone trying to use it at once."
}
The sock drawer, like a lighthouse - clean miss (fizzles, fizzles)
Input:
{
"task": "collide",
"thing": "the sock drawer",
"system": "a lighthouse",
"snags": "s1: Odd socks are all mixed up together\ns2: The drawer is too full to close",
"parts": "p1: Its own flash pattern\np2: The keeper's log\np3: The fog horn\np4: The relief keeper rota\np5: Polishing the lens",
"on_hand": "nothing listed",
"facts": "thing: the sock drawer\nsystem: a lighthouse (from the page's deck)\non hand: nothing listed\nparts:\np1: Its own flash pattern - job: find - needs: nothing\np2: The keeper's log - job: check - needs: a notebook\np3: The fog horn - job: remember - needs: a timer or alarm\np4: The relief keeper rota - job: share - needs: nothing\np5: Polishing the lens - job: reset - needs: nothing\nsnags, in order:\ns1: Odd socks are all mixed up together - job: sort (picked by the person) - collides with p1 Its own flash pattern (job: find) - same job: no - the part needs: nothing\ns2: The drawer is too full to close - job: fit (picked by the person) - collides with p2 The keeper's log (job: check) - same job: no - the part needs: a notebook\ndeck systems whose parts do every job these snags need: a beehive; an ant colony; a museum"
}
Reply:
{
"task": "collide",
"headline": "Treating the sock drawer like a lighthouse does not carry over for these snags.",
"collisions": [
{
"snag": "s1",
"part": "p1",
"same_job": false,
"needs_met": true,
"verdict": "fizzles",
"idea": "The flash pattern lets the lighthouse be spotted from far away, which is a finding job, but getting the odd socks sorted out is a sorting job, a different task entirely.",
"first_try": "",
"breaks": "A flash pattern announces presence from afar, but it gives no way to separate odd socks once mixed up together."
},
{
"snag": "s2",
"part": "p2",
"same_job": false,
"needs_met": false,
"verdict": "fizzles",
"idea": "The keeper's log lets the keeper check that nothing was missed, which is a checking job, while getting the drawer to close with everything inside is a fitting job instead.",
"first_try": "",
"breaks": "A log records what happened after the fact, but it cannot make more room so the drawer itself will close."
}
],
"contact": "miss",
"best": "",
"insight": "The lighthouse's parts are built for finding and checking, not for sorting the odd socks or fitting everything into the drawer, so a beehive might suit these jobs better."
}
The hallway, like a cloakroom (your own system) - glancing blow (sparks, sticks, fizzles)
Input:
{
"task": "collide",
"thing": "the hallway by the front door",
"system": "a cloakroom",
"snags": "s1: Shoes pile up where nobody can find a matching pair\ns2: Bags and coats get dumped on the floor\ns3: Everyone leaves at once and blocks the door",
"parts": "p1: Numbered tickets\np2: Coat rails in number order\np3: One attendant hands coats back\np4: The closing-time sweep for left coats",
"on_hand": "a row of coat hooks",
"facts": "thing: the hallway by the front door\nsystem: a cloakroom (the person's own)\non hand: a row of coat hooks\nparts:\np1: Numbered tickets - job: find - needs: paper tickets\np2: Coat rails in number order - job: sort - needs: nothing\np3: One attendant hands coats back - job: share - needs: nothing\np4: The closing-time sweep for left coats - job: reset - needs: nothing\nsnags, in order:\ns1: Shoes pile up where nobody can find a matching pair - job: find (picked by the person) - collides with p1 Numbered tickets (job: find) - same job: yes - the part needs: paper tickets\ns2: Bags and coats get dumped on the floor - job: reset (picked by the person) - collides with p4 The closing-time sweep for left coats (job: reset) - same job: yes - the part needs: nothing\ns3: Everyone leaves at once and blocks the door - job: wait (picked by the person) - collides with p2 Coat rails in number order (job: sort) - same job: no - the part needs: nothing\ndeck systems whose parts do every job these snags need: a railway timetable; a hotel front desk"
}
Reply:
{
"task": "collide",
"headline": "Treating the hallway by the front door like a cloakroom carries over in some ways and not in others.",
"collisions": [
{
"snag": "s1",
"part": "p1",
"same_job": true,
"needs_met": false,
"verdict": "sparks",
"idea": "Numbered tickets let a cloakroom match coats to the right ticket by marking each one. The same marking idea could label the shoes so a matching pair is easy to find.",
"first_try": "Once you have paper tickets, give each pair of shoes a matching ticket so they can be found by it.",
"breaks": "Tickets work because something checks that tickets match, and the hallway has nobody checking shoes against any ticket."
},
{
"snag": "s2",
"part": "p4",
"same_job": true,
"needs_met": true,
"verdict": "sticks",
"idea": "The closing-time sweep for left coats clears a cloakroom of anything left behind. The same sweep idea could clear the floor of bags and coats left there.",
"first_try": "Walk through and move any bags or coats from the floor onto the coat hooks.",
"breaks": "A sweep only tidies what has already landed on the floor, not what sends bags and coats there in the first place."
},
{
"snag": "s3",
"part": "p2",
"same_job": false,
"needs_met": true,
"verdict": "fizzles",
"idea": "In a cloakroom, coat rails in number order sort coats into their own places, which is a sorting job. Everyone leaving at once and blocking the door is a waiting job, not a sorting one.",
"first_try": "",
"breaks": "Sorting coats by rail order says nothing about timing, so it offers no way to ease everyone leaving through the door together."
}
],
"contact": "glancing",
"best": "s2",
"insight": "The closing-time sweep for left coats suggests that clearing bags and coats from the floor back onto the hooks works for the hallway by the front door."
}
Build the input yourself
Load the engine in node (global.window = global; require("./deck.js"); require("./bumpkit.js")), then:
const K = BumpKit;
const form = K.form({ thing: "the shared fridge", sys: "library", onhand: "", snags: [
{ text: "Nobody knows whose yoghurt is whose", job: "share" },
{ text: "Jars and tubs never go back to the same shelf" }] }); // no job: the page guesses it
const built = K.buildInput(form); // built.facts holds each snag's job, its aim and whether the jobs match
const wire = K.toWire(built.input); // POST this to /estimate and /run
// then: const reply = K.normalize(K.parseReply(job.output.output));
// K.check(reply, built).notes // the page's re-check of the reply
// K.md(built, reply, K.title(form)) // the collision as Markdown
Form keys: thing, sys (a deck id - air, library, hive, post, rail, light, ants, lost, museum, fire, hotel, ship - or custom), custom (your system's name), parts (for custom: 3 to 6, each with name, job and need), onhand and up to five snags, each with text, an optional job and an optional aim (p1 to p6).
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://treat-it-like.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://treat-it-like.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"]
// treat-it-like.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://treat-it-like.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://treat-it-like.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
}
// TreatItLike.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 TreatItLike {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://treat-it-like.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://treat-it-like.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://treat-it-like.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://treat-it-like.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":"treat-it-like"}' | 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": "treat-it-like"})["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: "treat-it-like" })).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": "treat-it-like"}, 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", "treat-it-like"), 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: "treat-it-like" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "treat-it-like"])["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 = "treat-it-like" }))!["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": "collide", "thing": "the shared fridge", "system": "a library", "snags": "...", "parts": "...", "on_hand": "nothing listed", "facts": "...", ...}
# - 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": "collide", "thing": "the shared fridge", "system": "a library", "snags": "...", "parts": "...", "on_hand": "nothing listed", "facts": "...", ...}
# - 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: "collide", thing, system, snags, parts, on_hand, facts} (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 treat-it-like:<task>:<inputHash>:a<attempt> (the one task is collide), 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="treat-it-like:collide:$(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 = "treat-it-like:" + 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("contact"))
import { createHash } from "node:crypto";
const key = `treat-it-like:${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.contact);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "treat-it-like:" + 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 = "treat-it-like:" + 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 = "treat-it-like:#{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["contact"]].inspect
$key = "treat-it-like:" . $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["contact"] ?? ""), "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"treat-it-like:{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: treat-it-like:collide:$(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": "collide", "thing": "the shared fridge", "system": "a library", "snags": "...", "parts": "...", "on_hand": "nothing listed", "facts": "...", ...}
# - 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": "treat-it-like:collide:" + 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": `treat-it-like:collide:${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", "treat-it-like:collide:"+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", "treat-it-like:collide:" + 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" => "treat-it-like:collide:#{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: treat-it-like:collide:" . 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", $"treat-it-like:collide:{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);