# Umpire - full API reference Umpire is the neutral third party for agent commerce, operated by Vima Brosta LLC. Two products: signed verdicts (pay once, get an Ed25519 signed pass/fail document on a deliverable against your acceptance spec) and payment verified reputation (feedback writes are free but require on chain proof of payment plus a payer wallet signature; reads are $0.01 and return a signed report). Base URL: https://umpire.vimabrosta.com No accounts. No API keys. Idempotency-Key header honored on paid POSTs. # Why the signature matters Every paid response is a document signed with the key at https://umpire.vimabrosta.com/.well-known/umpire-key.json. Verification recipe: remove the signature field; serialize the rest as JSON with object keys sorted at every depth, no whitespace, UTF-8, non ASCII characters left unescaped (Python: json.dumps(doc, sort_keys=True, separators=(",", ":"), ensure_ascii=False)); check sha256 of those bytes against signature.payload_sha256 (a mismatch means your serialization differs, not that the document is forged); verify the Ed25519 signature sig_base64 over those bytes. Anyone can do this offline: an escrow operator, a human auditor, another agent. A verdict embeds the normalized spec it was judged against (defaults applied); spec_hash is sha256 of the stable JSON of that embedded spec, and deliverable_hash is sha256 of the stable JSON of {"json": , "text": }, both spelled out in the document's hashing field. ERC-8183 users: put your job id in job_ref when purchasing a verdict and the signed document echoes it, giving the evaluator address verifiable evidence to attest completion with. # Payment (x402) 1. Send the request with no payment. You receive HTTP 402. The PAYMENT-REQUIRED response header (base64 JSON, mirrored in the JSON body under `requirements`) carries an accepts array with every enabled payment option. On this deployment: - USDC on Base (default rail) - USDC or native SOL on Solana (same endpoints; pick the Solana option in the 402 accepts list) - Native ETH and BTC are not accepted: the x402 exact scheme settles token authorizations, so native ETH has no path, and Bitcoin has no x402 rail at all (BTC agent payments use L402, a different protocol). Stablecoin amounts are fixed per product. Native SOL amounts are quoted per request from a cached spot rate plus a 1.5% buffer (rounded up), so payers in a volatile asset pay a known, slightly conservative premium for the privilege. 2. Produce payment and retry the IDENTICAL request with the X-PAYMENT header. Libraries: @x402/fetch (JS) wraps this in one call; most agent frameworks have an x402 client. Include an Idempotency-Key header (16+ characters; use a random UUID) on paid POSTs. A retry with the same key and the same body is answered in front of the paywall with the original response and no new settlement. A different body under a used key is 409 idempotency_key_reuse; a retry while the original is still settling is 409 in_flight (wait a few seconds). The key is scoped to the paying wallet when the payment header names one. 3. Requests that cannot run (malformed spec, policy violating URL, unknown tier) are rejected with corrective errors BEFORE settlement. You are not charged for a no. A purchase whose settlement fails after the checks ran is voided: nothing is delivered, nothing is retained, and the same Idempotency-Key can be used again. # Paid endpoints POST /v1/verdicts/witness ($0.25) | POST /v1/verdicts/basic ($1.00) | POST /v1/verdicts/probe ($2.00) | POST /v1/verdicts/sworn ($5.00) Body: {"spec":{"checks":[...]}, "deliverable":{"text?","json?"}, "webhook_url?" (sworn only), "job_ref?"} witness, basic and probe answer synchronously (201) with the signed verdict document, a status link, and a one time secret. sworn answers 202 with pass 1 results and runs every check again about 60 seconds later; the final signed document arrives at your webhook (HMAC signed, see below) and at GET /v1/verdicts/{id}. A sworn verdict passes only if every check passes in BOTH passes. witness is exactly one url_probe check, priced to clear unattended wallet caps. Use it when an agent claims it published, deployed, or updated something and you need a stake free party to say whether the public state agrees: proof against false success. Observations: every check that fetched a URL (url_probe, content_hash on url) carries an observations array in the signed document, one entry per pass: requested_url, url (final, after redirects), redirect_hops, fetched_at, http_status, resolved_address (the peer the response came from), tls {subject, issuer, valid_from, valid_to, fingerprint_sha256} or null for plain http, headers (content-type, content-length, last-modified, etag, cache-control, date, server; values capped at 200 chars), body_sha256 over the raw bytes read, body_bytes, truncated. Anyone can fetch the same URL and compare. GET /v1/reputation?merchant=0x... ($0.01) Returns {"report": , "receipt": ...}. The report contains feedback_count, unique_payers, usdc_volume_attested, median_amount_usdc, outcome counts, fulfillment_rate (by count), weighted_fulfillment_rate (by USDC amount), would_repeat_rate, median_latency_ms, payer_concentration (top_payer_share, single_use_payer_share, cross_merchant_payer_share), a 90 day window, first/last seen, min_feedback_usdc (the floor in force), and a caveat stating what a row proves. Read the independence signals and the weighted rate before the count: a ledger farmed from fresh wallets shows a high single_use_payer_share, a low cross_merchant_payer_share, and thin volume. A merchant with no feedback returns a valid signed report with zeros: absence of data is not a judgment. # Free endpoints POST /v1/feedback - see the signing recipe below. 201 on success. GET /v1/verdicts/{id} - Bearer secret (or ?secret=). Status, pass 1 preview, final document. GET /v1/find?spec_hash=&webhook= - recover sworn verdict ids (exact match, never secrets). POST /v1/validate - {"tier","spec","deliverable?","webhook_url?"}: problems list, URL policy verdicts, and the spec_hash, without paying. No target is fetched. GET /v1/quote - pricing manifest. GET /healthz - health. ## Acceptance spec format A spec is {"checks":[...]} with 1 to 10 checks. Six check types; each is deterministic and reproducible by anyone holding the same spec and deliverable. 1. json_schema - deliverable.json must conform to a JSON Schema (draft 2020-12). Any pattern or patternProperties inside the schema passes the same backtracking guard as a regex check. {"type":"json_schema","schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"}}}} 2. json_path - assert on a value inside deliverable.json. Dot/bracket path. ops: eq ne gt lt gte lte exists not_exists contains {"type":"json_path","path":"result.items[0].price","op":"lte","value":100} 3. content_hash - sha256 of the UTF-8 bytes of deliverable.text, or (probe/sworn) of the raw response bytes of a fetched URL, any content type, up to 2MB (larger bodies fail the check). {"type":"content_hash","sha256":"<64 hex chars>","target":"text"} {"type":"content_hash","sha256":"<64 hex chars>","target":"url","url":"https://example.com/artifact.json"} 4. regex - pattern tested against deliverable.text (flags ims). Patterns that backtrack catastrophically are refused before payment, and every regex and schema evaluates in an isolated worker with a hard time limit, so a hostile pattern fails its check and nothing more. {"type":"regex","pattern":"## Conclusion","expect":"match"} 5. length - character or word bounds on deliverable.text. {"type":"length","unit":"words","min":500,"max":5000} 6. url_probe (witness, probe and sworn tiers) - fetch a public URL and assert on the response. {"type":"url_probe","url":"https://api.example.com/health","expect":{"status":200,"json_path":{"path":"ok","op":"eq","value":true}}} expect may combine: status, keyword, selector {"selector":"h1","op":"contains","value":"..."}, json_path. At most 3 network checks per verdict. GET only, public hosts only, ports 80/443/8080/8443. The deliverable is {"text":"...", "json":{...}}, either or both, text up to 200KB. The verdict passes only if EVERY check passes (sworn: in both passes). ## Feedback signing recipe (submit feedback free) Feedback is accepted only with proof you paid the merchant. Two proofs, both checked: 1. tx_hash must be a settled transaction on Base containing a successful USDC Transfer from your payer address to the merchant address of at least 0.01 USDC, settled within 90 days. 2. signature must be an EIP-191 personal_sign of this exact message by the payer wallet: umpire-feedback-v1 tx: merchant: outcome: would_repeat: latency_ms: Lines joined with \n, no trailing newline. Example message for a feedback with no optional fields: umpire-feedback-v1 tx:0x59f0...c21a merchant:0x1234...abcd outcome:fulfilled would_repeat: latency_ms: In viem: await walletClient.signMessage({ message }). In ethers: await signer.signMessage(message). Then POST https://umpire.vimabrosta.com/v1/feedback with {"merchant","payer","tx_hash","outcome","would_repeat?","latency_ms?","signature"}. One feedback per transaction. Self reviews (payer = merchant) are refused. No free text anywhere: scores only, which is why nothing here needs moderation and no PII exists. Smart wallets: if the payer is a contract account (Coinbase Smart Wallet, Safe), sign with that account; Umpire verifies the signature through ERC-1271 on chain, read only. Error semantics: payment_not_found means the hash is not mined on this network or carries no qualifying transfer (check the network and the hash); chain_unavailable means the RPC could not be reached, retry shortly; amount_too_small means the transfer was under the floor. What a row proves, and what it does not: a settled payment to the merchant and a signature by the wallet that paid. It does not prove an independent counterparty; a merchant can pay itself from fresh wallets for the price of gas. The floor makes dust rows impossible and makes attacking a rival cost real money paid to that rival, and every report carries the independence signals (payer concentration, single use payers, cross merchant breadth) and an amount weighted rate, so a farmed ledger looks farmed. ## Webhook signature (sworn verdicts) Deliveries POST JSON with header X-Umpire-Signature: sha256=. The value is HMAC-SHA256 over the raw request body. The key is the 64 char lowercase hex STRING of sha256(), not raw bytes. Recipe (Node): const key = crypto.createHash("sha256").update(secret).digest("hex"); const expected = "sha256=" + crypto.createHmac("sha256", key).update(rawBody).digest("hex"); Deliveries retry up to 5 times with backoff; the polling endpoint is always the fallback. ## URL policy (probe checks and webhooks) http(s) only, public hosts only, ports 80/443/8080/8443, no embedded credentials. Private, loopback, link local, and *.internal/*.local hosts are refused, at validation time AND at connect time (DNS rebinding is closed). GET only, honest user agent (UmpireProbe/1.0 (+https://umpire.vimabrosta.com/bot)), per host politeness spacing, 4 redirect hops maximum, 2MB read cap, 12s timeout. Umpire never purchases, logs in, or bypasses any control. ## Rate limits (per IP, per minute) feedback 30 (HTTP and MCP share the bucket), validate 30, find 20, status/quote 120, mcp 120, idempotent replays 120. 429 responses carry Retry-After. Paid routes are limited by payment itself. ## Error codes invalid_request 422, invalid_spec 422, unknown_tier 404, invalid_webhook 422, blocked_host/blocked_port/invalid_scheme/credentials_in_url/dns_failure 422 (URL policy), bad_signature 401, payment_not_found 422, payer_mismatch 422, receipt_too_old 422, self_feedback 422, duplicate_feedback 409, amount_too_small 422, receipts_off 503, chain_unavailable 502, invalid_idempotency_key 422, idempotency_key_reuse 409, in_flight 409, already_created 409, rate_limited 429, not_found 404, invalid_json 400, internal 500 (safe to retry). Every error body is {"error":{"code","message","hint","docs"}}. ## Retention A deliverable is discarded the moment its verdict is issued (sworn: after pass 2, about a minute) and only its sha256 remains. Specs and signed documents are kept 90 days for re-reads, then deleted. Feedback rows are permanent, contain only addresses, hashes, enums, and numbers, and never free text. Do not submit personal data in deliverables.