Drive Zap Log from your own code
The one metered task, zap, is available over HTTP. It takes 2 to 8 logged static shocks - what zapped the person, what they did just before and what they were wearing - plus how the indoor air feels and the page's own keyword read. It returns a habit label for each zap with what rubbed and why it sparked, the pattern across the log, five whys down to one root cause, one fix to test by changing one thing, a touch-first habit for meanwhile, and a verdict (one-culprit, touch-first or not-static) - with a safety note instead of a fix when anything plugged in or tingly shows up.
The division of labour first: the page reads keywords for habits, materials and safety words and applies a fixed rule; the model reads every zap properly and does the debugging; the page then re-checks the verdict against the model's own habit labels, the zap count and order, its own safety words, the closing count and the word limits. Build zaps 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":"zap-log"}), 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, count, air, zaps, 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: debugging a log 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 habits and the verdicts
Every run sends task: zap (the one task is zap); a missing or different one is still answered as zap. Each zap gets one habit from its "just before" part: carpet-walk, seat-slide, chair-roll, chair-rise, top-off, blanket, hair-brush, trolley-push or other. The verdict is checked in this order, stopping at the first match: not-static (any zap names something plugged in, switched on, charging or wired, or a tingle or buzz instead of one snap), one-culprit (one habit other than other is on at least 3 in 5 of the zaps and on at least 2), touch-first (every other case).
Input (every field is a string)
| Field | Required | What it holds |
|---|---|---|
task | yes | Always zap. |
count | yes | How many zaps, 2 to 8, as digits. |
air | yes | feels dry, feels normal, feels humid or not sure. |
zaps | yes | Numbered lines, one per zap: 1. touched: car door | just before: slid out of the driver's seat | wearing: wool coat, rubber-soled boots. An empty part is written (blank). |
page_read | yes | The page's keyword read, one numbered line per zap: 1. habit guess: seat-slide; materials: wool, rubber; safety word: none. A hint only - the model reads each zap itself. |
retry_note | no | Only on the page's one reformat retry, after an unusable reply. |
Reply (one JSON object)
{"task": "zap", "verdict": "one-culprit", "reproduce": "...",
"zaps": [{"n": 1, "habit": "seat-slide", "charge": "...", "spark": "..."}, ...],
"whys": ["...", "...", "...", "...", "..."], "fix": "...", "isolate": "...", "fallback": "...", "safety": "", "closing": "..."}
| Field | What it holds |
|---|---|
verdict | one-culprit, touch-first or not-static, by the rule above. |
reproduce | The pattern across the zaps, at most 30 words. |
zaps | Exactly count entries in input order. Each: n, habit (one id from the list), charge (what rubbed, at most 20 words) and spark (why it jumped there, at most 14 words). |
whys | Exactly 5 steps of at most 16 words down to one root cause; empty for not-static. |
fix, isolate, fallback | One root-cause fix (at most 30 words), how to test it by changing only that (at most 30), and a touch-first habit for meanwhile (at most 25); all empty for not-static. |
safety | For not-static only: stop using the item and have a qualified electrician check it, at most 30 words. |
closing | One sentence of at most 20 words restating the verdict; digits only as "N of M". |
The page then checks the reply (ZapKit.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 - car-park season (one culprit, 3 of 4), office carpet (one culprit at exactly 3 of 5), zapped everywhere (touch first at 2 of 5) and a tingly lamp (not static, a safety stop).
Car-park season - one culprit
Input:
{
"task": "zap",
"count": "4",
"air": "feels dry",
"zaps": "1. touched: car door | just before: slid out of the driver's seat | wearing: wool coat, rubber-soled boots\n2. touched: car door | just before: got out of the passenger seat | wearing: fleece jacket, trainers\n3. touched: car door handle | just before: slid across the back seat to get out | wearing: polyester leggings, trainers\n4. touched: office door handle | just before: walked across the office carpet | wearing: wool coat, rubber-soled boots",
"page_read": "1. habit guess: seat-slide; materials: wool, rubber, seat fabric (often polyester); safety word: none\n2. habit guess: seat-slide; materials: rubber, polyester, seat fabric (often polyester); safety word: none\n3. habit guess: seat-slide; materials: rubber, polyester; safety word: none\n4. habit guess: carpet-walk; materials: carpet (often nylon), wool, rubber; safety word: none"
}
Reply:
{
"task": "zap",
"verdict": "one-culprit",
"reproduce": "Most zaps happen sliding out of or across a car seat, then touching the car door or its handle; the rest follows walking the office carpet to a handle.",
"zaps": [
{
"n": 1,
"habit": "seat-slide",
"charge": "Sliding out of the driver's seat rubbed your wool coat against it; the seat material isn't stated.",
"spark": "The charge jumped to the car door as you touched it."
},
{
"n": 2,
"habit": "seat-slide",
"charge": "Getting out of the passenger seat rubbed your fleece jacket against it; the seat material isn't stated.",
"spark": "The charge jumped there the moment you touched the car door."
},
{
"n": 3,
"habit": "seat-slide",
"charge": "Sliding across the back seat rubbed your polyester leggings against it; the seat material isn't stated.",
"spark": "The charge jumped to the car door handle when you touched it."
},
{
"n": 4,
"habit": "carpet-walk",
"charge": "Walking across the office carpet rubbed your rubber-soled boots against it; the carpet material isn't stated.",
"spark": "The charge jumped to the office door handle you touched."
}
],
"whys": [
"You got a shock because touching the car door or its handle let the charge jump.",
"Charge built up because your clothing rubbed against the car seat as you got out.",
"Rubbing different fabrics together like that builds up a static charge.",
"The dry air you described let more of that charge build up.",
"You never let the charge drain away before your hand meets the door."
],
"fix": "Keep a hand on the car's metal body as you get out, so the charge drains before you touch the door.",
"isolate": "For your next few times getting out of the car, keep your hand on the metal body and note whether you still feel a shock at the door.",
"fallback": "Meanwhile, touch the car door first with a metal key or coin, or your palm, rather than your fingertips.",
"safety": "",
"closing": "3 of 4 zaps trace back to sliding out of a car seat, so that's your main culprit."
}
Office carpet - one culprit at exactly 3 of 5
Input:
{
"task": "zap",
"count": "5",
"air": "feels normal",
"zaps": "1. touched: metal filing cabinet | just before: walked across the office carpet | wearing: nylon tights, leather shoes\n2. touched: lift button | just before: walked down the carpeted corridor | wearing: wool skirt, leather shoes\n3. touched: a colleague's hand | just before: rolled my chair back from the desk | wearing: polyester blouse\n4. touched: kitchen tap | just before: crossed the carpet to the kitchen | wearing: nylon tights, leather shoes\n5. touched: laptop's metal edge | just before: pulled off a cardigan | wearing: acrylic cardigan",
"page_read": "1. habit guess: carpet-walk; materials: leather, nylon, carpet (often nylon); safety word: none\n2. habit guess: carpet-walk; materials: leather, carpet (often nylon), wool; safety word: none\n3. habit guess: chair-roll; materials: polyester; safety word: none\n4. habit guess: carpet-walk; materials: leather, nylon, carpet (often nylon); safety word: none\n5. habit guess: top-off; materials: acrylic; safety word: none"
}
Reply:
{
"task": "zap",
"verdict": "one-culprit",
"reproduce": "Most of your shocks happened after walking across carpet - the office carpet, a corridor or the kitchen carpet - just before you touched something.",
"zaps": [
{
"n": 1,
"habit": "carpet-walk",
"charge": "Your leather shoes rubbed the office carpet (material not stated) as you walked, building charge.",
"spark": "Charge jumped to the metal filing cabinet, which conducts electricity readily."
},
{
"n": 2,
"habit": "carpet-walk",
"charge": "Your leather shoes rubbed the carpeted corridor floor (material not stated) as you walked, building charge.",
"spark": "Charge jumped to the lift button when your finger got close enough."
},
{
"n": 3,
"habit": "chair-roll",
"charge": "Your polyester blouse rubbed against the chair seat (material not stated) as you rolled back, building charge.",
"spark": "Charge jumped from you to your colleague's hand on contact."
},
{
"n": 4,
"habit": "carpet-walk",
"charge": "Your leather shoes rubbed the carpet (material not stated) as you crossed to the kitchen, building charge.",
"spark": "Charge jumped to the kitchen tap as your finger approached it."
},
{
"n": 5,
"habit": "top-off",
"charge": "Your acrylic cardigan rubbed against you as you pulled it off, building charge.",
"spark": "Charge jumped to the laptop's metal edge as your hand got close."
}
],
"whys": [
"You got a shock because the charge on you jumped into what you touched.",
"That charge jumped because enough of it had built up with nowhere to go.",
"Charge built up because your leather shoes rubbed against the carpet as you walked.",
"The rubbing charges you because leather soles and carpet are different materials.",
"The root cause is walking across carpet in leather shoes without letting that charge drain."
],
"fix": "Try anti-static spray on the carpet or your shoe soles to cut the charge that builds as you walk.",
"isolate": "For your next several carpet walks, use the spray and note each time whether you still get a shock when you touch something.",
"fallback": "Meanwhile, before touching metal, first touch a key or coin, or press your palm flat on a wall or non-metal surface.",
"safety": "",
"closing": "Most of your shocks, 3 of 5, happened after walking across carpet before you touched something."
}
Zapped everywhere - touch first
Input:
{
"task": "zap",
"count": "5",
"air": "feels dry",
"zaps": "1. touched: shopping trolley | just before: pushed the trolley round the shop | wearing: puffer jacket, trainers\n2. touched: front door handle | just before: pulled off a fleece hoodie | wearing: fleece hoodie, cotton jeans\n3. touched: light switch | just before: got up from the sofa | wearing: fleece pyjamas\n4. touched: car door | just before: slid out of the driver's seat | wearing: puffer jacket, trainers\n5. touched: car door | just before: slid out of the driver's seat | wearing: wool jumper, cotton jeans",
"page_read": "1. habit guess: trolley-push; materials: rubber; safety word: none\n2. habit guess: top-off; materials: cotton, polyester; safety word: none\n3. habit guess: chair-rise; materials: polyester; safety word: none\n4. habit guess: seat-slide; materials: rubber, seat fabric (often polyester); safety word: none\n5. habit guess: seat-slide; materials: wool, cotton, seat fabric (often polyester); safety word: none"
}
Reply:
{
"task": "zap",
"verdict": "touch-first",
"reproduce": "You get zapped after pushing a trolley, pulling off a fleece hoodie, rising from the sofa, or sliding out of the car seat, right as you touch something.",
"zaps": [
{
"n": 1,
"habit": "trolley-push",
"charge": "Your trainers rubbed against the floor as you pushed the trolley round the shop; the floor's material isn't stated.",
"spark": "It jumped to the shopping trolley when your hand touched it."
},
{
"n": 2,
"habit": "top-off",
"charge": "Pulling the fleece hoodie off rubbed it against your cotton jeans, building charge.",
"spark": "It jumped to the front door handle when your hand gripped it."
},
{
"n": 3,
"habit": "chair-rise",
"charge": "Your fleece pyjamas rubbed against the sofa as you got up; the sofa's fabric isn't stated.",
"spark": "The charge jumped to the light switch when your finger reached it."
},
{
"n": 4,
"habit": "seat-slide",
"charge": "Your puffer jacket rubbed against the car seat as you slid out; the seat's fabric isn't stated.",
"spark": "It jumped to the car door when your hand touched it."
},
{
"n": 5,
"habit": "seat-slide",
"charge": "As you slid out, your wool jumper rubbed against the car seat; the seat's fabric isn't stated.",
"spark": "The charge jumped to the car door as your hand reached it."
}
],
"whys": [
"You got a shock because built-up charge jumped from your body to what you touched.",
"That charge built up as your clothes and shoes rubbed while you pushed, sat or dressed.",
"You pushed a trolley, pulled off a hoodie, rose from a sofa, slid from a seat.",
"The air feels dry, letting static charge build on you instead of draining away.",
"The root cause is your everyday clothes and footwear holding that charge until you touch something."
],
"fix": "Switch to a different pair of shoes, so the footwear in most of these zaps changes, and see whether the shocks change.",
"isolate": "Wear a different pair of shoes for your next few zaps and note each time whether you still feel a shock.",
"fallback": "Before touching metal with your fingertips, touch it first with a key, a coin, or your palm on a wall or other surface.",
"safety": "",
"closing": "No one habit stands out; the closest is sliding from the car seat, which happened 2 of 5 times."
}
A tingly lamp - not static
Input:
{
"task": "zap",
"count": "3",
"air": "feels normal",
"zaps": "1. touched: metal desk lamp - a tingle, not a snap | just before: turned it on at the base | wearing: socks\n2. touched: car door | just before: slid out of the driver's seat | wearing: wool coat\n3. touched: front door handle | just before: walked across the hall rug in slippers | wearing: fleece dressing gown",
"page_read": "1. habit guess: other; materials: none recognised; safety word: lamp\n2. habit guess: seat-slide; materials: wool, seat fabric (often polyester); safety word: none\n3. habit guess: carpet-walk; materials: polyester; safety word: none"
}
Reply:
{
"task": "zap",
"verdict": "not-static",
"reproduce": "You touched a desk lamp after turning it on, a car door after sliding out of your seat, and a door handle after walking across a rug.",
"zaps": [
{
"n": 1,
"habit": "other",
"charge": "This is not static - you felt a tingle, not a snap, after turning the lamp on at its base.",
"spark": "Stop touching the metal desk lamp."
},
{
"n": 2,
"habit": "seat-slide",
"charge": "Your wool coat rubbed against the car seat as you slid out; the seat material is not stated.",
"spark": "The built-up charge discharged the moment your hand reached the car door."
},
{
"n": 3,
"habit": "carpet-walk",
"charge": "Your slippers rubbed against the hall rug as you walked across it; no material is stated.",
"spark": "The charge jumped out as soon as you touched the door handle."
}
],
"whys": [],
"fix": "",
"isolate": "",
"fallback": "",
"safety": "Stop using the metal desk lamp, switch it off at the wall only if it is safe, and have a qualified electrician check it.",
"closing": "Something you touched was switched on, not actually static electricity."
}
Build the input yourself
Load the engine in node (global.window = global; require("./zapkit.js"); require("./zapdraw.js")), then:
const K = ZapKit, D = ZapDraw;
const built = K.buildInput({ air: "dry", zaps: [
{ touched: "car door", before: "slid out of the driver's seat", wearing: "wool coat" },
{ touched: "car door", before: "got out of the passenger seat", wearing: "fleece jacket" }] });
built.prescan.habits; // ["seat-slide", "seat-slide"] - the page's keyword read
built.prescan.verdict; // "one-culprit" - 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 debugged log 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://zap-log.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://zap-log.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"]
// zap-log.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://zap-log.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://zap-log.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
}
// ZapLog.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 ZapLog {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://zap-log.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://zap-log.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://zap-log.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://zap-log.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":"zap-log"}' | 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": "zap-log"})["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: "zap-log" })).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": "zap-log"}, 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", "zap-log"), 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: "zap-log" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "zap-log"])["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 = "zap-log" }))!["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": "zap", "count": "4", "air": "feels dry", "zaps": "1. touched: car door | just before: ...
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": "zap", "count": "4", "air": "feels dry", "zaps": "1. touched: car door | just before: ...
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: "zap", count, air, zaps, 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 zap-log:<task>:<inputHash>:a<attempt> (the one task is zap), 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="zap-log:zap:$(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 = "zap-log:" + 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 = `zap-log:${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 := "zap-log:" + 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 = "zap-log:" + 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 = "zap-log:#{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 = "zap-log:" . $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 = $"zap-log:{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: zap-log:zap:$(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": "zap", "count": "4", "air": "feels dry", "zaps": "1. touched: car door | just before: ...
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": "zap-log:zap:" + 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": `zap-log:zap:${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", "zap-log:zap:"+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", "zap-log:zap:" + 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" => "zap-log:zap:#{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: zap-log:zap:" . 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", $"zap-log:zap:{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);