DefaultVerifier

Integrate

The current integration contract

This is the authoritative reference for calling DefaultVerifier's protected issuance endpoint directly, and for verifying what it returns. If you just want a working first request, start at /start.

Endpoint: POST /settlement-witness/attest

Public verification is not protected issuance

No credential

Public verification — reading and independently checking a receipt that already exists — requires nothing from you. The public key set, the receipt lookup route, and Explorer are all open. A consumer of a receipt never needs DefaultVerifier's secret; verification is possible with only the public key and the receipt itself.

Enrolled caller

Protected issuance/evaluation — calling /settlement-witness/attest to have your own task evaluated and signed — requires an enrolled scoped caller. This is the D31 authentication cutover contract, live in production. An unauthenticated call is rejected with 401.

Enrollment

Enrollment is operator-mediated today, not self-service. There is no public signup form or self-service key-generation flow. To request an enrolled caller, contact Default Settlement at keith@defaultverifier.com and be ready to provide:

  • the integration or organization name you want the caller scoped to;
  • a contact channel for credential delivery;
  • a short description of what you're calling the endpoint for (task/agent shape, expected volume).

An operator provisions a scoped caller key for you and delivers it out of band. Scope is per-caller: your credential authenticates only requests made under your caller identity — it does not grant access to other callers' receipts or credentials.

Contact: keith@defaultverifier.com

Auth headers

HeaderRequiredSemantics
AuthorizationYesBearer <enrolled-caller-key>. The key identifies your caller and is checked against the caller registry. An unrecognized or missing key is rejected.
X-Settlement-TimestampYesUnix seconds. Must fall within 120 seconds of server time in either direction. A missing or stale timestamp is rejected — send it fresh, at request time, not cached.
X-Settlement-NonceYesA value unique per request, per caller, for that 120-second window. Reusing a nonce within the window is rejected as a replay. Generate it fresh for every request — a UUID or random token is sufficient.

All three checks fail closed to the same generic response — the server does not reveal which specific check failed, to avoid helping an attacker narrow down a forged request:

HTTP/1.1 401 Unauthorized
{"detail": {"result": "UNAUTHORIZED"}}

A replayed nonce specifically returns:

HTTP/1.1 409 Conflict
{"detail": {"result": "REPLAY_REJECTED"}}

Auth is enforced before your request body is validated, and before your request reaches the evaluator or signer — an authentication failure is never masked by a downstream evaluation problem, and a malformed body from an unauthenticated caller is rejected as 401, not 422. A 422 response therefore implies your credential was accepted; a 401 implies nothing about whether your body was well-formed.

Request schema

{
  "task_id": "string, your identifier for this task",
  "spec": {
    "checks": [ /* one or more deterministic checks — see below */ ]
  },
  "output": { /* the data your checks evaluate against */ },
  "receipt_profile": "optional — omit for the generic profile",
  "counterparty": "optional — required only if receipt_profile requests counterparty binding"
}

Verdicts and reason codes

Exactly three verdicts exist — there is no fourth:

VerdictMeaningExample reason_code
PASSEvery check in spec.checks[] was satisfied.CONDITION_SATISFIED
FAILA check was evaluated and not satisfied.CONDITION_CONTRADICTED
INDETERMINATEA check could not be evaluated at all — malformed shape, unsupported kind, or a missing input path. This is not "no" — it means the evaluator couldn't reach a verdict.CONDITION_NOT_EVALUABLE

Deterministic evaluator grammar

Checks live in spec.checks[]. Each check has a kind, the inputs it reads from your output, and what it expected.

PASS example

{
  "task_id": "your-task-id-here",
  "spec": {
    "checks": [
      {
        "kind": "field_equals",
        "inputs": { "output_path": "$.status" },
        "expected": "ok"
      }
    ]
  },
  "output": { "status": "ok" }
}

FAIL example

Same spec, mismatched output:

