Slop Lens — API

Paste the frontend — learn what reads as template, and the direction out.

API tokens Open the app

Review a frontend's distinctiveness from your own scripts

Send frontend source — HTML, CSS, JSX or Tailwind markup — with an optional brief, and get back one JSON object: a distinctive / leaning-generic / templated verdict, the AI-default cluster the page most resembles, six axis readings, findings that each quote the verbatim line of your code they rest on, a keep list, and a revision direction — a subject-driven palette, a display/body type pairing, a layout concept and one signature element. Everything this app does goes through the SkillSafe App API — plain JSON over HTTPS — so the review can sit wherever design ships: a pre-release check on a marketing page, a design-system audit script, or a bot that reviews the static export of every new template. Pick a language once and the whole page follows.

Basics

Base URL: https://api.skillsafe.ai/v1/app-api, app slug slop-lens. Every request sends Authorization: Bearer <token> and JSON bodies with Content-Type: application/json. Responses are wrapped in an envelope: {"data": …} on success, {"error": {"code", "message"}} on failure. The review itself is produced by the gpt-terra model. Estimates are free; runs are metered against your credit balance. There is a single run task — one page's source in, one review out, no follow-up calls and no session state to carry.

StatusMeaning
401Missing, malformed or expired token — mint a new one (step 1) and retry once.
402Not enough credits to place the hold for this review — top up at skillsafe.ai/account/credits. The estimate's min_credits is the floor below which a run will not start.
404Unknown job id (or a job belonging to another subject). Job ids are only readable by the token that created them.
422The body did not validate — code missing or empty, or a bad enum in surface / ambition. The message names the field. The app itself also refuses source under 80 characters client-side; that is a UI rule, not a server one.
429Too many requests in flight for this subject. Back off and retry — reuse the same Idempotency-Key so a retry cannot start a second, double-charged run.
5xxTransient platform error — retry with backoff, again with the same Idempotency-Key.

Browsers enforce CORS for this API, so run these examples from a server, script or terminal — not from another website's frontend.

Step 0 — A tiny client

Every task below is a single HTTP call, so start with a short helper that adds the auth header, sends JSON and unwraps the {data}/{error} envelope. Set SKILLSAFE_TOKEN in your environment — the token page can copy a ready-made export line.

export API="https://api.skillsafe.ai/v1/app-api"
export TOKEN="YOUR_TOKEN"   # from /tokens.html, or step 1 below

# every call: curl -s "$API/<path>" -H "Authorization: Bearer $TOKEN" \
#   -H "Content-Type: application/json" [-d "$BODY"] | jq '.data'
import json, os, requests

API = "https://api.skillsafe.ai/v1/app-api"
TOKEN = os.environ["SKILLSAFE_TOKEN"]

def api(method, path, body=None, **kw):
    r = requests.request(method, API + path, json=body,
        headers={"Authorization": f"Bearer {TOKEN}"}, timeout=180, **kw)
    envelope = r.json()
    if "error" in envelope:
        raise RuntimeError(f"{envelope['error']['code']}: {envelope['error']['message']}")
    return envelope["data"]
// Node 18+ (built-in fetch)
const API = "https://api.skillsafe.ai/v1/app-api";
const TOKEN = "YOUR_TOKEN"; // read it from your env or secret store

async function api(method, path, body) {
  const res = await fetch(API + path, {
    method,
    headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  });
  const envelope = await res.json();
  if (envelope.error) throw new Error(`${envelope.error.code}: ${envelope.error.message}`);
  return envelope.data;
}
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

const API = "https://api.skillsafe.ai/v1/app-api"

func api(method, path string, body any, out any) error {
	var buf bytes.Buffer
	if body != nil {
		if err := json.NewEncoder(&buf).Encode(body); err != nil {
			return err
		}
	}
	req, _ := http.NewRequest(method, API+path, &buf)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("SKILLSAFE_TOKEN"))
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	var envelope struct {
		Data  json.RawMessage `json:"data"`
		Error *struct{ Code, Message string } `json:"error"`
	}
	if err := json.NewDecoder(res.Body).Decode(&envelope); err != nil {
		return err
	}
	if envelope.Error != nil {
		return fmt.Errorf("%s: %s", envelope.Error.Code, envelope.Error.Message)
	}
	if out != nil {
		return json.Unmarshal(envelope.Data, out)
	}
	return nil
}
// Java 17+, no dependencies. Pair with your JSON library (Jackson, Gson…)
import java.net.URI;
import java.net.http.*;

