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
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.
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
| Header | Required | Semantics |
|---|---|---|
Authorization | Yes | Bearer <enrolled-caller-key>. The key identifies your caller and is checked against the caller registry. An unrecognized or missing key is rejected. |
X-Settlement-Timestamp | Yes | Unix 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-Nonce | Yes | A 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:
| Verdict | Meaning | Example reason_code |
|---|---|---|
| PASS | Every check in spec.checks[] was satisfied. | CONDITION_SATISFIED |
| FAIL | A check was evaluated and not satisfied. | CONDITION_CONTRADICTED |
| INDETERMINATE | A 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
counterpartyvalue without also settingreceipt_profileto this exact string does not bind anything; your receipt stays generic and unbound, unchanged from before this profile existed. counterpartyis required and must be non-empty for this profile — an empty or missing value is rejected before evaluation.- Both
receipt_profileandcounterpartyare 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
/attestasenvelope, or fetched later from the lookup route below) is verified by re-derivingreceipt_id: canonicalize the receipt's signed-core fields with JCS (RFC 8785), SHA-256 the canonical bytes, and confirm the result equalsreceipt_id— then verify the Ed25519sigfield over those same digest bytes using the key matchingverifier_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
/attestresponse envelope additionally wraps the already-signed receipt in a separate outer compact JWS (itsjwsfield,alg: EdDSA) overpayload(the receipt minus_unsigned). This is a second, independent signature — verifying it does not verify the receipt's ownsig/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.