j:jurat

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

checkwhat it looks at
openapi_presentthe canonical discovery surface exists and parses
x402_manifest/.well-known/x402.json for fallback discovery
error_surface_jsonunknown paths answer JSON, not an HTML error page
https_originthe service is reached over TLS
http_not_servedplain http redirects rather than serving payment terms unencrypted, where the payTo address could be rewritten in transit
llms_txtan LLM-readable description of what is for sale
agent_card/.well-known/agent.json
contact_emailinfo.contact.email, used by registries to verify ownership
spec_guidanceinfo.x-guidance prose, expected for AgentCash and IETF compliance
paid_ops_declaredat least one operation is actually marked payable
post_body_examplespaid 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.