Verifiers
Clauses travel inline on the warrant. One read-back lists saved ones.
A verifier is not something you create over the API — clauses travel inline on the warrant's spec, and GET /v1/console/verifiers reads back the saved ones plus the template registry.
{
"spec": {
"clauses": [
{ "kind": "http.status", "equals": 200 },
{ "kind": "data.freshness", "maxAgeSeconds": 60 }
]
}
}The usual path is inline
Most integrations never touch a verifier resource. You pass spec.clauses to
POST /v1/warrants and the engine evaluates them.
The spec is hashed into spec_hash on the warrant, so the check that ran is
provable after the fact.
verifier_id on a warrant references a saved verifier instead. Saved verifiers
are created in the console.
No create, update or delete
There is no POST /v1/verifiers. The only verifier endpoint is the
session-authenticated read-back below; an API key gets 403
user_session_required.
The eight clause kinds
This is the whole registry. A clause with any other kind is rejected at the
API boundary by a discriminated union, so a warrant cannot be opened against a
check the engine cannot run.
kind | Fields | Passes when |
|---|---|---|
http.status | equals (int) | The response status equals it exactly. |
json.schema | schema (string) | The body validates against that named schema. |
json.jsonpath | path, equals? | The path resolves; if equals is given, it matches. |
file.sha256 | digest (string) | The bytes hash to that digest. |
data.freshness | maxAgeSeconds (int > 0) | The payload is no older than that. |
uptime.window | windowSeconds (int > 0), minRatio (0–1) | Coming soon. Reads a supplied uptimeRatio; returns no_uptime_data otherwise. |
chain.event | topic (string) | Coming soon. Reads supplied emittedTopics; returns no_event_log otherwise. |
llm.judge | rubric (string), minScore (0–1) | Coming soon. Reads a supplied judgeScore; returns no_judgement otherwise. |
The three marked coming soon evaluate, but they read a value supplied with the deliverable instead of collecting it. Nothing in Warrant populates those values yet, so they fail closed on a live warrant. Prefer the other five.
See concepts: verifiers for how the score is formed and why every clause is reported rather than only the first failure.
Read back the saved ones
GET /v1/console/verifiers — viewer and up, session only.
The response carries templates — the registry above, with a one-line
description each — alongside data, the organisation's saved verifiers. The
templates are returned by the API rather than hardcoded in the console so the
two can never drift.
GET /v1/console/verifiers HTTP/1.1
Host: api.gowarrant.xyz
Cookie: warrant_session=…200
{
"object": "list",
"templates": [
{ "kind": "http.status", "description": "The endpoint answered with the status you expected." },
{ "kind": "data.freshness", "description": "The payload was generated recently enough to be worth paying for." }
],
"data": [
{
"id": "vrf_01J8Z3M4YQ7K2V9XABCDEFGHJK",
"type": "data.freshness",
"name": "invoice feed, one minute",
"config": { "maxAgeSeconds": 60 },
"created_at": "2026-08-01T09:14:22.000Z"
}
]
}curl -sS https://api.gowarrant.xyz/v1/console/verifiers \
-b "warrant_session=$WARRANT_SESSION"templates is abridged above; the live response carries all eight.
Try one without opening a warrant
POST /bench/run evaluates clauses against a payload you supply and returns the
per-clause result. It needs no authentication, reads nothing and writes nothing,
and is rate limited to 30 per minute.
The deliverable is described rather than fetched: reached, status, raw
text (capped at 128 kB), generated_at, uptime_ratio, emitted_topics and
judge_score. now overrides the clock so a freshness clause is reproducible.
POST /bench/run HTTP/1.1
Host: api.gowarrant.xyz
Content-Type: application/json
{
"clauses": [
{ "kind": "http.status", "equals": 200 },
{ "kind": "data.freshness", "maxAgeSeconds": 60 }
],
"deliverable": {
"reached": true,
"status": 200,
"raw": "{\"invoice\":\"8812\"}",
"generated_at": "2026-08-15T11:00:00.000Z"
},
"now": "2026-08-15T12:00:00.000Z"
}200
{
"passed": false,
"score": 0.5,
"reason_codes": ["data_stale"],
"evidence_hash": "b1946ac92492d2347c6235b4d2611184",
"evaluated_at": "2026-08-15T12:00:00.000Z",
"details": [
{
"kind": "http.status",
"clause": { "kind": "http.status", "equals": 200 },
"passed": true,
"reason_code": null,
"observed": 200
},
{
"kind": "data.freshness",
"clause": { "kind": "data.freshness", "maxAgeSeconds": 60 },
"passed": false,
"reason_code": "data_stale",
"observed": 3600
}
]
}curl -sS https://api.gowarrant.xyz/bench/run \
-H "Content-Type: application/json" \
-d '{
"clauses": [
{ "kind": "http.status", "equals": 200 },
{ "kind": "data.freshness", "maxAgeSeconds": 60 }
],
"deliverable": {
"reached": true,
"status": 200,
"raw": "{\"invoice\":\"8812\"}",
"generated_at": "2026-08-15T11:00:00.000Z"
},
"now": "2026-08-15T12:00:00.000Z"
}'evidence_hash is the same hash the real path records, so a bench run and a
live check over the same bytes are directly comparable.
Two score scales, and they are not the same number
/bench/run returns score as a fraction between 0 and 1 — the share of
clauses that passed, straight from the engine.
Everywhere a score is stored or read back — the check record, the escalation in the console — it is basis points, an integer 0 to 10000.
Half the clauses passing is 0.5 on the bench and 5000 on an escalation. The
engine's figure is multiplied by 10000 and rounded when it is written. Compare
like for like.
Amounts
Clauses carry no amounts, but the warrant they attach to does, and it follows
the same rule as everywhere: an integer string of the asset's smallest unit,
^[0-9]+$.
"8400000" 8.40 USDC
8400000 rejected — a JSON number
"8.40" rejected — not the smallest unit