Signed conformance reports for x402 services
Sweep every paid endpoint an x402 service declares, grade how it advertises and gates them, and get back an ed25519-signed report the operator can publish and anyone can verify offline.
What it is
GET /v1/conformance?url=<service base URL> ($0.50) runs the pre-flight checks across every paid endpoint a service declares, adds service-level checks, grades the result, and signs it. Retrieval is free forever at /v1/conformance/<id>, so an operator can link the report publicly and any reader can check the signature without paying jurat anything.
What gets swept
jurat reads the service's own openapi.json and takes every operation that declares a 402 response or an x-payment-info price. Templated paths are resolved using the example values the spec itself declares for those path parameters, so a service that publishes everything as /v1/{country}/{series} still gets checked; the URL actually probed is recorded on each result as probed_path.
Paid POST operations
POST endpoints are probed too. The request body comes from the spec, in this order of preference: the requestBody example, a named example, a schema-level example, an object assembled from per-property examples, and only then a body synthesised from the schema with a type-correct minimal value for each required property. Every result records which of those it was in body_source, so a reader always knows whether the call came from the operator's own documentation or from a schema.
The rule that keeps this honest: a service is never marked down for rejecting a body jurat invented. If a synthesised body is refused, the challenge check is skipped, not failed, and the real issue is raised once at service level as post_body_examples. Nothing semantic is ever guessed either: a required string with a format, a pattern or a minimum length is left alone rather than filled with something plausible. Unpaid POST probes carry an x-jurat-probe header so an operator reading their access log can see exactly what an unexplained bodied request was.
On a POST resource the bare-probe and HEAD checks are skipped rather than failed. Aliasing HEAD onto a POST-only route is itself a misconfiguration, so asking for it and then complaining when it is absent would be wrong.
Coverage is inside the signature
This is the part that matters. Every paid operation in the spec goes into the denominator, including the ones that could not be probed, and coverage sits in the signed payload next to the grade. A report can therefore never claim to have swept a service it only sampled. Anything left out is listed in findings.not_checked with the reason: templated with no example, POST-only, over the 40 endpoint budget for one report, or past the sweep deadline. One sweep touches at most 40 endpoints, four at a time, because it is somebody else's production service.
Service-level checks
| check | what it looks at |
|---|---|
openapi_present | the canonical discovery surface exists and parses |
x402_manifest | /.well-known/x402.json for fallback discovery |
error_surface_json | unknown paths answer JSON, not an HTML error page |
https_origin | the service is reached over TLS |
http_not_served | plain http redirects rather than serving payment terms unencrypted, where the payTo address could be rewritten in transit |
llms_txt | an LLM-readable description of what is for sale |
agent_card | /.well-known/agent.json |
contact_email | info.contact.email, used by registries to verify ownership |
spec_guidance | info.x-guidance prose, expected for AgentCash and IETF compliance |
paid_ops_declared | at least one operation is actually marked payable |
post_body_examples | paid POST operations publish an example request body a buyer can copy |
Each swept endpoint then gets the fifteen endpoint checks from the pre-flight page, including price agreement between the spec and the live 402.
Scoring
Every check counts 1 for a pass, 0.5 for a warning and 0 for a failure. Skipped checks are excluded from both the numerator and the denominator, so an endpoint that could not be probed cannot quietly drag a grade down or prop one up. The score is round(100 * earned / counted). Grades: A at 95 and above, B at 85, C at 70, D at 50, F below that. The formula ships inside every report so the number can be recomputed by hand.
Verifying a report
The signature is ed25519 over the canonical form of the flat signed object, exactly as for an attestation: JSON.stringify with lexicographically sorted keys, no whitespace. The detailed findings sit outside the signature and are bound to it by findings_sha256, a sha256 over a deep canonical form where object keys are sorted at every level and array order is preserved.
import crypto from 'node:crypto';
const r = await (await fetch('https://jurat.dev/v1/conformance/<id>')).json();
const wk = await (await fetch('https://jurat.dev/.well-known/jurat.json')).json();
const raw = Buffer.from(wk.pubkeys.find(k => k.pubkey_id === r.pubkey_id).public_key_b64, 'base64');
const key = crypto.createPublicKey({ key: Buffer.concat([Buffer.from('302a300506032b6570032100','hex'), raw]), format: 'der', type: 'spki' });
// 1. the summary is signed by jurat
const canonical = JSON.stringify(Object.fromEntries(Object.entries(r.signed).sort(([a],[b]) => a < b ? -1 : 1)));
crypto.verify(null, Buffer.from(canonical), key, Buffer.from(r.signature,'base64')); // true
// 2. the findings are the ones that were signed
const dc = v => Array.isArray(v) ? '['+v.map(dc).join(',')+']'
: (v && typeof v === 'object') ? '{'+Object.keys(v).sort().map(k => JSON.stringify(k)+':'+dc(v[k])).join(',')+'}'
: JSON.stringify(v === undefined ? null : v);
crypto.createHash('sha256').update(dc(r.findings),'utf8').digest('hex') === r.signed.findings_sha256; // true
Change one status in the findings and the hash stops matching. Change one number in the summary and the signature stops verifying.
What a grade is not
A report records what jurat observed at checked_at, over the endpoints listed in findings.endpoints and no others. It grades how a service advertises and gates its paid resources. It is not an audit of the data those endpoints return, not a security assessment, not a statement that the operator will deliver, and not a statement of affiliation. jurat has no relationship with the services it reports on and takes no payment from them for a grade.