FLAM

Judge

4 Judge routes on the FLAM API: Draw it again, with the fix carried; The judge's verdicts for a batch of frames; One frame's verdict.

Base URL https://api.flam.fashion. Send Authorization: Bearer flam_sk_… on every call; a handful of routes are session-only and say so. How keys and roles work.

POST /api/toolkit/judge/redraw

Draw it again, with the fix carried

The one verb that answers a verdict. Re-submits the SAME develop as a new job through the normal priced path, with the verdict's findings preloaded as a corrective clause, and stamps retry_of_job_id so the attempt strip has a chain instead of two unrelated frames. PAID AT THE NORMAL PRICE — no free-retry cap, no discount, no separate price-book row. A correction changes the words, never the megapixels, so the reserve is the source frame's own cost including any pass its destination forces. The clause is built from OUR labels ("the left hand — drawn wrong"), never from the judge's note: no model-authored prose reaches a prompt any more than it reaches her screen. It lands on params.correction always, and after her own words in params.prompt for the tools that read one. TWO VERDICTS ARE REFUSED with 409 REDRAW_REFUSED and both are OURS: retryFutile (our compiler dropped her note, so a fresh draw reproduces it) and unreadable (our judge could not read the frame — her develop may be perfect). Charging for our own failure is the one thing a redraw must not do.

Request bodyapplication/json (required)

FieldTypeRequiredNotes
jobIdstringyesThe flagged frame, this house's own. Foreign ids are 404, never 403.
idempotencyKeystringnoSend one: without it a double-click reserves twice and queues two paid draws.
{
  "jobId": "string",
  "idempotencyKey": "string"
}

Responses

StatusMeaning
202A new attempt is queued and charged
400JOB_ID_REQUIRED
401No valid session
402INSUFFICIENT_TOKENS { need, spendable }
403A viewer may not spend the house's tokens
404No such frame, or not this house's frame
409NOT_DEVELOPED (no pixels yet), or REDRAW_REFUSED with reason retryFutile | unreadable
422UNKNOWN_TOOL / NO_INPUTS — never charged for work that cannot run

202 returns:

{
  "jobId": "string",
  "status": "queued",
  "tokens": 0,
  "retryOfJobId": "string",
  "correction": "string"
}

Call it

curl -X POST "https://api.flam.fashion/api/toolkit/judge/redraw" \
  -H "Authorization: Bearer $FLAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jobId":"string","idempotencyKey":"string"}'

GET /api/toolkit/judge/verdicts

The judge's verdicts for a batch of frames

BATCH, because the Darkroom renders a GRID and must not fire N requests. Up to 50 comma-separated job ids; extras are ignored rather than refused. Scoped to the caller's organisation — a foreign job id has no verdict here. A frame with no verdict yet is ABSENT from the array, never verdict: null. label is OUR predicate for the code, resolved server-side ("not your model"), because the browser has no vocabulary table. The judge's own note is NOT on this wire (CANON C-2: no model-authored sentence reaches a director), and neither is cost, model id or latency. answer carries the caller's OWN ruling back when one exists, so a client renders an answered frame as answered instead of asking the same question after every reload.

Parameters

InNameTypeRequiredNotes
queryjobIdsstringyesComma-separated generation_jobs ids, max 50.

Responses

StatusMeaning
200The verdicts that exist, in no guaranteed order
400JOB_IDS_REQUIRED
401No valid session

200 returns:

{
  "verdicts": [
    {
      "jobId": "string",
      "score": 0,
      "unreadable": true,
      "flagged": true,
      "showScore": true,
      "retryFutile": true,
      "findings": [
        null
      ],
      "rubricVersion": 0,
      "createdAt": "2026-07-27T09:00:00.000Z",
      "answer": {
        "agrees": true,
        "note": "string"
      }
    }
  ]
}

Call it

curl -X GET "https://api.flam.fashion/api/toolkit/judge/verdicts?jobIds=<jobIds>" \
  -H "Authorization: Bearer $FLAM_API_KEY"

GET /api/toolkit/judge/verdicts/{jobId}

One frame's verdict

Same scoping as the batch. 404 both when the verdict does not exist and when the job belongs to another house — a foreign frame must not be distinguishable from an unjudged one.

Parameters

InNameTypeRequiredNotes
pathjobIdstringyes

Responses

StatusMeaning
200The verdict
401No valid session
404No verdict for this frame, or not this house's frame

200 returns:

{
  "jobId": "string",
  "score": 0,
  "unreadable": true,
  "flagged": true,
  "showScore": true,
  "retryFutile": true,
  "findings": [
    {
      "code": "string",
      "where": "string",
      "label": "string",
      "confidence": "sure"
    }
  ],
  "rubricVersion": 0,
  "createdAt": "2026-07-27T09:00:00.000Z",
  "answer": {
    "agrees": true,
    "note": "string"
  }
}

Call it

curl -X GET "https://api.flam.fashion/api/toolkit/judge/verdicts/{jobId}" \
  -H "Authorization: Bearer $FLAM_API_KEY"

POST /api/toolkit/judge/verdicts/{jobId}/label

She tells us the judge was wrong (L2)

The only direct measure of the judge, and the reason this route exists at all: /api/toolkit/admin/judge/rollup can say "the judge flags 34% of packshots" and cannot say whether it is RIGHT about them. agrees is that answer, one boolean per frame. ONE GESTURE. agrees is the whole required body — a rating and a note are there for the director who reaches for them, and a form nobody opens collects nothing. rating is an integer 1-5 and a value outside that range is REFUSED (400), never clamped: a 5 stored for a 7 is a number nobody typed, sitting in a rollup. THE ROW IS SHARED WITH THE KEEP SIGNAL and the columns are not. This upserts onto the same generation_feedback row an export writes, setting agrees_with_judge, rating and note and nothing else. outcome stays first-wins, because a re-export is not a second opinion. The label itself is last-wins — changing your mind about a verdict is a real thing a person does, and the newer answer is the true one. A LABEL MAY ARRIVE WITH NO EXPORT BEHIND IT. She opens a flagged frame, says the judge is wrong, closes the tab. That row has a label and a null outcome; inventing a kept for her would land in the same rollup as the real ones. Scoped to the caller's organisation from the session and nothing else. No verdict, or another house's frame, is 404 — a foreign frame must not be distinguishable from an unjudged one. A viewer seat is 403.

Parameters

InNameTypeRequiredNotes
pathjobIdstringyesThe judged frame, this house's own.

Request bodyapplication/json (required)

FieldTypeRequiredNotes
agreesbooleanyestrue = we flagged it and we were right. false = we were wrong — the false-positive counter. Absent is NOT false; it is 'she never said'.
ratinginteger | nullnoHer 1-5 on the frame itself. A different fact from whether the judge was right; folding the two loses the false-positive rate.
notestring | nullnoHer sentence — the one thing no rollup can synthesise. Trimmed; empty stores null.
{
  "agrees": true,
  "rating": 0,
  "note": "string"
}

Responses

StatusMeaning
200The label as stored
400AGREES_REQUIRED (no gesture, no row) or RATING_OUT_OF_RANGE (1-5, refused rather than clamped)
401No valid session
403A viewer seat may read a verdict and may not answer it
404No verdict for this frame, or not this house's frame

200 returns:

{
  "jobId": "string",
  "agrees": true,
  "rating": 0,
  "note": "string"
}

Call it

curl -X POST "https://api.flam.fashion/api/toolkit/judge/verdicts/{jobId}/label" \
  -H "Authorization: Bearer $FLAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agrees":true,"rating":0,"note":"string"}'