public class SkillSafe {
  static final String API = "https://api.skillsafe.ai/v1/app-api";
  static final String TOKEN = System.getenv("SKILLSAFE_TOKEN");
  static final HttpClient http = HttpClient.newHttpClient();

  static String api(String method, String path, String jsonBody) throws Exception {
    var b = HttpRequest.newBuilder(URI.create(API + path))
        .header("Authorization", "Bearer " + TOKEN)
        .header("Content-Type", "application/json");
    b = jsonBody == null ? b.GET()
        : b.method(method, HttpRequest.BodyPublishers.ofString(jsonBody));
    var res = http.send(b.build(), HttpResponse.BodyHandlers.ofString());
    if (res.body().contains("\"error\""))
      throw new RuntimeException(res.body());
    return res.body(); // {"data": …} — unwrap with your JSON library
  }
}
require "net/http"
require "json"

API = "https://api.skillsafe.ai/v1/app-api"
TOKEN = ENV.fetch("SKILLSAFE_TOKEN")

def api(method, path, body = nil)
  uri = URI(API + path)
  req = Net::HTTP.const_get(method.capitalize).new(uri)
  req["Authorization"] = "Bearer #{TOKEN}"
  req["Content-Type"] = "application/json"
  req.body = body.to_json if body
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 180) { |h| h.request(req) }
  envelope = JSON.parse(res.body)
  raise "#{envelope.dig('error', 'code')}: #{envelope.dig('error', 'message')}" if envelope["error"]
  envelope["data"]
end
<?php
const API = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = getenv("SKILLSAFE_TOKEN");

function api(string $method, string $path, ?array $body = null) {
  global $TOKEN;
  $ch = curl_init(API . $path);
  curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => $method,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 180,
    CURLOPT_HTTPHEADER => [
      "Authorization: Bearer $TOKEN",
      "Content-Type: application/json",
    ],
  ]);
  if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
  $envelope = json_decode(curl_exec($ch), true);
  curl_close($ch);
  if (isset($envelope["error"]))
    throw new RuntimeException($envelope["error"]["code"] . ": " . $envelope["error"]["message"]);
  return $envelope["data"];
}
// .NET 8+
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

static class SkillSafe {
  const string Api = "https://api.skillsafe.ai/v1/app-api";
  static readonly string Token = Environment.GetEnvironmentVariable("SKILLSAFE_TOKEN")!;
  static readonly HttpClient Http = new() { Timeout = TimeSpan.FromMinutes(3) };

  public static async Task<JsonElement> ApiAsync(HttpMethod method, string path, object? body = null) {
    var req = new HttpRequestMessage(method, Api + path);
    req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token);
    if (body is not null) req.Content = JsonContent.Create(body);
    var res = await Http.SendAsync(req);
    var envelope = await res.Content.ReadFromJsonAsync<JsonElement>();
    if (envelope.TryGetProperty("error", out var err))
      throw new Exception(err.GetProperty("code").GetString() + ": " + err.GetProperty("message").GetString());
    return envelope.GetProperty("data");
  }
}

Step 1 — Get a token

POST /guest

Two kinds of token work here. A personal token is the one the app itself uses once you sign in — grab it from the token page (never from DevTools), and metered runs bill your account. A guest token can be minted by any script with no browser at all: it can call /me and the free /estimate, and it can run only if the app sponsors usage — Slop Lens does not, so use a personal token for real reviews.

curl -s -X POST "$API/guest" \
  -H "Content-Type: application/json" \
  -d '{"slug": "slop-lens"}' | jq -r '.data.token'
token = api("POST", "/guest", {"slug": "slop-lens"})["token"]
const { token } = await api("POST", "/guest", { slug: "slop-lens" });
var guest struct{ Token string `json:"token"` }
err := api("POST", "/guest", map[string]string{"slug": "slop-lens"}, &guest)
String envelope = api("POST", "/guest", """
  {"slug": "slop-lens"}
  """); // parse .data.token from the envelope
token = api("POST", "/guest", { slug: "slop-lens" })["token"]
$token = api("POST", "/guest", ["slug" => "slop-lens"])["token"];
var guest = await SkillSafe.ApiAsync(HttpMethod.Post, "/guest",
    new { slug = "slop-lens" });
var token = guest.GetProperty("token").GetString();

Step 2 — Check who you are and your balance

GET /me

Returns subject_type (user or guest), subject_id and credits. Compare credits against the estimate's hold_credits before running — a script that submits a run it cannot afford earns a 402 it could have predicted.

