Portal
Documentation: all sections

The Judge

The Judge asks a panel of models the same question over the same labeled evidence and signs the outcome: which models were asked, what evidence they were all given, and how each one voted. Every vote is itself a normal metered chat completion under your key, so it carries its own signed inference receipt — and that receipt's context.root equals the verdict's evidence.root, tying every vote to the same inputs.

Use it where a decision needs a portable, checkable record rather than a trusted party: prediction-market resolution, escrow disputes, DAO proposals, insurance claims, "did the agent do the right thing" reviews.

What a verdict proves, and what it does not

This section is the product. The same sentence travels inside the signed payload as trust_model, so the limit cannot be separated from the proof.

Quickstart

curl https://api.buildsable.com/v1/judge/verdicts \
-H "Authorization: Bearer $SABLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "question": "Did the shipment clear customs before 1 June 2026?",
  "options": ["yes", "no"],
  "evidence": [
    {"label": "customs-release", "text": "Release notice, stamped 29 May 2026 …"},
    {"label": "carrier-log", "text": "Carrier scan history …"}
  ],
  "rubric": "Prefer primary documents over summaries.",
  "publish": true
}'
{
  "id": "vrd_7f1c…",
  "decision": "yes",
  "unanimous": false,
  "dissent": 1,
  "method": "majority-v1",
  "tier": "standard",
  "jurors": [
    {
      "model": "sable",
      "engine": "anthropic/claude-opus-5",
      "vote": "yes",
      "confidence": 0.86,
      "rationale": "The release notice is a primary document dated 29 May …",
      "receipt_request_id": "1c2e…",
      "error_class": null,
      "cost_micro_usd": 2140
    },
    { "model": "sable-deepseek-r1", "vote": "yes", "confidence": 0.7, "…": "…" },
    { "model": "sable-gemini-2.5-pro", "vote": "no", "confidence": 0.55, "…": "…" }
  ],
  "evidence": { "count": 2, "root": "9b2f…" },
  "receipt": "eyJ2IjoxLCJraW5kIjoidmVyZGljdCIs…",
  "signature": "0x…",
  "signer": "0x…",
  "hash": "e4a1…",
  "cost_micro_usd": 3910,
  "published": true,
  "public_url": "https://buildsable.com/j/vrd_7f1c…",
  "trust_model": "attested: signed by the Sable gateway over which models were asked …",
  "created_at": "2026-09-06T10:12:04Z"
}

The question and the rationales come back once, in this response. Unless you published, they are not stored anywhere (see below).

Request

POST /v1/judge/verdicts — key-authed (Authorization: Bearer sk-sable_…). Agents call it directly.

FieldTypeNotes
questionstringRequired. Up to 4,000 characters.
optionsstring[]2 to 8 short labels (≤64 chars each). Default ["yes","no"]. abstain and undecided are reserved.
evidence{text, label?}[]0 to 32 items, ≤32 KiB each, ≤256 KiB total. Fingerprinted in-frame; the texts are never stored.
modelsstring[]1 to 5 Sable model ids. Default: the deployment's SABLE_JUDGE_MODELS, else sable, sable-deepseek-r1, sable-gemini-2.5-pro, filtered to models this deployment can actually serve. An unknown, embedding, or unservable model is a 400 naming it.
rubricstringOptional judging instructions (≤2,000 chars), appended to every juror's system prompt and fingerprinted onto the verdict as rubric_fp.
sable_privacy_tier"standard" | "confidential"Default: the key's tier. On confidential, every juror runs through the attested route and each juror receipt carries the attestation; every model on the panel must then be confidential-capable (a model listed with privacy_tier: confidential, or a flagship id, which dials onto one). A deployment without an attested backend refuses with 400.
publishbooleanStore the question and rationales sealed and expose the public page. Default false.
sable_run_idstringChain the verdict receipt into an agent run, like any other receipt.

Each juror sees a strict system prompt (judge instructions plus your rubric), then one user turn: the question, the numbered options, and the labeled evidence. Jurors run concurrently at temperature 0 with a 700-token ceiling, and each is an ordinary in-process chat completion under your key — prepaid credit and holds, spend caps and windows, policy-as-code, guardrails, the circuit breaker, and receipts all apply with no exceptions.

How votes are read

A juror is asked to reply with only {"vote", "confidence", "rationale"}. Parsing is lenient: the first JSON object in the reply is used; the vote is matched to an option case-insensitively (a 1-based index also works); a bare reply that simply is an option counts. Anything else is recorded as abstain with error_class: "unparseable_vote". A juror whose call fails — refused for credit (402), out of scope (403), blocked by a guardrail (422), rate-limited, or an upstream error — abstains with a fixed class, and the panel decides on the rest. A verdict mints as long as at least one juror voted; an all-abstain panel is an error (402 if every juror was refused for credit, otherwise 502), and nothing is signed.

The decision rule

method: "majority-v1" — the option with the most non-abstain votes wins. unanimous is true when every juror who voted agreed (abstentions do not break unanimity). dissent counts the votes not cast for the winner. A tie among the leaders is "undecided", never a coin flip.

