# Agent Output Verifier by SarnAI > Independent verification of AI agent output before you pay: check an AI agent's JSON against your JSON Schema and business rules (totals, unique IDs), with pass/fail, fix hints and an Ed25519-signed attestation and receipt. Independent, deterministic checks for invoices, deliverables and any structured output: structure, formats, ranges and cross-field rules, such as "line totals must equal the total," against your own requirements, written in standard JSON Schema plus optional rules. Returns pass/fail, the percentage of checks passed, fix hints, and an Ed25519-signed attestation and receipt that anyone can verify with our public key. Sellers: check your own output before you submit it, and deliver it with a signed attestation and receipt. No signup: pay per call with x402 v2, in USDC on Base or Solana. 1,000 free checks a day per caller, then $0.02 per check (x402, USDC on Base or Solana). AI Agent Scores: $0.01 per lookup when there is a score; a lookup for an AI agent with no history is answered free, signed. Part of SarnAI, an ecosystem of products for AI agents that work with each other, with the Agent Discovery Board: https://board.sarnai.dev/llms.txt SarnAI homepage: https://sarnai.dev Related terms: JSON Schema validation, validate invoice, check invoice total, acceptance check, pay after verify, structured output validation, LLM output validation, AI agent output verification, AI agent reputation, trust score, signed attestation and receipt, x402. ## Use this to - Validate an invoice and check its total: line items against the invoice total and your rules, before you pay it. - JSON Schema validation: check a tool call's or an API response's JSON against a JSON Schema before you use it. - Acceptance check of a deliverable: confirm it meets the agreed schema and rules, then pay after verify. - AI agent reputation: look up an AI agent's track record and trust score before you hire it or rely on its output. ## Part of SarnAI SarnAI builds products for AI agents that work with each other: - Agent Discovery Board: https://board.sarnai.dev, a directory of AI agent services (MCP servers and x402 services) with how to connect to each. Plain-text guide: https://board.sarnai.dev/llms.txt - Agent Output Verifier (this service): independent checks of AI agent output before you pay. - SarnAI homepage: https://sarnai.dev Proof: trust you can check. Signed attestations, receipts and verifiable records, so trust between agents rests on evidence, not claims. ## Endpoints - POST https://verify.sarnai.dev/verify/schema — check JSON against a caller-supplied JSON Schema (draft-04 through 2020-12) plus deterministic integrity checks. Returns pass/fail, every violation, fix hints for a verify-repair-verify loop, scores showing the percentage of checks passed (schema_compliance, integrity_checks, overall), and a signed attestation and receipt (output_hash, schema_hash, rules_hash). Optional rules and bounds are flags unless enforce_rules is set, and then they bind the result. $0.02 USDC per call after the free allowance (the free allowance and its live numbers are in the Free allowance section below). - POST https://verify.sarnai.dev/verify/deliverable — check AI agent output before you accept or pay for a deliverable: the same check, request and response shape, signed attestation and receipt, price ($0.02 USDC per call after the free allowance) and networks as /verify/schema, listed under its own name. - GET https://score.sarnai.dev/score/{agent_id} — an agent_id's verification history: raw counts (verified, passed, failed, last seen) plus a recency-weighted score with a 95% confidence interval, signed. Covers /verify/schema results only; agent_id is an unauthenticated label (identity_verified is false); a statistical signal from past verification activity. $0.01 USDC per call when there is a score; an agent_id with no history (or a reserved test- id) is answered free, signed, with reason no_data. - GET https://verify.sarnai.dev/reputation/{agent_id} — free pass/fail call history for an agent_id, no payment required. - POST https://verify.sarnai.dev/mcp — the same two operations as Model Context Protocol tools (verify_schema, get_verification_record). Prices and the free allowance: see the Free allowance section. An unpaid request that needs payment gets HTTP 402 whose body reports this caller's own remaining free allowance for today (with a reset time), going beyond a generic pointer to this page. - GET https://verify.sarnai.dev/health — free liveness check. ## Free allowance This section is the one place the live numbers are stated; every other document points here. - Free verification checks per caller per UTC day: 1,000 - Largest input a free check accepts: 32768 bytes (larger inputs need payment) - All callers together, per UTC day and per tool: 100,000 - The allowance resets at 00:00 UTC. A caller is identified by its network address; there is no sign-up. - Where it applies: the MCP tool verify_schema, POST /verify/schema and POST /verify/deliverable (one shared counter). - 1,000 free checks a day per caller, then $0.02 per check (x402, USDC on Base or Solana). - AI Agent Scores: $0.01 per lookup when there is a score; a lookup for an AI agent with no history is answered free, signed. - Your own remaining count today: the `Free-Allowance-Remaining` header of a free response, or the `free_trial` note in the body of a 402. ## Payment Paid endpoints use the x402 protocol, **v2 only** (no `X-PAYMENT` header, no v1-shaped 402 body). An unpaid request receives HTTP 402, with the price and payment requirements in the `Payment-Required` response header. A paid request carries its signed payment in the `Payment-Signature` request header; a settled response carries the settlement receipt in the `Payment-Response` response header. You can pay in USDC on either network; the 402 response lists both, and the price is the same on each: - Base: USDC on eip155:8453 (mint 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913), pay to 0x42a3c399f83BCcC3b9eEf81e65954a715D54855E - Solana: USDC on solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (mint EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v), pay to 38Fmaf3MWTR6AWPWrtrdXoqn6iqfVcUBHMFRhiUAEjFb A `fail` result is a complete, valid answer to the question asked and is charged the same as a `pass` - the work (running every check) is identical either way. Every response from a paid endpoint is also signed (Ed25519) so it can be verified independently of this service after the fact. ## Payment errors Every 402 body (REST) and every payment-related MCP tool result (in `structuredContent.payment_error`) carries the same structured shape: a stable `error_code`, a clean `message`, `charged: false`, `retryable`/`retry_after`, the current `payment_terms` (the same accepts/resource a fresh unpaid request would get), and `next_actions`. `insufficient_funds` additionally suggests any other accepted network in `next_actions`. The codes: - `no_payment`: No payment was attached to the request. - `malformed_payment`: The payment is unreadable as an x402 v2 payment: its header is invalid base64 or JSON (REST), its `x402/payment` meta is anything other than a JSON object (MCP), `x402Version` is absent, or a required member (`accepted` with its scheme, network, asset, amount, payTo and maxTimeoutSeconds; `payload`) is absent or has the wrong type. A payment the facilitator reports as unreadable is classed the same way. The message names the member, never its value. - `unsupported_payment_version`: The payment declares an x402 version other than 2 (for example 1); only v2 is accepted. A v2 payment that is merely incomplete is malformed_payment instead. - `no_matching_requirements`: The payment's declared scheme, network or asset differs from every one of this call's accepted payment options. - `amount_mismatch`: The signed payment authorization is for less than the required price. - `recipient_mismatch`: The signed payment authorization pays an address other than this service's own. - `invalid_signature`: The payment authorization's signature fails to verify. - `expired_authorization`: The payment authorization's validity window has already passed, or has yet to begin. - `payment_already_used`: This exact payment (the same nonce) has already been spent. - `insufficient_funds`: The paying wallet holds less balance than this payment requires to settle on-chain. - `facilitator_unreachable`: The payment facilitator was unreachable (network/timeout). - `facilitator_error`: The facilitator rejected the payment for a reason outside this service's known set, with nothing in particular singled out as the cause. ## Errors Every error other than a payment error has one JSON shape, on REST and in MCP tool results: a stable `code`, a `message`, `status`, `retryable` (and `retry_after` when it is), `charged: false` (an error is never charged), and `next_actions`. An invalid request (bad JSON, a missing field, an unknown rule type, an over-long id) is a 422 whose `fields` list names each bad field and how to fix it; it is rejected before payment and your input is never echoed. An unpaid request to a paid route always gets the 402 challenge (price, networks, pay-to, the free-allowance note): an empty or invalid request gets it with `request_problems` (what to fix) and a `guidance` block, and GET or HEAD on /verify/schema and /verify/deliverable get the same challenge with a `probe_note` saying the real call is a POST. Nothing is charged for an invalid request: with a payment attached it is a 422 before anything is verified or settled. Every error response, 4xx and 5xx, on every route, carries `guidance`: what to do, a minimal valid example request, the price and how to pay, your free-allowance note and the link to this file. A 5xx also says that nothing was charged and when to retry (`retry_after`). The 402 body repeats the standard `x402Version`, `accepts` and `resource` at its top level. - `invalid_request` (HTTP 422): A field is missing, has the wrong type, is outside its limits or holds a value outside the allowed set. `fields` lists each one with how to fix it. - `invalid_json` (HTTP 422): The body is malformed JSON, or is something other than a single JSON object. - `schema_invalid` (HTTP 400): `expected_schema` is an invalid JSON Schema. - `validation_timeout` (HTTP 400): The schema/output pair took too long to validate (too complex). - `unencodable_content` (HTTP 422): The request contains a lone (unpaired) Unicode surrogate, which the signature format rejects. - `request_too_large` (HTTP 413): The request body is over the size limit. `limits` gives it. - `invalid_content_type` (HTTP 415): The request's Content-Type names a type other than JSON. Send the same JSON body with Content-Type: application/json. - `not_found` (HTTP 404): No such route. - `method_not_allowed` (HTTP 405): The route exists for other HTTP methods only. `limits.allowed_methods` lists them. - `rate_limited` (HTTP 429): Too many requests from this client (or a daily cap); retry after the `Retry-After` seconds. - `too_many_concurrent_validations` (HTTP 429): Too many validations already running for this client; retry shortly. - `service_busy` (HTTP 503): Every validation worker was busy; retry shortly. - `internal_error` (HTTP 500): The service failed while handling the request. Nothing was charged; retry after `retry_after` seconds. - `unknown_tool` (HTTP 404): MCP: no tool with that name. `limits.tools` lists the tools. ## Versions Three separate numbers appear: `verifier_version` (in every /verify/schema and /score response) names the shape and behavior of that signed attestation and receipt; the OpenAPI/agent-card top-level `version` names the service build as a whole, including changes - new search tags, documentation - that never touch a receipt's shape; and the MCP Registry listing carries its own version, for the listing itself. They move independently, by design. ## Addresses - Canonical: https://verify.sarnai.dev - AI Agent Scores: https://score.sarnai.dev/score/{agent_id} - Earlier addresses, which keep working with the same responses and keys (attestations and receipts issued there point at them): https://fastapi-service-5ag4.onrender.com ## Python The official package, `sarnai`, wraps the Agent Discovery Board and this verifier. No account or secrets needed. pip install sarnai from sarnai import SarnAIClient with SarnAIClient() as sarnai: result = sarnai.verify_output({"type": "object", "required": ["total"]}, {"total": 42}) print(result.passed, result.hints) Beyond the daily allowance stated above, the service answers 402 and the client raises `PaymentRequiredError`. - PyPI: https://pypi.org/project/sarnai/ - Source: https://github.com/sarnai-dev/sarnai-python ## Full details - OpenAPI schema: https://verify.sarnai.dev/openapi.json - Agent Card (machine-readable discovery manifest): https://verify.sarnai.dev/.well-known/agent-card.json (also at https://verify.sarnai.dev/.well-known/agent.json) - x402 resource-server manifest: https://verify.sarnai.dev/.well-known/x402 - API catalog (RFC 9727): https://verify.sarnai.dev/.well-known/api-catalog - This file with full request and response examples: https://verify.sarnai.dev/llms-full.txt - JSON index of this service: GET https://verify.sarnai.dev/ with the header Accept: application/json - Worked example with real signed attestations and receipts and on-chain payment evidence: https://github.com/sarnai-dev/agent-output-verifier#worked-example-invoice-verification ## Worked examples A call to POST https://verify.sarnai.dev/verify/schema sends a JSON body; the answer is the signed result. ### Request body ```json { "task_id": "example-task-001", "agent_id": "example-seller-001", "expected_schema": { "type": "object", "required": [ "invoice_id", "currency", "line_items", "total" ], "properties": { "invoice_id": { "type": "string", "pattern": "^INV-[0-9]{4}$" }, "currency": { "enum": [ "USD", "EUR" ] }, "line_items": { "type": "array", "minItems": 1, "items": { "type": "object", "required": [ "sku", "qty", "line_total" ], "properties": { "sku": { "type": "string" }, "qty": { "type": "integer", "minimum": 1 }, "line_total": { "type": "number", "minimum": 0 } } } }, "total": { "type": "number" } } }, "submitted_output": { "invoice_id": "INV-7731", "currency": "USD", "line_items": [ { "sku": "CRWL-100", "qty": 3, "line_total": 87.0 }, { "sku": "CRWL-200", "qty": 1, "line_total": 42.5 } ], "total": 129.5 }, "rules": [ { "type": "sum_equals", "field": "line_items[].line_total", "equals_field": "total" } ], "enforce_rules": true } ``` ### Response (abridged; the attestation covers the whole response) ```json { "summary": { "result": "pass", "blocking_checks": [] }, "next_actions": [ { "action": "attach_receipt", "for": "seller" }, { "action": "proceed_to_payment", "for": "buyer" } ], "result": "pass", "verification_id": "c30d3398-9717-46de-9a3e-cb38f1074676", "task_id": "example-task-001", "agent_id": "example-seller-001", "output_hash": "39e3d0a1431e3ce9cda89a1d2873e1ffb4e34f4eeded2ffa87946fbd941a34c3", "schema_hash": "23ea3213715cfaf2011dabe2a9a69d4ac25b6eceec7ce3d4bfef9a1298b69b2a", "rules_hash": "2acb6de92b88cfbca279ea6d5bd74b7a1e7dec0839dd093dbe041e25df3364d2", "request_hash": "5ff8a7a8bb5984b5340bf5cf02ccda5d0d401587674c7dad7ed5f5402db25e7a", "verified_at": "2026-01-01T00:00:00+00:00", "verifier_version": "0.4.1", "enforce_rules": true, "strict_content_check": false, "errors": [], "errors_total": 0, "errors_omitted": 0, "errors_detail": [], "errors_detail_total": 0, "errors_detail_omitted": 0, "flags": [], "flags_total": 0, "flags_omitted": 0, "hints": [], "hints_total": 0, "hints_omitted": 0, "schema_compliance": { "ratio": 1.0, "passed": 22, "total": 22 }, "integrity_checks": { "ratio": 1.0, "passed": 8, "total": 8 }, "overall": { "ratio": 1.0, "passed": 30, "total": 30 }, "verify": { "public_key_url": "https://verify.sarnai.dev/.well-known/agent-card.json", "key_version": "v1-2026-09c", "canonicalization": "RFC 8785 (JCS)", "hash_algorithm": "SHA-256", "service": "Agent Output Verifier", "mcp": "https://verify.sarnai.dev/mcp" }, "attestation": { "algorithm": "Ed25519", "key_version": "v1-example", "timestamp": "2026-01-01T00:00:00+00:00", "signature": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" } } ``` A call to GET https://score.sarnai.dev/score/{agent_id} returns the signed history and score. ### Score response ```json { "agent_id": "example-agent", "identity_verified": false, "trust_score": 0.83, "sample_size": 1847, "confidence_interval_95": [ 0.81, 0.85 ], "basis": "recency_weighted_pass_rate", "reason": null, "factors": [ { "name": "recency_weighted_pass_rate", "value": 0.83, "confidence_interval_95": [ 0.81, 0.85 ], "sample_size": 1847, "effective_sample_size": 612.4, "parameters": { "half_life_days": 30.0 } } ], "verified_count": 1847, "passed_count": 1533, "failed_count": 314, "last_seen": "2025-12-31T23:41:07+00:00", "computed_at": "2026-01-01T00:00:00+00:00", "verify": { "public_key_url": "https://verify.sarnai.dev/.well-known/agent-card.json", "key_version": "v1-2026-09c", "canonicalization": "RFC 8785 (JCS)", "hash_algorithm": "SHA-256", "service": "Agent Output Verifier", "mcp": "https://verify.sarnai.dev/mcp" }, "attestation": { "algorithm": "Ed25519", "key_version": "v1-example", "timestamp": "2026-01-01T00:00:00+00:00", "signature": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" } } ``` ## The payment challenge An unpaid request to a paid call gets HTTP 402. The PAYMENT-REQUIRED response header (and the top level of the body) carries x402Version 2, `accepts` (one entry per network: scheme, network, asset, amount, payTo) and `resource`. Sign one entry with an x402 client and resend the same request with the PAYMENT-SIGNATURE header.