curl -s "$API/me" -H "Authorization: Bearer $TOKEN" | jq '.data'
# {"subject_type":"user","subject_id":"usr_…","credits":50000,…}
me = api("GET", "/me")
print(me["subject_type"], me["credits"])
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
var me struct {
	SubjectType string `json:"subject_type"`
	Credits     int64  `json:"credits"`
}
err := api("GET", "/me", nil, &me)
String envelope = api("GET", "/me", null);
// parse .data.subject_type and .data.credits
me = api("GET", "/me")
puts "#{me['subject_type']} #{me['credits']}"
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"];
var me = await SkillSafe.ApiAsync(HttpMethod.Get, "/me");
Console.WriteLine($"{me.GetProperty("subject_type")} {me.GetProperty("credits")}");

Step 3 — Estimate the cost, and prove the model binding

POST /estimate

Free, and worth calling every time: it returns hold_credits (the worst-case amount reserved for the run — not the price), min_credits (the floor below which the run will not start) and the exact model the app is bound to. The input shape is the same as /run: code is required; brief, surface, ambition and prescan_facts are optional. When the run settles you are charged only what it actually used — typically far less than the hold, which prices the full output cap.

cat > page.html <<'PAGE'
<body class="bg-zinc-950 text-zinc-100 font-sans">
  <h1 class="text-6xl font-extrabold">Unlock your team's
    <span class="bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent">full potential</span></h1>
  <a class="rounded-xl bg-lime-400 px-6 py-3 font-semibold text-zinc-950">Start free trial</a>
</body>
PAGE

BODY=$(jq -n --rawfile code page.html \
  '{code: $code, brief: "SaaS landing page for a project tool", surface: "landing", ambition: "restyle"}')

curl -s -X POST "$API/estimate" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "$BODY" | jq '.data'
# {"model":"gpt-5.6-terra","model_alias":"gpt-terra","hold_credits":…,"min_credits":…,…}
CODE = open("page.html").read()

payload = {
    "code": CODE,
    "brief": "SaaS landing page for a project tool",
    "surface": "landing",     # landing | app | docs | portfolio | commerce | other
    "ambition": "restyle",    # restyle | rethink
}
est = api("POST", "/estimate", payload)
assert est["model_alias"] == "gpt-terra"
print(f"hold {est['hold_credits']}, floor {est['min_credits']}, model {est['model']}")
import assert from "node:assert";
import { readFileSync } from "node:fs";

const payload = {
  code: readFileSync("page.html", "utf8"),
  brief: "SaaS landing page for a project tool",
  surface: "landing",   // landing | app | docs | portfolio | commerce | other
  ambition: "restyle",  // restyle | rethink
};
const est = await api("POST", "/estimate", payload);
assert.equal(est.model_alias, "gpt-terra");
console.log(`hold ${est.hold_credits}, floor ${est.min_credits}, model ${est.model}`);
code, _ := os.ReadFile("page.html")
payload := map[string]any{
	"code":     string(code),
	"brief":    "SaaS landing page for a project tool",
	"surface":  "landing",
	"ambition": "restyle",
}
var est struct {
	Model       string `json:"model"`
	ModelAlias  string `json:"model_alias"`
	HoldCredits int64  `json:"hold_credits"`
	MinCredits  int64  `json:"min_credits"`
}
if err := api("POST", "/estimate", payload, &est); err != nil {
	panic(err)
}
fmt.Printf("hold %d, floor %d, model %s\n", est.HoldCredits, est.MinCredits, est.Model)
String code = java.nio.file.Files.readString(java.nio.file.Path.of("page.html"));
// Build the JSON with your library; fields: code, brief, surface, ambition
String payload = toJson(java.util.Map.of(
    "code", code,
    "brief", "SaaS landing page for a project tool",
    "surface", "landing",
    "ambition", "restyle"));
