Machine-readable contract

Response schemas

Every JSON response on this site is self-describing. Each one carries a three-field envelope whose $schema pointer resolves to the JSON Schema document for that shape, so an agent can validate a response before it influences a decision.

The envelope

Three top-level fields appear on every JSON response:

{ "$schema": "https://x402-trust.com/schemas/<name>.schema.json", "schemaVersion": "1.0.0", "schemaType": "<name>", ... }

Response signatures

JCS + SHA-256 + Ed25519

Every response whose schema references signature.schema.json carries a top-level signature object: a provider signature proving the body was assembled by x402-trust and not modified afterwards. Hashing a response yourself only proves you saw it; this proves we attested it. Two schema types are deliberately unsigned: watch and watch-view carry capability secrets that must never be forwarded as evidence.

{ ..., "signature": { "alg": "Ed25519", "canon": "RFC8785", "hash": "SHA-256", "keyId": "x402trust-2026-08", "digest": "<hex sha256 of the canonical bytes>", "value": "<base64url Ed25519 signature, no padding>", "publicKeys": "https://x402-trust.com/.well-known/x402-trust-keys.json" } }

Trust anchor: pin, do not follow. The publicKeys field is a discovery hint, never a trust source. A verifier that fetches the key URL from the response it is checking verifies against a key chosen by the sender, which proves nothing: a forged response would simply carry the attacker's own key URL and still verify. Hardcode the trust anchor in your verifier: either the public key itself (strongest, works fully offline; add new keys on rotation) or the pinned URL https://x402-trust.com/.well-known/x402-trust-keys.json, fetched over HTTPS at bootstrap and cached (rotation-friendly; retired keys stay published, so cached copies keep verifying).

To verify a response:

1. Remove the whole signature object (it is excluded from the canonical input, digest included).
2. Canonicalize the rest with JCS (RFC 8785): object keys sorted by UTF-16 code unit order, no whitespace, numbers per ECMAScript rules.
3. SHA-256 the canonical UTF-8 bytes. The hex must equal signature.digest, a short stable reference you can keep in your own evidence records.
4. Verify signature.value (base64url, no padding) against the public key that signature.keyId resolves to in your pinned copy of the key document.

Keys rotate; retired public keys stay published forever, so a response you froze as evidence remains verifiable. Two live calls never produce identical signatures because generatedAt is part of the signed content: freeze a concrete signed response, not the abstract query. A complete worked example (signed response, canonical bytes, digest, test-only key) is published at /schemas/signature-test-vector.json so you can test your verifier without asking us.

Reference verifier (Node 20+, no dependencies). The pinned key map below is the live content of the key document as of this page render:

import { createHash, createPublicKey, verify } from "node:crypto"; // TRUST ANCHOR: pinned at the verifier, NEVER taken from the response. // Alternative: pin the key-document URL and fetch it once at bootstrap. const PINNED_KEYS = { "x402trust-2026-09": "ykdtI-8Qvs137NGUY82ioTl2TRkgW8nNxxTt9ITp4TI", "x402trust-2026-08": "i4jrHKvmZ98-IGgseDfMTjMV4lAaLAgk-EnBeRIJQ5Y", // retired, still verifies }; function canon(v) { // RFC 8785 if (v === null || typeof v !== "object") return JSON.stringify(v); if (Array.isArray(v)) return "[" + v.map(canon).join(",") + "]"; return "{" + Object.keys(v).sort() .map((k) => JSON.stringify(k) + ":" + canon(v[k])).join(",") + "}"; } export function verifyX402TrustResponse(res) { const { signature: sig, ...unsigned } = res; if (!sig || sig.alg !== "Ed25519") return false; // Resolve keyId against the PINNED map. sig.publicKeys is a discovery // hint only: following it would verify against a sender-chosen key. const publicKeyB64url = PINNED_KEYS[sig.keyId]; if (!publicKeyB64url) return false; const bytes = Buffer.from(canon(unsigned), "utf8"); if (createHash("sha256").update(bytes).digest("hex") !== sig.digest) return false; const spki = Buffer.concat([ // Ed25519 SPKI header + raw key Buffer.from("302a300506032b6570032100", "hex"), Buffer.from(publicKeyB64url, "base64url")]); return verify(null, bytes, createPublicKey({ key: spki, format: "der", type: "spki" }), Buffer.from(sig.value, "base64url")); }

Compatibility rules

same version Additive fields

A new optional field is backward compatible and does not change the version.

major bump Remove / rename / resemantize

Removing or renaming a field, or changing what it means, is a new major version.

documented New enum value

A new recommendation or grade value is a documented version change, because strict buyers may reject unknown values.

Two closed enums are worth pinning: recommendation is one of proceed, caution, avoid, parameterize, unverified, not-payable, free (a resource that answers plain requests with data and never presented a payment challenge; no payment flow to grade). grade is one of A, B, C, D, F, or ? for unmeasured.

Data & scoring

11 schemas

Endpoint watches

3 schemas

Signing

1 schemas