Drive Aura Print from your own code
The one metered task, read-the-aura, is available over HTTP. It takes one everyday moment, what the person did, what they were going for and five cue ratings (each strong, present, missing, forced or not sure), and returns a rating and a note for every cue, a reading, a caption for the print and one quieter move for next time.
The division of labour first: the points are fixed (strong 30, present 10, missing 0, forced -20) and the reading follows a fixed order from the reply's own ratings. The run keeps every rating you set and chooses one only for cues marked not sure.
The token is minted for this app (the guest call takes {"slug":"aura-print"}), 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, moment, move, aim, cues) is missing. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: the reading is metered and needs a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field, the input and the readings
Every run sends task: read-the-aura (the one task is read-the-aura); a missing or different one is still answered as read-the-aura. All fields are strings.
| Field | Required | What it holds |
|---|---|---|
task | yes | Always read-the-aura. |
moment | yes | What was going on, in the person's words (the page sends at most 280 characters, cut at a word break). |
move | yes | What the person did or said (at most 240 characters). |
aim | yes | What they were going for, or not given. |
cues | yes | Five numbered lines, always in this order: 1. read-the-room | rating: not sure, then own-voice, kind-edge, fresh-material, timing. The rating is strong, present, missing, forced or not sure. |
retry_note | no | Only for a reformat retry: why the previous reply was unusable. |
The reply is one JSON object:
{"verdict": "aura-gained", "headline": "...", "cues": [{"cue": "read-the-room", "rating": "strong", "points": 30, "note": "..."}, ...], "quiet_move": "...", "caption": "...", "last_word": "..."}
| Field | What it holds |
|---|---|
verdict | First that applies: aura-debt (total below 0), aura-farmed (total 100 or more and no cue forced), aura-gained (total 40 or more), aura-even. The total is the sum of the five cues' points. |
headline | A plain reading of the move with the signed total in digits (at most 26 words). |
cues | Five objects in the input's order: cue (the id), rating (a rating you set is copied; for not sure it is judged from the words, and the lower rating wins a tie), points, note (at most 18 words). |
quiet_move | One quieter, kinder or quicker way to handle a similar moment, aimed at the cue with the fewest points (a tie goes to the earlier cue), at most 30 words. |
caption | A title for the print from the input's own nouns (at most 8 words). |
last_word | One line that agrees with the reading (at most 18 words). |
The page then checks the reply (AuraKit.check) and lists any disagreement under "The page's check". Worked examples, one per reading:
The dropped soup - +150 aura, 3 ratings set by you: aura farmed
Input:
{
"task": "read-the-aura",
"moment": "Lunch rush in the office canteen. My tray slipped and a bowl of soup hit the floor right beside the queue for the till.",
"move": "I set the tray down, said \"well, that's my soup tasted\", fetched paper towels from the counter and wiped it up before anyone else had to.",
"aim": "Nothing much - just getting it cleaned up.",
"cues": "1. read-the-room | rating: strong\n2. own-voice | rating: not sure\n3. kind-edge | rating: strong\n4. fresh-material | rating: not sure\n5. timing | rating: strong"
}
Reply:
{
"verdict": "aura-farmed",
"headline": "Spilling the soup and clearing it up fast, with a light touch throughout, lands a strong +150.",
"cues": [
{
"cue": "read-the-room",
"rating": "strong",
"points": 30,
"note": "Soup spilled beside the till queue during the lunch rush, and clearing it up matched that exactly."
},
{
"cue": "own-voice",
"rating": "strong",
"points": 30,
"note": "Setting the tray down and clearing up stayed plain and understated, with nothing suggesting a performance."
},
{
"cue": "kind-edge",
"rating": "strong",
"points": 30,
"note": "Wiping up the spill before anyone else had to help kept the joke only on themself."
},
{
"cue": "fresh-material",
"rating": "strong",
"points": 30,
"note": "The quip names the soup itself, a specific detail from what had just happened, not a stock line."
},
{
"cue": "timing",
"rating": "strong",
"points": 30,
"note": "The spill was dealt with right away, with paper towels fetched and everything wiped up without delay."
}
],
"quiet_move": "Pause to glance at how busy the queue is before clearing a spill, keeping your reading the room sharp.",
"caption": "Tray, Soup, and the Lunch Rush Queue",
"last_word": "The whole clean-up came across as effortless, nothing about it looked like hard work."
}
The forgotten snacks - +80 aura, 3 ratings set by you: aura gained
Input:
{
"task": "read-the-aura",
"moment": "Book club at Dana's flat. Dana realised she had forgotten to buy snacks and kept apologising while we took our coats off.",
"move": "I said I'd been meaning to try the corner shop's crisps anyway, popped out to get some, and asked who had finished the book.",
"aim": "Help Dana stop feeling bad.",
"cues": "1. read-the-room | rating: not sure\n2. own-voice | rating: present\n3. kind-edge | rating: not sure\n4. fresh-material | rating: present\n5. timing | rating: missing"
}
Reply:
{
"verdict": "aura-gained",
"headline": "Fetching snacks and asking about the book redirected Dana's apology into something easy and generous. +80.",
"cues": [
{
"cue": "read-the-room",
"rating": "strong",
"points": 30,
"note": "Dana had forgotten snacks and kept apologising; getting crisps answered exactly what was happening."
},
{
"cue": "own-voice",
"rating": "present",
"points": 10,
"note": "Saying you'd been meaning to try the crisps anyway sounds like an ordinary, natural remark."
},
{
"cue": "kind-edge",
"rating": "strong",
"points": 30,
"note": "Saying you'd been meaning to try the crisps covered for Dana while you fetched snacks."
},
{
"cue": "fresh-material",
"rating": "present",
"points": 10,
"note": "Buying crisps from the corner shop is a natural choice, not pulled from a stock line."
},
{
"cue": "timing",
"rating": "missing",
"points": 0,
"note": "The words don't say how quickly you left or returned, or how long it took."
}
],
"quiet_move": "Keep the errand brief and mention it in passing so the timing stays tight next time.",
"caption": "Dana's Snacks and Corner Shop Crisps",
"last_word": "This finished net positive and didn't cost Dana anything in the moment."
}
The fun-fact circle - +20 aura, 3 ratings set by you: aura even
Input:
{
"task": "read-the-aura",
"moment": "First evening of a pottery class. The teacher asked each of us to share a fun fact about ourselves.",
"move": "I gave the fact I always use at work introductions, about owning too many houseplants, and rushed it because I was nervous.",
"aim": "Just to get through it.",
"cues": "1. read-the-room | rating: present\n2. own-voice | rating: present\n3. kind-edge | rating: not sure\n4. fresh-material | rating: forced\n5. timing | rating: not sure"
}
Reply:
{
"verdict": "aura-even",
"headline": "Reusing your usual work fact under nerves nets +20: fits the moment fine but leans on an old line instead of something new.",
"cues": [
{
"cue": "read-the-room",
"rating": "present",
"points": 10,
"note": "Sharing a fun fact when the teacher asked fits what the moment called for."
},
{
"cue": "own-voice",
"rating": "present",
"points": 10,
"note": "Giving the fact you always use at work sounds like your own easy answer, not a performance."
},
{
"cue": "kind-edge",
"rating": "present",
"points": 10,
"note": "Sharing a houseplant fact about yourself doesn't take anything away from anyone else in the class."
},
{
"cue": "fresh-material",
"rating": "forced",
"points": -20,
"note": "The houseplant fact is the one you always use at work, not made for this class."
},
{
"cue": "timing",
"rating": "present",
"points": 10,
"note": "Giving the fact right when asked, though rushed from nerves, still answered in time."
}
],
"quiet_move": "Notice one small, real detail from the pottery class itself, and offer that as your fresh material next time instead of your usual work fact.",
"caption": "Pottery Class Houseplant Fact",
"last_word": "This one roughly balanced out: it fit the moment but leaned on a fact used elsewhere while nervous."
}
The leaving-do roast - -100 aura, 1 rating set by you: aura debt
Input:
{
"task": "read-the-aura",
"moment": "Leaving drinks for Sam at the pub after work. People were taking turns to say a few kind words.",
"move": "I read a roast from my phone that I had written the night before, mostly about the spreadsheet mistake Priya made last spring, and kept going after the table went quiet.",
"aim": "To be the funny one everyone talks about afterwards.",
"cues": "1. read-the-room | rating: not sure\n2. own-voice | rating: not sure\n3. kind-edge | rating: forced\n4. fresh-material | rating: not sure\n5. timing | rating: not sure"
}
Reply:
{
"verdict": "aura-debt",
"headline": "Reading a prepared roast about Priya's old spreadsheet mistake during Sam's leaving drinks, then continuing after the table went quiet, totals -100.",
"cues": [
{
"cue": "read-the-room",
"rating": "forced",
"points": -20,
"note": "People were saying kind words, but the roast kept going after the table went quiet."
},
{
"cue": "own-voice",
"rating": "forced",
"points": -20,
"note": "Written the night before and aimed at being the funny one afterwards, not a spontaneous voice."
},
{
"cue": "kind-edge",
"rating": "forced",
"points": -20,
"note": "The roast singled out Priya's old mistake instead of helping or crediting anyone at the table."
},
{
"cue": "fresh-material",
"rating": "forced",
"points": -20,
"note": "The roast was written the night before, a prepared line rather than material made for this event."
},
{
"cue": "timing",
"rating": "forced",
"points": -20,
"note": "The roast kept going after the table went quiet, instead of stopping once that happened."
}
],
"quiet_move": "Read the room before you start, and offer Sam a few kind words instead of a prepared story about someone else's old mistake.",
"caption": "The Roast That Replaced the Kind Words",
"last_word": "This one cost more aura than it earned, plain and simple."
}
Build the input yourself
Load the engine in node (global.window = global; require("./aurakit.js")), then:
const K = AuraKit;
const built = K.buildInput(K.form({
moment: "The printer jammed during the team meeting and the room went quiet.",
move: "I opened the tray, pulled the page out and handed it round.",
aim: "", ratings: { timing: "strong" } }));
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, "my moment") // the reading 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://aura-print.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://aura-print.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"]
// aura-print.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://aura-print.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://aura-print.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// WhatWasThat.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class WhatWasThat {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://aura-print.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://aura-print.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://aura-print.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://aura-print.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":"aura-print"}' | 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": "aura-print"})["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: "aura-print" })).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": "aura-print"}, 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", "aura-print"), 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: "aura-print" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "aura-print"])["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 = "aura-print" }))!["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": "read-the-aura", "moment": "...", "move": "...", "aim": "not given", "cues": "1. read-the-room | rating: not sure\n2. ..."}
# - 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": "read-the-aura", "moment": "...", "move": "...", "aim": "not given", "cues": "1. read-the-room | rating: not sure\n2. ..."}
# - 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: "read-the-aura", moment, move, aim, cues} (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 aura-print:<task>:<inputHash>:a<attempt> (the one task is read-the-aura), 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="aura-print:read-the-aura:$(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 = "aura-print:" + 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 = `aura-print:${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 := "aura-print:" + 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 = "aura-print:" + 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 = "aura-print:#{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 = "aura-print:" . $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 = $"aura-print:{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: aura-print:read-the-aura:$(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": "read-the-aura", "moment": "...", "move": "...", "aim": "not given", "cues": "1. read-the-room | rating: not sure\n2. ..."}
# - 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": "aura-print:read-the-aura:" + 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": `aura-print:read-the-aura:${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", "aura-print:read-the-aura:"+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", "aura-print:read-the-aura:" + 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" => "aura-print:read-the-aura:#{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: aura-print:read-the-aura:" . 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", $"aura-print:read-the-aura:{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);