String envelope = api("POST", "/estimate", payload);
// assert .data.model_alias == "gpt-terra"; read .data.hold_credits
payload = {
  code: File.read("page.html"),
  brief: "SaaS landing page for a project tool",
  surface: "landing",
  ambition: "restyle",
}
est = api("POST", "/estimate", payload)
raise "unexpected model" unless est["model_alias"] == "gpt-terra"
puts "hold #{est['hold_credits']}, floor #{est['min_credits']}, model #{est['model']}"
$payload = [
  "code" => file_get_contents("page.html"),
  "brief" => "SaaS landing page for a project tool",
  "surface" => "landing",
  "ambition" => "restyle",
];
$est = api("POST", "/estimate", $payload);
assert($est["model_alias"] === "gpt-terra");
echo "hold {$est['hold_credits']}, floor {$est['min_credits']}, model {$est['model']}";
var payload = new {
  code = await File.ReadAllTextAsync("page.html"),
  brief = "SaaS landing page for a project tool",
  surface = "landing",
  ambition = "restyle",
};
var est = await SkillSafe.ApiAsync(HttpMethod.Post, "/estimate", payload);
if (est.GetProperty("model_alias").GetString() != "gpt-terra") throw new Exception("unexpected model");
Console.WriteLine($"hold {est.GetProperty("hold_credits")}, floor {est.GetProperty("min_credits")}");

prescan_facts is optional on the API. In the app it carries what the free client-side scanner found — {"tokens": {…}, "signals": [{"id","label"}]} — and the review must reconcile every signal id in coverage_check. From a script you can omit it (you get an empty reconciliation) or replicate it; the review judges the full source either way.

There is one further optional field, retry_note, which the app sends only when a previous attempt at the same review came back in the wrong format. It is a plain-text string naming what was wrong with that reply; the review then re-runs in full and returns the corrected payload without referring to the earlier failure. From a script you almost never want it — send the same Idempotency-Key to replay a run, and reserve retry_note for the case where you genuinely got an unparseable reply and are deliberately paying for one more attempt.

Step 4 — Run the review and wait for the verdict

POST /run GET /jobs/{id}

POST /run returns a job_id immediately; poll GET /jobs/{id} until status is terminal. The review object arrives as a JSON string in output.output — parse it and you have the same object the app renders. Always send an Idempotency-Key derived from a hash of the input: a retry after a network blip then replays the original run instead of paying for a second one.

KEY="slop-lens:$(shasum -a 256 page.html | cut -c1-40)"

JOB_ID=$(curl -s -X POST "$API/run" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d "$BODY" | jq -r '.data.job_id')

while :; do
  JOB=$(curl -s "$API/jobs/$JOB_ID" -H "Authorization: Bearer $TOKEN")
  STATUS=$(echo "$JOB" | jq -r '.data.status')
  [ "$STATUS" = "succeeded" ] || [ "$STATUS" = "failed" ] && break
  sleep 2
done

echo "$JOB" | jq -r '.data.output.output' > review.json
jq '{verdict, cluster, one_line}' review.json
import hashlib, json, time

key = "slop-lens:" + hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest()[:40]
job = api("POST", "/run", payload, headers={"Idempotency-Key": key})

while True:
    j = api("GET", f"/jobs/{job['job_id']}")
    if j["status"] in ("succeeded", "failed"):
        break
    time.sleep(2)

assert j["status"] == "succeeded", j.get("error")
review = json.loads(j["output"]["output"])
print(review["verdict"], review["cluster"], "-", review["one_line"])
for f in review["findings"]:
    print(f"[{f['severity']}] {f['id']} {f['what']}")
import { createHash } from "node:crypto";

const key = "slop-lens:" + createHash("sha256")
  .update(JSON.stringify(payload)).digest("hex").slice(0, 40);

const { job_id } = await fetch(API + "/run", {
  method: "POST",
  headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json",
    "Idempotency-Key": key },
  body: JSON.stringify(payload),
}).then(r => r.json()).then(e => e.data);

let job;
do {
  await new Promise(r => setTimeout(r, 2000));
  job = await api("GET", `/jobs/${job_id}`);
} while (job.status !== "succeeded" && job.status !== "failed");

const review = JSON.parse(job.output.output);
console.log(review.verdict, review.cluster, "-", review.one_line);
sum := sha256.Sum256(bodyBytes) // the JSON payload you send
key := "slop-lens:" + hex.EncodeToString(sum[:])[:40]

