Drive Other Eyes from your own code
The one metered task, retell, is available over HTTP. It takes up to seven everyday things (one per line), one of six animals and a reader (a kid or a grown-up), and returns a verdict (a different world, a tilted world, or mostly familiar), one frame per thing - what you notice, what the animal probably gets, and why - plus a dead-simple picture of how its senses work, a safe one-minute thing to try, and what scientists are still unsure about.
The division of labour first: the page owns the sense cards, the keyword guess and the verdict rule; the model owns the retelling; the page re-checks the reply. All three come from eyekit.js, which you can run yourself (see Build the input yourself). The sense cards are rounded popular-science summaries, not measurements.
Get a token on the token page (step 2 below also shows how to mint a guest token in code).
Base URL and the envelope
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }
The token is minted for this app (the guest call takes {"slug":"other-eyes"}), 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, animal, reader, senses, scene, guess) 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: retelling a reel 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 shifts and the verdict
Every run sends task: retell; a missing or different one is still answered as retell. Each thing gets one shift, checked in a fixed order:
- new - the animal picks something up from the thing through a sense its card says people lack (ultraviolet, polarised light, heat sensing, echoes, ultrasound, infrasound, a magnetic sense).
- gone - what a person most notices is something the card says the animal has no sense for at all (red for a honeybee, red or green as colours for a dog, a high sound in the air for a rattlesnake).
- sharper - it senses the thing more keenly than a person through a keen sense on its card.
- duller - everything else.
The verdict then follows the shifts, first match wins: alien if three or more frames are new or gone; otherwise shifted if one or two are; otherwise familiar. The page computes the same rule over the reply's own frames (EyeKit.verdictFor) and flags a reply that disagrees.
The input
Every value is a string (objects are never sent). app.js builds it with EyeKit.buildInput(form).input.
| Field | Required | What it holds |
|---|---|---|
task | yes | Always retell. |
animal | yes | <id> - <name>: shrimp - Mantis shrimp, bee - Honeybee, dog - Dog, bat - Insect-eating bat, snake - Rattlesnake or pigeon - Pigeon. |
reader | yes | Starts with kid or grownup, then a short description in brackets. |
senses | yes | The page's sense card for the animal (EyeKit.senseCard): its facts, what is still unsure, the senses it has that people lack, and what it has no sense for at all. The model's only source of animal facts. |
scene | yes | Numbered lines, 1. ..., one thing per line. At most 7 are sent (the reel holds seven pairs); a line over 160 characters keeps its start and end around [...]. |
guess | yes | The page's keyword guess, one line per thing: <n> | <shift> | <channel>. Only a guess; the shift rules win. |
clip_note | no | What the page shortened, or absent. |
retry_note | no | Sent only on the page's one reformat retry. |
The reply
The reply is one JSON object in job.output.output. The page parses it with EyeKit.parseReply and EyeKit.normalize (a cut-off stream is closed by EyeKit.closeJson and shown as "N of 8 sections recovered").
{"task": "retell", "verdict": "alien | shifted | familiar", "verdict_reason": "...", "title": "...",
"big_idea": "...", "picture": "...",
"frames": [{"n": 1, "shift": "new | gone | sharper | duller",
"channel": "colour | uv | polarized | smell | hearing | ultrasound | infrasound | heat | echo | vibration | magnetic | motion",
"you_notice": "...", "it_gets": "...", "why": "..."}],
"try_it": "...", "not_sure": "..."}
| Field | Rule |
|---|---|
frames | Exactly one entry per numbered thing, same numbers and order. you_notice at most 12 words from the thing's own line; it_gets at most 24 words, hedged; why one card fact of at most 18 words. A new frame's channel is one of the animal's extra senses; no other frame uses an extra-sense channel. |
big_idea | One sentence of at most 25 words, the way you would tell a five-year-old. |
picture | Two or three short sentences: one everyday comparison ("Imagine ...") built only from the card. |
try_it | One safe thing to do at home in a minute, at most 30 words - never the sun, bright lights, lasers, ultraviolet lamps, wild animals or anything hot. |
not_sure | One sentence from the card's "Still unsure". |
The page then checks the reply against its own facts (EyeKit.check): one frame per thing, the verdict against the reply's own frames, every extra sense one the animal's card lists, no extra-sense channel on a frame not marked as one, nothing gone for an animal whose card lists nothing it cannot sense, the keyword guess where it differs, an unsafe try-it idea, and no internal field name in the prose.
Worked examples, one per verdict
These are the four free samples on the page: the exact input and a stand-in model's reply, unedited.
Honeybee at the kitchen table - alien
Input:
{
"task": "retell",
"animal": "bee - Honeybee",
"reader": "kid (a curious reader aged about 6 to 9: short everyday words, one idea per sentence)",
"senses": "Honeybee (a flower-visiting insect). Three kinds of colour receptors like ours, but tuned to ultraviolet, blue and green - there is no red one, so red looks dark. Many petals carry ultraviolet patterns, like landing stripes, that people cannot see. Reads polarised light in the sky to keep its direction, and smells keenly through its antennae. Still unsure: Exactly how a bee's brain blends its colours into what it experiences is still being worked out. Senses it has that people lack: ultraviolet light, polarised light. Has no sense at all for: the colour red (it has no red receptor).",
"scene": "1. A bunch of yellow sunflowers in a vase\n2. A ripe red strawberry on a plate\n3. Blue sky through the open window\n4. A bowl of white sugar\n5. A jar of honey with the lid off",
"guess": "1 | new | uv\n2 | gone | colour\n3 | new | polarized\n4 | duller | colour\n5 | sharper | smell"
}
Reply:
{
"task": "retell",
"verdict": "alien",
"verdict_reason": "Two things reach it through senses you lack and one is gone for it, making three unusual shifts in total.",
"title": "How a Honeybee Senses These Things",
"big_idea": "A honeybee sees colours differently from you, catches light patterns you cannot see, and smells very well with its antennae.",
"picture": "Imagine petals have hidden stripes painted on them, like a landing strip only the bee can see. To you the petal looks plain, but to the bee the stripes point the way in.",
"frames": [
{
"n": 1,
"shift": "new",
"channel": "uv",
"you_notice": "A bunch of yellow sunflowers in a vase",
"it_gets": "It likely sees ultraviolet patterns on the petals, like landing stripes no person can see, guiding it toward the flower.",
"why": "Many petals carry ultraviolet patterns, like landing stripes, that people cannot see."
},
{
"n": 2,
"shift": "gone",
"channel": "colour",
"you_notice": "A ripe red strawberry on a plate",
"it_gets": "It probably sees the strawberry as a dark shape rather than red.",
"why": "It has no red colour receptor, so red looks dark to it."
},
{
"n": 3,
"shift": "new",
"channel": "polarized",
"you_notice": "Blue sky through the open window",
"it_gets": "It likely reads the polarised light pattern in the sky, which helps it know which way is home.",
"why": "It reads polarised light in the sky to keep its direction."
},
{
"n": 4,
"shift": "duller",
"channel": "colour",
"you_notice": "A bowl of white sugar",
"it_gets": "The bee probably sees the white sugar about the way a person does, with no extra colour clues.",
"why": "None of its special senses is tuned to this white colour."
},
{
"n": 5,
"shift": "sharper",
"channel": "smell",
"you_notice": "A jar of honey with the lid off",
"it_gets": "The bee likely smells the honey very strongly through its antennae, picking up more than a person would.",
"why": "It smells keenly through its antennae."
}
],
"try_it": "Close your eyes and smell a flower or some honey. Notice how much your nose can tell you without looking.",
"not_sure": "Scientists are still not sure exactly how a bee's brain blends its colours into what it experiences."
}
Pigeon in the town square - shifted
Input:
{
"task": "retell",
"animal": "pigeon - Pigeon",
"reader": "kid (a curious reader aged about 6 to 9: short everyday words, one idea per sentence)",
"senses": "Pigeon (a city bird). Four kinds of colour receptors, one reaching into ultraviolet light. Eyes on the sides of its head give it a very wide view, nearly all the way around. Can detect very low sounds, far below what people hear, and is thought to sense Earth's magnetic field to find home. Still unsure: How birds sense the magnetic field is still debated by scientists. Senses it has that people lack: ultraviolet light, magnetic sense, infrasound. Has no sense at all for: nothing on this list.",
"scene": "1. Breadcrumbs scattered on the pavement\n2. A bus rumbling past\n3. People walking all around the square\n4. A yellow umbrella\n5. The fountain splashing",
"guess": "1 | duller | colour\n2 | new | infrasound\n3 | sharper | motion\n4 | sharper | colour\n5 | duller | colour"
}
Reply:
{
"task": "retell",
"verdict": "shifted",
"verdict_reason": "One thing reaches it through a sense you lack, and none is gone for it.",
"title": "How a Pigeon Senses the Square",
"big_idea": "A pigeon sees far more around it at once, sees extra colours, and may feel low rumbles and the Earth's pull for home.",
"picture": "Imagine having eyes on the sides of your head so you could watch almost the whole square without turning your head. Now imagine feeling a quiet pull, like a compass, nudging you toward home.",
"frames": [
{
"n": 1,
"shift": "duller",
"channel": "colour",
"you_notice": "Breadcrumbs on the pavement",
"it_gets": "It probably sees the crumbs about as plainly as you do, with nothing special drawing its eye.",
"why": "None of its special senses is especially tuned to this."
},
{
"n": 2,
"shift": "new",
"channel": "infrasound",
"you_notice": "A bus rumbling past",
"it_gets": "It may pick up a deep rumble from the bus, in sounds far too low for you to hear.",
"why": "It can detect very low sounds, far below what people hear."
},
{
"n": 3,
"shift": "sharper",
"channel": "motion",
"you_notice": "People walking all around the square",
"it_gets": "It likely notices people moving all around it at once, in a wider view than you have.",
"why": "Eyes on the sides of its head give it a very wide view, nearly all around."
},
{
"n": 4,
"shift": "sharper",
"channel": "colour",
"you_notice": "A yellow umbrella",
"it_gets": "It probably sees the umbrella's yellow very clearly, a bit sharper than most people would notice.",
"why": "It has four kinds of colour receptors, one reaching into ultraviolet light."
},
{
"n": 5,
"shift": "duller",
"channel": "hearing",
"you_notice": "The fountain splashing",
"it_gets": "It probably hears the splashing about as well as you do, with nothing extra there for it.",
"why": "None of its special senses is especially tuned to ordinary splashing sounds."
}
],
"try_it": "Hold your arms out to the sides and try to see them without turning your head, to feel how wide a pigeon's view is.",
"not_sure": "Scientists still debate exactly how birds sense Earth's magnetic field."
}
Mantis shrimp at a desk - shifted
Input:
{
"task": "retell",
"animal": "shrimp - Mantis shrimp",
"reader": "grown-up (plain words for an adult, still no jargon)",
"senses": "Mantis shrimp (a reef crustacean). About 12 kinds of colour receptors - people have 3 - including several for ultraviolet light. Sees polarised light, the direction light waves wiggle, including a kind almost no other animal is known to see. Each eye sits on a stalk and moves on its own, scanning the scene in strips. Still unsure: Lab tests suggest it is worse than people at telling two close colours apart, so it may recognise colours quickly rather than finely. Senses it has that people lack: ultraviolet light, polarised light. Has no sense at all for: nothing on this list.",
"scene": "1. A glass of water on the windowsill\n2. My phone screen lit up with a message\n3. A bowl of oranges\n4. A striped beach towel on the chair\n5. A poster of tropical fish on the wall",
"guess": "1 | new | polarized\n2 | new | polarized\n3 | duller | motion\n4 | duller | motion\n5 | duller | motion"
}
Reply:
{
"task": "retell",
"verdict": "shifted",
"verdict_reason": "Two things reach it through senses you lack, and the other three come through the same ordinary senses as yours.",
"title": "How the Mantis Shrimp Sees Your Room",
"big_idea": "A mantis shrimp sees many more kinds of light than you do, including kinds you cannot see at all.",
"picture": "Imagine wearing special glasses that show a hidden shimmer in ordinary light, a pattern no one around you can see. A mantis shrimp sees that shimmer naturally, with eyes that sit on stalks and scan the scene on their own.",
"frames": [
{
"n": 1,
"shift": "new",
"channel": "polarized",
"you_notice": "A glass of water on the windowsill",
"it_gets": "It probably sees a shimmer of polarised light bouncing off the water and the glass that you cannot see.",
"why": "Mantis shrimp can see polarised light, a sense people do not have."
},
{
"n": 2,
"shift": "new",
"channel": "polarized",
"you_notice": "A phone screen lighting up with a message",
"it_gets": "It probably picks up a pattern of polarised light in the screen's glow that you cannot notice at all.",
"why": "It senses polarised light, something people cannot detect at all."
},
{
"n": 3,
"shift": "duller",
"channel": "motion",
"you_notice": "A bowl of oranges",
"it_gets": "It probably takes in the bowl of oranges about as plainly as you do, maybe a little less.",
"why": "None of its special senses is tuned to this ordinary sight."
},
{
"n": 4,
"shift": "duller",
"channel": "motion",
"you_notice": "A striped beach towel on the chair",
"it_gets": "It likely notices the towel about as well as you do, or perhaps a bit less clearly.",
"why": "None of its special senses is tuned to this ordinary pattern."
},
{
"n": 5,
"shift": "duller",
"channel": "motion",
"you_notice": "A poster of tropical fish on the wall",
"it_gets": "It probably sees the poster about as clearly as you do, maybe a little less sharply.",
"why": "None of its special senses is tuned to this ordinary picture."
}
],
"try_it": "Hold a pair of polarised sunglasses in front of a phone or computer screen and slowly turn them to watch the brightness change.",
"not_sure": "Scientists are still unsure whether it can tell two very similar colours apart, since tests suggest it may recognise colours quickly rather than finely."
}
Dog in the front hall - familiar
Input:
{
"task": "retell",
"animal": "dog - Dog",
"reader": "grown-up (plain words for an adult, still no jargon)",
"senses": "Dog (a pet that greets you at the door). Two kinds of colour receptors - blue and yellow - so reds and greens look alike, a dull yellowish grey. A nose far keener than ours, with a large part of the brain given to smell. Hears high pitches above the top of human hearing, and spots movement well in dim light. Still unsure: Nobody can measure exactly how much keener a dog's nose is; published estimates vary enormously. Senses it has that people lack: ultrasound. Has no sense at all for: red and green as colours (they look alike).",
"scene": "1. A pot of soup bubbling on the stove\n2. The doorbell ringing\n3. A blue ball under the chair\n4. A newspaper on the table\n5. Muddy boots by the door",
"guess": "1 | sharper | smell\n2 | sharper | hearing\n3 | duller | colour\n4 | duller | colour\n5 | sharper | smell"
}
Reply:
{
"task": "retell",
"verdict": "familiar",
"verdict_reason": "All five things reach it through senses it already has: three sharper than yours and two duller.",
"title": "A Dog's Senses Around the House",
"big_idea": "A dog explores the world mostly through its nose and ears, noticing smells and sounds far more than colours.",
"picture": "Imagine turning up a radio so quiet sounds become easy to hear. A dog's nose works a bit like that, picking up faint smells that a person would never notice.",
"frames": [
{
"n": 1,
"shift": "sharper",
"channel": "smell",
"you_notice": "A pot of soup bubbling on the stove.",
"it_gets": "It probably catches the soup's smell far more strongly than a person would, its nose keenly tuned to such scents.",
"why": "Its nose is far keener than a person's, with a large part of its brain devoted to smell."
},
{
"n": 2,
"shift": "sharper",
"channel": "hearing",
"you_notice": "The doorbell ringing.",
"it_gets": "It probably hears the doorbell's ring more keenly than a person does, its hearing sharper overall.",
"why": "It hears high pitches above what a person's ears can reach."
},
{
"n": 3,
"shift": "duller",
"channel": "colour",
"you_notice": "A blue ball under the chair.",
"it_gets": "It probably sees the ball's blue about the same as a person, or a little less vividly.",
"why": "None of its special senses is tuned especially to this blue ball."
},
{
"n": 4,
"shift": "duller",
"channel": "colour",
"you_notice": "A newspaper on the table.",
"it_gets": "It probably takes little notice of the newspaper, since paper means much less to it than to a person.",
"why": "None of its special senses is tuned to a newspaper like this one."
},
{
"n": 5,
"shift": "sharper",
"channel": "smell",
"you_notice": "Muddy boots by the door.",
"it_gets": "It likely catches the mud's scent powerfully, its nose far more sensitive to such smells than a person's.",
"why": "Its nose is far keener than a person's, with much of its brain devoted to smell."
}
],
"try_it": "Close your eyes and try to name a scent from across the room - a dog could likely do this with smells far fainter than you can catch.",
"not_sure": "Scientists are still unsure exactly how much keener a dog's nose is, since estimates vary widely."
}
Build the input yourself
Load the script in node (global.window = global; require("./eyekit.js")), then:
const K = EyeKit;
const form = K.form({ animal: "snake", reader: "kid",
scene: "A mug of hot tea\nA wind chime by the door\nThe dog asleep on the rug\nA bowl of crisps" });
K.things(form); // one thing per line, with the page's shift and channel guess
K.verdictFor(K.counts(K.things(form))); // {verdict: "alien", why: [...]} - the page's rule over its guesses
K.senseCard(K.animal("snake")); // the sense card the model is given
const built = K.buildInput(form);
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 reel 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://other-eyes.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://other-eyes.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"]
// other-eyes.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://other-eyes.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://other-eyes.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
}
// OtherEyes.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 OtherEyes {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://other-eyes.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://other-eyes.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://other-eyes.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://other-eyes.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":"other-eyes"}' | 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": "other-eyes"})["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: "other-eyes" })).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": "other-eyes"}, 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", "other-eyes"), 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: "other-eyes" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "other-eyes"])["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 = "other-eyes" }))!["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": "retell", "animal": "shrimp - Mantis shrimp", "reader": "grown-up (plain words for an adult, still no jargon)", "senses": "Mantis shrimp (a reef crustacean). About 12 kinds of colour receptors - people have 3 - including several for ultraviolet light. Sees polarised light, the direction light waves wiggle, including a kind almost no other animal is known to see. Each eye sits on a stalk and moves on its own, scanning the scene in strips. Still unsure: Lab tests suggest it is worse than people at telling two close colours apart, so it may recognise colours quickly rather than finely. Senses it has that people lack: ultraviolet light, polarised light. Has no sense at all for: nothing on this list.", "scene": "1. A glass of water on the windowsill\n2. My phone screen lit up with a message\n3. A bowl of oranges\n4. A striped beach towel on the chair\n5. A poster of tropical fish on the wall", "guess": "1 | new | polarized\n2 | new | polarized\n3 | duller | motion\n4 | duller | motion\n5 | duller | motion"}
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": "retell", "animal": "shrimp - Mantis shrimp", "reader": "grown-up (plain words for an adult, still no jargon)", "senses": "Mantis shrimp (a reef crustacean). About 12 kinds of colour receptors - people have 3 - including several for ultraviolet light. Sees polarised light, the direction light waves wiggle, including a kind almost no other animal is known to see. Each eye sits on a stalk and moves on its own, scanning the scene in strips. Still unsure: Lab tests suggest it is worse than people at telling two close colours apart, so it may recognise colours quickly rather than finely. Senses it has that people lack: ultraviolet light, polarised light. Has no sense at all for: nothing on this list.", "scene": "1. A glass of water on the windowsill\n2. My phone screen lit up with a message\n3. A bowl of oranges\n4. A striped beach towel on the chair\n5. A poster of tropical fish on the wall", "guess": "1 | new | polarized\n2 | new | polarized\n3 | duller | motion\n4 | duller | motion\n5 | duller | motion"}
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: "retell", animal, reader, senses, scene, guess, clip_note} (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 other-eyes:<task>:<inputHash>:a<attempt> (the one task is retell), 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="other-eyes:retell:$(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 = "other-eyes:" + 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 = `other-eyes:${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 := "other-eyes:" + 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 = "other-eyes:" + 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 = "other-eyes:#{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 = "other-eyes:" . $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 = $"other-eyes:{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: other-eyes:retell:$(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": "retell", "animal": "shrimp - Mantis shrimp", "reader": "grown-up (plain words for an adult, still no jargon)", "senses": "Mantis shrimp (a reef crustacean). About 12 kinds of colour receptors - people have 3 - including several for ultraviolet light. Sees polarised light, the direction light waves wiggle, including a kind almost no other animal is known to see. Each eye sits on a stalk and moves on its own, scanning the scene in strips. Still unsure: Lab tests suggest it is worse than people at telling two close colours apart, so it may recognise colours quickly rather than finely. Senses it has that people lack: ultraviolet light, polarised light. Has no sense at all for: nothing on this list.", "scene": "1. A glass of water on the windowsill\n2. My phone screen lit up with a message\n3. A bowl of oranges\n4. A striped beach towel on the chair\n5. A poster of tropical fish on the wall", "guess": "1 | new | polarized\n2 | new | polarized\n3 | duller | motion\n4 | duller | motion\n5 | duller | motion"}
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "other-eyes:retell:" + 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": `other-eyes:retell:${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", "other-eyes:retell:"+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", "other-eyes:retell:" + 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" => "other-eyes:retell:#{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: other-eyes:retell:" . 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", $"other-eyes:retell:{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);