Review code from CI, a pre-commit hook or a bot — not from a browser tab
Code Review Desk splits a review in two. The deterministic half — guessing the language, counting
the size, and matching the classic patterns (secret-shaped assignments, string-built SQL, dynamic
execution, innerHTML writes, swallowed exceptions, debug prints, TODO markers) with line
numbers — runs in the browser, free, in /review.js. This API is
the other half: the metered review that reads the code, decides which of those hits is real and which is
a false positive, finds what the pattern scan can never find, and returns a verdict with every finding
carrying where it is, the problem with its impact, and the fix to apply.
The reply is plain text, not JSON. Four tagged header lines and five ##
sections, in a fixed order. That contract is documented in full below, and
/report.js is a vendored parser for it you can lift straight into
your pipeline.
Basics
Base URL https://api.skillsafe.ai/v1/app-api. Every response is the same envelope:
{"ok":true,"data":{...}} on success,
{"ok":false,"error":{"code":"...","message":"...","details":{...}}} on failure. Check
ok before touching data.
There is no app slug in the path. The routes are exactly the ones above — there is
no /apps/{slug}/ segment anywhere. The slug is bound to the token, once, by
POST /guest with a body of {"slug":"code-review-desk"}. Every later call just
sends that token as a bearer and the platform already knows which app it belongs to.
Money. Credits are ten-thousandths of a dollar. The app runs on the
gpt-terra alias, which resolves to gpt-5.6-terra, at
markup_bps: 1000 — you pay the model's metered cost plus the publisher's 10%, and
nothing to open the page. /estimate is free and creates no job.
Error codes
| HTTP | code | What to do |
|---|---|---|
400 | validation_error | The body is not the shape the app expects. On /guest this is almost always a missing slug in the body — an X-App-Slug header is not accepted. On /run and /estimate it is almost always the input wrapped in an {"input": {...}} envelope, which this API does not take: post the input object itself. |
401 | unauthorized | No bearer token, or an expired guest token. Mint a new one with POST /guest, or take a personal one from /tokens.html. |
402 | payment_required | The balance is below min_credits. Call /estimate first and check it against /me: a 402 after submit is a client bug, not a user error. |
404 | not_found | Wrong slug on /guest, or a job id that does not belong to this token. Note that a path like /apps/code-review-desk/run also lands here — there is no slug path segment. |
409 | conflict | The request conflicts with existing state. Note that a reused Idempotency-Key does not 409: it replays, answering {"job_id", "deduped": true} with the original job even when the body has changed. Change the key whenever you want a new answer. |
429 | rate_limited | Back off and retry with the same idempotency key; do not tight-loop. |
500 – 599 | internal | Transient platform or upstream failure. Retry with the same idempotency key so a run that actually started is not billed twice. |
retry_note in the input table and step 6.Step 1 · A tiny client, and a token
Two ways in. A personal token is the one this browser already holds — open
/tokens.html and press Copy shell export, so you never have to open a
DevTools console or dig through storage by hand. A guest token is minted by
POST /guest with the slug in the body; that call is what binds the token to this
app. Guests can call /me and /estimate freely and can run only when the app
sponsors them — /estimate reports that as sponsor_enabled.
# Option A - take the token this browser already has: open /tokens.html,
# press "Copy shell export", and paste the line it gives you.
export SKILLSAFE_TOKEN="aut_..."
# Option B - mint a guest token. The slug goes in the BODY, and this is the
# only call that ever mentions it. An X-App-Slug header is not accepted and
# answers 400 "slug is required".
curl -s -X POST https://api.skillsafe.ai/v1/app-api/guest \
-H "Content-Type: application/json" \
-d '{"slug":"code-review-desk"}'
# -> {"ok":true,"data":{"token":"aut_...","subject_type":"guest","credits":0}}
# Every later call sends it as a bearer token, and the path never names the app:
# -H "Authorization: Bearer $SKILLSAFE_TOKEN"
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
SLUG = "code-review-desk"
TOKEN = "YOUR_TOKEN" # from /tokens.html, or minted below
def call(path, body=None, method=None, token=None):
"""Every endpoint in this API is JSON in, {data}/{error} out."""
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(BASE + path, data=data,
method=method or ("POST" if data else "GET"))
req.add_header("Content-Type", "application/json")
if token:
req.add_header("Authorization", "Bearer " + token)
with urllib.request.urlopen(req) as r:
payload = json.load(r)
if not payload.get("ok"):
raise RuntimeError(payload.get("error", {}).get("code", "unknown"))
return payload["data"]
if TOKEN == "YOUR_TOKEN":
TOKEN = call("/guest", {"slug": SLUG})["token"] # slug in the body, not a header
print(TOKEN[:12] + "...")
const BASE = "https://api.skillsafe.ai/v1/app-api";
const SLUG = "code-review-desk";
// Read the token from a constant or an injected global - never from a Node env object.
let TOKEN = globalThis.SKILLSAFE_TOKEN || "YOUR_TOKEN";
async function call(path, body, method) {
const res = await fetch(BASE + path, {
method: method || (body ? "POST" : "GET"),
headers: {
"Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {})
},
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;
}
if (TOKEN === "YOUR_TOKEN") {
TOKEN = ""; // no bearer on the guest call
TOKEN = (await call("/guest", { slug: SLUG })).token;
}
package main
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"os"
)
const base = "https://api.skillsafe.ai/v1/app-api"
const slug = "code-review-desk"
// os.Getenv keeps the secret out of the source; a literal works just as well.
var token = os.Getenv("SKILLSAFE_TOKEN") // from /tokens.html, or minted by guest()
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, method string) (json.RawMessage, error) {
var rdr io.Reader
if body != nil {
b, _ := json.Marshal(body)
rdr = bytes.NewReader(b)
if method == "" {
method = "POST"
}
}
if method == "" {
method = "GET"
}
req, _ := http.NewRequest(method, base+path, rdr)
req.Header.Set("Content-Type", "application/json")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
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, errors.New(env.Error.Code + ": " + env.Error.Message)
}
return env.Data, nil
}
func guest() error {
raw, err := call("/guest", map[string]string{"slug": slug}, "")
if err != nil {
return err
}
var out struct{ Token string }
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
token = out.Token
return nil
}
import java.net.URI;
import java.net.http.*;
public class CodeReviewDesk {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static final String SLUG = "code-review-desk";
static String token = "YOUR_TOKEN"; // from /tokens.html, or minted by guest()
static final HttpClient HTTP = HttpClient.newHttpClient();
static String call(String path, String jsonBody, String method) throws Exception {
HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(BASE + path))
.header("Content-Type", "application/json");
if (token != null && !token.isEmpty() && !token.equals("YOUR_TOKEN"))
b.header("Authorization", "Bearer " + token);
if (jsonBody != null) b.method(method == null ? "POST" : method,
HttpRequest.BodyPublishers.ofString(jsonBody));
else b.method(method == null ? "GET" : method, HttpRequest.BodyPublishers.noBody());
HttpResponse<String> res = HTTP.send(b.build(), HttpResponse.BodyHandlers.ofString());
if (res.statusCode() >= 400) throw new RuntimeException(res.body());
return res.body(); // {"ok":true,"data":{...}} - parse with your JSON library
}
static void guest() throws Exception {
token = "";
String body = call("/guest", "{\"slug\":\"" + SLUG + "\"}", null);
token = body.split("\"token\":\"")[1].split("\"")[0];
}
}
require "json"
require "net/http"
BASE = URI("https://api.skillsafe.ai/v1/app-api")
SLUG = "code-review-desk"
TOKEN = ENV["SKILLSAFE_TOKEN"] # from /tokens.html, or minted below
def call(path, body = nil, method: nil, token: TOKEN)
uri = URI(BASE.to_s + path)
klass = method == "DELETE" ? Net::HTTP::Delete : (body ? Net::HTTP::Post : Net::HTTP::Get)
req = klass.new(uri, "Content-Type" => "application/json")
req["Authorization"] = "Bearer #{token}" if token && !token.empty?
req.body = JSON.dump(body) if body
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
TOKEN2 = TOKEN || call("/guest", { "slug" => SLUG }, token: nil)["token"]
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
const SLUG = "code-review-desk";
$token = getenv("SKILLSAFE_TOKEN") ?: ""; // from /tokens.html, or minted below
function call(string $path, $body = null, ?string $method = null) {
global $token;
$headers = ["Content-Type: application/json"];
if ($token !== "") $headers[] = "Authorization: Bearer $token";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CUSTOMREQUEST => $method ?? ($body === null ? "GET" : "POST"),
]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$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"];
}
if ($token === "") {
$token = call("/guest", ["slug" => SLUG])["token"]; // slug in the body
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
class CodeReviewDesk {
const string Base = "https://api.skillsafe.ai/v1/app-api";
const string Slug = "code-review-desk";
static string Token = "YOUR_TOKEN"; // from /tokens.html, or minted by GuestAsync()
static readonly HttpClient Http = new();
static async Task<JsonElement> CallAsync(string path, object? body = null, HttpMethod? method = null) {
var req = new HttpRequestMessage(method ?? (body is null ? HttpMethod.Get : HttpMethod.Post), Base + path);
if (body is not null)
req.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
if (Token is { Length: > 0 } and not "YOUR_TOKEN")
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token);
var res = await Http.SendAsync(req);
var payload = JsonDocument.Parse(await res.Content.ReadAsStringAsync()).RootElement;
if (!payload.GetProperty("ok").GetBoolean())
throw new Exception(payload.GetProperty("error").GetProperty("code").GetString());
return payload.GetProperty("data");
}
static async Task GuestAsync() {
Token = "";
Token = (await CallAsync("/guest", new { slug = Slug })).GetProperty("token").GetString()!;
}
}
Step 2 · Check who you are and what you can spend
GET /me tells you the subject type, the balance, and the app's own model and markup. Compare
the balance against /estimate before you submit — a 402 after submit is a client-side
failure, not a user error.
curl -s https://api.skillsafe.ai/v1/app-api/me \
-H "Authorization: Bearer $SKILLSAFE_TOKEN"
# -> {"ok":true,"data":{
# "subject_type":"user","credits":184203,"app":{"slug":"code-review-desk",
# "model":"gpt-terra","markup_bps":1000,"price_credits":0}}}
#
# credits are in ten-thousandths of a dollar: 184203 = $18.42.
# subject_type is "user" for a personal token and "guest" for a minted one.
me = call("/me", token=TOKEN)
print(me["subject_type"], me["credits"] / 10000, "USD")
const me = await call("/me");
console.log(me.subject_type, me.credits / 10000, "USD");
raw, err := call("/me", nil, "")
// raw is {"subject_type":"user","credits":184203,...}
String me = call("/me", null, null);
System.out.println(me);
me = call("/me")
puts "#{me['subject_type']} #{me['credits'] / 10_000.0} USD"
$me = call("/me");
printf("%s %.2f USD\n", $me["subject_type"], $me["credits"] / 10000);
var me = await CallAsync("/me");
Console.WriteLine($"{me.GetProperty("subject_type")} {me.GetProperty("credits").GetInt32() / 10000.0} USD");
Step 3 · Price the run, and prove the model binding
POST /estimate costs nothing and creates no job. Its body is the input object
itself — the same one /run takes, documented under The input below.
It returns model, model_alias, markup_bps,
hold_credits, min_credits and sponsor_enabled. On this app
model reads gpt-5.6-terra, model_alias reads
gpt-terra and markup_bps is 1000 (10%). Assert on those three in
CI: they are the authoritative proof that the app is wired to the right model at the right markup.
hold_credits is what gets reserved — it prices the full output cap and is
usually far more than you end up paying.
# body.json is the INPUT OBJECT ITSELF - not {"input": {...}}:
# {"code":"...","notes":"...","focus":"Security audit","facts":"..."}
#
# /estimate is free: no job is created, no credits are held, nothing is charged.
curl -s -X POST https://api.skillsafe.ai/v1/app-api/estimate \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-d @body.json
# -> {"ok":true,"data":{"model":"gpt-5.6-terra","model_alias":"gpt-terra",
# "markup_bps":1000,"hold_credits":2860,"min_credits":380,
# "sponsor_enabled":false}}
#
# hold_credits is RESERVED, not charged: it prices the full output cap. What you
# pay is charged_credits on the finished job, and it is usually far lower.
# sponsor_enabled tells you whether a guest token may run at all.
body = {
"code": open("payments.py").read(),
"notes": "Flask service behind an API gateway that already does auth.",
"focus": "Security audit",
}
est = call("/estimate", body, token=TOKEN) # free, no job created
assert est["model"] == "gpt-5.6-terra"
assert est["model_alias"] == "gpt-terra"
assert est["markup_bps"] == 1000
print("reserved up to", est["hold_credits"] / 10000, "USD")
if me["credits"] < est["min_credits"]:
raise SystemExit("top up first - a 402 after submit is a client bug, not a user error")
const body = {
code: source, // the code to review, as a string
notes: "Express handler; the ORM is off the table.",
focus: "Full review"
};
const est = await call("/estimate", body); // free, no job created
console.assert(est.model_alias === "gpt-terra" && est.markup_bps === 1000);
if (me.credits < est.min_credits) throw new Error("top up first");
body := map[string]any{
"code": source,
"notes": "Go HTTP handler; the pool is shared process-wide.",
"focus": "Performance pass",
}
raw, err = call("/estimate", body, "")
var est struct {
Model string `json:"model"`
ModelAlias string `json:"model_alias"`
MarkupBps int `json:"markup_bps"`
HoldCredits int `json:"hold_credits"`
MinCredits int `json:"min_credits"`
Sponsored bool `json:"sponsor_enabled"`
}
json.Unmarshal(raw, &est)
// est.Model == "gpt-5.6-terra", est.ModelAlias == "gpt-terra", est.MarkupBps == 1000
// bodyJson is the input object itself: {"code":"...","focus":"Full review",...}
String est = call("/estimate", bodyJson, null);
// assert est.contains("\"model_alias\":\"gpt-terra\"");
// assert est.contains("\"markup_bps\":1000");
body = {
"code" => source,
"notes" => "Rails service object; ActiveRecord is available.",
"focus" => "Correctness and error handling"
}
est = call("/estimate", body)
raise "wrong model" unless est["model_alias"] == "gpt-terra" && est["markup_bps"] == 1000
puts "reserved up to #{est['hold_credits'] / 10_000.0} USD"
$body = [
"code" => $source,
"notes" => "Laravel controller; validation happens in a form request.",
"focus" => "Full review",
];
$est = call("/estimate", $body);
assert($est["model_alias"] === "gpt-terra" && $est["markup_bps"] === 1000);
var body = new {
code = source,
notes = "ASP.NET minimal API handler; EF Core is in play.",
focus = "Full review"
};
var est = await CallAsync("/estimate", body);
if (est.GetProperty("model_alias").GetString() != "gpt-terra") throw new Exception("wrong model");
Step 4 · Run the review and poll for it
POST /run is metered and returns {"job_id":"job_..."}; poll
GET /jobs/{job_id} until status is succeeded or
failed. The body is the input object directly, exactly as for /estimate. Always
send an Idempotency-Key request header derived from the input — a network blip
or a retry with the same key returns the same job instead of billing twice.
Key it from the input, never from a clock. A good shape is
code-review-desk:<inputhash>:a1. Because the hash comes from the input, every transport
retry of one attempt replays the same job. The reformat retry — the one extra run you make when the
first reply does not satisfy the output contract, carrying retry_note — is a
different input, so it needs a different key: reuse the same base and bump the attempt counter to
:a2 (the page itself appends -reformat to the same base, which does the same job
without ever colliding with the key a later deliberate re-run would use). That way a malformed first reply
can never double-bill the identical attempt, and the two runs stay separable in the ledger. Reusing the key
here would not save money — it would replay the original job and hand you back the same
malformed reply the retry exists to replace.
output.output on the finished job is the plain-text review. It is a string, not JSON —
do not call a JSON parser on it. Step 6 splits it.
# Metered. Always send Idempotency-Key: a retry with the same key returns the
# same job instead of billing twice. Derive it from the input, not from a clock.
HASH=$(python3 -c 'import hashlib;print(hashlib.sha256(open("body.json","rb").read()).hexdigest()[:16])')
KEY="code-review-desk:$HASH:a1" # the reformat retry sends :a2 with retry_note
JOB=$(curl -s -X POST https://api.skillsafe.ai/v1/app-api/run \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d @body.json | python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["job_id"])')
# Poll until terminal.
while :; do
OUT=$(curl -s "https://api.skillsafe.ai/v1/app-api/jobs/$JOB" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN")
ST=$(echo "$OUT" | python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["status"])')
[ "$ST" = "succeeded" ] || [ "$ST" = "failed" ] && break
sleep 2
done
echo "$OUT" | python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["output"]["output"])'
# The output field is PLAIN TEXT - the review contract documented below.
import hashlib, time
digest = hashlib.sha256(json.dumps(body, sort_keys=True).encode()).hexdigest()[:16]
key = "code-review-desk:" + digest + ":a1"
req = urllib.request.Request(BASE + "/run", data=json.dumps(body).encode(), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Idempotency-Key", key) # a retry with this key never double-bills
with urllib.request.urlopen(req) as r:
job_id = json.load(r)["data"]["job_id"]
while True:
job = call("/jobs/" + job_id, token=TOKEN)
if job["status"] in ("succeeded", "failed"):
break
time.sleep(2)
reply = job["output"]["output"] # plain text, NOT json.loads
review = parse_review(reply) # step 6
print(review["verdict"], review["confidence"], len(review["critical"]), "critical")
print("charged", job.get("charged_credits", 0) / 10000, "USD")
const enc = new TextEncoder().encode(JSON.stringify(body));
const digest = [...new Uint8Array(await crypto.subtle.digest("SHA-256", enc))]
.map(b => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
const started = await fetch(BASE + "/run", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + TOKEN,
"Idempotency-Key": "code-review-desk:" + digest + ":a1"
},
body: JSON.stringify(body)
}).then(r => r.json());
let job;
do {
await new Promise(r => setTimeout(r, 2000));
job = await call("/jobs/" + started.data.job_id);
} while (job.status !== "succeeded" && job.status !== "failed");
const review = parseReview(job.output.output); // plain text in, object out - step 6
console.log(review.verdict, review.critical.length, "critical findings");
// POST /run needs the Idempotency-Key header, so build the request directly.
b, _ := json.Marshal(body)
sum := sha256.Sum256(b)
idemKey := "code-review-desk:" + hex.EncodeToString(sum[:8]) + ":a1"
req, _ := http.NewRequest("POST", base+"/run", bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", idemKey)
res, _ := http.DefaultClient.Do(req)
// decode {"data":{"job_id":"..."}} then poll GET /jobs/{id} until status is
// terminal; data.output.output is the plain-text review, not JSON.
String key = "code-review-desk:" + Integer.toHexString(bodyJson.hashCode()) + ":a1";
HttpRequest run = HttpRequest.newBuilder(URI.create(BASE + "/run"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.header("Idempotency-Key", key)
.POST(HttpRequest.BodyPublishers.ofString(bodyJson))
.build();
String started = HTTP.send(run, HttpResponse.BodyHandlers.ofString()).body();
// extract job_id, then poll GET /jobs/{id} every two seconds until terminal.
// data.output.output is a STRING holding the review - feed it to parseReview().
require "digest"
key = "code-review-desk:#{Digest::SHA256.hexdigest(JSON.dump(body))[0, 16]}:a1"
uri = URI("#{BASE}/run")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json",
"Authorization" => "Bearer #{TOKEN2}",
"Idempotency-Key" => key)
req.body = JSON.dump(body)
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
job_id = JSON.parse(res.body)["data"]["job_id"]
job = nil
loop do
job = call("/jobs/#{job_id}", token: TOKEN2)
break if %w[succeeded failed].include?(job["status"])
sleep 2
end
review = parse_review(job["output"]["output"]) # plain text - step 6
puts review[:verdict]
$key = "code-review-desk:" . substr(hash("sha256", json_encode($body)), 0, 16) . ":a1";
$ch = curl_init(BASE . "/run");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer $token",
"Idempotency-Key: $key",
],
]);
$jobId = json_decode(curl_exec($ch), true)["data"]["job_id"];
curl_close($ch);
do {
sleep(2);
$job = call("/jobs/$jobId");
} while (!in_array($job["status"], ["succeeded", "failed"], true));
$review = parse_review($job["output"]["output"]); // plain text - step 6
var json = JsonSerializer.Serialize(body);
var key = "code-review-desk:" + Convert.ToHexString(
System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(json)))[..16] + ":a1";
var run = new HttpRequestMessage(HttpMethod.Post, Base + "/run") {
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
run.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token);
run.Headers.Add("Idempotency-Key", key);
var started = JsonDocument.Parse(await (await Http.SendAsync(run)).Content.ReadAsStringAsync()).RootElement;
var jobId = started.GetProperty("data").GetProperty("job_id").GetString();
JsonElement job;
do {
await Task.Delay(2000);
job = await CallAsync("/jobs/" + jobId);
} while (job.GetProperty("status").GetString() is not ("succeeded" or "failed"));
var reply = job.GetProperty("output").GetProperty("output").GetString()!; // plain text
Step 5 · Or stream it
POST /run-stream is the same metered run over server-sent events, and it is what the app
itself uses. Same body, same Idempotency-Key header. Frame names arrive on the
event: line, not as a type field inside the payload — switch on
the event name. The job frame carries the job_id and arrives first; every
delta frame carries a text fragment, and concatenating them in order rebuilds
the review; the terminal frame (done, or error when the run failed) carries
status, charged_credits and truncated.
Prefer the done payload's own output.output when it is present
— that is what the app does, because an SSE stream can drop its tail and a review missing its last
section fails the contract for no good reason. If truncated is true the balance capped the
output: render what parsed and say so, rather than presenting a half-finished review as complete.
# Server-sent events. Frame names arrive on the `event:` line, not as a field in
# the payload - switch on the event name, not on data.type.
curl -N -s -X POST https://api.skillsafe.ai/v1/app-api/run-stream \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d @body.json
#
# event: job data: {"job_id":"job_..."}
# event: delta data: {"text":"VERDICT: Do not merge\nLANGUAGE: Py"}
# event: delta data: {"text":"thon\nCONFIDENCE: 88\nSUMMARY: ..."}
# event: done data: {"status":"succeeded","charged_credits":1740,"truncated":false}
#
# A failed run ends on `event: error` with {"code":"...","message":"..."} instead.
# Concatenate every delta.text in order: the result is the plain-text review. The
# `done` frame also carries output.output when the run finished - prefer it, an
# SSE stream can drop its tail. truncated:true means the balance capped the reply.
req = urllib.request.Request(BASE + "/run-stream", data=json.dumps(body).encode(), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Idempotency-Key", key)
raw, event, job_id, final = "", None, None, None
with urllib.request.urlopen(req) as stream:
for line in stream:
line = line.decode().rstrip("\n")
if line.startswith("event: "):
event = line[7:].strip() # the frame NAME lives here
elif line.startswith("data: "):
payload = json.loads(line[6:])
if event == "job":
job_id = payload.get("job_id")
elif event == "delta":
raw += payload.get("text", "")
elif event == "done":
final = (payload.get("output") or {}).get("output")
if payload.get("truncated"):
print("cut short by the balance - showing what arrived")
elif event == "error":
raise RuntimeError(payload.get("code", "internal"))
review = parse_review(final or raw) # the done payload is authoritative
const res = await fetch(BASE + "/run-stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + TOKEN,
"Idempotency-Key": "code-review-desk:" + digest + ":a1"
},
body: JSON.stringify(body)
});
const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = "", raw = "", event = null, final = null;
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
const lines = buf.split("\n");
buf = lines.pop();
for (const line of lines) {
if (line.startsWith("event: ")) event = line.slice(7).trim();
else if (line.startsWith("data: ")) {
const p = JSON.parse(line.slice(6));
if (event === "delta") raw += p.text || "";
if (event === "done") {
final = p.output && p.output.output;
if (p.truncated) console.warn("truncated - keep the partial and say so");
}
if (event === "error") throw new Error(p.code + ": " + p.message);
}
}
}
const review = parseReview(final || raw); // the done payload is authoritative
req, _ = http.NewRequest("POST", base+"/run-stream", bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", idemKey)
res, _ = http.DefaultClient.Do(req)
defer res.Body.Close()
sc := bufio.NewScanner(res.Body)
sc.Buffer(make([]byte, 1024*1024), 1024*1024)
var raw, event string
for sc.Scan() {
line := sc.Text()
switch {
case strings.HasPrefix(line, "event: "):
event = strings.TrimSpace(line[7:])
case strings.HasPrefix(line, "data: ") && event == "delta":
var d struct{ Text string }
json.Unmarshal([]byte(line[6:]), &d)
raw += d.Text
}
}
// raw is the plain-text review; hand it to parseReview (step 6). The `done`
// frame's output.output carries the same text and is safer against a lost tail.
HttpRequest stream = HttpRequest.newBuilder(URI.create(BASE + "/run-stream"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.header("Idempotency-Key", key)
.POST(HttpRequest.BodyPublishers.ofString(bodyJson))
.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).trim();
else if (line.startsWith("data: ") && "delta".equals(event[0])) {
String d = line.substring(6);
int i = d.indexOf("\"text\":\"");
if (i >= 0) raw.append(d.substring(i + 8, d.lastIndexOf("\""))); // use a JSON library
}
});
// raw holds the review text once every delta has been unescaped properly.
uri = URI("#{BASE}/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json",
"Authorization" => "Bearer #{TOKEN2}",
"Idempotency-Key" => key)
req.body = JSON.dump(body)
raw = ""
final = nil
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.chomp!
if line.start_with?("event: ") then event = line[7..].strip
elsif line.start_with?("data: ")
p = JSON.parse(line[6..])
raw << (p["text"] || "") if event == "delta"
final = p.dig("output", "output") if event == "done"
end
end
end
end
end
review = parse_review(final || raw)
$raw = "";
$final = null;
$event = null;
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer $token",
"Idempotency-Key: $key",
],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) use (&$raw, &$final, &$event) {
foreach (explode("\n", $chunk) as $line) {
if (str_starts_with($line, "event: ")) {
$event = trim(substr($line, 7));
} elseif (str_starts_with($line, "data: ")) {
$p = json_decode(substr($line, 6), true);
if ($event === "delta") $raw .= $p["text"] ?? "";
if ($event === "done") $final = $p["output"]["output"] ?? null;
}
}
return strlen($chunk);
},
]);
curl_exec($ch);
curl_close($ch);
$review = parse_review($final ?? $raw);
var stream = new HttpRequestMessage(HttpMethod.Post, Base + "/run-stream") {
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
stream.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Token);
stream.Headers.Add("Idempotency-Key", key);
using var res2 = await Http.SendAsync(stream, HttpCompletionOption.ResponseHeadersRead);
using var reader = new StreamReader(await res2.Content.ReadAsStreamAsync());
var raw = new StringBuilder();
string? evt = null, line;
while ((line = await reader.ReadLineAsync()) is not null) {
if (line.StartsWith("event: ")) evt = line[7..].Trim();
else if (line.StartsWith("data: ") && evt == "delta")
raw.Append(JsonDocument.Parse(line[6..]).RootElement.GetProperty("text").GetString());
}
var review = ParseReview(raw.ToString());
Step 6 · Parse the reply
The reply is plain text. Split it in two passes: read the four tagged header lines off the top, then walk
the ## headings and collect the - bullets underneath each. Bullets in the three
issue sections split on " | " into exactly four fields. A section with nothing to report holds
the single bullet - None., which decodes to an empty list.
Validate before you trust it. If any header line is missing, the confidence is not an
integer in 0–100, or any of the five sections is absent, the reply failed the contract: retry once
with retry_note set (a different idempotency key — bump to :a2), and if the
second attempt fails too, surface the raw text rather than a half-parsed review. That is what the app does:
it re-runs once under its own -reformat key, tells you in the panel that this is one extra run,
and falls back to the raw reply if the second attempt misses the contract as well.
# Header lines off the top:
VERDICT=$(sed -n 's/^VERDICT: *//p' review.txt | head -1)
LANGUAGE=$(sed -n 's/^LANGUAGE: *//p' review.txt | head -1)
CONF=$(sed -n 's/^CONFIDENCE: *//p' review.txt | head -1)
# The summary: everything after "SUMMARY: " up to the first blank line.
SUMMARY=$(awk '/^SUMMARY: /{f=1;sub(/^SUMMARY: /,"")} f&&NF{printf "%s ",$0} f&&!NF{exit}' review.txt)
# One section's bullets, e.g. the critical findings:
awk '/^## Critical issues/{f=1;next} /^## /{f=0} f&&/^- /' review.txt
# Split a finding bullet into its four fields:
awk '/^## Critical issues/{f=1;next} /^## /{f=0} f&&/^- /' review.txt |
sed 's/^- //' |
awk -F' \\| ' '{print "finding: "$1"\nwhere: "$2"\nproblem: "$3"\nfix: "$4"\n"}'
# Gate the build on the verdict:
[ "$VERDICT" = "Do not merge" ] && { echo "blocked: $SUMMARY"; exit 1; }
echo "$VERDICT ($LANGUAGE, confidence $CONF)"
import re
SECTIONS = ["Critical issues", "High priority", "Medium priority",
"What's solid", "Recommendations"]
KEYS = ["critical", "high", "medium", "solid", "recs"]
def parse_review(text):
"""Plain text in, dict out. Returns None when the contract is not met."""
text = text.strip()
if text.startswith("```"): # tolerate a stray fence
text = re.sub(r"^```[^\n]*\n?", "", text)
text = re.sub(r"\n?```$", "", text).strip()
head = {}
for tag in ("VERDICT", "LANGUAGE", "CONFIDENCE"):
m = re.search(r"^%s:\s*(.+)$" % tag, text, re.M)
if not m:
return None
head[tag.lower()] = m.group(1).strip()
m = re.search(r"^SUMMARY:\s*([\s\S]*?)(?:\n\s*\n|\n##\s)", text + "\n\n", re.M)
summary = " ".join(m.group(1).split()) if m else ""
body, current = {k: None for k in KEYS}, None
for line in text.split("\n"):
h = re.match(r"^#{2,3}\s+(.*?)\s*$", line)
if h:
name = h.group(1).rstrip(":")
current = KEYS[SECTIONS.index(name)] if name in SECTIONS else None
if current:
body[current] = []
continue
if current is not None and line.startswith("- "):
body[current].append(line[2:].strip())
if any(v is None for v in body.values()) or not summary:
return None
if head["verdict"] not in ("Ship it", "Needs fixes", "Do not merge", "Not reviewable"):
return None
if not re.fullmatch(r"\d{1,3}", head["confidence"]) or int(head["confidence"]) > 100:
return None
out = {"verdict": head["verdict"], "language": head["language"],
"confidence": int(head["confidence"]), "summary": summary}
for key in KEYS:
items = [b for b in body[key] if b.lower().rstrip(".") != "none"]
if key in ("critical", "high", "medium"):
rows = []
for item in items:
parts = [p.strip() for p in item.split(" | ")]
parts += [""] * (4 - len(parts))
rows.append(dict(zip(("finding", "where", "problem", "fix"), parts[:4])))
out[key] = rows
else:
out[key] = items
return out
review = parse_review(reply)
if review is None:
body["retry_note"] = RETRY_NOTE # then re-run with idempotency key ...:a2
elif review["verdict"] == "Do not merge":
raise SystemExit("blocked: " + review["critical"][0]["finding"])
const SECTIONS = {
"Critical issues": "critical", "High priority": "high", "Medium priority": "medium",
"What's solid": "solid", "Recommendations": "recs"
};
const VERDICTS = ["Ship it", "Needs fixes", "Do not merge", "Not reviewable"];
function parseReview(text) {
let t = String(text || "").trim();
if (t.startsWith("```")) t = t.replace(/^```[^\n]*\n?/, "").replace(/\n?```$/, "").trim();
const head = {};
for (const tag of ["VERDICT", "LANGUAGE", "CONFIDENCE"]) {
const m = t.match(new RegExp("^" + tag + ":\\s*(.+)$", "m"));
if (!m) return null;
head[tag.toLowerCase()] = m[1].trim();
}
if (!VERDICTS.includes(head.verdict)) return null;
const conf = Number(head.confidence);
if (!Number.isInteger(conf) || conf < 0 || conf > 100) return null;
const lines = t.split("\n");
const body = {}, summary = [];
let mode = null;
for (const line of lines) {
const h = line.match(/^#{2,3}\s+(.*?)\s*$/);
if (h) {
mode = SECTIONS[h[1].replace(/:$/, "")] || null;
if (mode) body[mode] = [];
continue;
}
const s = line.match(/^SUMMARY:\s*(.*)$/);
if (s && !summary.length) { if (s[1].trim()) summary.push(s[1].trim()); mode = "__sum__"; continue; }
if (mode === "__sum__") { if (!line.trim()) { mode = null; continue; } summary.push(line.trim()); continue; }
if (mode && body[mode] && /^\s*-\s+/.test(line)) body[mode].push(line.replace(/^\s*-\s+/, "").trim());
}
if (Object.values(SECTIONS).some(k => !body[k]) || !summary.length) return null;
const drop = a => a.filter(x => !/^none\.?$/i.test(x));
const rows = a => drop(a).map(item => {
const p = item.split(" | ").map(s => s.trim());
return { finding: p[0] || "", where: p[1] || "General", problem: p[2] || "", fix: p[3] || "" };
});
return {
verdict: head.verdict, language: head.language, confidence: conf,
summary: summary.join(" "),
critical: rows(body.critical), high: rows(body.high), medium: rows(body.medium),
solid: drop(body.solid), recs: drop(body.recs)
};
}
const review = parseReview(reply);
if (!review) { /* re-run once with retry_note and idempotency key ...:a2 */ }
else if (review.verdict === "Do not merge") process.exitCode = 1;
// Two passes: the tagged header lines, then the ## sections.
type Finding struct{ Finding, Where, Problem, Fix string }
var sections = map[string]string{
"Critical issues": "critical", "High priority": "high", "Medium priority": "medium",
"What's solid": "solid", "Recommendations": "recs",
}
func parseReview(text string) (verdict, language string, confidence int,
summary string, out map[string][]string, ok bool) {
out = map[string][]string{}
var mode string
var sum []string
for _, line := range strings.Split(strings.TrimSpace(text), "\n") {
switch {
case strings.HasPrefix(line, "## "):
mode = sections[strings.TrimSuffix(strings.TrimSpace(line[3:]), ":")]
if mode != "" {
out[mode] = []string{}
}
case strings.HasPrefix(line, "VERDICT: "):
verdict, mode = strings.TrimSpace(line[9:]), ""
case strings.HasPrefix(line, "LANGUAGE: "):
language, mode = strings.TrimSpace(line[10:]), ""
case strings.HasPrefix(line, "CONFIDENCE: "):
confidence, _ = strconv.Atoi(strings.TrimSpace(line[12:]))
mode = ""
case strings.HasPrefix(line, "SUMMARY: "):
sum, mode = append(sum, strings.TrimSpace(line[9:])), "sum"
case mode == "sum" && strings.TrimSpace(line) == "":
mode = ""
case mode == "sum":
sum = append(sum, strings.TrimSpace(line))
case mode != "" && strings.HasPrefix(line, "- "):
if item := strings.TrimSpace(line[2:]); !strings.EqualFold(strings.TrimSuffix(item, "."), "none") {
out[mode] = append(out[mode], item)
}
}
}
summary = strings.Join(sum, " ")
ok = verdict != "" && language != "" && summary != "" && len(out) == 5 &&
confidence >= 0 && confidence <= 100
return
}
// A finding bullet splits into exactly four fields:
func splitFinding(item string) Finding {
p := strings.SplitN(item, " | ", 4)
for len(p) < 4 {
p = append(p, "")
}
return Finding{p[0], p[1], p[2], p[3]}
}
import java.util.*;
record Finding(String finding, String where, String problem, String fix) {}
static final Map<String, String> SECTIONS = Map.of(
"Critical issues", "critical", "High priority", "high", "Medium priority", "medium",
"What's solid", "solid", "Recommendations", "recs");
static Map<String, Object> parseReview(String text) {
Map<String, List<String>> body = new HashMap<>();
Map<String, Object> head = new HashMap<>();
StringBuilder summary = new StringBuilder();
String mode = "";
for (String line : text.strip().split("\n")) {
if (line.startsWith("## ")) {
mode = SECTIONS.getOrDefault(line.substring(3).strip().replaceAll(":$", ""), "");
if (!mode.isEmpty()) body.put(mode, new ArrayList<>());
} else if (line.startsWith("VERDICT: ")) { head.put("verdict", line.substring(9).strip()); mode = ""; }
else if (line.startsWith("LANGUAGE: ")) { head.put("language", line.substring(10).strip()); mode = ""; }
else if (line.startsWith("CONFIDENCE: ")) { head.put("confidence", Integer.parseInt(line.substring(12).strip())); mode = ""; }
else if (line.startsWith("SUMMARY: ")) { summary.append(line.substring(9).strip()); mode = "sum"; }
else if (mode.equals("sum") && line.isBlank()) mode = "";
else if (mode.equals("sum")) summary.append(" ").append(line.strip());
else if (!mode.isEmpty() && line.startsWith("- ")) {
String item = line.substring(2).strip();
if (!item.replaceAll("\\.$", "").equalsIgnoreCase("none")) body.get(mode).add(item);
}
}
if (body.size() != 5 || head.size() != 3 || summary.isEmpty()) return null; // contract failure
head.put("summary", summary.toString());
head.put("sections", body);
return head;
}
// A finding bullet has exactly four " | "-separated fields:
static Finding splitFinding(String item) {
String[] p = Arrays.copyOf(item.split(" \\| ", 4), 4);
for (int i = 0; i < 4; i++) if (p[i] == null) p[i] = "";
return new Finding(p[0].strip(), p[1].strip(), p[2].strip(), p[3].strip());
}
SECTIONS = {
"Critical issues" => :critical, "High priority" => :high, "Medium priority" => :medium,
"What's solid" => :solid, "Recommendations" => :recs
}.freeze
VERDICTS = ["Ship it", "Needs fixes", "Do not merge", "Not reviewable"].freeze
def parse_review(text)
t = text.to_s.strip
head = {}
%w[VERDICT LANGUAGE CONFIDENCE].each do |tag|
m = t[/^#{tag}:\s*(.+)$/, 1]
return nil unless m
head[tag.downcase.to_sym] = m.strip
end
return nil unless VERDICTS.include?(head[:verdict])
return nil unless head[:confidence] =~ /\A\d{1,3}\z/ && head[:confidence].to_i <= 100
body = {}
summary = []
mode = nil
t.each_line do |line|
line = line.chomp
if (h = line[/^\#{2,3}\s+(.*?)\s*$/, 1])
mode = SECTIONS[h.sub(/:$/, "")]
body[mode] = [] if mode
elsif line.start_with?("SUMMARY: ")
summary << line[9..].strip
mode = :sum
elsif mode == :sum
line.strip.empty? ? mode = nil : summary << line.strip
elsif mode && body[mode] && line.start_with?("- ")
item = line[2..].strip
body[mode] << item unless item.sub(/\.$/, "").casecmp?("none")
end
end
return nil if SECTIONS.values.any? { |k| body[k].nil? } || summary.empty?
rows = ->(a) { a.map { |i| f = i.split(" | ").map(&:strip)
{ finding: f[0].to_s, where: f[1] || "General",
problem: f[2].to_s, fix: f[3].to_s } } }
head.merge(confidence: head[:confidence].to_i, summary: summary.join(" "),
critical: rows.(body[:critical]), high: rows.(body[:high]),
medium: rows.(body[:medium]), solid: body[:solid], recs: body[:recs])
end
<?php
const SECTIONS = [
"Critical issues" => "critical", "High priority" => "high",
"Medium priority" => "medium", "What's solid" => "solid",
"Recommendations" => "recs",
];
const VERDICTS = ["Ship it", "Needs fixes", "Do not merge", "Not reviewable"];
function parse_review(string $text): ?array {
$text = trim($text);
$head = [];
foreach (["VERDICT", "LANGUAGE", "CONFIDENCE"] as $tag) {
if (!preg_match("/^$tag:\s*(.+)$/m", $text, $m)) return null;
$head[strtolower($tag)] = trim($m[1]);
}
if (!in_array($head["verdict"], VERDICTS, true)) return null;
if (!preg_match('/^\d{1,3}$/', $head["confidence"]) || (int) $head["confidence"] > 100) return null;
$body = [];
$summary = [];
$mode = null;
foreach (explode("\n", $text) as $line) {
if (preg_match('/^#{2,3}\s+(.*?)\s*$/', $line, $h)) {
$mode = SECTIONS[rtrim($h[1], ":")] ?? null;
if ($mode) $body[$mode] = [];
} elseif (str_starts_with($line, "SUMMARY: ")) {
$summary[] = trim(substr($line, 9));
$mode = "sum";
} elseif ($mode === "sum") {
if (trim($line) === "") { $mode = null; } else { $summary[] = trim($line); }
} elseif ($mode && str_starts_with(ltrim($line), "- ")) {
$item = trim(substr(ltrim($line), 2));
if (strcasecmp(rtrim($item, "."), "none") !== 0) $body[$mode][] = $item;
}
}
foreach (SECTIONS as $key) if (!isset($body[$key])) return null;
if (!$summary) return null;
$rows = function (array $items): array {
return array_map(function ($item) {
$p = array_pad(array_map("trim", explode(" | ", $item)), 4, "");
return ["finding" => $p[0], "where" => $p[1] ?: "General",
"problem" => $p[2], "fix" => $p[3]];
}, $items);
};
return $head + [
"confidence" => (int) $head["confidence"],
"summary" => implode(" ", $summary),
"critical" => $rows($body["critical"]), "high" => $rows($body["high"]),
"medium" => $rows($body["medium"]),
"solid" => $body["solid"], "recs" => $body["recs"],
];
}
using System.Text.RegularExpressions;
record Finding(string Finding_, string Where, string Problem, string Fix);
static readonly Dictionary<string, string> Sections = new() {
["Critical issues"] = "critical", ["High priority"] = "high",
["Medium priority"] = "medium", ["What's solid"] = "solid",
["Recommendations"] = "recs"
};
static readonly string[] Verdicts = { "Ship it", "Needs fixes", "Do not merge", "Not reviewable" };
static Dictionary<string, object>? ParseReview(string text) {
var body = new Dictionary<string, List<string>>();
var summary = new List<string>();
string verdict = "", language = "", mode = "";
int confidence = -1;
foreach (var line in text.Trim().Split('\n')) {
var h = Regex.Match(line, @"^#{2,3}\s+(.*?)\s*$");
if (h.Success) {
mode = Sections.GetValueOrDefault(h.Groups[1].Value.TrimEnd(':'), "");
if (mode.Length > 0) body[mode] = new List<string>();
}
else if (line.StartsWith("VERDICT: ")) { verdict = line[9..].Trim(); mode = ""; }
else if (line.StartsWith("LANGUAGE: ")) { language = line[10..].Trim(); mode = ""; }
else if (line.StartsWith("CONFIDENCE: ")) { int.TryParse(line[12..].Trim(), out confidence); mode = ""; }
else if (line.StartsWith("SUMMARY: ")) { summary.Add(line[9..].Trim()); mode = "sum"; }
else if (mode == "sum" && line.Trim().Length == 0) mode = "";
else if (mode == "sum") summary.Add(line.Trim());
else if (mode.Length > 0 && line.TrimStart().StartsWith("- ")) {
var item = line.TrimStart()[2..].Trim();
if (!string.Equals(item.TrimEnd('.'), "none", StringComparison.OrdinalIgnoreCase))
body[mode].Add(item);
}
}
if (body.Count != 5 || summary.Count == 0 || !Verdicts.Contains(verdict)
|| confidence < 0 || confidence > 100) return null; // contract failure
static Finding Split(string item) {
var p = item.Split(" | ", 4);
Array.Resize(ref p, 4);
return new Finding(p[0] ?? "", p[1] ?? "General", p[2] ?? "", p[3] ?? "");
}
return new Dictionary<string, object> {
["verdict"] = verdict, ["language"] = language, ["confidence"] = confidence,
["summary"] = string.Join(" ", summary),
["critical"] = body["critical"].Select(Split).ToList(),
["high"] = body["high"].Select(Split).ToList(),
["medium"] = body["medium"].Select(Split).ToList(),
["solid"] = body["solid"], ["recs"] = body["recs"]
};
}
/report.js is
the app's own implementation and the single place this contract is decoded — see the last section for
how to load it in Node.The input
One flat object, posted directly as the body of /estimate and
/run. There is no input wrapper: {"code": "...", "focus": "Full review"}
is the body, and {"input": {"code": ...}} is a 400 validation_error.
| Field | Type | Notes |
|---|---|---|
code | string, required | The code to review: a function, a module, a script or a unified diff, in any mainstream language. The web app clips anything over 60,000 characters by dropping the middle on whole-line boundaries and keeping both ends, with an in-band marker line naming the original line range that was removed - the end of a file carries the error handling and the entry point, and the later hunks of a diff are as much the change as the first ones, so a plain head-truncation throws away the wrong half. Driving the API yourself, you choose your own clipping; if you clip, say so in-band. Line references in the reply count from 1 over the text exactly as you send it, so do not renumber or reindent it first. |
notes | string, optional, max 6,000 chars | What the code does and where it runs, what you want checked, and the constraints (“the ORM is off the table”, “this is a prototype”, “auth is handled by the gateway”). Often the highest-value field in the body: it is what stops the review recommending something the team cannot do, and a constraint you state is taken at its word — though a critical finding is still reported, with your caveat attached in its problem field. |
focus | string, required | Exactly one of Full review, Security audit, Performance pass or Correctness and error handling. Any other value is a 400. Full review weighs all categories in priority order; the narrower focuses set where the depth goes and never suppress a critical finding from another category. |
facts | string, optional | Plain text: the output of the browser-side mechanical scan — a language guess, size counts, and pattern hits with line numbers. A hint, not a verdict. The review cross-checks it against the code: a hit the code does not actually misuse is a false positive and is dropped silently, and a real issue the scan missed still gets reported. You can generate the exact same block yourself with /review.js (last section), or omit the field entirely. |
retry_note | string, optional | Sent only on the app's automatic one-shot reformat retry, when the first reply failed the parse contract. It restates the required output shape in full. Callers driving the API themselves normally omit it — add it only on a second attempt after a failed parse, and give that attempt its own idempotency key. |
A complete body
{
"code": "import sqlite3\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\nAPI_KEY = \"sk_live_9f2c1b7d4e8a\"\n\ndef get_user(user_id):\n conn = sqlite3.connect(\"app.db\")\n query = \"SELECT * FROM users WHERE id = \" + str(user_id)\n try:\n return conn.execute(query).fetchone()\n except Exception:\n pass\n\n@app.route(\"/users\")\ndef users():\n ids = request.args.get(\"ids\", \"\").split(\",\")\n return jsonify([get_user(i) for i in ids])\n",
"notes": "Small internal Flask service behind an API gateway that already handles authentication. The ids list is usually short. We cannot add an ORM.",
"focus": "Security audit",
"facts": "Mechanical scan of the pasted code (pattern-matching, not judgement):\n- 18 non-empty lines, 486 chars; reads like Python.\n- Pattern hits: 1 possible hardcoded secret(s); 1 string-built SQL statement(s); 1 swallowed exception(s)."
}
Line 9 or
function get_user, and those references are only useful if the text you sent is the text you
have in front of you. For a unified diff the reply references the hunk or the added line's content instead
of absolute numbers.The output contract
The model returns plain text — not JSON, and with no code fence around the whole
response. Four tagged header lines, then five ## sections in a fixed order. These are the
rules the app's own render path enforces, so a client that parses the same way will not be surprised:
- Line 1 is
VERDICT:followed by exactly one ofShip it,Needs fixes,Do not merge,Not reviewable. - Line 2 is
LANGUAGE:followed by the language it reviewed the code as (“Python”, “TypeScript”, “a unified diff of Go”), or exactlyNot statedwhen the input is not identifiable code. - Line 3 is
CONFIDENCE:followed by a bare integer 0–100. No percent sign, no range, no words. It is confidence that the review is complete and correct for what was pasted: high for a self-contained function, low for a fragment whose dependencies are invisible. - Then
SUMMARY:and 2–4 sentences. It may wrap over several lines and ends at the first blank line. - Then exactly five
##sections, in this order:## Critical issues,## High priority,## Medium priority,## What's solid,## Recommendations. All five appear, spelled exactly like that; a missing one is a contract failure, not an empty section. - Every line inside a section is a
-bullet, optionally wrapping onto indented continuation lines. Bullets under the three issue sections carry exactly four|-separated fields: the finding in a few words, where it is (Line 12,Lines 30-41,function getUser, orGeneral), the problem with its concrete impact, and the fix to apply. No pipes inside a field; fix code is inline in backticks, never a fenced block.What's solidandRecommendationsare plain bullets. - A section with nothing to report contains the single bullet
- None.— decode that to an empty list, not to a one-item list holding the word.
Cross-rules the verdict must satisfy
| Verdict | What must hold |
|---|---|
Ship it | Critical issues and High priority are both - None. Medium findings and recommendations may still be present. |
Needs fixes | High-priority findings exist that a competent author can fix before shipping. |
Do not merge | Critical issues holds at least one finding. |
Not reviewable | All three issue sections are - None., and the summary says what was supplied instead of reviewable code. |
The verdict follows the findings, never the reverse. If a reply breaks one of these, the safe reading is the findings: a “Ship it” that still lists a critical finding should be treated as “Needs fixes” until you have checked it, which is exactly what the app flags on screen.
A complete reply
VERDICT: Do not merge
LANGUAGE: Python
CONFIDENCE: 88
SUMMARY: A small Flask module exposing a /users endpoint that looks up rows by id. The user id is
concatenated straight into a SQL string and a live-looking API key is hardcoded at module scope, so
this cannot merge as written. The endpoint also queries once per id and swallows every database
error, which will make the injection above hard to notice in production.
## Critical issues
- SQL injection in the user lookup | Line 9, function get_user | `user_id` is concatenated into the
query string, so a crafted id runs arbitrary SQL against app.db - the endpoint takes ids straight
from the query string, so this is remotely reachable | Use a parameterized query:
`conn.execute("SELECT * FROM users WHERE id = ?", (user_id,))`
- Hardcoded API key in source | Line 5 | `API_KEY` holds what looks like a live secret, so anyone
with repository read access has it and it survives in git history | Read it from configuration at
startup and rotate the exposed value now.
## High priority
- Every database error is swallowed | Lines 12-13, function get_user | The bare `except Exception:
pass` returns None on any failure, so a broken query is indistinguishable from a missing user and
the injection above fails silently | Let the exception propagate, or log it and raise a typed
error the route can turn into a 500.
- One query per id, and the connection is never closed | Lines 8 and 19 | The list comprehension
opens a new sqlite connection per id and none of them are closed; a long ids list exhausts file
handles | Open one connection with a context manager and fetch with a single
`WHERE id IN (...)` query.
## Medium priority
- No validation on the ids parameter | Line 18, function users | `ids` is split on commas with no
type check, so non-numeric input reaches the query builder | Coerce each entry with `int()` inside
a try block and reject the request with a 400 on failure.
## What's solid
- The route layer stays thin and the lookup lives in its own function, so fixing the query is a
one-place change.
- `jsonify` is used for the response rather than hand-built JSON, which gets the content type and
escaping right.
## Recommendations
- Add a test that requests `ids=1%20OR%201=1` and asserts the response is empty, so the injection
cannot come back unnoticed.
- Consider `sqlite3.Row` as the row factory so callers get named columns instead of positional
tuples.
The free lane is client-side, and you can have it too
The facts block is produced by one vendored module in the bundle,
/review.js, with no network access and no dependencies. It exposes
window.ReviewScan.scan(text) for the structured result (language guess, line and character
counts, and every pattern hit with its line number) and
window.ReviewScan.summarize(text) for the exact plain-text block this API takes as
facts. The companion /report.js is the other half:
window.ReviewReport.parseResult(text) decodes a reply into the review object and returns
null when the contract is not met — the same check step 6 rebuilds by hand.
So a pipeline that only wants the mechanical scan does not need this API at all: load those two files in a browser or a JS runtime and call them. The metered endpoint is the judgement half — deciding which hit is real, finding what no pattern can find, and writing the fix.
<!-- In a browser: two plain script tags, no bundler, no network. -->
<script src="/review.js"></script>
<script src="/report.js"></script>
// In Node: both files are plain scripts that assign to `window`, so pointing
// `window` at the global object and requiring them is all it takes. No bundler,
// no network.
const fs = require("fs");
global.window = global;
require("./review.js");
require("./report.js");
const source = fs.readFileSync("payments.py", "utf8");
// The same `facts` string the app sends - hand it straight to /estimate and /run.
const facts = window.ReviewScan.summarize(source);
console.log(facts);
// The structured form, if you would rather gate on it directly:
const scan = window.ReviewScan.scan(source);
console.log(scan.language, scan.lines, "lines,", scan.secrets, "secret-shaped hits");
// And the decoder for the reply, contract check included:
const review = window.ReviewReport.parseResult(replyText);
if (!review) throw new Error("reply failed the output contract - retry with retry_note");
console.log(review.verdict, review.findings.critical.length, "critical findings");
if (review.verdict === "Do not merge") process.exitCode = 1;