Drive Almost Human from your own code
The one metered task, valley-check, is available over HTTP. It takes one almost-human thing - what it is, where people meet it, how it should feel - and a realism rating from 0 to 10 with a short note for each cue it has (face, eyes, skin or surface, proportions, movement, voice, replies and timing), plus the page's own numbers. It returns the cues that give the thing away, concrete moves each marked less real, more real or keep, the cues already working, a short ride report and a verdict (match-the-cues, stylize, push-real or leave-it).
The division of labour first: the page does the arithmetic and applies a fixed rule; the model explains which cue gives it away and how to move it; the page then re-checks the verdict, the zone, the first tell and first fix, every direction, the cues named and every number against its own. Build cues and page_read the same way (see "Build the input yourself") so the lines match.
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":"almost-human"}), 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, where, goal, count, cues, page_read) 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 check 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, the numbers and the verdicts
Every run sends task: valley-check (the one task is valley-check); a missing or different one is still answered as valley-check. Human-likeness is the mean rating times 10, rounded (0 to 100); the spread is the highest rating minus the lowest. Zones: under 60 cartoon-side, 60 to 91 valley, 92 and up far-side. The verdict is checked in this order, stopping at the first match: match-the-cues (spread 5 or more), leave-it (outside the valley), push-real (in the valley, goal "passes as real"), stylize (in the valley, any other goal). The first tell and first fix name the outlier (for match-the-cues and leave-it), the highest cue (stylize) or the lowest cue (push-real); every tie goes to the cue listed earlier.
Input (every field is a string)
| Field | Required | What it holds |
|---|---|---|
task | yes | Always valley-check. |
thing | yes | What it is, at most 80 characters: Robot greeter in the bakery window. |
where | yes | Where people meet it, or not given. |
goal | yes | friendly or cute, passes as real or not sure. |
count | yes | How many cues were rated (at least 2), as digits. |
cues | yes | One line per rated cue: eyes: 9 of 10 | note: glass eyes that seem to follow you (note none when blank). |
page_read | yes | The page's numbers on one line: human-likeness, spread, zone, verdict by the rule, outlier, highest, lowest, first cue, direction and the cues not rated. |
retry_note | no | Only on the page's one reformat retry, after an unusable reply. |
Reply (one JSON object)
{"task": "valley-check", "verdict": "match-the-cues", "zone": "cartoon-side", "headline": "...",
"tells": [{"cue": "eyes", "note": "..."}],
"fixes": [{"cue": "eyes", "direction": "less-real", "move": "..."}],
"keep": ["face", "skin"], "ride": "...", "closing": "..."}
| Field | What it holds |
|---|---|
verdict, zone | The ids above; they equal the page's. |
headline | What it is and which zone it sits in, at most 16 words. |
tells | Rated cues that give it away, first cue first: 1 to 3, or 0 to 1 for leave-it. Each note at most 20 words. |
fixes | 1 to 2 for match-the-cues, 2 to 3 for stylize and push-real, exactly 1 for leave-it. Each: a rated cue, a direction (less-real, more-real or keep) and a move of at most 25 words. |
keep | Rated cues that already work and are not tells. |
ride | Where the car stops ("H of 100", the zone) and the cue that makes it feel off, at most 35 words. |
closing | One sentence of at most 20 words restating the verdict. |
The page then checks the reply (ValleyKit.check) and lists any disagreement under "The page's check".
Worked examples
These are the four free samples on the page: the exact input and a stand-in model's reply, unedited. Together they cover every verdict - the window robot greeter (match the cues: glass eyes on a cartoon screen face), the shop mannequin (pull it toward cartoon), the roast-chicken cake (finish the realism) and the googly-eyed vacuum (leave it be).
Window robot greeter - match the cues
Input:
{
"task": "valley-check",
"thing": "Robot greeter in the bakery window",
"where": "waves at people walking past the bakery",
"goal": "friendly or cute",
"count": "5",
"cues": "face: 3 of 10 | note: flat screen with a drawn-on smile\neyes: 9 of 10 | note: glass eyes that seem to follow you\nskin: 4 of 10 | note: shiny white plastic shell\nmovement: 5 of 10 | note: waves its arm in a slow loop\nvoice: 8 of 10 | note: warm recorded voice says good morning",
"page_read": "human-likeness: 58 of 100 | spread: 6 | zone: cartoon-side | verdict by the rule: match-the-cues | outlier: eyes (9, above the mean of 5.8) | highest: eyes (9) | lowest: face (3) | first cue: eyes | direction: less-real | not rated: proportions, replies"
}
Reply:
{
"task": "valley-check",
"verdict": "match-the-cues",
"zone": "cartoon-side",
"headline": "Robot greeter in the bakery window sitting on the cartoon side.",
"tells": [
{
"cue": "eyes",
"note": "Glass eyes that seem to follow you look far too real for the rest of the robot."
}
],
"fixes": [
{
"cue": "eyes",
"direction": "less-real",
"move": "Swap the glass eyes for flat painted or printed dot eyes with no glassy shine or pupils that follow."
}
],
"keep": [
"face",
"skin",
"movement",
"voice"
],
"ride": "The car stops at 58 of 100 on the cartoon side, where eyes rated 9 of 10 feel the most out of place.",
"closing": "The cues don't agree with each other yet, so bring the odd one into line with the rest."
}
Shop mannequin - pull it toward cartoon
Input:
{
"task": "valley-check",
"thing": "Life-size mannequin in the coat display",
"where": "stands by the front door of a clothes shop",
"goal": "friendly or cute",
"count": "4",
"cues": "face: 8 of 10 | note: painted lips and lashes\neyes: 7 of 10 | note: painted irises that never blink\nskin: 6 of 10 | note: matte peach finish with no pores\nproportions: 9 of 10 | note: exactly adult height and build",
"page_read": "human-likeness: 75 of 100 | spread: 3 | zone: valley | verdict by the rule: stylize | outlier: skin (6, below the mean of 7.5) | highest: proportions (9) | lowest: skin (6) | first cue: proportions | direction: less-real | not rated: movement, voice, replies"
}
Reply:
{
"task": "valley-check",
"verdict": "stylize",
"zone": "valley",
"headline": "A life-size mannequin in the coat display, sitting in the valley.",
"tells": [
{
"cue": "proportions",
"note": "Exactly adult height and build reads as a real body, not a stylised one."
},
{
"cue": "face",
"note": "Painted lips and lashes read as a fully formed, realistic face."
},
{
"cue": "eyes",
"note": "Painted irises that never blink read as lifelike rather than drawn-on."
}
],
"fixes": [
{
"cue": "proportions",
"direction": "less-real",
"move": "Resize the body with a slightly larger head and shorter limbs for a toy-like build."
},
{
"cue": "face",
"direction": "less-real",
"move": "Simplify the painted lips and lashes into bold, flat shapes instead of fine realistic detail."
},
{
"cue": "eyes",
"direction": "less-real",
"move": "Paint the irises as larger, simplified circles instead of fine lifelike detail."
}
],
"keep": [
"skin"
],
"ride": "The car stops at 75 of 100, in the valley, where exactly adult proportions make it feel too real for a friendly, cute read.",
"closing": "Overall, the mannequin reads best once its realistic details are simplified toward a friendlier, more cartoon-like look."
}
Roast-chicken cake - finish the realism
Input:
{
"task": "valley-check",
"thing": "Cake made to look like a roast chicken",
"where": "centrepiece for a birthday table",
"goal": "passes as real",
"count": "2",
"cues": "skin: 8 of 10 | note: glossy browned icing like roasted skin\nproportions: 6 of 10 | note: drumsticks look a bit stubby",
"page_read": "human-likeness: 70 of 100 | spread: 2 | zone: valley | verdict by the rule: push-real | outlier: skin (8, above the mean of 7) | highest: skin (8) | lowest: proportions (6) | first cue: proportions | direction: more-real | not rated: face, eyes, movement, voice, replies"
}
Reply:
{
"task": "valley-check",
"verdict": "push-real",
"zone": "valley",
"headline": "This roast-chicken cake sits in the valley.",
"tells": [
{
"cue": "proportions",
"note": "drumsticks look a bit stubby"
}
],
"fixes": [
{
"cue": "proportions",
"direction": "more-real",
"move": "Reshape the drumsticks fuller and rounder at the ends instead of stubby."
},
{
"cue": "skin",
"direction": "more-real",
"move": "Vary the icing's browning and shine unevenly instead of smooth and uniform."
}
],
"keep": [],
"ride": "The car stops at 70 of 100, in the valley, where the stubby drumsticks make it feel off.",
"closing": "Overall, this cake needs to move further toward realism to pass as the real thing."
}
Googly-eyed vacuum - leave it be
Input:
{
"task": "valley-check",
"thing": "Robot vacuum with stick-on googly eyes",
"where": "cleans the living room floor",
"goal": "friendly or cute",
"count": "3",
"cues": "eyes: 2 of 10 | note: big wobbly googly eyes\nmovement: 3 of 10 | note: bumps into chair legs and spins\nvoice: 1 of 10 | note: beeps",
"page_read": "human-likeness: 20 of 100 | spread: 2 | zone: cartoon-side | verdict by the rule: leave-it | outlier: movement (3, above the mean of 2) | highest: movement (3) | lowest: voice (1) | first cue: movement | direction: keep | not rated: face, skin, proportions, replies"
}
Reply:
{
"task": "valley-check",
"verdict": "leave-it",
"zone": "cartoon-side",
"headline": "A robot vacuum with googly eyes sits clearly in the cartoon side.",
"tells": [],
"fixes": [
{
"cue": "movement",
"direction": "keep",
"move": "Keep the bump-and-spin motion exactly as it is; skip adding smoother obstacle-avoidance navigation."
}
],
"keep": [
"eyes",
"voice"
],
"ride": "The car parks at 20 of 100, resting firmly on the cartoon side. The bumping, spinning movement is what keeps it happily there.",
"closing": "This googly-eyed vacuum already fits its playful cartoon look, so there's nothing here to change."
}
Build the input yourself
Load the engine in node (global.window = global; require("./valleykit.js")), then:
const K = ValleyKit;
const built = K.buildInput({ thing: "Talking doll", where: "", goal: "friendly", cues: {
face: { r: 6, note: "soft painted cheeks" },
eyes: { r: 8, note: "glossy eyes that blink" },
voice: { r: 7, note: "giggles on cue" } } });
built.facts.h; // 70 - human-likeness
built.facts.verdict; // "stylize" - the page's own rule
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) // the report 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://almost-human.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://almost-human.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"]
// almost-human.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://almost-human.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://almost-human.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
}
// AlmostHuman.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 AlmostHuman {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://almost-human.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://almost-human.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://almost-human.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://almost-human.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":"almost-human"}' | 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": "almost-human"})["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: "almost-human" })).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": "almost-human"}, 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", "almost-human"), 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: "almost-human" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "almost-human"])["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 = "almost-human" }))!["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": "valley-check", "thing": "Robot greeter in the bakery window", "goal": "friendly or cute", "count": "5", "cues": "face: 3 of 10 | note: flat screen with a drawn-on smile ...
2. ..."} - 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": "valley-check", "thing": "Robot greeter in the bakery window", "goal": "friendly or cute", "count": "5", "cues": "face: 3 of 10 | note: flat screen with a drawn-on smile ...
2. ..."} - 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: "valley-check", thing, where, goal, count, cues, page_read} (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 almost-human:<task>:<inputHash>:a<attempt> (the one task is valley-check), 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="almost-human:valley-check:$(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 = "almost-human:" + 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 = `almost-human:${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 := "almost-human:" + 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 = "almost-human:" + 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 = "almost-human:#{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 = "almost-human:" . $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 = $"almost-human:{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: almost-human:valley-check:$(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": "valley-check", "thing": "Robot greeter in the bakery window", "goal": "friendly or cute", "count": "5", "cues": "face: 3 of 10 | note: flat screen with a drawn-on smile ...
2. ..."} - 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": "almost-human:valley-check:" + 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": `almost-human:valley-check:${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", "almost-human:valley-check:"+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", "almost-human:valley-check:" + 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" => "almost-human:valley-check:#{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: almost-human:valley-check:" . 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", $"almost-human:valley-check:{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);