Drive Referral Loop Audit from your own code
The page does its free work in the browser: it reads a pasted referral or affiliate program and its context, names
the program type, finds the four stages of the referral loop (trigger moment, share action, converting the referred
friend, the reward) with the line that shows each, prices the reward per referred customer against the maximum
reward (LTV x gross margin - CAC), computes the referral rate, referrals per referrer and the K-factor, and raises
flags RF-01..RF-20. Its two paid lanes are available over HTTP. You send one JSON object with task set
to "audit" or "design", and the model replies with one JSON object. The easiest way to build
the facts string is the page itself:
Refkit.buildInput(lane, Refkit.analyze(program, {context, type, question}), {auditHandoff}) in
refkit.js, which also runs under Node.
Base URL: https://api.skillsafe.ai/v1/app-api. Every call sends
Content-Type: application/json and, once you have a token,
Authorization: Bearer <token>. Both lanes run on gpt-terra with a 10%
publisher markup. A run needs a signed-in (user) token and is metered: the platform holds
the estimate's hold_credits while the run is in flight and then charges only
what it used (charged_credits). /estimate is free. A plain-text summary is at
/llms.txt.
The response envelope
Every response is {"data": ..., "error": null} on success or {"data": null, "error": {"code", "message"}} on failure.
Error codes
| HTTP | code | meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | The body is not a JSON object, or a field is the wrong type. Every field of the run input is a string. |
| 401 | UNAUTHORIZED | No token, or an expired one: get a new one (step 2). |
| 402 | INSUFFICIENT_CREDITS | The balance is below the run's minimum. Top up at skillsafe.ai/account/billing/. |
| 403 | FORBIDDEN | A guest token tried a metered run: sign in for a personal token. |
| 404 | NOT_FOUND | Unknown job id. |
| 429 | RATE_LIMITED | Back off and retry with the same Idempotency-Key. |
The task field: two lanes
| task | what the model does |
|---|---|
"audit" | The program audit: the type and stage; the viral fit (natural, limited or unclear, with product-embedded mechanisms that fit); the four loop stages (trigger, share, convert, reward) each strong, weak or missing with a finding and a fix; the reward against the economics (the page's maximum reward and reward cost, within, over or unknown); the numbers; fraud protection and the gaps left open; the issues as finding, impact and fix with the page's flag ids; 3 to 6 fixes; a verdict ship, fix_first or redesign. |
"design" | The program: the type; product-embedded mechanisms when the product spreads by use; 1 to 3 trigger moments; the share mechanism and a pre-written message; the reward (structure, referrer, friend, framing, when it is paid, cap, within the maximum); the landing for the referred friend; 3 to 5 program emails; 3 to 6 fraud rules; affiliate terms; 3 to 6 metrics; 2 to 4 experiments; a launch checklist. |
Input fields (all strings)
| field | when | content |
|---|---|---|
task | required | audit or design |
program | required | The program, verbatim. Past 6,000 characters the page cuts the middle on line boundaries and leaves a [referral-loop-audit: ...] marker. |
facts | required | A JSON string: program (type, basis, stage, audience), economics (price, price_period, ltv, margin_pct, cac, cac_kind, max_reward, reward_value, reward_vs_max_pct), reward (found, sided, timing, types, referrer, friend, commission), viral, loop (trigger, share, convert, reward: found, evidence, note), found, protection, nurture, terms, counts, metrics, flags, notes. It is Refkit.factsOf(A). |
context | optional | The product, the price, LTV, gross margin, CAC and the program's numbers. Cut past 4,000 characters. |
program_type | optional | referral, affiliate or both, when the user chose it. |
question | optional | What the user wants to know. |
audit_handoff | design only | A JSON string of an audit's type, verdict, max_reward, missing[] (loop stages), issues[{flag, finding, fix}] and fixes[], for the same input. |
retry_note | optional | Only on a reformat retry: what was wrong with the previous reply. |
The reply contracts
Both lanes share an envelope: task, headline, questions[], summary.
task: "audit"
program{type: referral|affiliate|both, stage: running|planned, why}, viral_fit{potential: natural|limited|unclear, mechanisms[], why}, loop[{stage: trigger|share|convert|reward, status: strong|weak|missing, finding, evidence[], fix}] (exactly four, in order), economics{max_reward, reward_cost, verdict: within|over|unknown, read}, numbers{read}, protection{status: strong|weak|missing, gaps[], fix}, issues[{flag, kind: incentive|economics|loop|fraud|performance|measurement|affiliate, severity: high|medium|low, finding, impact, evidence[], fix}], fixes[] (3 to 6), verdict (ship|fix_first|redesign).
task: "design"
source (audit handoff|from the input), program{type, why}, viral{potential, mechanisms[{mechanism: powered_by|exposure_loop|social_sharing|embed|watermark, how}]}, trigger_moments[{moment, where, why}] (1 to 3), share{mechanism: in_product|personal_link|email_invite|social|code, message, steps}, reward{structure: double_sided|single_sided|tiered, referrer, friend, framing, paid_when, cap, within_max: yes|no|unknown}, landing{headline, body, cta}, emails[{when, subject, body, cta}] (3 to 5), fraud_rules[] (3 to 6), affiliate{commission, cookie, buyout, recruit[], enablement[]}, metrics[{metric, definition}], experiments[{test, hypothesis, metric}] (2 to 4), launch_checklist[].
Worked examples
These are the page's own bundled examples (invented products and numbers), with the input as app.js builds it and the saved model reply the page replays for free.
Audit: a coffee subscription that pays $15 a side at signup, with the ask under Billing
Input (program and facts shortened here):
{
"task": "audit",
"program": "Tamperlee refer-a-friend program (running since March):\n\nWhere it lives: a \"Refer a friend\" link on the Account page, under Billing. It is not shown anywhere else in the app or in our emails.\nHow people share: copy your referral code (TL-XXXX) and send it however you like. There is no share link and no message to send.\nWhat your friend sees: the code works at checkout on the regular homepage; the page does not say wh... (568 characters in all)",
"facts": "(a JSON string; decoded: referral program, 4 of 4 loop stages found (trigger, share, convert, reward), maximum reward $17, reward cost $30, metrics referral_rate 6.49, per_referrer 0.67, signup_conversion 22.4, k_factor 0.04, flags RF-03, RF-08, RF-01, RF-05, RF-07, RF-11, RF-12, RF-14, RF-18)",
"context": "Tamperlee - a specialty coffee subscription for home brewers (B2C). A bag is $24 and ships every month.\nAverage customer LTV is $180, gross margin is 40%, and our blended CAC from paid social is $55.\nLast 90 days: 9,400 active subscribers; 610 subscribers shared their code; 1,830 friends signed up with a code; 410 became paying subscribers.\nAbout 30% of code signups cancel before their first bag ships.\n",
"question": "Why do so few subscribers refer anyone?"
}
Reply:
{
"task": "audit",
"program": {
"type": "referral",
"stage": "running",
"why": "A refer-a-friend program for subscribers, running since March, with a code and a reward on both sides and no affiliates."
},
"viral_fit": {
"potential": "unclear",
"mechanisms": [],
"why": "A home coffee subscription is not used in front of non-users the way a collaboration or communication tool is, so the program has to carry the referrals."
},
"loop": [
{
"stage": "trigger",
"status": "weak",
"finding": "The only ask is a link on the Account page under Billing, never shown at a moment of value.",
"evidence": [
"Where it lives: a \"Refer a friend\" link on the Account page, under Billing.",
"It is not shown anywhere else in the app or in our emails."
],
"fix": "Ask right after a high-intent moment, such as a delivered bag or a renewal, in the app and in email."
},
{
"stage": "share",
"status": "weak",
"finding": "Sharing means copying a code and writing your own message, with no link and nothing pre-written.",
"evidence": [
"How people share: copy your referral code (TL-XXXX) and send it however you like.",
"There is no share link and no message to send."
],
"fix": "Give each subscriber a personal link with a one-click, pre-written message, and keep the code for offline use."
},
{
"stage": "convert",
"status": "weak",
"finding": "The friend lands on the regular homepage and only sees the offer at checkout, with no sign of who invited them.",
"evidence": [
"What your friend sees: the code works at checkout on the regular homepage; the page does not say who sent them."
],
"fix": "Send the friend to a referral landing page that names the referrer and shows the $15 off their first bag up front.
... (shortened here; the page shows it whole)
Design: the same program, carrying the audit
Input (with the audit handed over):
{
"task": "design",
"program": "Tamperlee refer-a-friend program (running since March):\n\nWhere it lives: a \"Refer a friend\" link on the Account page, under Billing. It is not shown anywhere else in the app or in our emails.\nHow people share: copy your referral code (TL-XXXX) and send it however you like. There is no share link and no message to send.\nWhat your friend sees: the code works at checkout on the regular homepage; the page does not say wh... (568 characters in all)",
"facts": "(a JSON string; decoded: referral program, 4 of 4 loop stages found (trigger, share, convert, reward), maximum reward $17, reward cost $30, metrics referral_rate 6.49, per_referrer 0.67, signup_conversion 22.4, k_factor 0.04, flags RF-03, RF-08, RF-01, RF-05, RF-07, RF-11, RF-12, RF-14, RF-18)",
"context": "Tamperlee - a specialty coffee subscription for home brewers (B2C). A bag is $24 and ships every month.\nAverage customer LTV is $180, gross margin is 40%, and our blended CAC from paid social is $55.\nLast 90 days: 9,400 active subscribers; 610 subscribers shared their code; 1,830 friends signed up with a code; 410 became paying subscribers.\nAbout 30% of code signups cancel before their first bag ships.\n",
"question": "Why do so few subscribers refer anyone?",
"audit_handoff": "{\"type\":\"referral\",\"verdict\":\"redesign\",\"max_reward\":\"$17\",\"missing\":[],\"issues\":[{\"flag\":\"RF-03\",\"finding\":\"The reward costs $30 per referred customer, above the $17 maximum (LTV x gross margin - CAC).\",\"fix\":\"Cut the combined referrer and friend reward below $17, or tie part of it to product value such as a free bag ..."
}
Reply:
{
"task": "design",
"source": "audit handoff",
"program": {
"type": "referral",
"why": "Tamperlee is a B2C coffee subscription with no partners or affiliates named, so a refer-a-friend program between subscribers fits."
},
"viral": {
"potential": "unclear",
"mechanisms": []
},
"trigger_moments": [
{
"moment": "Right after a bag is delivered",
"where": "The delivery confirmation screen in the app and the delivery email",
"why": "A fresh bag is the moment a home brewer feels the value and wants to tell a friend."
},
{
"moment": "Right after a renewal",
"where": "The renewal confirmation page and its receipt email",
"why": "A subscriber who just chose to stay is at high intent to recommend Tamperlee."
},
{
"moment": "After a support conversation rated as great",
"where": "The support follow-up email",
"why": "Great support leaves the customer happy and open to sharing."
}
],
"share": {
"mechanism": "personal_link",
"message": "I get my coffee from Tamperlee and it is the best I have brewed at home. Here is $8 off your first bag: [referral link]",
"steps": "The subscriber clicks Share, picks email, text or social, and sends the pre-written message with their personal link already in it."
},
"reward": {
"structure": "double_sided",
"referrer": "$8 in Tamperlee credit toward their next bag",
"friend": "$8 off their first bag",
"framing": "Give $8, get $8: your friend gets $8 off their first bag and you get $8 credit when it ships.",
"paid_when": "After the friend's first paid bag ships, never at signup.",
"cap": "[monthly referral cap] rewarded referrals per referrer per month and [lifetime referral cap] per lifetime",
"within_max": "yes"
},
"landing": {
"headline": "[referrer name] sent you $8
... (shortened here; the page shows it whole)
Audit: a B2B form builder's affiliate program, 40% for life and a 7-day cookie
Input:
{
"task": "audit",
"program": "Fillowby affiliate program\n\nWho: bloggers, YouTubers and agencies who write about no-code tools. Anyone can join from the \"Affiliates\" link in our site footer.\nCommission: 40% recurring commission for the lifetime of every customer you refer.\nCookie: 7-day cookie.\nTracking and payouts: through Rewardful, paid monthly by PayPal 30 days after the customer's payment; refunded payments are clawed back.\nAssets: a logo pac... (515 characters in all)",
"facts": "(a JSON string; decoded: affiliate program, 4 of 4 loop stages found (trigger, share, convert, reward), maximum reward $306, reward cost $248, metrics active_affiliates_pct 14.62, per_referrer 4.77, referred_share 11.75, flags RF-05, RF-11, RF-15, RF-16, RF-14, RF-17, RF-18, RF-19)",
"context": "Fillowby - a form and survey builder for small businesses and agencies (B2B SaaS). Pro is $29/month.\nLTV is about $620, gross margin 80%, target CAC $190.\nEvery published form is seen by the people who fill it in.\nLast quarter: 212 affiliates signed up; 31 referred at least one customer; affiliates brought 148 new paying customers out of 1,260 new customers in total.\n"
}
Reply:
{
"task": "audit",
"program": {
"type": "affiliate",
"stage": "running",
"why": "Fillowby pays bloggers, YouTubers and agencies a recurring commission tracked through Rewardful, with no reward for customers who refer."
},
"viral_fit": {
"potential": "natural",
"mechanisms": [
"powered_by",
"embed"
],
"why": "Every published form is seen by the people who fill it in, yet no badge or embed turns those viewers into visitors."
},
"loop": [
{
"stage": "trigger",
"status": "weak",
"finding": "The only way in is a footer link, so the ask never appears at a moment of value.",
"evidence": [
"Anyone can join from the \"Affiliates\" link in our site footer."
],
"fix": "Invite agencies and power users to the program inside the app after a milestone, such as their first published form, and keep the footer link as an extra place."
},
{
"stage": "share",
"status": "weak",
"finding": "Affiliates get a tracked link but only a logo pack and one paragraph, with no pre-written message or content to share.",
"evidence": [
"Assets: a logo pack and a one-paragraph product description."
],
"fix": "Add pre-written posts, an email template, screenshots and a demo video to the affiliate kit."
},
{
"stage": "convert",
"status": "weak",
"finding": "Referred visitors see the regular pricing page, so the affiliate's endorsement is lost.",
"evidence": [
"Referred visitors land on the regular pricing page."
],
"fix": "Send affiliate traffic to a landing page that names the affiliate and what the visitor gets."
},
{
"stage": "reward",
"status": "weak",
"finding": "A 40% commission for the customer's lifetime has no end and no buyout, and sits above the skill's typical 10-25% recurring range
... (shortened here; the page shows it whole)
Design: a newsletter platform, from a rough idea
Input:
{
"task": "design",
"program": "We want to start a referral program for Letterwick and have nothing yet. Rough idea:\n\n- Give writers 1 month free for every friend who signs up\n- Put a \"Refer a friend\" page in Settings\n- Friends would sign up on the normal signup page\n",
"facts": "(a JSON string; decoded: referral program, 3 of 4 loop stages found (trigger, convert, reward), maximum reward $105, reward cost $12, metrics none, flags RF-01, RF-02, RF-05, RF-08, RF-11, RF-13, RF-14, RF-18, RF-19)",
"context": "Letterwick - a newsletter platform for independent writers. The Creator plan is $12/month, billed monthly; writers pay for themselves.\nLTV $260, gross margin 75%, CAC $90 from search ads.\nEvery newsletter issue a writer sends goes out to their readers.\n4,800 paying writers today.\nWriters who stay usually publish their 3rd issue in the first month.\n",
"program_type": "referral"
}
Reply:
{
"task": "design",
"source": "from the input",
"program": {
"type": "referral",
"why": "You chose a referral program, and the context names writers referring writers, not partners or affiliates."
},
"viral": {
"potential": "natural",
"mechanisms": [
{
"mechanism": "powered_by",
"how": "Add a small \"Powered by Letterwick\" line to the footer of every newsletter issue, linking to the writer's personal referral link."
},
{
"mechanism": "exposure_loop",
"how": "Readers who want their own newsletter land on a page that says which writer they came from and offers them the same free month."
}
]
},
"trigger_moments": [
{
"moment": "Right after a writer publishes their 3rd issue",
"where": "The post-publish confirmation screen",
"why": "Writers who stay usually publish their 3rd issue in the first month, so this is the milestone where they have seen the value."
},
{
"moment": "Right after a writer's monthly renewal goes through",
"where": "The renewal receipt email",
"why": "A renewal is a fresh signal the writer chose to keep paying for Letterwick."
},
{
"moment": "After a support ticket is closed with a good rating",
"where": "The support follow-up email",
"why": "A writer who just got great support is most willing to recommend the product."
}
],
"share": {
"mechanism": "personal_link",
"message": "I write my newsletter on Letterwick and like it. Use my link to get your first month of the Creator plan free: [referral link]",
"steps": "The writer clicks Copy link or Send by email on the share card, with the message already filled in."
},
"reward": {
"structure": "double_sided",
"referrer": "1 month free ($12 credit) for each friend who pays for their first month",
"friend": "Fi
... (shortened here; the page shows it whole)
The page re-checks the reply; the API does not
The page holds every reply to account (recon.js): for an audit, the program type you chose; four loop stages in order, none called present that the page found absent without a quote, none called strong that a flag faults; the maximum reward and the reward cost equal to the page's arithmetic and "over" exactly when the reward is above the maximum; fraud protection never present when the page found none; every high and medium flag carried and a high one never downgraded; redesign when the reward is over the maximum or two stages are missing, and no "ship" while a high flag or three medium flags stand. For a design, no trigger moment at signup and not only in settings; a pre-written share message; a reward paid after the friend buys, inside the maximum, framed in dollars on a low price; a landing with a call to action; 3 to 5 emails with the launch first; 3 to 6 fraud rules with a delayed payout or verification; affiliate terms with an end or a buyout; referral rate among the metrics; experiments with metrics; the handed-off audit's missing stages designed. In both, quotes are checked word for word and no count, rate, price or window may be invented beyond the skill's constants. Over the API you get the model's reply as it is; run the same checks yourself with Recon.reconcile in recon.js, or paste the same input into the page.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the envelope.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here" # from https://referral-loop-audit.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://referral-loop-audit.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"]
// referral-loop-audit.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://referral-loop-audit.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://referral-loop-audit.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
}
// ReferralLoopAudit.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 ReferralLoopAudit {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://referral-loop-audit.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://referral-loop-audit.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://referral-loop-audit.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://referral-loop-audit.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":"referral-loop-audit"}' | 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": "referral-loop-audit"})["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: "referral-loop-audit" })).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": "referral-loop-audit"}, 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", "referral-loop-audit"), 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: "referral-loop-audit" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "referral-loop-audit"])["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 = "referral-loop-audit" }))!["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 (not wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model reads gpt-5.6-terra and model_alias gpt-terra.
# input.json: the run input object - {"task": "audit", "program": "...", "context": "...", "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": "audit", "program": "...", "context": "...", "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, program, context, program_type, question, 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 lane and the input: a retry with the same key never bills twice. The page uses referral-loop-audit:<lane>:<inputHash>:a<attempt>. The job's output.output is the reply text; truncated: true means the balance cut it short.
KEY="referral-loop-audit:audit:$(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 lane's contract
echo "$J" | jq '.data | {charged_credits, truncated}'
import hashlib, time
key = "referral-loop-audit:" + 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"))
import { createHash } from "node:crypto";
const key = `referral-loop-audit:${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 := "referral-loop-audit:" + 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 = "referral-loop-audit:" + 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 = "referral-loop-audit:#{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 = "referral-loop-audit:" . $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 = $"referral-loop-audit:{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 (design)
The same run as Server-Sent Events. design.json is a design input, with audit_handoff when it follows an audit.
# 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: referral-loop-audit:design:$(shasum -a 256 design.json | cut -c1-16):a1" --data @design.json
rev = json.load(open("design.json")) # {"task": "design", "program": "...", "facts": "...", "audit_handoff": "..."}
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "referral-loop-audit:design:" + 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("design.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": `referral-loop-audit:design:${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("design.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", "referral-loop-audit:design:"+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("design.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", "referral-loop-audit:design:" + 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("design.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "referral-loop-audit:design:#{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("design.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: referral-loop-audit:design:" . 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("design.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", $"referral-loop-audit:design:{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);