req, _ := http.NewRequest("POST", API+"/run", bytes.NewReader(bodyBytes))
req.Header.Set("Authorization", "Bearer "+os.Getenv("SKILLSAFE_TOKEN"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", key)
// … send, decode envelope, keep data.job_id …

var job struct {
	Status string `json:"status"`
	Output struct{ Output string `json:"output"` } `json:"output"`
}
for {
	if err := api("GET", "/jobs/"+jobID, nil, &job); err != nil {
		panic(err)
	}
	if job.Status == "succeeded" || job.Status == "failed" {
		break
	}
	time.Sleep(2 * time.Second)
}
var review map[string]any
json.Unmarshal([]byte(job.Output.Output), &review)
fmt.Println(review["verdict"], review["cluster"])
String key = "slop-lens:" + sha256Hex(payload).substring(0, 40);
// send POST /run with header Idempotency-Key: key, keep data.job_id

while (true) {
  String envelope = api("GET", "/jobs/" + jobId, null);
  // parse .data.status; break on "succeeded" or "failed"
  Thread.sleep(2000);
}
// parse .data.output.output as JSON — that string is the review object
require "digest"

key = "slop-lens:" + Digest::SHA256.hexdigest(payload.to_json)[0, 40]
job = api_with_headers("POST", "/run", payload, { "Idempotency-Key" => key })

loop do
  j = api("GET", "/jobs/#{job['job_id']}")
  break @job = j if %w[succeeded failed].include?(j["status"])
  sleep 2
end

review = JSON.parse(@job["output"]["output"])
puts "#{review['verdict']} #{review['cluster']} - #{review['one_line']}"
$key = "slop-lens:" . substr(hash("sha256", json_encode($payload)), 0, 40);
$job = api_with_headers("POST", "/run", $payload, ["Idempotency-Key: $key"]);

do {
  sleep(2);
  $j = api("GET", "/jobs/" . $job["job_id"]);
} while (!in_array($j["status"], ["succeeded", "failed"], true));

$review = json_decode($j["output"]["output"], true);
echo $review["verdict"], " ", $review["cluster"], " - ", $review["one_line"];
var key = "slop-lens:" + Convert.ToHexString(
    System.Security.Cryptography.SHA256.HashData(
        JsonSerializer.SerializeToUtf8Bytes(payload)))[..40].ToLower();

// send POST /run with header Idempotency-Key: key, keep data.job_id
JsonElement job;
do {
  await Task.Delay(2000);
  job = await SkillSafe.ApiAsync(HttpMethod.Get, $"/jobs/{jobId}");
} while (job.GetProperty("status").GetString() is not ("succeeded" or "failed"));

var review = JsonDocument.Parse(job.GetProperty("output").GetProperty("output").GetString()!).RootElement;
Console.WriteLine($"{review.GetProperty("verdict")} {review.GetProperty("cluster")}");

The review object — output schema

One JSON object. Top-level fields, in the order they stream:

FieldTypeMeaning
design_namestringShort name for what was reviewed, 3–9 words.
verdictenumdistinctive · leaning-generic · templated.
clusterenumThe AI-default look the page most resembles: cream-serif · dark-acid · broadsheet · mixed · none.
one_linestringThe verdict in one sentence, with the single highest-leverage change.
exec_summarystring2–4 paragraphs, the crit itself. Paragraphs are separated by blank lines.
readingsarrayExactly six: {axis, reading, note} for color, typography, layout, structure, motion, copy; reading is deliberate · leaning-default · default.
findingsarray{id, axis, severity, what, evidence, why, fix}evidence is a verbatim quote from the submitted code, or "" only when the finding is an absence (no focus styles, no reduced-motion guard).
keeparrayWhat is already distinctive and must survive the revision.
coverage_checkarray{id, addressed, note} — one entry per prescan signal id sent in prescan_facts.signals, confirmed or overruled with a reason.
directionobject{thesis, palette[], type[], layout, signature, motion, copy_notes[]}. palette is 4–6 {name, hex, role} entries including background, ink and accent roles; type is 2–3 {role, family, fallback, note} entries including display and body.
summarystringA closing paragraph ready to paste into a design-review thread.

The two client-side verdict invariants

The app re-applies two rules after parsing, so the badge can never contradict the findings under it — apply the same ones in your script if you surface the verdict anywhere that matters: a review containing any high-severity finding is never distinctive (it degrades to leaning-generic), and a review with zero findings is never templated.

The fourteen prescan signal families

Signal ids are family:hash, where the hash is derived from the matched text so a re-scan after a revision compares like with like. Families: cluster-cream, cluster-dark, cluster-broadsheet (the three AI-default looks), default-type, single-face, numbered-markers, gradient-accent, purple-pink, glass, uniform-radius, buzzword, stat-hero, and the two quality-floor checks no-focus and no-reduced-motion.

Step 5 — Stream the review as it is written

POST /run-stream

Same body as /run, but the response is a Server-Sent Events stream: delta events carry output text as it is generated, and the final job event carries the terminal job object (including charged_credits). The app drives its progress panel off the top-level JSON keys appearing in the accumulated text — "verdict", "readings", "findings", "direction", "summary" — which you can replicate for a live console display.

# -N disables buffering so events print as they arrive
curl -sN -X POST "$API/run-stream" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d "$BODY"
# event: delta   data: {"text":"{\n  \"design_name\""}
# …
# event: job     data: {"status":"succeeded","charged_credits":…}
import json, requests

with requests.post(API + "/run-stream", json=payload, stream=True, timeout=300,
    headers={"Authorization": f"Bearer {TOKEN}", "Idempotency-Key": key}) as r:
    event, raw = None, []
    for line in r.iter_lines(decode_unicode=True):
        if line.startswith("event:"):
            event = line[6:].strip()
        elif line.startswith("data:"):
            data = json.loads(line[5:])
            if event == "delta":
                raw.append(data.get("text", ""))
            elif event == "job" and data["status"] == "succeeded":
                review = json.loads("".join(raw) or data["output"]["output"])
                print(review["verdict"], "-", review["one_line"])
const res = await fetch(API + "/run-stream", {
  method: "POST",
  headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json",
    "Idempotency-Key": key },
  body: JSON.stringify(payload),
});
const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = "", raw = "", event = null;
for (;;) {
  const { done, value } = await reader.read();
  if (done) break;
  buf += dec.decode(value, { stream: true });
  for (const line of buf.split("\n")) {
    if (line.startsWith("event:")) event = line.slice(6).trim();
    else if (line.startsWith("data:")) {
      const data = JSON.parse(line.slice(5));
      if (event === "delta") raw += data.text ?? "";
      else if (event === "job" && data.status === "succeeded")
        console.log(JSON.parse(raw || data.output.output).verdict);
    }
  }
  buf = buf.slice(buf.lastIndexOf("\n") + 1);
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", API+"/run-stream", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("SKILLSAFE_TOKEN"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", key)
res, err := http.DefaultClient.Do(req)
if err != nil {
	panic(err)
}
defer res.Body.Close()

sc := bufio.NewScanner(res.Body)
sc.Buffer(make([]byte, 1024*1024), 1024*1024)
var event string
var raw strings.Builder
for sc.Scan() {
	line := sc.Text()
	switch {
	case strings.HasPrefix(line, "event:"):
		event = strings.TrimSpace(line[6:])
	case strings.HasPrefix(line, "data:"):
		var data map[string]any
		json.Unmarshal([]byte(line[5:]), &data)
		if event == "delta" {
			if t, ok := data["text"].(string); ok {
				raw.WriteString(t)
			}
		}
	}
}
// Java 17+ — read the stream line by line instead of buffering the body.
var req = HttpRequest.newBuilder(URI.create(API + "/run-stream"))
    .header("Authorization", "Bearer " + TOKEN)
    .header("Content-Type", "application/json")
    .header("Idempotency-Key", key)
    .POST(HttpRequest.BodyPublishers.ofString(payloadJson))
    .build();
var res = http.send(req, HttpResponse.BodyHandlers.ofLines());
var raw = new StringBuilder();
String[] event = {null};
res.body().forEach(line -> {
  if (line.startsWith("event:")) event[0] = line.substring(6).trim();
  else if (line.startsWith("data:") && "delta".equals(event[0]))
    raw.append(parseTextField(line.substring(5))); // your JSON library
});
require "net/http"

uri = URI(API + "/run-stream")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req["Idempotency-Key"] = key
req.body = payload.to_json

raw, event = +"", nil
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 300) do |h|
  h.request(req) do |res|
    res.read_body do |chunk|
      chunk.each_line do |line|
        if line.start_with?("event:") then event = line[6..].strip
        elsif line.start_with?("data:") && event == "delta"
          raw << (JSON.parse(line[5..])["text"] || "")
        end
      end
    end
  end
