Skip to content
Warrantv0.1

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.

kindFieldsPasses when
http.statusequals (int)The response status equals it exactly.
json.schemaschema (string)The body validates against that named schema.
json.jsonpathpath, equals?The path resolves; if equals is given, it matches.
file.sha256digest (string)The bytes hash to that digest.
data.freshnessmaxAgeSeconds (int > 0)The payload is no older than that.
uptime.windowwindowSeconds (int > 0), minRatio (0–1)Coming soon. Reads a supplied uptimeRatio; returns no_uptime_data otherwise.
chain.eventtopic (string)Coming soon. Reads supplied emittedTopics; returns no_event_log otherwise.
llm.judgerubric (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