{
  "task_id": "your-task-id-here",
  "spec": {
    "checks": [
      {
        "kind": "field_equals",
        "inputs": { "output_path": "$.status" },
        "expected": "ok"
      }
    ]
  },
  "output": { "status": "error" }
}

INDETERMINATE — the shape that doesn't work

A bare top-level {"expected": "..."} with no checks[] array is not an evaluable contract. It does not mean "compare spec.expected to output.expected" — the evaluator has no check to run, so it returns INDETERMINATE / CONDITION_NOT_EVALUABLE. If your integration currently sends this shape, add a checks[] array as shown above; you do not need to change anything else about your output to fix it.

Receipt profiles

Omit receipt_profile and you get the generic issuer profile, settlement-witness-verified-v0.2 — this is not a formal SAR protocol version; it's Default Settlement's own issuer receipt shape, distinct from the portable sar.v0.1 spec. Full signed-core field set, canonicalization rules, and reason codes: /spec/settlement-witness-v0.2/.

Counterparty-bound profile

Set receipt_profile explicitly to opt into binding a counterparty into the signed receipt:

"receipt_profile": "settlement-witness-verified-v0.2-counterparty-bound",
"counterparty": "<your identifier>"
  • This is explicit opt-in — sending a counterparty value without also setting receipt_profile to this exact string does not bind anything; your receipt stays generic and unbound, unchanged from before this profile existed.
  • counterparty is required and must be non-empty for this profile — an empty or missing value is rejected before evaluation.
  • Both receipt_profile and counterparty are part of the signed core of the receipt — a receipt that names one counterparty cannot be silently rebound to another; changing either value invalidates the signature.

What binding is not: counterparty binding is a cryptographic scope on the identifier string you supplied — it is not independent proof of ownership, control, identity, or wallet state. DefaultVerifier does not verify that you control the counterparty you named; it signs that this specific receipt was issued for that specific declared counterparty, and that the pairing cannot be altered without breaking the signature.

Verifying the receipt

Every issued receipt is signed. There are two distinct verification procedures depending on which artifact you're checking — they are not interchangeable, and a verifier written for one will not validate the other.

  • The receipt itself (returned by /attest as envelope, or fetched later from the lookup route below) is verified by re-deriving receipt_id: canonicalize the receipt's signed-core fields with JCS (RFC 8785), SHA-256 the canonical bytes, and confirm the result equals receipt_id — then verify the Ed25519 sig field over those same digest bytes using the key matching verifier_kid. This is not a JWS. Full field-set and canonicalization rules: /spec/settlement-witness-v0.2/ (production profiles) — see also /spec/sar-v0.1/ for the separate portable protocol.
  • The /attest response envelope additionally wraps the already-signed receipt in a separate outer compact JWS (its jws field, alg: EdDSA) over payload (the receipt minus _unsigned). This is a second, independent signature — verifying it does not verify the receipt's own sig/receipt_id, and vice versa.
  • Public keys for both: GET https://defaultverifier.com/.well-known/jwks.json.
  • Or use Explorer, which performs the receipt-level (JCS/receipt_id) check in the browser and shows you each step.

Receipt lookup

GET /settlement-witness/receipt/{receipt_id} — no authentication required. Pass the full receipt_id exactly as issued, including the literal sha256: prefix (e.g. sha256:d8b37f9254f58d6b94bafc9119d97347b8e5c67f80d86edacce6e3044ce3384e) — the route matches by exact string equality, not by the hex digest alone. Returns the flat receipt object (the same shape as /attest's envelope), or 404 if not found. This is a separate route from /attest: it only reads back a receipt that already exists and never evaluates, signs, or issues anything. Verify what it returns using the receipt-level (JCS/receipt_id) procedure above — the lookup response has no outer JWS.

Fee notice

Real x402 fee enforcement is not active. No fee is currently charged for issuance or verification. If and when enforcement activation becomes a live candidate, existing integrations will be notified in advance of activation — this page will be updated with the specific commercial terms at that time. There is no enforcement date to report today.