end
review = JSON.parse(raw)
$event = null;
$raw = "";
$ch = curl_init(API . "/run-stream");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode($payload),
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer $TOKEN",
    "Content-Type: application/json",
    "Idempotency-Key: $key",
  ],
  CURLOPT_WRITEFUNCTION => function ($ch, $chunk) use (&$event, &$raw) {
    foreach (explode("\n", $chunk) as $line) {
      if (str_starts_with($line, "event:")) $event = trim(substr($line, 6));
      elseif (str_starts_with($line, "data:") && $event === "delta") {
        $data = json_decode(substr($line, 5), true);
        $raw .= $data["text"] ?? "";
      }
    }
    return strlen($chunk);
  },
]);
curl_exec($ch);
curl_close($ch);
$review = json_decode($raw, true);
var req = new HttpRequestMessage(HttpMethod.Post, Api + "/run-stream") {
  Content = JsonContent.Create(payload)
};
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token);
req.Headers.Add("Idempotency-Key", key);
using var res = await Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
using var stream = new StreamReader(await res.Content.ReadAsStreamAsync());
string? line, ev = null;
var raw = new StringBuilder();
while ((line = await stream.ReadLineAsync()) is not null) {
  if (line.StartsWith("event:")) ev = line[6..].Trim();
  else if (line.StartsWith("data:") && ev == "delta") {
    var data = JsonDocument.Parse(line[5..]).RootElement;
    if (data.TryGetProperty("text", out var t)) raw.Append(t.GetString());
  }
}
var review = JsonDocument.Parse(raw.ToString()).RootElement;

