Drive Party Sized from your own code
The one metered task, fold-it-down, is available over HTTP. It takes a job title and how the person describes the job at work (jargon welcome), plus an optional line on the part they most want people to get. It returns a plain swap for every flagged word, a plain rewrite of the knottiest sentence, a version for a curious stranger, a one-sentence party answer, a lift-sized line, and one everyday picture of the job with where that picture breaks.
The division of labour first: the page reads; the model folds. The page flags abbreviations, jargon and long words, picks the knottiest sentence and sets the word caps, and sends those as facts. After the reply it counts every fold itself and checks that no flagged word survived. The paper colour is the page's own and never sent.
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": "..." } }
The token is minted for this app (the guest call takes {"slug":"party-sized"}), 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, job, blurb, proud, facts) 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 reasons are metered and need 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 and the input
Every run sends task: fold-it-down (the one task is fold-it-down); a missing or different one is still answered as fold-it-down. All fields are strings.
| Field | Required | What it holds |
|---|---|---|
task | yes | Always fold-it-down. |
job | yes | The job title, up to 60 characters. |
blurb | yes | How the person describes the job at work, cut on sentence boundaries to at most 900 characters. |
proud | yes | The part they most want people to get, or not given. |
facts | yes | The page's lines: the flagged words in order (at most ten), each with its kind (jargon, abbreviation or long word), or "none"; the knottiest sentence; the word caps. |
retry_note | no | Only on a reformat retry: why the previous reply was unusable. |
Flagged words: abbreviations of two or more capitals (everyday ones such as TV, UK or CEO are skipped), words from a fixed jargon list, and words of fourteen letters or more. None or one is a flat sheet, two to five a few creases, six or more tightly creased. The knottiest sentence is the one with the most words; ties go to the earlier one.
The reply
One JSON object, no prose around it:
{"task": "fold-it-down", "swaps": [{"term": "...", "plain": "..."}], "after": "...", "stranger": "...", "party": "...", "lift": "...", "picture": "...", "breaks": "..."}
| Field | What it holds |
|---|---|
swaps[] | One per flagged word, same order, term copied exactly; plain is 2 to 8 everyday words. Empty when nothing was flagged. |
after | The knottiest sentence rewritten plainly, first person, at most 35 words. |
stranger | Two or three sentences, 25 to 60 words, for a curious stranger. |
party | One sentence, at most 20 words, starting with "I". |
lift | At most 8 words, starting with "I". |
picture | One sentence, at most 25 words, comparing the job to one familiar everyday thing. |
breaks | One sentence, at most 25 words: one honest way the job is not like the picture. |
The page then checks the reply (FoldKit.check) - a swap for every flagged word, no flagged word left in any fold, every fold within its cap, the party answer one sentence, and no time, number, pronoun or contact detail you did not give - and lists anything it finds under "The page's check".
Worked examples - all three sheets
These are the 3 free samples on the page: the exact input the page sends and the reply that replays for free. Together they cover a flat sheet, a few creases and a tightly creased sheet.
Bike mechanic - flat sheet (0 flagged)
Input:
{
"task": "fold-it-down",
"job": "Bicycle mechanic",
"blurb": "I fix bikes in a small shop. Mostly brakes, gears and punctures, and now and then I build a wheel from scratch for someone who wants it just so.",
"proud": "Building a wheel by hand",
"facts": "flagged words: none (return an empty swaps list)\nknottiest sentence (rewrite it plainly as the after line): Mostly brakes, gears and punctures, and now and then I build a wheel from scratch for someone who wants it just so.\nword caps: stranger 60, party 20, lift 8, picture 25, breaks 25, after 35, each swap 8"
}
Reply:
{
"task": "fold-it-down",
"swaps": [],
"after": "Mostly I fix brakes, gears and punctures, and now and then I build a wheel from scratch for someone who wants it just so.",
"stranger": "I work on bikes in a small shop. Mostly that means brakes, gears and punctures, but now and then I build a wheel from scratch by hand for someone who wants it just so.",
"party": "I fix bikes in a small shop, mostly brakes, gears and punctures, and now and then a wheel from scratch.",
"lift": "I fix bikes in a small shop.",
"picture": "Like keeping a home running, mostly small fixes, but now and then building a piece from scratch for someone who wants it just so.",
"breaks": "Unlike a home, what gets fixed here is bikes, not the home itself."
}
Payroll - a few creases (3 flagged)
Input:
{
"task": "fold-it-down",
"job": "Payroll coordinator",
"blurb": "I run the monthly payroll cycle for about 400 staff. I reconcile timesheets against the HRIS, chase managers for late approvals, and handle the year-end P60 queries from people who think they were taxed twice.",
"proud": "not given",
"facts": "flagged words (one plain swap each, in this order):\n1. reconcile (jargon)\n2. HRIS (abbreviation)\n3. P60 (abbreviation)\nknottiest sentence (rewrite it plainly as the after line): I reconcile timesheets against the HRIS, chase managers for late approvals, and handle the year-end P60 queries from people who think they were taxed twice.\nword caps: stranger 60, party 20, lift 8, picture 25, breaks 25, after 35, each swap 8"
}
Reply:
{
"task": "fold-it-down",
"swaps": [
{
"term": "reconcile",
"plain": "compare records to find mismatches"
},
{
"term": "HRIS",
"plain": "the system holding staff records"
},
{
"term": "P60",
"plain": "a document showing tax paid"
}
],
"after": "I check that timesheets match the system that holds staff records, chase managers for late approvals, and handle year-end queries about the tax document from people who think they were taxed twice.",
"stranger": "I run the monthly payroll cycle for about 400 staff, making sure timesheets match the system that holds their records. I also follow up with managers who are late with approvals, and I answer year-end questions about the tax document from people who think they were taxed twice.",
"party": "I run payroll for about 400 staff and sort out pay and tax queries.",
"lift": "I run payroll and answer tax queries.",
"picture": "It is like balancing a shop's till, chasing missing slips from staff, and answering people's questions about their charges.",
"breaks": "Unlike a shop's till, this payroll work covers about 400 staff, not just a handful of people."
}
Data engineer - tightly creased (10 flagged)
Input:
{
"task": "fold-it-down",
"job": "Senior data platform engineer",
"blurb": "I own the ingestion pipelines and orchestration layer for our analytics stack, partnering with cross-functional stakeholders to deliver ELT workflows with SLAs on data freshness. Most days that means fixing whatever broke overnight before anyone opens a dashboard.",
"proud": "Nobody notices when the numbers are right, and that is the point",
"facts": "flagged words (one plain swap each, in this order):\n1. ingestion (jargon)\n2. pipelines (jargon)\n3. orchestration (jargon)\n4. analytics (jargon)\n5. stack (jargon)\n6. cross-functional (jargon)\n7. stakeholders (jargon)\n8. ELT (abbreviation)\n9. workflows (jargon)\n10. SLAs (abbreviation)\nknottiest sentence (rewrite it plainly as the after line): I own the ingestion pipelines and orchestration layer for our analytics stack, partnering with cross-functional stakeholders to deliver ELT workflows with SLAs on data freshness.\nword caps: stranger 60, party 20, lift 8, picture 25, breaks 25, after 35, each swap 8"
}
Reply:
{
"task": "fold-it-down",
"swaps": [
{
"term": "ingestion",
"plain": "bringing the data in"
},
{
"term": "pipelines",
"plain": "the paths that carry data"
},
{
"term": "orchestration",
"plain": "making sure every step runs in order"
},
{
"term": "analytics",
"plain": "making sense of the numbers"
},
{
"term": "stack",
"plain": "the set of tools used together"
},
{
"term": "cross-functional",
"plain": "people from different teams"
},
{
"term": "stakeholders",
"plain": "people who have an interest in it"
},
{
"term": "ELT",
"plain": "pulling in and reshaping the data"
},
{
"term": "workflows",
"plain": "the steps someone follows to get it done"
},
{
"term": "SLAs",
"plain": "agreed targets it has to meet"
}
],
"after": "I own bringing data in and keeping it running in order to make sense of numbers, and work with people from different teams to pull in and reshape data, meeting agreed targets for data freshness.",
"stranger": "I bring data into our systems and keep it running in order, working with people from different teams to pull it in, reshape it, and meet agreed targets for how fresh it stays. Most days that means fixing whatever broke overnight before anyone opens a dashboard, and when the numbers are right, nobody notices, which is the point.",
"party": "I bring data in, keep it running, and most days fix what breaks overnight before anyone opens a dashboard.",
"lift": "I keep the numbers right without anyone noticing.",
"picture": "The job is like keeping a home's electricity and water running, where nothing is noticed unless something breaks overnight.",
"breaks": "Unlike a home's electricity and water, this job also means working with people from different teams to pull in and reshape data."
}
Build the input yourself
Load the engine in node (global.window = global; require("./foldkit.js")), then:
const K = FoldKit;
const form = K.form({ job: "Payroll coordinator", blurb: "I reconcile timesheets against the HRIS.", proud: "", paper: "sage" });
const built = K.buildInput(form); // built.facts holds the flagged words, the knottiest sentence and the sheet
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, K.title(form)) // the folds 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://party-sized.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://party-sized.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"]
// party-sized.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://party-sized.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://party-sized.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
}
// PartySized.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 PartySized {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://party-sized.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://party-sized.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://party-sized.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://party-sized.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":"party-sized"}' | 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": "party-sized"})["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: "party-sized" })).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": "party-sized"}, 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", "party-sized"), 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: "party-sized" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "party-sized"])["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 = "party-sized" }))!["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": "fold-it-down", "job": "Payroll coordinator", "blurb": "I run the monthly payroll cycle...", "proud": "not given", "facts": "...", ...}
# - 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": "fold-it-down", "job": "Payroll coordinator", "blurb": "I run the monthly payroll cycle...", "proud": "not given", "facts": "...", ...}
# - 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: "fold-it-down", job, blurb, proud, facts} (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 party-sized:<task>:<inputHash>:a<attempt> (the one task is fold-it-down), 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="party-sized:fold-it-down:$(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 = "party-sized:" + 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"), len(reply.get("swaps", [])))
import { createHash } from "node:crypto";
const key = `party-sized:${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.swaps || []).length);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "party-sized:" + 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 = "party-sized:" + 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 = "party-sized:#{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["swaps"] || []).length].inspect
$key = "party-sized:" . $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"], " ", count($reply["swaps"] ?? []), "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"party-sized:{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: party-sized:fold-it-down:$(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": "fold-it-down", "job": "Payroll coordinator", "blurb": "I run the monthly payroll cycle...", "proud": "not given", "facts": "...", ...}
# - 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": "party-sized:fold-it-down:" + 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": `party-sized:fold-it-down:${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", "party-sized:fold-it-down:"+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", "party-sized:fold-it-down:" + 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" => "party-sized:fold-it-down:#{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: party-sized:fold-it-down:" . 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", $"party-sized:fold-it-down:{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);