Drive One Black Cell from your own code
The one metered task, reading, is available over HTTP. It takes a rule number, the seed in words, the person's wish and the page's own facts about the universe (its estimate, density, spread, widest gap, centre column, twins, a documented resemblance and the eight one-law flips). It returns the behaviour the wish points to (fades, repeats, nests, churns or edge), a verdict (already, one_flip or no_flip), the law to flip, a note on the flip, a short field guide, two or three things to look for and a title.
The division of labour first: the page owns the universe (64 rows on a strip wide enough to act as an endless row), the behaviour estimate, every measurement, the eight flips and the verdict rule; the model owns reading the wish and all the wording; the page then re-checks the reply.
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":"one-black-cell"}), 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, rule, seed, wish, page_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: a reading is metered and needs 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 and the verdicts
Every run sends task: reading (the one task is reading); a missing or different one is still answered as reading. The model first turns the wish into one behaviour, wish_target. The verdict is then a fixed rule, applied in order: already when the page's estimate equals wish_target; one_flip when at least one of the eight flip lines shows wish_target; otherwise no_flip. For one_flip, pick_flip names the law whose flip gives that behaviour with the density closest to the current one.
Input fields (all strings)
| Field | Required | What it holds |
|---|---|---|
task | yes | Always reading. |
rule | yes | The rule number, 0 to 255. |
seed | yes | The seed in words: one black cell, or the name ADA spelled as 11 cells: 10010000001 (five bits per letter, A = 00001). |
wish | yes | What the person wishes the universe did, or not given; over about 300 characters it keeps its start and end with the middle marked as cut. |
page_facts | yes | One key | ... per line: rule (with its eight laws, like WBB->B), seed, estimate, density, spread, widest_gap, centre_column, twins, echo, eight flip <LAW> lines and verdict_rule. |
clip_note | no | What the page shortened, if anything. |
retry_note | no | Present only on the page's one reformat retry. |
The reply
One JSON object, no prose around it:
{"task": "reading", "title": "...", "wish_target": "nests", "verdict": "one_flip",
"verdict_reason": "...", "pick_flip": "WBB", "flip_note": "...", "philosophy": "...",
"look_for": [{"fact": "widest_gap", "text": "..."}, {"fact": "density", "text": "..."}]}
| Field | What it holds |
|---|---|
wish_target | One of fades, repeats, nests, churns, edge, read from the wish alone. |
verdict, verdict_reason | The verdict id by the rule above; one sentence of at most 30 words on what decided it. |
pick_flip, flip_note | The law to flip (three letters, e.g. WBB) or ""; one sentence of at most 30 words about the flip, or why none is needed or enough. |
philosophy | The field guide: 40 to 80 words on what the universe does, built only from the facts. |
look_for | Two or three {fact, text} items; fact is a fact key, text at most 25 words. |
title | At most 6 words. |
The page then checks the reply (CellKit.check): the verdict against the rule for the reply's own wish_target, the named flip really giving that behaviour and being the closest in density, every look_for pointing to a real fact, every number in the prose being one the page supplied, no internal ids in the prose, and the word limits.
Worked examples, one per sample
These are the four free samples on the page: the exact input and a stand-in model's reply, unedited. Together they cover all three verdicts - rule 30 wishing for lace (one flip away), a 23 October birthday rule wishing for something wild (no single flip), the name ADA under rule 110 wishing for travelling shapes (already there) and the name JUNIPER under rule 184 wishing it would fade (one flip away).
Rule 30 from one black cell, wishing for lace - one flip away
Input:
{
"task": "reading",
"rule": "30",
"seed": "one black cell",
"wish": "Triangles inside triangles, like a piece of old lace",
"page_facts": "rule | 30 - laws BBB->W BBW->W BWB->W BWW->B WBB->B WBW->B WWB->B WWW->W\nseed | one black cell\nestimate | churns - it never settles - busy, irregular, hard to predict; the page's estimate over 64 rows\ndensity | 53% of the cells inside the growing pattern are black\nspread | the left side moves 1 cell per row, the right side 1 cell per row\nwidest_gap | 11 white cells with black on both sides, at row 46\ncentre_column | rows 0-31 down the middle: 11011100110001011001001110101110 (18 black of 32)\ntwins | mirror twin rule 86; colour-swapped twin rule 135\necho | pigment bands on some cone-snail shells look strikingly like this rule's triangles; its centre column has been used as a source of random-looking bits\nflip BBB | W becomes B -> rule 158: repeats, density 64%\nflip BBW | W becomes B -> rule 94: repeats, density 53%\nflip BWB | W becomes B -> rule 62: repeats, density 63%\nflip BWW | B becomes W -> rule 14: repeats, density 3%\nflip WBB | B becomes W -> rule 22: nests, density 24%\nflip WBW | B becomes W -> rule 26: nests, density 18%\nflip WWB | B becomes W -> rule 28: repeats, density 27%\nflip WWW | W becomes B -> rule 31: fades, density 51%\nverdict_rule | already when the current estimate equals wish_target; else one_flip when at least one flip's estimate equals wish_target; else no_flip"
}
Reply:
{
"task": "reading",
"title": "The Churning One-Row Quilt",
"wish_target": "nests",
"verdict": "one_flip",
"verdict_reason": "The current pattern churns and never settles, but flipping law WBB yields nesting triangles at a density closer to today's than the other nesting flip.",
"pick_flip": "WBB",
"flip_note": "Flipping law WBB turns this into rule 22, a nesting pattern of triangles within triangles, with density dropping to 24%.",
"philosophy": "From a single black cell, rule 30 spreads outward, each edge creeping out one cell every row, while the middle churns through busy, unpredictable bands - about 53% of the growing quilt stays black. Watch row 46, where an 11-cell white gap sits bordered by black on both sides. This rule's triangle motif is said to resemble pigment bands on some cone-snail shells, and its centre column has even been used as a source of random-looking bits.",
"look_for": [
{
"fact": "widest_gap",
"text": "At row 46, spot an 11-cell stretch of white fully boxed in by black on each side."
},
{
"fact": "density",
"text": "The quilt runs 53% black - watch for that dark, speckled balance across the rows."
},
{
"fact": "centre_column",
"text": "Trace the middle column through rows 0 to 31: 18 of those 32 cells land black."
}
]
}
A 23 October birthday rule, wishing for something wild - no single flip
Input:
{
"task": "reading",
"rule": "40",
"seed": "one black cell",
"wish": "Make it wild and unpredictable",
"page_facts": "rule | 40 (from a birthday: day 296 of the year, wrapped past 255) - laws BBB->W BBW->W BWB->B BWW->W WBB->B WBW->W WWB->W WWW->W\nseed | one black cell\nestimate | fades - everything settles into flat rows of one colour (flat from row 1 on); the page's estimate over 64 rows\ndensity | 0% of the cells inside the growing pattern are black\nspread | the left side moves 0 cells per row, the right side 0 cells per row\nwidest_gap | no white gap with black on both sides\ncentre_column | rows 0-31 down the middle: 10000000000000000000000000000000 (1 black of 32)\ntwins | mirror twin rule 96; colour-swapped twin rule 235\necho | none\nflip BBB | W becomes B -> rule 168: fades, density 0%\nflip BBW | W becomes B -> rule 104: fades, density 0%\nflip BWB | B becomes W -> rule 8: fades, density 0%\nflip BWW | W becomes B -> rule 56: repeats, density 2%\nflip WBB | B becomes W -> rule 32: fades, density 0%\nflip WBW | W becomes B -> rule 44: repeats, density 2%\nflip WWB | W becomes B -> rule 42: repeats, density 2%\nflip WWW | W becomes B -> rule 41: repeats, density 49%\nverdict_rule | already when the current estimate equals wish_target; else one_flip when at least one flip's estimate equals wish_target; else no_flip"
}
Reply:
{
"task": "reading",
"title": "The One-Cell Stillness",
"wish_target": "churns",
"verdict": "no_flip",
"verdict_reason": "The current estimate is fades, not wild and unpredictable, and every one-law flip leads only to fades or repeats, not churns.",
"pick_flip": "",
"flip_note": "No single law change reaches a wild, unpredictable pattern from this seed; try seeding with a name instead, or change two laws at once.",
"philosophy": "A single black cell falls onto an empty row, and the pattern goes still almost immediately. Only two laws, BWB and WBB, turn white into black, and once the cell's neighbours turn white those laws stop firing, so every later row is flat. The seed never spreads sideways, and the centre column keeps just one black cell among thirty-two. Density sits at zero percent, and no gap ever opens between black cells, with almost nothing left to frame one.",
"look_for": [
{
"fact": "density",
"text": "Look for the rows to stay blank white, since 0% of the cells ever turn black here."
},
{
"fact": "centre_column",
"text": "Follow the centre column down the quilt - just 1 of the first 32 cells is black, the rest white."
},
{
"fact": "spread",
"text": "Watch the edges hold still - the pattern spreads 0 cells per row on both the left and right."
}
]
}
The name ADA under rule 110, wishing for travelling shapes - already there
Input:
{
"task": "reading",
"rule": "110",
"seed": "the name ADA spelled as 11 cells: 10010000001",
"wish": "Something alive - little shapes that travel and bump into each other",
"page_facts": "rule | 110 - laws BBB->W BBW->B BWB->B BWW->W WBB->B WBW->B WWB->B WWW->W\nseed | the name ADA spelled as 11 cells: 10010000001\nestimate | edge - a known complex rule - steady stretches with structures that travel and collide; the page's estimate over 64 rows\ndensity | 33% of the cells inside the growing pattern are black\nspread | the left side moves 1 cell per row, the right side 0 cells per row\nwidest_gap | 8 white cells with black on both sides, at row 39\ncentre_column | rows 0-31 down the middle: 00000110111100000101010101011000 (13 black of 32)\ntwins | mirror twin rule 124; colour-swapped twin rule 137\necho | proved able, in principle, to carry out any computation given enough room and time\nflip BBB | W becomes B -> rule 238: repeats, density 57%\nflip BBW | B becomes W -> rule 46: repeats, density 8%\nflip BWB | B becomes W -> rule 78: repeats, density 32%\nflip BWW | W becomes B -> rule 126: churns, density 46%\nflip WBB | B becomes W -> rule 102: churns, density 29%\nflip WBW | B becomes W -> rule 106: repeats, density 4%\nflip WWB | B becomes W -> rule 108: repeats, density 4%\nflip WWW | W becomes B -> rule 111: repeats, density 53%\nverdict_rule | already when the current estimate equals wish_target; else one_flip when at least one flip's estimate equals wish_target; else no_flip"
}
Reply:
{
"task": "reading",
"title": "Travelers of the Calm Edge",
"wish_target": "edge",
"verdict": "already",
"verdict_reason": "The page's estimate already shows this universe lives on the edge, with steady stretches and colliding, traveling structures, which is exactly the lively, bumping shapes the wish asks for.",
"pick_flip": "",
"flip_note": "The estimate itself is the tell: this rule already lives on the edge, its steady stretches broken by structures that travel and collide.",
"philosophy": "From the name ADA's eleven seed cells, rule 110 grows unevenly: its left edge advances one cell every row while its right edge holds still. Black cells settle near 33% of the grid, calm for long stretches, until a gap of eight white cells opens and small structures travel and collide, true to a universe that lives on the edge. The pattern is proved able, in principle, to carry out any computation given enough room and time.",
"look_for": [
{
"fact": "widest_gap",
"text": "Find the stretch near row 39 where eight white cells sit between black walls - the widest calm gap in the quilt."
},
{
"fact": "density",
"text": "About 33% of the cells are black, speckled through the pattern rather than packed into solid blocks."
},
{
"fact": "spread",
"text": "Watch how the left edge creeps out one cell every row while the right edge never moves at all."
}
]
}
The name JUNIPER under rule 184, wishing it would fade - one flip away
Input:
{
"task": "reading",
"rule": "184",
"seed": "the name JUNIPER spelled as 33 cells: 101010101011100100110000001011001",
"wish": "Let it all fade away to nothing",
"page_facts": "rule | 184 - laws BBB->B BBW->W BWB->B BWW->B WBB->B WBW->W WWB->W WWW->W\nseed | the name JUNIPER spelled as 33 cells: 101010101011100100110000001011001\nestimate | repeats - it falls into a beat - the same rows again, standing still or sliding sideways (every 1 row, sliding 1 cell right each time); the page's estimate over 64 rows\ndensity | 15% of the cells inside the growing pattern are black\nspread | the left side moves 0 cells per row, the right side 1 cell per row\nwidest_gap | 6 white cells with black on both sides, at row 1\ncentre_column | rows 0-31 down the middle: 01001010101010101010000000000000 (9 black of 32)\ntwins | mirror twin rule 226; colour-swapped twin rule 226\necho | a classic toy model of traffic: black cells are cars that move right when the space ahead is free\nflip BBB | B becomes W -> rule 56: repeats, density 14%\nflip BBW | W becomes B -> rule 248: repeats, density 59%\nflip BWB | B becomes W -> rule 152: repeats, density 5%\nflip BWW | B becomes W -> rule 168: fades, density 1%\nflip WBB | B becomes W -> rule 176: repeats, density 12%\nflip WBW | W becomes B -> rule 188: repeats, density 51%\nflip WWB | W becomes B -> rule 186: repeats, density 55%\nflip WWW | W becomes B -> rule 185: repeats, density 84%\nverdict_rule | already when the current estimate equals wish_target; else one_flip when at least one flip's estimate equals wish_target; else no_flip"
}
Reply:
{
"task": "reading",
"title": "Juniper's Rightward Sliding Beat",
"wish_target": "fades",
"verdict": "one_flip",
"verdict_reason": "The current pattern repeats in a sliding beat, but flipping law BWW gives rule 168, which fades to nothing.",
"pick_flip": "BWW",
"flip_note": "Flipping law BWW turns this into rule 168, which fades away, settling down to just 1% density.",
"philosophy": "Starting from the name JUNIPER as thirty-three cells, rule 184 settles almost at once into a steady beat: the same row returns, creeping one cell right each time while the left edge holds still. About fifteen percent of the cells stay black, with one wide gap of six empty cells near the start. It echoes a classic model of traffic, where black cells are cars that move right when the space ahead is free.",
"look_for": [
{
"fact": "spread",
"text": "Watch the left edge hold at 0 cells of drift while the right edge slides 1 cell over on every row."
},
{
"fact": "density",
"text": "Only about 15% of the cells in the pattern are black - scan each row for how thin the black islands stay."
},
{
"fact": "widest_gap",
"text": "Near the top, find the widest stretch: 6 white cells in a row with black cells fencing both ends."
}
]
}
Build the input yourself
Load the script in node (global.window = global; require("./cellkit.js")), then:
const K = CellKit;
const built = K.buildInput({ rule: 30, seed: "one", name: "", wish: "Triangles inside triangles, like old lace" });
built.d; // { rule, cls, density, gap, spread, centre, flips: [{ law, rule, cls, density }], ... } - the free universe
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 reading
// K.verdictFor(built.d, reply.wish_target) // the verdict rule for the reply's own wish target
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://one-black-cell.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://one-black-cell.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"]
// one-black-cell.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://one-black-cell.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://one-black-cell.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
}
// OneBlackCell.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 OneBlackCell {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://one-black-cell.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://one-black-cell.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://one-black-cell.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://one-black-cell.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":"one-black-cell"}' | 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": "one-black-cell"})["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: "one-black-cell" })).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": "one-black-cell"}, 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", "one-black-cell"), 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: "one-black-cell" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "one-black-cell"])["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 = "one-black-cell" }))!["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": "reading", "rule": "30", "seed": "one black cell", "wish": "...", "page_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": "reading", "rule": "30", "seed": "one black cell", "wish": "...", "page_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: "reading", rule, seed, wish, page_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 one-black-cell:<task>:<inputHash>:a<attempt> (the one task is reading), 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="one-black-cell:reading:$(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 = "one-black-cell:" + 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("title"))
import { createHash } from "node:crypto";
const key = `one-black-cell:${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.title);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "one-black-cell:" + 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 = "one-black-cell:" + 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 = "one-black-cell:#{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 = "one-black-cell:" . $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 = $"one-black-cell:{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: one-black-cell:reading:$(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": "reading", "rule": "30", "seed": "one black cell", "wish": "...", "page_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": "one-black-cell:reading:" + 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": `one-black-cell:reading:${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", "one-black-cell:reading:"+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", "one-black-cell:reading:" + 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" => "one-black-cell:reading:#{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: one-black-cell:reading:" . 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", $"one-black-cell:reading:{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);