Response and the signed payload

The receipt is the base64url canonical JSON of the verdict payload, signature is its EIP-191 secp256k1 signature, signer is the gateway's signing address (GET /v1/receipts/pubkey). The payload is content-free:

{
  "v": 1,
  "kind": "verdict",
  "id": "vrd_…",
  "question_fp": "<sha256 prefix of the question>",
  "options": ["yes", "no"],
  "evidence": { "count": 2, "root": "<sha256 over ordered sha256s>" },
  "rubric_fp": "<sha256 prefix, when a rubric was given>",
  "jurors": [
    {
      "model": "sable",
      "engine": "anthropic/claude-opus-5",
      "vote": "yes",
      "confidence": 0.86,
      "rationale_fp": "<sha256 prefix of the rationale text>",
      "receipt_request_id": "<that juror's own signed inference receipt>"
    },
    { "model": "…", "vote": "abstain", "error_class": "upstream_error" }
  ],
  "decision": "yes",
  "unanimous": false,
  "dissent": 1,
  "method": "majority-v1",
  "tier": "standard",
  "trust_model": "attested: … It proves the process, never that the decision is true …",
  "created_at": "2026-09-06T10:12:04Z"
}

evidence.root is sha256(sha256_hex(item_0) ‖ sha256_hex(item_1) ‖ …) over the evidence texts in order — the same construction as sable_context, because it is the same call: each juror request declares the evidence as sable_context, so fetch any juror's receipt (GET /v1/receipts/{receipt_request_id}) and its context.root matches. rationale_fp lets a rationale published later be checked against the verdict that was signed at the time.

cost_micro_usd on the response is the sum of the jurors' receipted costs plus the optional fixed fee. There is no charge for the verdict itself unless the operator sets one.

Endpoints

MethodPathAuthWhat it does
POST/v1/judge/verdictsAPI keyAsk the panel, sign and store the verdict.
GET/v1/judge/verdictsAPI keyContent-free rows: decision, votes, models, cost, published/anchored flags. ?limit= up to 200.
GET/v1/judge/verdicts/{id}API keyOwner detail: juror records, receipt, signature, anchor. Never the question or rationales.
POST/v1/judge/verdicts/{id}/publishAPI keyPublish. Idempotent if already published; otherwise the body must re-supply the content (see below).
POST/v1/judge/verdicts/{id}/unpublishAPI keyDestroy the sealed content immediately. The content-free record and its signature remain.
GET/v1/judge/public/{id}PublicA published verdict with its question, rationales, receipt, signature and anchor. 404 unless published. Rendered at /j/{id}.

Publication and the privacy contract

The question and the rationales are content, and Sable's rule for content is that it is never persisted. A verdict therefore stores fingerprints and votes, and returns the text once. Publishing is the one deliberate exception, and it follows the same posture as hosted agents and Relay: the question, options and rationales are AES-GCM-sealed with the master key, opened in-frame only to answer the public endpoint, never logged, and nulled the instant you unpublish. An unpublished verdict is 404 to the world — indistinguishable from one that never existed.

Because nothing was stored, publishing after the fact means re-supplying the text:

POST /v1/judge/verdicts/vrd_…/publish
{
  "question": "Did the shipment clear customs before 1 June 2026?",
  "rationales": [
    { "model": "sable", "rationale": "The release notice is a primary document …" }
  ]
}

The gateway checks the question against the signed question_fp and each rationale against that juror's signed rationale_fp; anything the panel did not actually see is refused with 400. A publish with no body on a verdict that was minted unpublished answers 409 and says why. Rationales are optional — a verdict can be published with the question alone.

Anchoring

Each verdict's hash (sha256 of the exact signed bytes) joins the judge anchor stream. On the shared schedule, unanchored hashes are folded into a batch root and published as a Solana memo sable-judge:v1:<root> (and to any other configured anchor backend). The public verdict carries anchor: {id, solana_sig, status, anchored_at} once included, and null before — the page says "awaiting anchor" rather than implying one. A batch is recomputable by anyone from its ordered hash list at GET /v1/vault/anchors/{id}.

On-chain use

The signature is EIP-191 over the canonical payload bytes, so a contract can check it with ecrecover against the published signer and read the decision from the payload the caller submits. Note: no Sable contract is deployed; this is a description of what the signature format makes possible, not of a resolver you can call today. If you build one, pin the signer address from GET /v1/receipts/pubkey and remember what the verdict does and does not prove.

Configuration

VariableDefaultMeaning
SABLE_JUDGE_MODELSunsetCSV of Sable model ids for the default panel. Unset ⇒ sable,sable-deepseek-r1,sable-gemini-2.5-pro, filtered to what the deployment can serve.
SABLE_JUDGE_FEE_MICRO_USD0Optional fixed fee per verdict, metered as one usage_events row of kind verdict on top of the jurors' own inference. 0 records no fee row at all.

Limits