Drive GenUI Clinic from your own code
Everything the web page does is available over HTTP: post the catalog that constrains your model
and a spec it generated against that catalog, get the same structured reliability review back. The
natural use is a CI job that re-reviews the catalog whenever it changes, or an eval harness that
runs every spec your model produced overnight through the same review and fails the build when one
comes back unsafe-to-render.
Base URL and the envelope
Every endpoint lives under https://api.skillsafe.ai/v1/app-api and every response uses
the same envelope, so one helper covers the whole API:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }
Send your app slug as X-App-Slug: genui-clinic and your token as
Authorization: Bearer … on every call.
Error codes
| code | status | what to do |
|---|---|---|
unauthorized | 401 | The token is missing, malformed or expired. Get a new one from the token page. |
forbidden | 403 | The token is valid but not for this app. Check the X-App-Slug header. |
payment_required | 402 | The balance is below min_credits. Call /estimate first and top up. |
validation_error | 400 | The input object is missing a required field - `catalog` and `spec` are the usual ones. |
rate_limited | 429 | Too many requests. Back off and retry; do not tight-loop. /similar is capped at 30 per minute per IP. |
not_found | 404 | Unknown job id, an undeclared collection, or the app slug does not exist. |
internal | 500 | A server-side failure. Retry with the SAME Idempotency-Key so you are not billed twice. |
1. Get a token
The easiest route is the token page: it shows the token this browser already holds, with Copy token and Copy shell export buttons, and a sign-in button for a personal token. You never need to open the developer console.
A guest token can call /me and /estimate. Running a
review is metered, so it needs a personal token from signing in — and the
reviews collection is scoped to the calling subject, so only a personal token sees a
history worth querying.
# A guest token is enough for /me and /estimate. Running a review is metered and
# needs a personal token: open the token page and press "Sign in".
#
# https://genui-clinic.skillsafe.ai/tokens.html
#
# That page also gives you a ready-made shell export:
# export SKILLSAFE_TOKEN="..."
#
# To mint a guest token from the command line instead:
curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/guest" -H "X-App-Slug: genui-clinic"
# Open https://genui-clinic.skillsafe.ai/tokens.html and press "Copy token".
# Never read it out of the browser devtools console - the token page exists so
# you do not have to.
#
# A guest token, which can call /me and /estimate but cannot run:
guest = call("guest")
TOKEN = guest["token"]
// Open https://genui-clinic.skillsafe.ai/tokens.html and press "Copy token".
// A guest token can call /me and /estimate but cannot run a metered review.
const guest = await call("guest");
// Use guest.token as the bearer for subsequent calls.
// Open https://genui-clinic.skillsafe.ai/tokens.html and press "Copy token".
// Or mint a guest token, which can call /me and /estimate but cannot run:
raw, err := call("guest", map[string]any{})
if err != nil {
panic(err)
}
var guest struct {
Token string `json:"token"`
}
_ = json.Unmarshal(raw, &guest)
// Open https://genui-clinic.skillsafe.ai/tokens.html and press "Copy token".
// A guest token can call /me and /estimate but cannot run a metered review.
String guest = call("guest", "{}");
System.out.println(guest);
# Open https://genui-clinic.skillsafe.ai/tokens.html and press "Copy token".
# A guest token can call /me and /estimate but cannot run a metered review.
guest = call("guest", {})
puts guest["token"]
<?php
// Open https://genui-clinic.skillsafe.ai/tokens.html and press "Copy token".
// A guest token can call /me and /estimate but cannot run a metered review.
$guest = call("guest", []);
echo $guest["token"];
// Open https://genui-clinic.skillsafe.ai/tokens.html and press "Copy token".
// A guest token can call /me and /estimate but cannot run a metered review.
var guest = await Clinic.Call("guest", new { });
Console.WriteLine(guest.GetProperty("token").GetString());
2. A tiny client
One helper that adds the headers, unwraps data and raises on error.
# Every call is the same three things: the base URL, your bearer token,
# and a JSON body. Keep the token in a shell variable.
BASE="https://api.skillsafe.ai/v1/app-api"
SLUG="genui-clinic"
TOKEN="YOUR_TOKEN" # from https://genui-clinic.skillsafe.ai/tokens.html
call() { # call <path> [json-body]
if [ -n "$2" ]; then
curl -sS -X POST "$BASE/$1" \
-H "Authorization: Bearer $TOKEN" \
-H "X-App-Slug: $SLUG" \
-H "Content-Type: application/json" \
-d "$2"
else
curl -sS "$BASE/$1" -H "Authorization: Bearer $TOKEN" -H "X-App-Slug: $SLUG"
fi
}
import json, os, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
SLUG = "genui-clinic"
TOKEN = os.environ["SKILLSAFE_TOKEN"] # from https://genui-clinic.skillsafe.ai/tokens.html
def call(path, body=None):
"""Returns the unwrapped `data`, or raises with the API error code."""
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(f"{BASE}/{path}", data=data, method="POST" if body is not None else "GET")
req.add_header("Authorization", f"Bearer {TOKEN}")
req.add_header("X-App-Slug", SLUG)
if body is not None:
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req) as r:
payload = json.load(r)
except urllib.error.HTTPError as e:
payload = json.load(e)
if not payload.get("ok"):
err = payload.get("error", {})
raise RuntimeError(f"{err.get('code')}: {err.get('message')}")
return payload["data"]
const BASE = "https://api.skillsafe.ai/v1/app-api";
const SLUG = "genui-clinic";
const TOKEN = "YOUR_TOKEN"; // from https://genui-clinic.skillsafe.ai/tokens.html
async function call(path, body) {
const res = await fetch(`${BASE}/${path}`, {
method: body ? "POST" : "GET",
headers: {
Authorization: `Bearer ${TOKEN}`,
"X-App-Slug": SLUG,
...(body ? { "Content-Type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const payload = await res.json();
if (!payload.ok) throw new Error(`${payload.error.code}: ${payload.error.message}`);
return payload.data;
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
const (
base = "https://api.skillsafe.ai/v1/app-api"
slug = "genui-clinic"
)
var token = os.Getenv("SKILLSAFE_TOKEN") // from https://genui-clinic.skillsafe.ai/tokens.html
type envelope struct {
OK bool `json:"ok"`
Data json.RawMessage `json:"data"`
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func call(path string, body any) (json.RawMessage, error) {
method := http.MethodGet
var rdr io.Reader
if body != nil {
method = http.MethodPost
b, _ := json.Marshal(body)
rdr = bytes.NewReader(b)
}
req, _ := http.NewRequest(method, base+"/"+path, rdr)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("X-App-Slug", slug)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var env envelope
if err := json.NewDecoder(res.Body).Decode(&env); err != nil {
return nil, err
}
if !env.OK {
return nil, fmt.Errorf("%s: %s", env.Error.Code, env.Error.Message)
}
return env.Data, nil
}
import java.net.URI;
import java.net.http.*;
public class Clinic {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static final String SLUG = "genui-clinic";
static final String TOKEN = System.getenv("SKILLSAFE_TOKEN"); // from /tokens.html
static final HttpClient HTTP = HttpClient.newHttpClient();
static String call(String path, String jsonBody) throws Exception {
HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(BASE + "/" + path))
.header("Authorization", "Bearer " + TOKEN)
.header("X-App-Slug", SLUG);
if (jsonBody != null) {
b.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
} else {
b.GET();
}
HttpResponse<String> res = HTTP.send(b.build(), HttpResponse.BodyHandlers.ofString());
// The envelope is always {"ok":...,"data":...} or {"ok":false,"error":...}.
return res.body();
}
}
require "json"
require "net/http"
require "uri"
BASE = "https://api.skillsafe.ai/v1/app-api"
SLUG = "genui-clinic"
TOKEN = ENV["SKILLSAFE_TOKEN"] # from https://genui-clinic.skillsafe.ai/tokens.html
def call(path, body = nil)
uri = URI("#{BASE}/#{path}")
req = body ? Net::HTTP::Post.new(uri) : Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["X-App-Slug"] = SLUG
if body
req["Content-Type"] = "application/json"
req.body = JSON.generate(body)
end
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
payload = JSON.parse(res.body)
raise "#{payload['error']['code']}: #{payload['error']['message']}" unless payload["ok"]
payload["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
const SLUG = "genui-clinic";
define("TOKEN", getenv("SKILLSAFE_TOKEN")); // from /tokens.html
function call(string $path, ?array $body = null) {
$ch = curl_init(BASE . "/" . $path);
$headers = ["Authorization: Bearer " . TOKEN, "X-App-Slug: " . SLUG];
if ($body !== null) {
$headers[] = "Content-Type: application/json";
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$payload = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($payload["ok"])) {
throw new RuntimeException($payload["error"]["code"] . ": " . $payload["error"]["message"]);
}
return $payload["data"];
}
using System.Net.Http.Json;
using System.Text.Json;
static class Clinic
{
const string Base = "https://api.skillsafe.ai/v1/app-api";
const string Slug = "genui-clinic";
static readonly string Token =
Environment.GetEnvironmentVariable("SKILLSAFE_TOKEN") ?? "YOUR_TOKEN";
static readonly HttpClient Http = new();
public static async Task<JsonElement> Call(string path, object? body = null)
{
var req = new HttpRequestMessage(body is null ? HttpMethod.Get : HttpMethod.Post, $"{Base}/{path}");
req.Headers.Add("Authorization", $"Bearer {Token}");
req.Headers.Add("X-App-Slug", Slug);
if (body is not null) req.Content = JsonContent.Create(body);
var res = await Http.SendAsync(req);
var payload = await res.Content.ReadFromJsonAsync<JsonElement>();
if (!payload.GetProperty("ok").GetBoolean())
{
var e = payload.GetProperty("error");
throw new Exception($"{e.GetProperty("code")}: {e.GetProperty("message")}");
}
return payload.GetProperty("data");
}
}
3. Check the session and the balance
/me tells you whether the token is a guest or a person, and what the balance is.
Compare it against min_credits from the next step before you run, so a shortfall
surfaces as your own clear message rather than a 402.
call me
# {"ok":true,"data":{"subject_type":"user","username":"you","credits":51234}}
# subject_type is "guest" or "user". Only a "user" token can run a review or
# read the reviews collection.
me = call("me")
print(me["subject_type"], me.get("credits"))
const me = await call("me");
console.log(me.subject_type, me.credits);
raw, err := call("me", nil)
if err != nil {
panic(err)
}
var me struct {
SubjectType string `json:"subject_type"`
Credits int `json:"credits"`
}
_ = json.Unmarshal(raw, &me)
fmt.Println(me.SubjectType, me.Credits)
System.out.println(call("me", null));
me = call("me")
puts "#{me['subject_type']} #{me['credits']}"
<?php
$me = call("me");
echo $me["subject_type"], " ", $me["credits"], PHP_EOL;
var me = await Clinic.Call("me");
Console.WriteLine(me.GetProperty("subject_type").GetString());
4. Price the run — free
The input object is exactly what the app's own form submits:
| field | type | meaning |
|---|---|---|
catalog | string, required | The defineCatalog(...) source, or a JSON catalog of the form {"components": {...}, "actions": {...}}. This is the only evidence for what is allowed, so a review of a spec without its catalog is a review of nothing. |
spec | string, required | The generated JSON spec under review — the flat {root, elements} document, a Remotion timeline or a Next.js app spec. Either string may be clipped in the middle with a /* ... clipped ... */ marker. |
renderer | string | react, shadcn, react-native, vue, svelte, solid, next, remotion, react-pdf, react-email, ink, three or unknown. |
concern | string | general, reliability, state-and-actions, catalog-design, streaming or accessibility. Emphasis, not exclusivity: a high-severity finding from another category is never suppressed. |
context | string, optional | Free-form notes: what the UI is for, who uses it, which model generates the specs, what has already gone wrong in production. |
prescan_facts | object | {resources: [{id,label}], flags: [{id,label}]} — see below. |
retry_note | string, optional | Only used by the reformat-retry lane: if a previous reply failed to parse, the app re-sends the same input with a note telling the model exactly what the reply must look like. You do not normally send it. |
prescan_facts is not a schema the platform validates — it is whatever a deterministic
walker established before the run. In the browser that walker is the app's own
specscan.js: it resolves the element graph and reports resources (the
components and actions the catalog declares, element counts, state paths, computed functions) and
flags (checks that fired: child-dangling:trend-1,
prop-unconstrained:Metric.format, state-read-never-written:/user/name and
so on). The model is required to reconcile every flags id exactly once
in coverage_check, which is what holds it to facts you established rather than to its
own impressions.
If you have not run a walker of your own, send
{"resources": [], "flags": []} and the review still works — it is simply an unassisted
read of the catalog and the spec, with an empty coverage_check. Sending real flags is
what makes the review reproducible.
/estimate creates no job and charges nothing. It returns the model
binding and the reservation: model, model_alias, markup_bps,
hold_credits, min_credits and sponsor_enabled.
hold_credits is what gets held, and the actual charge is normally far lower because the
hold prices the full output cap.
INPUT='{"catalog": "export const catalog = defineCatalog(schema, {\n components: {\n Dashboard: { props: z.object({ title: z.string() }), description: \"The page shell, always the root.\" },\n Metric: { props: z.object({ label: z.string(), value: z.string(), format: z.string() }), description: \"One headline number with its label.\" }\n },\n actions: { refresh_data: { description: \"Refetch every metric\" } }\n});", "spec": "{\"root\":\"dash\",\"elements\":{\"dash\":{\"type\":\"Dashboard\",\"props\":{\"title\":\"Q3\"},\"children\":[\"m-1\",\"trend-1\"]},\"m-1\":{\"type\":\"Metric\",\"props\":{\"label\":\"MRR\",\"value\":\"41k\",\"format\":\"dollars\"}}}}", "renderer": "react", "concern": "reliability", "context": "Specs are generated per request and rendered while streaming.", "prescan_facts": {"resources": [{"id": "component:Metric", "label": "Component Metric (3 props)"}, {"id": "action:refresh_data", "label": "Action refresh_data"}], "flags": [{"id": "child-dangling:trend-1", "label": "dash lists trend-1 in children, which elements does not define"}, {"id": "prop-unconstrained:Metric.format", "label": "Metric.format is a free-form string where a closed set is implied"}]}}'
call estimate "$INPUT"
# {"ok":true,"data":{"model":"gpt-5.6-terra","model_alias":"gpt-terra",
# "markup_bps":1000,"hold_credits":1730,"min_credits":260,"sponsor_enabled":false}}
#
# estimate is FREE. It creates no job and charges nothing. hold_credits is what
# gets RESERVED; the charge afterwards is normally much lower.
CATALOG = """export const catalog = defineCatalog(schema, {
components: {
Dashboard: { props: z.object({ title: z.string() }), description: "The page shell, always the root." },
Metric: { props: z.object({ label: z.string(), value: z.string(), format: z.string() }), description: "One headline number with its label." }
},
actions: { refresh_data: { description: "Refetch every metric" } }
});"""
SPEC = """{"root":"dash","elements":{
"dash": {"type":"Dashboard","props":{"title":"Q3"},"children":["m-1","trend-1"]},
"m-1": {"type":"Metric","props":{"label":"MRR","value":"41k","format":"dollars"}}
}}"""
INPUT = {
"catalog": CATALOG,
"spec": SPEC,
"renderer": "react",
"concern": "reliability",
"context": "Specs are generated per request and rendered while streaming.",
# What your own walker established. Send empty lists if you have not run one.
"prescan_facts": {
"resources": [
{"id": "component:Metric", "label": "Component Metric (3 props)"},
{"id": "action:refresh_data", "label": "Action refresh_data"},
],
"flags": [
{"id": "child-dangling:trend-1",
"label": "dash lists trend-1 in children, which elements does not define"},
{"id": "prop-unconstrained:Metric.format",
"label": "Metric.format is a free-form string where a closed set is implied"},
],
},
}
est = call("estimate", INPUT)
print(est["model"], est["model_alias"], est["hold_credits"], est["min_credits"])
# estimate is free: no job is created and nothing is charged.
const CATALOG = `export const catalog = defineCatalog(schema, {
components: {
Dashboard: { props: z.object({ title: z.string() }), description: "The page shell, always the root." },
Metric: { props: z.object({ label: z.string(), value: z.string(), format: z.string() }), description: "One headline number with its label." }
},
actions: { refresh_data: { description: "Refetch every metric" } }
});`;
const SPEC = JSON.stringify({
root: "dash",
elements: {
"dash": { type: "Dashboard", props: { title: "Q3" }, children: ["m-1", "trend-1"] },
"m-1": { type: "Metric", props: { label: "MRR", value: "41k", format: "dollars" } },
},
});
const INPUT = {
catalog: CATALOG,
spec: SPEC,
renderer: "react",
concern: "reliability",
context: "Specs are generated per request and rendered while streaming.",
// What your own walker established. Send empty lists if you have not run one.
prescan_facts: {
resources: [
{ id: "component:Metric", label: "Component Metric (3 props)" },
{ id: "action:refresh_data", label: "Action refresh_data" },
],
flags: [
{ id: "child-dangling:trend-1", label: "dash lists trend-1 in children, which elements does not define" },
{ id: "prop-unconstrained:Metric.format", label: "Metric.format is a free-form string where a closed set is implied" },
],
},
};
const est = await call("estimate", INPUT);
console.log(est.model, est.model_alias, est.hold_credits, est.min_credits, est.sponsor_enabled);
// estimate is free: no job is created and nothing is charged.
input := map[string]any{
"catalog": "export const catalog = defineCatalog(schema, { components: { Metric: { props: z.object({ label: z.string(), format: z.string() }) } }, actions: {} });",
"spec": `{"root":"dash","elements":{"dash":{"type":"Dashboard","children":["m-1","trend-1"]}}}`,
"renderer": "react",
"concern": "reliability",
"context": "Specs are generated per request and rendered while streaming.",
// What your own walker established. Send empty slices if you have not run one.
"prescan_facts": map[string]any{
"resources": []any{map[string]string{"id": "component:Metric", "label": "Component Metric"}},
"flags": []any{map[string]string{
"id": "child-dangling:trend-1",
"label": "dash lists trend-1 in children, which elements does not define",
}},
},
}
raw, err := call("estimate", input)
if err != nil {
panic(err)
}
fmt.Println(string(raw)) // estimate is free - no job, no charge
String input = """
{
"catalog": "export const catalog = defineCatalog(schema, { components: { Metric: { props: z.object({ label: z.string(), format: z.string() }) } }, actions: {} });",
"spec": "{\\"root\\":\\"dash\\",\\"elements\\":{\\"dash\\":{\\"type\\":\\"Dashboard\\",\\"children\\":[\\"m-1\\",\\"trend-1\\"]}}}",
"renderer": "react",
"concern": "reliability",
"context": "Specs are generated per request and rendered while streaming.",
"prescan_facts": {
"resources": [ { "id": "component:Metric", "label": "Component Metric" } ],
"flags": [
{
"id": "child-dangling:trend-1",
"label": "dash lists trend-1 in children, which elements does not define"
}
]
}
}
""";
System.out.println(call("estimate", input));
// estimate is free: no job is created and nothing is charged.
// prescan_facts may be {"resources":[],"flags":[]} if you have no walker.
input = {
"catalog" => 'export const catalog = defineCatalog(schema, { components: { Metric: { props: z.object({ label: z.string(), format: z.string() }) } }, actions: {} });',
"spec" => '{"root":"dash","elements":{"dash":{"type":"Dashboard","children":["m-1","trend-1"]}}}',
"renderer" => "react",
"concern" => "reliability",
"context" => "Specs are generated per request and rendered while streaming.",
# What your own walker established. Send empty arrays if you have not run one.
"prescan_facts" => {
"resources" => [{ "id" => "component:Metric", "label" => "Component Metric" }],
"flags" => [
{ "id" => "child-dangling:trend-1",
"label" => "dash lists trend-1 in children, which elements does not define" }
]
}
}
est = call("estimate", input)
puts "#{est['model']} #{est['hold_credits']} (min #{est['min_credits']})"
# estimate is free: no job is created and nothing is charged.
<?php
$input = [
"catalog" => 'export const catalog = defineCatalog(schema, { components: { Metric: { props: z.object({ label: z.string(), format: z.string() }) } }, actions: {} });',
"spec" => '{"root":"dash","elements":{"dash":{"type":"Dashboard","children":["m-1","trend-1"]}}}',
"renderer" => "react",
"concern" => "reliability",
"context" => "Specs are generated per request and rendered while streaming.",
// What your own walker established. Send empty arrays if you have not run one.
"prescan_facts" => [
"resources" => [["id" => "component:Metric", "label" => "Component Metric"]],
"flags" => [[
"id" => "child-dangling:trend-1",
"label" => "dash lists trend-1 in children, which elements does not define",
]],
],
];
$est = call("estimate", $input);
echo $est["model"], " ", $est["hold_credits"], PHP_EOL;
// estimate is free: no job is created and nothing is charged.
var input = new
{
catalog = "export const catalog = defineCatalog(schema, { components: { Metric: { props: z.object({ label: z.string(), format: z.string() }) } }, actions: {} });",
spec = """{"root":"dash","elements":{"dash":{"type":"Dashboard","children":["m-1","trend-1"]}}}""",
renderer = "react",
concern = "reliability",
context = "Specs are generated per request and rendered while streaming.",
// What your own walker established. Send empty arrays if you have not run one.
prescan_facts = new
{
resources = new[] { new { id = "component:Metric", label = "Component Metric" } },
flags = new[]
{
new { id = "child-dangling:trend-1",
label = "dash lists trend-1 in children, which elements does not define" }
}
}
};
var est = await Clinic.Call("estimate", input);
Console.WriteLine(est.GetProperty("hold_credits").GetInt32());
// estimate is free: no job is created and nothing is charged.
5. Run it, then poll
/run returns a job_id; poll jobs/{job_id} until
status is succeeded or failed. The review JSON is the string
at data.output.output.
Always send an Idempotency-Key. Derive it from the input, as the web
app does (genui-clinic:<hash>:a<attempt>). A retried request carrying the
same key returns the same job instead of billing a second run — which is what makes a CI retry
safe. The a<attempt> suffix is how the app's own reformat retry stays
distinguishable from a deliberate re-run.
# Always send an Idempotency-Key derived from the input. A retried request with
# the same key returns the SAME job instead of billing a second run.
KEY="genui-clinic:$(printf '%s' "$INPUT" | shasum -a 256 | cut -c1-16):a1"
JOB=$(curl -sS -X POST "$BASE/run" \
-H "Authorization: Bearer $TOKEN" \
-H "X-App-Slug: $SLUG" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d "$INPUT" | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["job_id"])')
# Poll until the job reaches a terminal status.
while :; do
OUT=$(call "jobs/$JOB")
STATUS=$(printf '%s' "$OUT" | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["status"])')
[ "$STATUS" = "succeeded" ] && break
[ "$STATUS" = "failed" ] && echo "$OUT" && exit 1
sleep 2
done
printf '%s' "$OUT" | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["output"]["output"])'
import hashlib, time
# Always send an Idempotency-Key derived from the input: a retried request with
# the same key returns the SAME job instead of billing a second run.
digest = hashlib.sha256(json.dumps(INPUT, sort_keys=True).encode()).hexdigest()[:16]
key = f"genui-clinic:{digest}:a1"
req = urllib.request.Request(f"{BASE}/run", data=json.dumps(INPUT).encode(), method="POST")
req.add_header("Authorization", f"Bearer {TOKEN}")
req.add_header("X-App-Slug", SLUG)
req.add_header("Content-Type", "application/json")
req.add_header("Idempotency-Key", key)
with urllib.request.urlopen(req) as r:
job_id = json.load(r)["data"]["job_id"]
while True:
job = call(f"jobs/{job_id}")
if job["status"] == "succeeded":
break
if job["status"] == "failed":
raise RuntimeError(job.get("error"))
time.sleep(2)
review = json.loads(job["output"]["output"])
print(review["verdict_level"], review["renderer"], len(review["findings"]), "findings")
import { createHash } from "node:crypto";
// Always send an Idempotency-Key derived from the input: a retried request with
// the same key returns the SAME job instead of billing a second run.
const digest = createHash("sha256").update(JSON.stringify(INPUT)).digest("hex").slice(0, 16);
const key = `genui-clinic:${digest}:a1`;
const started = await fetch(`${BASE}/run`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"X-App-Slug": SLUG,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(INPUT),
}).then((r) => r.json());
let job = started.data;
while (job.status !== "succeeded" && job.status !== "failed") {
await new Promise((r) => setTimeout(r, 2000));
job = await call(`jobs/${job.job_id}`);
}
if (job.status === "failed") throw new Error(JSON.stringify(job.error));
const review = JSON.parse(job.output.output);
console.log(review.verdict_level, review.findings.length, "findings");
// Always send an Idempotency-Key derived from the input: a retried request with
// the same key returns the SAME job instead of billing a second run.
body, _ := json.Marshal(input)
sum := sha256.Sum256(body)
key := fmt.Sprintf("genui-clinic:%x:a1", sum[:8])
req, _ := http.NewRequest(http.MethodPost, base+"/run", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("X-App-Slug", slug)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", key)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
var started struct {
Data struct {
JobID string `json:"job_id"`
} `json:"data"`
}
_ = json.NewDecoder(res.Body).Decode(&started)
for {
raw, err := call("jobs/"+started.Data.JobID, nil)
if err != nil {
panic(err)
}
var job struct {
Status string `json:"status"`
Output struct {
Output string `json:"output"`
} `json:"output"`
}
_ = json.Unmarshal(raw, &job)
if job.Status == "succeeded" {
fmt.Println(job.Output.Output)
break
}
if job.Status == "failed" {
panic("run failed")
}
time.Sleep(2 * time.Second)
}
// Always send an Idempotency-Key derived from the input: a retried request with
// the same key returns the SAME job instead of billing a second run.
var digest = java.security.MessageDigest.getInstance("SHA-256")
.digest(input.getBytes(java.nio.charset.StandardCharsets.UTF_8));
var key = "genui-clinic:" + java.util.HexFormat.of().formatHex(digest).substring(0, 16) + ":a1";
var start = HttpRequest.newBuilder(URI.create(BASE + "/run"))
.header("Authorization", "Bearer " + TOKEN)
.header("X-App-Slug", SLUG)
.header("Content-Type", "application/json")
.header("Idempotency-Key", key)
.POST(HttpRequest.BodyPublishers.ofString(input))
.build();
String started = HTTP.send(start, HttpResponse.BodyHandlers.ofString()).body();
// Parse job_id out of `started`, then poll GET jobs/{job_id} every two seconds
// until status is "succeeded" or "failed"; the review JSON is data.output.output.
System.out.println(started);
require "digest"
# Always send an Idempotency-Key derived from the input: a retried request with
# the same key returns the SAME job instead of billing a second run.
digest = Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]
key = "genui-clinic:#{digest}:a1"
uri = URI("#{BASE}/run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["X-App-Slug"] = SLUG
req["Content-Type"] = "application/json"
req["Idempotency-Key"] = key
req.body = JSON.generate(input)
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
job_id = JSON.parse(res.body)["data"]["job_id"]
loop do
job = call("jobs/#{job_id}")
break puts(job["output"]["output"]) if job["status"] == "succeeded"
raise "run failed" if job["status"] == "failed"
sleep 2
end
<?php
// Always send an Idempotency-Key derived from the input: a retried request with
// the same key returns the SAME job instead of billing a second run.
$digest = substr(hash("sha256", json_encode($input)), 0, 16);
$key = "genui-clinic:{$digest}:a1";
$ch = curl_init(BASE . "/run");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($input));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . TOKEN,
"X-App-Slug: " . SLUG,
"Content-Type: application/json",
"Idempotency-Key: " . $key,
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$jobId = json_decode(curl_exec($ch), true)["data"]["job_id"];
curl_close($ch);
while (true) {
$job = call("jobs/" . $jobId);
if ($job["status"] === "succeeded") { echo $job["output"]["output"]; break; }
if ($job["status"] === "failed") { throw new RuntimeException("run failed"); }
sleep(2);
}
using System.Security.Cryptography;
using System.Text;
// Always send an Idempotency-Key derived from the input: a retried request with
// the same key returns the SAME job instead of billing a second run.
var json = JsonSerializer.Serialize(input);
var digest = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(json)))[..16].ToLowerInvariant();
var key = $"genui-clinic:{digest}:a1";
var run = new HttpRequestMessage(HttpMethod.Post, "https://api.skillsafe.ai/v1/app-api/run");
run.Headers.Add("Authorization", "Bearer YOUR_TOKEN");
run.Headers.Add("X-App-Slug", "genui-clinic");
run.Headers.Add("Idempotency-Key", key);
run.Content = JsonContent.Create(input);
// POST it, read data.job_id, then poll GET jobs/{job_id} every two seconds until
// status is "succeeded" or "failed"; the review JSON is data.output.output.
6. Or stream it
/run-stream is the same call over server-sent events, and it is what the web app
actually uses. Section headings arrive in order, so a UI can advance a staged progress display, and
whatever parsed survives if the stream dies mid-flight. The final done event carries
charged_credits and a truncated flag — truncated: true means
the balance cut the reply short, not that the model finished.
# Server-sent events. Each `delta` carries a chunk of the JSON review; the final
# `done` event carries the whole thing plus charged_credits.
curl -N -X POST "$BASE/run-stream" \
-H "Authorization: Bearer $TOKEN" \
-H "X-App-Slug: $SLUG" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-H "Accept: text/event-stream" \
-d "$INPUT"
# event: job {"job_id":"job_..."}
# event: delta {"text":"{\"review_name\":\"Revenue dashboard"}
# event: delta {"text":" catalog - 1 blocker\",\"verdict_level\":"}
# event: done {"status":"succeeded","charged_credits":412,"truncated":false}
# Server-sent events: the review arrives in chunks, so a UI can show progress.
req = urllib.request.Request(f"{BASE}/run-stream", data=json.dumps(INPUT).encode(), method="POST")
req.add_header("Authorization", f"Bearer {TOKEN}")
req.add_header("X-App-Slug", SLUG)
req.add_header("Content-Type", "application/json")
req.add_header("Idempotency-Key", key)
req.add_header("Accept", "text/event-stream")
raw = ""
event = None
with urllib.request.urlopen(req) as stream:
for line in stream:
line = line.decode().rstrip("\n")
if line.startswith("event: "):
event = line[7:]
elif line.startswith("data: ") and event == "delta":
raw += json.loads(line[6:]).get("text", "")
review = json.loads(raw[raw.index("{"):raw.rindex("}") + 1])
print(review["verdict_level"], len(review["findings"]), "findings")
// Server-sent events: the review arrives in chunks, so a UI can show progress.
const res = await fetch(`${BASE}/run-stream`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"X-App-Slug": SLUG,
"Content-Type": "application/json",
"Idempotency-Key": key,
Accept: "text/event-stream",
},
body: JSON.stringify(INPUT),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let raw = "";
let event = null;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (line.startsWith("event: ")) event = line.slice(7);
else if (line.startsWith("data: ") && event === "delta") {
raw += JSON.parse(line.slice(6)).text ?? "";
}
}
}
const review = JSON.parse(raw.slice(raw.indexOf("{"), raw.lastIndexOf("}") + 1));
console.log(review.verdict_level, review.findings.length, "findings");
// Server-sent events: the review arrives in chunks, so a UI can show progress.
req, _ = http.NewRequest(http.MethodPost, base+"/run-stream", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("X-App-Slug", slug)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", key)
req.Header.Set("Accept", "text/event-stream")
res, _ = http.DefaultClient.Do(req)
defer res.Body.Close()
var raw strings.Builder
var event string
sc := bufio.NewScanner(res.Body)
sc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)
for sc.Scan() {
line := sc.Text()
switch {
case strings.HasPrefix(line, "event: "):
event = strings.TrimPrefix(line, "event: ")
case strings.HasPrefix(line, "data: ") && event == "delta":
var d struct {
Text string `json:"text"`
}
_ = json.Unmarshal([]byte(strings.TrimPrefix(line, "data: ")), &d)
raw.WriteString(d.Text)
}
}
fmt.Println(raw.String())
// Server-sent events: the review arrives in chunks, so a UI can show progress.
var stream = HttpRequest.newBuilder(URI.create(BASE + "/run-stream"))
.header("Authorization", "Bearer " + TOKEN)
.header("X-App-Slug", SLUG)
.header("Content-Type", "application/json")
.header("Idempotency-Key", key)
.header("Accept", "text/event-stream")
.POST(HttpRequest.BodyPublishers.ofString(input))
.build();
StringBuilder raw = new StringBuilder();
String[] event = { null };
HTTP.send(stream, HttpResponse.BodyHandlers.ofLines()).body().forEach(line -> {
if (line.startsWith("event: ")) event[0] = line.substring(7);
else if (line.startsWith("data: ") && "delta".equals(event[0])) {
raw.append(line.substring(6)); // each data line is {"text":"..."} - decode and append .text
}
});
System.out.println(raw);
# Server-sent events: the review arrives in chunks, so a UI can show progress.
uri = URI("#{BASE}/run-stream")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["X-App-Slug"] = SLUG
req["Content-Type"] = "application/json"
req["Idempotency-Key"] = key
req["Accept"] = "text/event-stream"
req.body = JSON.generate(input)
raw = +""
event = nil
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(req) do |res|
res.read_body do |chunk|
chunk.each_line do |line|
line = line.chomp
if line.start_with?("event: ") then event = line[7..]
elsif line.start_with?("data: ") && event == "delta"
raw << (JSON.parse(line[6..])["text"] || "")
end
end
end
end
end
review = JSON.parse(raw[raw.index("{")..raw.rindex("}")])
puts "#{review['verdict_level']} #{review['findings'].length} findings"
<?php
// Server-sent events: the review arrives in chunks, so a UI can show progress.
$raw = "";
$event = null;
$ch = curl_init(BASE . "/run-stream");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($input));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . TOKEN,
"X-App-Slug: " . SLUG,
"Content-Type: application/json",
"Idempotency-Key: " . $key,
"Accept: text/event-stream",
]);
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$raw, &$event) {
foreach (explode("\n", $chunk) as $line) {
if (str_starts_with($line, "event: ")) {
$event = substr($line, 7);
} elseif (str_starts_with($line, "data: ") && $event === "delta") {
$raw .= json_decode(substr($line, 6), true)["text"] ?? "";
}
}
return strlen($chunk);
});
curl_exec($ch);
curl_close($ch);
$review = json_decode(substr($raw, strpos($raw, "{")), true);
echo $review["verdict_level"], PHP_EOL;
// Server-sent events: the review arrives in chunks, so a UI can show progress.
var stream = new HttpRequestMessage(HttpMethod.Post, "https://api.skillsafe.ai/v1/app-api/run-stream");
stream.Headers.Add("Authorization", "Bearer YOUR_TOKEN");
stream.Headers.Add("X-App-Slug", "genui-clinic");
stream.Headers.Add("Idempotency-Key", key);
stream.Headers.Add("Accept", "text/event-stream");
stream.Content = JsonContent.Create(input);
using var res = await Http.SendAsync(stream, HttpCompletionOption.ResponseHeadersRead);
using var reader = new StreamReader(await res.Content.ReadAsStreamAsync());
var raw = new StringBuilder();
string? evt = null;
while (await reader.ReadLineAsync() is { } line)
{
if (line.StartsWith("event: ")) evt = line[7..];
else if (line.StartsWith("data: ") && evt == "delta")
{
var d = JsonSerializer.Deserialize<JsonElement>(line[6..]);
if (d.TryGetProperty("text", out var t)) raw.Append(t.GetString());
}
}
Console.WriteLine(raw.ToString());
The output contract
data.output.output is a JSON string holding one object. The app strips a leading and
trailing code fence, then takes everything from the first { to the last }
before parsing — do the same and a chatty model costs you nothing. This is exactly what the web app
parses, so anything that renders here will render there:
{
"review_name": "Revenue dashboard catalog - 3 blockers before it can stream",
"verdict_level": "ship-it | tighten-before-ship | unsafe-to-render",
"verdict": "one sentence naming the single thing that decides the level",
"renderer": "react",
"spec_shape": "flat | timeline | next-app | unknown",
"exec_summary": "2-3 paragraphs separated by blank lines",
"assumptions": ["explicit assumption that fills a gap in the paste"],
"open_questions": ["question whose answer would change the review"],
"inventory": [
{ "kind": "Component", "name": "Metric", "detail": "label, value, format", "role": "..." }
],
"findings": [
{
"id": "GU-001",
"category": "spec-validity | catalog-design | state | actions | streaming | accessibility | prompt | hygiene",
"severity": "low | medium | high",
"likelihood": "low | medium | high",
"priority": "critical | high | medium | low",
"resource": "Component/Metric, Element/m-1, Prop/Metric.format, Action/refresh_data, StatePath//user/name",
"problem": "what is wrong, in this catalog or this spec",
"impact": "what it costs at render time, in practice",
"fix": "the concrete change to make",
"snippet": "corrected JSON or TypeScript fragment, or \"\""
}
],
"coverage_check": [
{ "id": "child-dangling:trend-1", "addressed": true, "note": "GU-001." }
],
"corrected_spec": "the whole corrected spec as a JSON string, or \"\" if the spec did not parse",
"catalog_upgrades": [
{ "component": "Metric", "change": "Close the format prop.", "snippet": "props: z.object({ ... })" }
],
"prompt_guidance": ["a line to add to a component description so the model stops producing this"],
"quick_wins": ["one-line change worth doing immediately"],
"focus_areas": [{ "area": "...", "why": "...", "finding_ids": ["GU-001"] }],
"summary": "closing paragraph"
}
Field by field, and what the app's own normalize() does with each one:
| field | type | notes |
|---|---|---|
review_name | string | Short title naming the app and the verdict. Empty falls back to "Untitled generative-UI review". |
verdict_level | enum | ship-it, tighten-before-ship, unsafe-to-render. Anything else is coerced to tighten-before-ship. |
verdict | string | One sentence justifying the level and naming the thing that decides it. |
renderer | string | The renderer echoed back. Empty becomes unknown. |
spec_shape | enum | flat, timeline, next-app, unknown — coerced to unknown. |
exec_summary | string | Two or three paragraphs separated by blank lines. |
assumptions | string[] | Blank entries are dropped. |
open_questions | string[] | Blank entries are dropped. |
inventory | {kind,name,detail,role}[] | Rows with neither a kind nor a name are dropped. kind is one of Component, Action, Element, Prop, StatePath, Expression, Route, Layout, Track, Clip. |
findings | object[] | Ids are sequential GU-001, GU-002, …; a missing id is filled in by position. category, severity, likelihood and priority are coerced to the enums above (hygiene, medium, medium, medium). A row with neither a problem nor a fix is dropped. |
coverage_check | {id,addressed,note}[] | One row per prescan_facts.flags id, exactly once. addressed: false with a reason in note is a legitimate answer for a flag that is not a real problem here. |
corrected_spec | string | The whole corrected spec as a JSON string, in the same shape as the input. "" only when the spec did not parse at all. |
catalog_upgrades | {component,change,snippet}[] | The cause, not the symptom: the schema change that makes a class of bad output impossible. Rows with neither a component nor a change are dropped. |
prompt_guidance | string[] | Lines to add to the catalog prompt or a component description. |
quick_wins | string[] | One-line changes worth doing immediately. |
focus_areas | {area,why,finding_ids}[] | Every finding_ids entry that does not name a real finding id is silently dropped; a row with no area is dropped whole. |
summary | string | Closing paragraph: what to do first and what remains. |
Two rules worth enforcing on your side, because the app enforces them too.
findings must be non-empty — a reply whose findings all get dropped is
rejected outright and the app re-runs once with a retry_note rather than showing an
empty review. And every prescan_facts.flags id must appear exactly once in
coverage_check: if a flag is missing from the reconciliation, the model quietly skipped
a fact you established — treat that as a failed run, not a passing one.
7. Your saved reviews
Every run the app completes is written to the reviews collection, so a review follows
the user across devices. It is declared acl_read: owner and
acl_write: user: rows are scoped to the calling subject, which means a script must
reuse one token across the run and the query or it will see an empty collection. Each
POST /guest mints a new guest subject, so guest tokens are not a way to share
history.
| field | type | meaning |
|---|---|---|
title | string | review_name from the reply. |
verdict_level | string | ship-it, tighten-before-ship or unsafe-to-render. |
verdict | string | The one-sentence verdict. |
renderer | string | The renderer the review targeted. |
spec_shape | string | flat, timeline, next-app or unknown. |
input_hash | string | Hash of the submitted catalog and spec — the cheap way to tell whether anything actually changed between runs. |
findings_count | number | findings.length. |
high_count | number | How many findings came back priority: "critical" or "high". |
ran_at | timestamp | When the run completed. The natural sort key. |
Those nine fields are declared, and therefore filterable and sortable. The rest of the document —
the whole review, its meta and the original input — round-trips intact but is not indexed.
embed is ["title", "verdict", "renderer"], so
POST /collections/reviews/similar finds past reviews that read like this one: useful
for "have we seen this failure shape before?" across a fleet of catalogs. /similar is
rate-limited to 30 requests per minute per IP, the query text is capped at 2000 characters, and
limit maxes out at 20.
Two shapes to get right. Every where entry must be an operator object —
eq, ne, lt, lte, gt,
gte, in (up to 20 values) or contains; the bare-value
shorthand {"verdict_level": "unsafe-to-render"} is rejected. And the sort key is
sort, an object: {"field": "ran_at", "dir": "desc"}. An
order_by key is silently ignored, which looks exactly like a
collection that is not sorting. Records come back wrapped:
data.records[].doc holds the fields, alongside a record_id — never read
the fields flat off the record.
# Your saved reviews, newest first. The sort key is `sort`, an object -
# `order_by` is accepted and then silently ignored.
call collections/reviews/query '{"sort":{"field":"ran_at","dir":"desc"},"limit":10}'
# Only the React specs that came back unsafe with something high in them.
call collections/reviews/query '{"where":{"verdict_level":{"eq":"unsafe-to-render"},"renderer":{"eq":"react"},"high_count":{"gte":1}},"sort":{"field":"ran_at","dir":"desc"},"limit":20}'
# Past reviews that read like this one, over embed = title, verdict, renderer.
call collections/reviews/similar '{"text":"dangling child id in a streamed dashboard spec","limit":5}'
# Every `where` entry must be an operator object - the bare-value shorthand
# ({"verdict_level": "unsafe-to-render"}) is rejected. The sort key is `sort`.
recent = call("collections/reviews/query", {
"where": {"verdict_level": {"eq": "unsafe-to-render"}, "high_count": {"gte": 1}},
"sort": {"field": "ran_at", "dir": "desc"},
"limit": 20,
})
for rec in recent["records"]:
d = rec["doc"] # the fields nest under .doc, not flat
print(d["ran_at"], d["title"], d["renderer"], d["findings_count"], "findings")
# Nearest neighbours over embed = ["title", "verdict", "renderer"].
# Rate-limited to 30/min per IP; text is capped at 2000 chars, limit at 20.
similar = call("collections/reviews/similar",
{"text": recent["records"][0]["doc"]["verdict"][:2000], "limit": 5})
for rec in similar["records"]:
print(rec["doc"]["title"])
const recent = await call("collections/reviews/query", {
where: { verdict_level: { eq: "unsafe-to-render" }, high_count: { gte: 1 } },
sort: { field: "ran_at", dir: "desc" }, // `order_by` is silently ignored
limit: 20,
});
for (const rec of recent.records) {
const d = rec.doc; // the fields nest under .doc, not flat
console.log(d.ran_at, d.title, d.renderer, d.high_count);
}
// Nearest neighbours over embed = ["title", "verdict", "renderer"].
// Rate-limited to 30/min per IP; text is capped at 2000 chars, limit at 20.
const similar = await call("collections/reviews/similar", {
text: "dangling child id in a streamed dashboard spec",
limit: 5,
});
console.log(similar.records.map((r) => r.doc.title));
query := map[string]any{
"where": map[string]any{
"verdict_level": map[string]any{"eq": "unsafe-to-render"},
"renderer": map[string]any{"eq": "react"},
},
"sort": map[string]string{"field": "ran_at", "dir": "desc"}, // not order_by
"limit": 20,
}
raw, err := call("collections/reviews/query", query)
if err != nil {
panic(err)
}
var out struct {
Records []struct {
RecordID string `json:"record_id"`
Doc map[string]any `json:"doc"`
} `json:"records"`
}
_ = json.Unmarshal(raw, &out)
for _, r := range out.Records {
// The fields nest under .doc - never read them flat off the record.
fmt.Println(r.Doc["ran_at"], r.Doc["title"], r.Doc["high_count"])
}
// collections/reviews/similar takes {"text": "...", "limit": 5} and ranks over
// the declared embed fields: title, verdict and renderer.
String query = """
{"where":{"verdict_level":{"eq":"unsafe-to-render"},"high_count":{"gte":1}},
"sort":{"field":"ran_at","dir":"desc"},"limit":20}
""";
String reviews = call("collections/reviews/query", query);
System.out.println(reviews);
// {"ok":true,"data":{"records":[{"record_id":"...","doc":{"title":"...","verdict_level":"unsafe-to-render",...}}]}}
// The fields nest under .doc - never read them flat off the record.
// `sort` is the key; an `order_by` key is accepted and silently ignored.
String similar = call("collections/reviews/similar",
"{\"text\":\"dangling child id in a streamed dashboard spec\",\"limit\":5}");
System.out.println(similar);
recent = call("collections/reviews/query", {
"where" => { "verdict_level" => { "eq" => "unsafe-to-render" }, "high_count" => { "gte" => 1 } },
"sort" => { "field" => "ran_at", "dir" => "desc" }, # `order_by` is ignored
"limit" => 20,
})
recent["records"].each do |r|
d = r["doc"] # the fields nest under .doc, not flat
puts "#{d['ran_at']} #{d['title']} #{d['renderer']}"
end
# Nearest neighbours over embed = ["title", "verdict", "renderer"].
# Rate-limited to 30/min per IP; text is capped at 2000 chars, limit at 20.
similar = call("collections/reviews/similar",
{ "text" => "dangling child id in a streamed spec", "limit" => 5 })
similar["records"].each { |r| puts r["doc"]["title"] }
<?php
$recent = call("collections/reviews/query", [
"where" => ["verdict_level" => ["eq" => "unsafe-to-render"], "high_count" => ["gte" => 1]],
"sort" => ["field" => "ran_at", "dir" => "desc"], // `order_by` is ignored
"limit" => 20,
]);
foreach ($recent["records"] as $rec) {
$d = $rec["doc"]; // the fields nest under .doc, not flat
echo $d["ran_at"], " ", $d["title"], " ", $d["renderer"], PHP_EOL;
}
// Nearest neighbours over embed = ["title", "verdict", "renderer"].
// Rate-limited to 30/min per IP; text is capped at 2000 chars, limit at 20.
$similar = call("collections/reviews/similar",
["text" => "dangling child id in a streamed spec", "limit" => 5]);
foreach ($similar["records"] as $rec) {
echo $rec["doc"]["title"], PHP_EOL;
}
var query = new
{
where = new
{
verdict_level = new { eq = "unsafe-to-render" },
high_count = new { gte = 1 },
},
sort = new { field = "ran_at", dir = "desc" }, // `order_by` is silently ignored
limit = 20,
};
var recent = await Clinic.Call("collections/reviews/query", query);
foreach (var rec in recent.GetProperty("records").EnumerateArray())
{
var d = rec.GetProperty("doc"); // the fields nest under .doc, not flat
Console.WriteLine($"{d.GetProperty("ran_at")} {d.GetProperty("title")}");
}
// Nearest neighbours over embed = ["title", "verdict", "renderer"].
// Rate-limited to 30/min per IP; text is capped at 2000 chars, limit at 20.
var similar = await Clinic.Call("collections/reviews/similar",
new { text = "dangling child id in a streamed spec", limit = 5 });
Console.WriteLine(similar.GetProperty("records").GetArrayLength());
A CI gate
The verdict level is the natural exit code. Fail the job when a generated spec drifts into
unsafe-to-render, warn on tighten-before-ship, and pass on
ship-it — with the Idempotency-Key derived from the input so a re-run of the same
commit replays instead of re-billing. Comparing input_hash against the newest row in
the reviews collection tells you whether the catalog changed at all, and therefore
whether it is worth spending the credits.
LEVEL=$(printf '%s' "$REVIEW" | python3 -c 'import sys,json;print(json.load(sys.stdin)["verdict_level"])')
HIGH=$(printf '%s' "$REVIEW" | python3 -c 'import sys,json;print(sum(1 for f in json.load(sys.stdin)["findings"] if f["priority"] in ("critical","high")))')
case "$LEVEL" in
unsafe-to-render) echo "::error::GenUI review: unsafe to render ($HIGH high)"; exit 1 ;;
tighten-before-ship) echo "::warning::GenUI review: tighten before ship"; exit 0 ;;
ship-it) echo "GenUI review: ship it"; exit 0 ;;
esac