Step 6 — The recipe: a distinctiveness gate before the page ships

The job this app was built for. A landing page (or a template, or a themed export) is about to ship, and nobody has asked whether it looks like every other AI-generated page. Instead: feed the built HTML and CSS into Slop Lens, print the findings into the build output, and flag the release when verdict comes back templated. The Idempotency-Key is derived from a hash of the input, so a CI retry of the same build replays the first review instead of paying for a second one.

#!/usr/bin/env bash
# design-gate.sh — run against the built static export.
set -euo pipefail

API="https://api.skillsafe.ai/v1/app-api"
TOKEN="${SKILLSAFE_TOKEN:?set SKILLSAFE_TOKEN in the CI secret store}"

cat dist/index.html dist/assets/*.css 2>/dev/null | head -c 60000 > page-src.txt

jq -n --rawfile code page-src.txt \
  --arg brief "${DESIGN_BRIEF:-Marketing page for this repo's product}" \
  '{code: $code, brief: $brief, surface: "landing", ambition: "restyle"}' > input.json

KEY="slop-lens:$(shasum -a 256 input.json | cut -c1-40)"

JOB_ID=$(curl -sf -X POST "$API/run" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d @input.json | jq -r '.data.job_id')

for _ in $(seq 1 120); do
  JOB=$(curl -sf "$API/jobs/$JOB_ID" -H "Authorization: Bearer $TOKEN")
  STATUS=$(echo "$JOB" | jq -r '.data.status')
  case "$STATUS" in succeeded|failed) break ;; esac
  sleep 2
done
[ "$STATUS" = "succeeded" ] || { echo "review run $STATUS"; exit 2; }

echo "$JOB" | jq -r '.data.output.output' > review.json
VERDICT=$(jq -r '.verdict' review.json)
echo "design verdict: $VERDICT ($(jq -r '.cluster' review.json))"
jq -r '.findings[] | "[\(.severity)] \(.id) \(.what)"' review.json
jq -r '"direction: " + .direction.thesis' review.json

# warn on leaning-generic, fail only on templated — tune to taste
[ "$VERDICT" = "templated" ] && exit 1 || exit 0
import hashlib, json, pathlib, sys, time

src = pathlib.Path("dist/index.html").read_text()[:60000]
payload = {"code": src, "brief": "Marketing page for this repo's product",
           "surface": "landing", "ambition": "restyle"}
key = "slop-lens:" + hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest()[:40]

job = api("POST", "/run", payload, headers={"Idempotency-Key": key})
while True:
    j = api("GET", f"/jobs/{job['job_id']}")
    if j["status"] in ("succeeded", "failed"):
        break
    time.sleep(2)
assert j["status"] == "succeeded"

review = json.loads(j["output"]["output"])
print(f"design verdict: {review['verdict']} ({review['cluster']})")
for f in review["findings"]:
    print(f"[{f['severity']}] {f['id']} {f['what']}")
print("direction:", review["direction"]["thesis"])
pathlib.Path("review.json").write_text(json.dumps(review, indent=2))
sys.exit(1 if review["verdict"] == "templated" else 0)
import { readFileSync, writeFileSync } from "node:fs";
import { createHash } from "node:crypto";

const payload = {
  code: readFileSync("dist/index.html", "utf8").slice(0, 60000),
  brief: "Marketing page for this repo's product",
  surface: "landing", ambition: "restyle",
};
const key = "slop-lens:" + createHash("sha256")
  .update(JSON.stringify(payload)).digest("hex").slice(0, 40);

// POST /run with Idempotency-Key: key (step 4), poll /jobs/{id} …
const review = JSON.parse(job.output.output);
console.log(`design verdict: ${review.verdict} (${review.cluster})`);
for (const f of review.findings) console.log(`[${f.severity}] ${f.id} ${f.what}`);
console.log("direction:", review.direction.thesis);
writeFileSync("review.json", JSON.stringify(review, null, 2));
process.exit(review.verdict === "templated" ? 1 : 0);
src, _ := os.ReadFile("dist/index.html")
if len(src) > 60000 {
	src = src[:60000]
}
payload := map[string]any{"code": string(src),
	"brief": "Marketing page for this repo's product",
	"surface": "landing", "ambition": "restyle"}
// derive key, POST /run, poll /jobs/{id} — step 4 —
var review struct {
	Verdict  string `json:"verdict"`
	Cluster  string `json:"cluster"`
	Findings []struct{ ID, Severity, What string } `json:"findings"`
}
json.Unmarshal([]byte(job.Output.Output), &review)
fmt.Printf("design verdict: %s (%s)\n", review.Verdict, review.Cluster)
for _, f := range review.Findings {
	fmt.Printf("[%s] %s %s\n", f.Severity, f.ID, f.What)
}
if review.Verdict == "templated" {
	os.Exit(1)
}
String src = java.nio.file.Files.readString(java.nio.file.Path.of("dist/index.html"));
src = src.substring(0, Math.min(src.length(), 60000));
// build payload {code, brief, surface, ambition}; derive the key; run and poll (step 4)
// then parse .data.output.output as the review object:
String verdict = review.get("verdict").asText();
System.out.println("design verdict: " + verdict + " (" + review.get("cluster").asText() + ")");
for (var f : review.get("findings"))
  System.out.println("[" + f.get("severity").asText() + "] " + f.get("id").asText()
      + " " + f.get("what").asText());
System.exit("templated".equals(verdict) ? 1 : 0);
src = File.read("dist/index.html")[0, 60000]
payload = { code: src, brief: "Marketing page for this repo's product",
            surface: "landing", ambition: "restyle" }
key = "slop-lens:" + Digest::SHA256.hexdigest(payload.to_json)[0, 40]
# POST /run with Idempotency-Key: key, poll /jobs/{id} — step 4 —

review = JSON.parse(job["output"]["output"])
puts "design verdict: #{review['verdict']} (#{review['cluster']})"
review["findings"].each { |f| puts "[#{f['severity']}] #{f['id']} #{f['what']}" }
puts "direction: #{review.dig('direction', 'thesis')}"
exit(review["verdict"] == "templated" ? 1 : 0)
$src = substr(file_get_contents("dist/index.html"), 0, 60000);
$payload = ["code" => $src, "brief" => "Marketing page for this repo's product",
            "surface" => "landing", "ambition" => "restyle"];
$key = "slop-lens:" . substr(hash("sha256", json_encode($payload)), 0, 40);
// POST /run with Idempotency-Key: $key, poll /jobs/{id} — step 4 —

$review = json_decode($job["output"]["output"], true);
echo "design verdict: {$review['verdict']} ({$review['cluster']})\n";
foreach ($review["findings"] as $f)
  echo "[{$f['severity']}] {$f['id']} {$f['what']}\n";
echo "direction: " . $review["direction"]["thesis"] . "\n";
exit($review["verdict"] === "templated" ? 1 : 0);
var src = (await File.ReadAllTextAsync("dist/index.html"));
src = src[..Math.Min(src.Length, 60000)];
var payload = new { code = src, brief = "Marketing page for this repo's product",
                    surface = "landing", ambition = "restyle" };
// derive the key, POST /run, poll /jobs/{id} — step 4 —

var review = JsonDocument.Parse(job.GetProperty("output").GetProperty("output").GetString()!).RootElement;
var verdict = review.GetProperty("verdict").GetString();
Console.WriteLine($"design verdict: {verdict} ({review.GetProperty("cluster")})");
foreach (var f in review.GetProperty("findings").EnumerateArray())
  Console.WriteLine($"[{f.GetProperty("severity")}] {f.GetProperty("id")} {f.GetProperty("what")}");
return verdict == "templated" ? 1 : 0;

Two things to decide before you turn this on for a team. What to do with leaning-generic: failing on templated only, as above, is the version people leave switched on; a warning comment carrying the finding list and direction.thesis is the useful half either way. Whose taste this is: the review is AI-generated judgement calibrated against the three known AI-default looks — treat it as a crit from a colleague, not a court ruling, and let a human overrule it with a brief that names the deliberate choice.