Drive Was Price from your own code
The metered task is available over HTTP. A natural use is a price-tracking spreadsheet, a deals newsletter or a group-chat bot that already holds an item's price history: your code sends the deal, the printed 'was' price, the prices recorded before and the promo text, and gets back a verdict (buy_now, wait or skip), the honest discount against a price the store really charged, a rating of the 'was' price, every pressure tactic in the promo decoded, a target price to wait for and a walk-away price.
The split first: the page does the arithmetic; the model judges and writes; the page checks it. The facts (the lowest price recorded, the lowest in the 30 days before the sale, the 90-day median, how often the 'was' price was really charged, whether the price climbed in the 30 days before the sale, the pressure tactics found in the promo text) come from dealkit.js, which runs in node as well as in the browser. Its checkReply checks a verdict before it is shown: a discount that matches the page's own numbers, quotes that are really in the promo text, a target below today's price, a walk-away price that agrees with the verdict, and citations that exist. The thresholds are rules of thumb. Was Price never looks up a price anywhere: every price is one the caller supplies.
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":"was-price"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare: never wrap it as {"input": {…}}.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or task 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 verdict task 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 comes first
Every run sends task. There is one task, verdict; a missing or unknown task is treated as verdict.
| task | Does | Reply |
|---|---|---|
verdict | Judges whether one sale price is a real deal for this shopper, decodes the promo text and says what to do. | verdict (buy_now - at or within 5% of the lowest recorded price with a real discount, under the cap, needed or wanted; wait - wanted, but it has sold for less, the price was raised before the sale, or the discount is only real against a padded reference; skip - the sale price is the everyday price, it is over the cap, or it is an impulse dressed up by pressure tactics), verdict_why, title, honest_discount {percent, against, note, cite} with against one of 30-day low, 90-day median, lowest recorded, claimed was price, anchor {rating, note, cite} with rating one of honest, stretched, fiction, unknown, tactics [{id, pressure, decoded, counter, cite}] (one per tactic the page found, plus up to two with id: "" and a verbatim quote), wait_plan {target_price, watch, recheck, cite} or null, walk_away_price, checks, caveats. |
Cites are the fact ids in facts. The declared input schema carries scalars only, so object-valued fields travel JSON-encoded as strings (DealKit.toWire does this). Only task is required by the schema.
Input fields
| Field | Type | Notes |
|---|---|---|
task | string | verdict. |
item | string | What is on sale, in the shopper's words; may be empty. |
store | string | Where, in the shopper's words (for example online marketplace). |
currency | string | A symbol or code, e.g. $. |
deal_price | number | Today's price. |
was_price | number | The printed reference price; 0 when there is none. |
seen | string | The date the deal was seen, YYYY-MM-DD. |
ends | string | When the promo says it ends, in its own words; may be empty. |
need | string | need, want or impulse. |
cap | number | The most the shopper would pay; 0 when not set. |
copy | string | The promo text as shown; past 4,000 characters the middle is cut around a marker. |
prices | string | The earlier prices from the year before the deal, one per line: 2026-09-25 | 119.99 | online marketplace | note. |
facts | string (JSON) | [{id, text}] from DealKit.facts: F-deal, F-history, F-low, F-30d, F-median, F-anchor, F-cap, F-need, and F-hike, F-ends, F-sparse, F-nocopy and F-T1 … (one per pressure tactic) when they apply. |
retry_note | string | Only on a reformat retry. |
The reply
The job's output.output is a string holding one JSON object. Parse it with DealKit.parseReply, then normalize(reply), then checkReply(reply, deal), which returns {notes, analysis} - the notes (ok, warn, bad) that the page shows under "Checked against the ladder".
Worked examples
Replies produced against the app's prompt for the sample deals (items, stores and prices are made up). Between them they cover all three verdicts. The page replays each of them free from its sample buttons; the full replies ship in example-result.js. Long fields are folded.
task: verdict - Robot vacuum - buy_now
Input:
{
"task": "verdict",
"item": "Robot vacuum with mop pad",
"store": "big-box store",
"currency": "$",
"deal_price": 199,
"was_price": 349,
"seen": "2026-10-19",
"ends": "Sunday",
"need": "need",
"cap": 250,
"copy": "Early Black Friday deal! Was $349, now $199.\nDeal ends Sunday. Free returns within 30 days.",
"prices": "2026-04-18 | 349 | big-box store | launch price\n2026-05-30 | 299 | big-box store\n... 6 more lines",
"facts": "[{\"id\": \"F-deal\", \"text\": \"Deal: $199 at big-box store, seen 2026-10-19; the store's 'was' price is $349 - 43% off as advertised.\"}, {\"id\": \"F-history\", \"text\": \"8 earlier prices recorded, 2026-04-18 to 2026-10-12 (5 in the 90 days before the deal, 3 in the last 30).\"}, {\"id\": \"F-low\", \"text\": \"Lowest price recorded: $229 on 2026-07-11 at online marketplace. The deal is $30 (13%) below it.\"}, \"... 9 more facts\"]"
}
Reply:
{
"task": "verdict",
"verdict": "buy_now",
"verdict_why": "At $199 you're $30 below the lowest price ever recorded for this item and $51 under your $250 cap, and you marked it a need - that's as good as this item gets no matter what the 'was' price claims.",
"title": "Robot vacuum with mop pad",
"honest_discount": {
"percent": 29,
"against": "30-day low",
"note": "Against the lowest price seen in the 30 days before the sale, $199 is a real 29% cut, well short of the store's claimed 43% off.",
"cite": [
"F-30d",
"F-deal"
]
},
"anchor": {
"rating": "stretched",
"note": "The $349 'was' price only shows up once, at the launch price back in April, and never appears in the 90 days before this sale.",
"cite": [
"F-anchor"
]
},
"tactics": [
{
"id": "T1",
"pressure": "medium",
"decoded": "The countdown wants you to decide before Sunday instead of comparing prices. It doesn't change the fact that $199 is already below every price on record for this item.",
"counter": "Decide on the price itself, not the clock - you don't need to rush a price this good.",
"cite": [
"F-T1",
"F-deal"
]
},
{
"id": "T2",
"pressure": "high",
"decoded": "Quoting $349 as the 'was' price wants $199 to look like a massive cut. That price was only charged once, at launch, and hasn't shown up again in the months before this sale.",
"counter": "Judge the deal against the recent prices in the $270s-$290s, not the old $349 tag.",
"cite": [
"F-T2",
"F-anchor"
]
},
"... 1 more"
],
"wait_plan": null,
"walk_away_price": 250,
"checks": [
"Confirm the robot vacuum with mop pad at checkout is the exact model behind the prices you've seen before, not a different variant.",
"Check that the 'free returns within 30 days' mentioned in the promo really applies to this item before you buy.",
"... 1 more"
],
"caveats": [
"The store's 43% off claim is measured against the $349 price, which was only charged once, months before this sale."
]
}
task: verdict - Air fryer - wait
Input:
{
"task": "verdict",
"item": "6-quart air fryer",
"store": "online marketplace",
"currency": "$",
"deal_price": 89.99,
"was_price": 159.99,
"seen": "2026-10-19",
"ends": "in 2 hours",
"need": "want",
"cap": 90,
"copy": "Flash deal - ends in 02:14:33.\nOnly 4 left at this price! Compare at $159.99.\nEarly Black Friday pricing, 44% off.",
"prices": "2025-11-28 | 79.99 | online marketplace | last Black Friday\n2026-01-10 | 99.99 | online marketplace\n... 6 more lines",
"facts": "[{\"id\": \"F-deal\", \"text\": \"Deal: $89.99 at online marketplace, seen 2026-10-19; the store's 'was' price is $159.99 - 44% off as advertised.\"}, {\"id\": \"F-history\", \"text\": \"8 earlier prices recorded, 2025-11-28 to 2026-10-08 (4 in the 90 days before the deal, 2 in the last 30).\"}, {\"id\": \"F-low\", \"text\": \"Lowest price recorded: $79.99 on 2025-11-28 at online marketplace. The deal is $10 (13%) above it.\"}, \"... 11 more facts\"]"
}
Reply:
{
"task": "verdict",
"verdict": "wait",
"verdict_why": "This air fryer sold for $79.99 as recently as last Black Friday, 13% below today's $89.99 price, and the price climbed 22% in the 30 days before this sale. The $159.99 'was' price was never actually charged, so the 44% off claim isn't measured against a real price.",
"title": "6-Quart Air Fryer",
"honest_discount": {
"percent": 25,
"against": "30-day low",
"note": "Against the $119.99 low price from the 30 days before the sale, $89.99 is a real 25% discount.",
"cite": [
"F-30d"
]
},
"anchor": {
"rating": "fiction",
"note": "The $159.99 'was' price never shows up in the recorded prices - not in the 90 days before, and not in the year before.",
"cite": [
"F-anchor"
]
},
"tactics": [
{
"id": "T1",
"pressure": "medium",
"decoded": "The countdown wants you to decide before you have time to compare prices, as if the deal is about to vanish.",
"counter": "Pause and check the recorded prices before the clock runs out.",
"cite": [
"F-T1"
]
},
{
"id": "T2",
"pressure": "medium",
"decoded": "'Only 4 left' wants you to fear missing out, but there's no way to confirm the real stock count from here.",
"counter": "Don't let a stock claim rush you - judge the price on its own.",
"cite": [
"F-T2"
]
},
"... 2 more"
],
"wait_plan": {
"target_price": 79.99,
"watch": "Watch for the price to drop back to $79.99, which is what it sold for last Black Friday.",
"recheck": "Check again when the real Black Friday sale this 'early' pricing is previewing arrives.",
"cite": [
"F-low",
"F-hike",
"F-30d"
]
},
"walk_away_price": 79.99,
"checks": [
"Make sure this is the same 6-quart size as the one that sold for $79.99 before.",
"Check whether the 'only 4 left' stock count is something you can verify before paying.",
"... 1 more"
],
"caveats": [
"Only 2 of the 8 recorded prices are from the last 30 days, so the 30-day low is based on limited history."
]
}
task: verdict - Smartwatch - skip
Input:
{
"task": "verdict",
"item": "Fitness smartwatch, 41 mm",
"store": "the maker's own site",
"currency": "$",
"deal_price": 279,
"was_price": 399,
"seen": "2026-10-19",
"ends": "today only",
"need": "impulse",
"cap": 200,
"copy": "LOWEST PRICE OF THE YEAR! Compare at $399, now $279.\nFree sport band ($49 value) - today only. Selling fast.\nOr pay in 4 payments of $69.75.",
"prices": "2026-05-14 | 279 | the maker's own site\n2026-06-21 | 279 | the maker's own site\n... 5 more lines",
"facts": "[{\"id\": \"F-deal\", \"text\": \"Deal: $279 at the maker's own site, seen 2026-10-19; the store's 'was' price is $399 - 30% off as advertised.\"}, {\"id\": \"F-history\", \"text\": \"7 earlier prices recorded, 2026-05-14 to 2026-10-11 (4 in the 90 days before the deal, 2 in the last 30).\"}, {\"id\": \"F-low\", \"text\": \"Lowest price recorded: $259 on 2026-07-19 at online marketplace. The deal is $20 (8%) above it.\"}, \"... 12 more facts\"]"
}
Reply:
{
"task": "verdict",
"verdict": "skip",
"verdict_why": "At $279 this is the price the maker's own site has charged repeatedly since May, not a special low, and it is $79 over your $200 cap. The $399 it is compared against was never actually charged, and you marked this as an impulse buy, so the pressure around it is not backed by a real deal.",
"title": "Fitness smartwatch",
"honest_discount": {
"percent": 0,
"against": "30-day low",
"note": "The deal matches the lowest price charged in the 30 days before the sale, so there is no real discount against the most recent price the store actually used.",
"cite": [
"F-30d"
]
},
"anchor": {
"rating": "fiction",
"note": "The $399 'was' price was never recorded in the 90 days before the sale or anywhere in the year of history shown.",
"cite": [
"F-anchor"
]
},
"tactics": [
{
"id": "T1",
"pressure": "medium",
"decoded": "Wants a fast yes before you can compare; this same price has shown up repeatedly over the past several months, so 'today only' is about urgency, not a one-time price.",
"counter": "Check the price history before deciding - a price that repeats often isn't a one-day event.",
"cite": [
"F-T1",
"F-30d"
]
},
{
"id": "T2",
"pressure": "medium",
"decoded": "Wants fear of missing out to skip the comparison step; nothing in the recorded facts can confirm stock is actually low.",
"counter": "Don't let a stock claim rush the decision - weigh the price and your cap first.",
"cite": [
"F-T2"
]
},
"... 4 more"
],
"wait_plan": null,
"walk_away_price": 200,
"checks": [
"Confirm this listing is the same 41 mm model as the prices you've recorded, not a different size.",
"Check the 4-payment plan for any added fee - the four payments alone total the full $279.",
"... 1 more"
]
}
Build the input yourself
Load the script in node (global.window = {}; require("./dealkit.js")), then:
const K = DealKit;
const { deal } = K.parseDeal([
"Item: 6-quart air fryer", "Deal: 89.99", "Was: 159.99", "Seen: 2026-10-19", "Need: want", "Cap: 90",
"Promo: Flash deal - ends in 02:14:33. Compare at $159.99.",
"2026-08-05 | 99.99 | online marketplace", "2026-10-08 | 129.99 | online marketplace"
].join("\n"));
const { input } = K.buildInput(deal);
const wire = K.toWire(input); // POST this to /estimate and /run
// later: const n = K.normalize(K.parseReply(job.output.output));
// const { notes } = K.checkReply(n, deal);
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://was-price.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://was-price.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"]
// was-price.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://was-price.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://was-price.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
}
// WasPrice.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 WasPrice {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://was-price.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://was-price.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://was-price.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://was-price.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":"was-price"}' | 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": "was-price"})["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: "was-price" })).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": "was-price"}, 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", "was-price"), 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: "was-price" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "was-price"])["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 = "was-price" }))!["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": "verdict", "item": "...", "deal_price": 89.99, "was_price": 159.99, "seen": "2026-10-19", "need": "want", "cap": 90, "copy": "...", "prices": "2026-09-25 | 119.99 | ...", "facts": "[...]"}
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": "verdict", "item": "...", "deal_price": 89.99, "was_price": 159.99, "seen": "2026-10-19", "need": "want", "cap": 90, "copy": "...", "prices": "2026-09-25 | 119.99 | ...", "facts": "[...]"}
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: "verdict", item, store, currency, deal_price, was_price, seen, ends, need, cap, copy, prices, facts}
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 task and the input: a retry with the same key never bills twice. The page uses was-price:verdict:<inputHash>:a<attempt>, and its one reformat retry (with retry_note added) is attempt 2 - a new key and a second, named run. The job's output.output is the reply text; truncated: true means the balance cut it short.
KEY="was-price:verdict:$(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 = "was-price:" + 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 lane's JSON object
print(j["charged_credits"], j.get("truncated"), reply.get("verdict") or reply.get("style_prompt"))
import { createHash } from "node:crypto";
const key = `was-price:${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 || reply.style_prompt);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "was-price:" + 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 = "was-price:" + 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 = "was-price:#{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 = "was-price:" . $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 = $"was-price:{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: was-price:verdict:$(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": "verdict", "item": "...", "deal_price": 89.99, "was_price": 159.99, "seen": "2026-10-19", "need": "want", "cap": 90, "copy": "...", "prices": "2026-09-25 | 119.99 | ...", "facts": "[...]"}
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "was-price:verdict:" + 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": `was-price:verdict:${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", "was-price:verdict:"+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", "was-price:verdict:" + 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" => "was-price:verdict:#{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: was-price:verdict:" . 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", $"was-price:verdict:{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);