Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Agent Output Verifier by SarnAI Score API
description: '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). 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. `POST /verify/schema` (also offered as `POST /verify/deliverable`: the same check, shape, signed attestation and receipt, price and networks) is $0.02 USDC per call and `GET /score/{agent_id}` is $0.01 USDC per call, both paid via x402 in USDC on Base or Solana (an unpaid request gets HTTP 402, which lists the options in the `Payment-Required` response header; a paid request''s signed payment rides in the `Payment-Signature` request header, and a settled response''s settlement receipt rides in the `Payment-Response` response header). A `fail` result is a complete, valid answer and is charged the same as a `pass`. **Payment errors:** every 402 body is a structured `payment_error` - a stable `error_code` (`no_payment`, `malformed_payment`, `unsupported_payment_version`, `no_matching_requirements`, `amount_mismatch`, `recipient_mismatch`, `invalid_signature`, `expired_authorization`, `payment_already_used`, `insufficient_funds`, `facilitator_unreachable`, `facilitator_error`), a clean `message`, `charged: false`, `retryable`/`retry_after`, the current `payment_terms`, and `next_actions` - identical on REST and in MCP''s `structuredContent.payment_error`. See the 402 response below for what each code means. **Free verification:** 1,000 free checks a day per caller, then $0.02 per check (x402, USDC on Base or Solana). One counter is shared by the MCP tool `verify_schema` (`POST /mcp`) and the REST routes `POST /verify/schema` and `POST /verify/deliverable`, where a valid unpaid request is served free and says what is left in the `Free-Allowance-Remaining`, `Free-Allowance-Limit` and `Free-Allowance-Resets` response headers. An unpaid request that needs payment gets a 402 whose body reports this caller''s own remaining free allowance. **AI Agent Scores:** `GET /score/{agent_id}` and the MCP tool `get_verification_record` are $0.01 per lookup when there is a score, with no free allowance; a lookup for an AI agent with no history (or a reserved `test-` id) is answered free, signed, with `reason: no_data`, on REST and through the MCP tool. **Errors:** every error other than a payment error has one shape (`code`, `message`, `status`, `retryable`, `charged`, `next_actions`; validation errors add a `fields` list naming each bad field and how to fix it), on REST and in MCP tool results, and the caller''s input is never echoed. An invalid request is rejected with 422 before anything is verified or settled; an unpaid request to a paid route always gets the 402 challenge, an empty or invalid one with `request_problems`, and GET/HEAD on the verify routes get the same challenge. Every error response (4xx and 5xx, every route) carries a `guidance` block: what to do, a minimal valid example, the price and how to pay, the free-allowance note and the llms.txt link; a 5xx also says nothing was charged and when to retry. The live free-allowance numbers are stated in one place, the "Free allowance" section of `/llms.txt`.'
version: 0.5.8
tags:
- name: Score
paths:
/score/{agent_id}:
get:
summary: Get Trust Score
operationId: get_trust_score_score__agent_id__get
parameters:
- name: agent_id
in: path
required: true
schema:
type: string
minLength: 1
maxLength: 200
pattern: ^[^\x00-\x1f\x7f]+$
description: 'The agent_id whose verification history to score (as passed to /verify/schema): the AI agent that produced the output checked by those calls (for example, the seller). agent_ids starting with ''test-'' are reserved for testing and never have a history.'
title: Agent Id
description: 'The agent_id whose verification history to score (as passed to /verify/schema): the AI agent that produced the output checked by those calls (for example, the seller). agent_ids starting with ''test-'' are reserved for testing and never have a history.'
- name: Payment-Signature
in: header
required: false
schema:
type: string
description: The caller's signed x402 v2 payment (base64). Omit it to receive 402 with the price and requirements in the Payment-Required response header instead.
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/TrustScoreResponse'
headers:
Payment-Response:
description: Settlement receipt (base64 JSON) once a paid call settles on-chain.
schema:
type: string
'422':
description: 'Invalid request: bad JSON, a missing field, a wrong type, an unknown rule type, an over-long id, with a payment attached (an UNPAID invalid request gets the 402 challenge instead, with `request_problems`); `fields` names each bad field and how to fix it. Nothing is verified or charged, and the input is never echoed.'
content:
application/json:
schema:
type: object
properties:
code:
type: string
enum:
- invalid_request
- invalid_json
- schema_invalid
- validation_timeout
- unencodable_content
- request_too_large
- invalid_content_type
- not_found
- method_not_allowed
- rate_limited
- too_many_concurrent_validations
- service_busy
- internal_error
- unknown_tool
description: '- `invalid_request`: 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`: The body is malformed JSON, or is something other than a single JSON object.
- `schema_invalid`: `expected_schema` is an invalid JSON Schema.
- `validation_timeout`: The schema/output pair took too long to validate (too complex).
- `unencodable_content`: The request contains a lone (unpaired) Unicode surrogate, which the signature format rejects.
- `request_too_large`: The request body is over the size limit. `limits` gives it.
- `invalid_content_type`: The request''s Content-Type names a type other than JSON. Send the same JSON body with Content-Type: application/json.
- `not_found`: No such route.
- `method_not_allowed`: The route exists for other HTTP methods only. `limits.allowed_methods` lists them.
- `rate_limited`: Too many requests from this client (or a daily cap); retry after the `Retry-After` seconds.
- `too_many_concurrent_validations`: Too many validations already running for this client; retry shortly.
- `service_busy`: Every validation worker was busy; retry shortly.
- `internal_error`: The service failed while handling the request. Nothing was charged; retry after `retry_after` seconds.
- `unknown_tool`: MCP: no tool with that name. `limits.tools` lists the tools.'
message:
type: string
description: One sentence a person can act on. Never repeats the caller's input.
status:
type: integer
description: The HTTP status (also present in MCP tool errors).
retryable:
type: boolean
description: Whether sending the same request again can succeed.
retry_after:
type:
- integer
- 'null'
description: 'Present on a retryable error: seconds to wait, or null.'
charged:
type: boolean
description: 'Always false: an error is never charged.'
next_actions:
type: array
items:
type: object
description: Machine-readable next steps.
fields:
type: array
description: 'Validation errors (422): one entry per bad field, at most ten.'
items:
type: object
properties:
field:
type: string
description: 'Where, as a path: `rules[0].type`.'
problem:
type: string
fix:
type: string
required:
- field
- problem
- fix
fields_total:
type: integer
description: Present when more fields were bad than are listed.
limits:
type: object
description: The numbers that apply (a size limit, the allowed methods, the tool list).
detail:
description: 'Kept for older clients: FastAPI''s former `detail` (never the caller''s input).'
required:
- code
- message
- status
- retryable
- charged
- next_actions
'402':
description: 'Payment required (x402 v2) - see the Payment-Required response header for the price and requirements. The body is always a structured `payment_error` (stable `error_code`, a clean `message`, `charged: false`, `retryable`/`retry_after`, `payment_terms`, and `next_actions`), the same shape MCP returns in `structuredContent.payment_error`, merged with a `free_trial` note when the free trial is on.'
headers:
Payment-Required:
description: 'x402 v2 payment requirements (base64 JSON): price, network(s) and pay-to address.'
schema:
type: string
content:
application/json:
schema:
type: object
properties:
payment_error:
type: object
properties:
error_code:
type: string
enum:
- no_payment
- malformed_payment
- unsupported_payment_version
- no_matching_requirements
- amount_mismatch
- recipient_mismatch
- invalid_signature
- expired_authorization
- payment_already_used
- insufficient_funds
- facilitator_unreachable
- facilitator_error
description: '- `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.'
message:
type: string
description: A clean, human-readable sentence - never a raw exception or facilitator JSON.
charged:
type: boolean
description: 'Always false: none of these codes represent a settled payment.'
retryable:
type: boolean
description: Whether resubmitting (the same payment, or a corrected one) can succeed.
retry_after:
type:
- integer
- 'null'
description: Seconds to wait before retrying, when retryable and the failure is transient (facilitator-side).
payment_terms:
type: object
description: The same accepts[]/resource a fresh unpaid request to this route would receive.
next_actions:
type: array
items:
type: object
description: Machine-readable next step(s), same pattern as a verification result's next_actions.
free_trial_status:
type: string
description: One-line free-trial status, present only when relevant.
required:
- error_code
- message
- charged
- retryable
- retry_after
- payment_terms
- next_actions
free_trial:
type: object
description: Present when the free trial is on - see GET /llms.txt's Free trial section.
required:
- payment_error
'429':
description: Too many requests, or too many validations running for this client; see `retry_after`.
content:
application/json:
schema:
type: object
properties:
code:
type: string
enum:
- invalid_request
- invalid_json
- schema_invalid
- validation_timeout
- unencodable_content
- request_too_large
- invalid_content_type
- not_found
- method_not_allowed
- rate_limited
- too_many_concurrent_validations
- service_busy
- internal_error
- unknown_tool
description: '- `invalid_request`: 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`: The body is malformed JSON, or is something other than a single JSON object.
- `schema_invalid`: `expected_schema` is an invalid JSON Schema.
- `validation_timeout`: The schema/output pair took too long to validate (too complex).
- `unencodable_content`: The request contains a lone (unpaired) Unicode surrogate, which the signature format rejects.
- `request_too_large`: The request body is over the size limit. `limits` gives it.
- `invalid_content_type`: The request''s Content-Type names a type other than JSON. Send the same JSON body with Content-Type: application/json.
- `not_found`: No such route.
- `method_not_allowed`: The route exists for other HTTP methods only. `limits.allowed_methods` lists them.
- `rate_limited`: Too many requests from this client (or a daily cap); retry after the `Retry-After` seconds.
- `too_many_concurrent_validations`: Too many validations already running for this client; retry shortly.
- `service_busy`: Every validation worker was busy; retry shortly.
- `internal_error`: The service failed while handling the request. Nothing was charged; retry after `retry_after` seconds.
- `unknown_tool`: MCP: no tool with that name. `limits.tools` lists the tools.'
message:
type: string
description: One sentence a person can act on. Never repeats the caller's input.
status:
type: integer
description: The HTTP status (also present in MCP tool errors).
retryable:
type: boolean
description: Whether sending the same request again can succeed.
retry_after:
type:
- integer
- 'null'
description: 'Present on a retryable error: seconds to wait, or null.'
charged:
type: boolean
description: 'Always false: an error is never charged.'
next_actions:
type: array
items:
type: object
description: Machine-readable next steps.
fields:
type: array
description: 'Validation errors (422): one entry per bad field, at most ten.'
items:
type: object
properties:
field:
type: string
description: 'Where, as a path: `rules[0].type`.'
problem:
type: string
fix:
type: string
required:
- field
- problem
- fix
fields_total:
type: integer
description: Present when more fields were bad than are listed.
limits:
type: object
description: The numbers that apply (a size limit, the allowed methods, the tool list).
detail:
description: 'Kept for older clients: FastAPI''s former `detail` (never the caller''s input).'
required:
- code
- message
- status
- retryable
- charged
- next_actions
tags:
- Score
components:
schemas:
VerifySection:
properties:
public_key_url:
type: string
title: Public Key Url
description: Agent-card URL listing the public key for key_version below, and every key this service has ever signed with.
key_version:
type: string
title: Key Version
description: Matches attestation.key_version.
canonicalization:
type: string
title: Canonicalization
description: How every hash below, and the signed bytes themselves, are computed.
hash_algorithm:
type: string
title: Hash Algorithm
description: Algorithm behind every *_hash field in this response.
service:
type: string
title: Service
description: This service's name.
mcp:
type: string
title: Mcp
description: This service's MCP (Model Context Protocol) endpoint.
type: object
required:
- public_key_url
- key_version
- canonicalization
- hash_algorithm
- service
- mcp
title: VerifySection
description: 'Compact, self-describing block every signed attestation and receipt carries (2026-09-30,
verifier_version 0.4.0) - inside the signature, alongside `attestation` itself - so a
verifier needs nothing but this response and the URL below, not a separate lookup or
prior knowledge of this service''s conventions.'
ScoreFactor:
properties:
name:
type: string
title: Name
description: Stable identifier of this factor.
value:
anyOf:
- type: number
- type: 'null'
title: Value
description: Factor value in [0, 1]; null when it cannot be computed (see the response's `reason`).
confidence_interval_95:
anyOf:
- items:
type: number
type: array
maxItems: 2
minItems: 2
- type: 'null'
title: Confidence Interval 95
description: '[lower, upper] 95% Wilson score interval; null when value is null.'
sample_size:
type: integer
title: Sample Size
description: Raw number of verification calls behind this factor, all history.
effective_sample_size:
type: number
title: Effective Sample Size
description: 'Sum of the recency weights: how many ''recent-equivalent'' observations the history is worth. This, not sample_size, is what the interval is computed from.'
parameters:
additionalProperties:
anyOf:
- type: number
- type: integer
- type: string
type: object
title: Parameters
description: Parameters in force, so the value can be reproduced.
type: object
required:
- name
- value
- confidence_interval_95
- sample_size
- effective_sample_size
- parameters
title: ScoreFactor
description: One input to the score. v1 has exactly one; later versions add entries here.
TrustScoreResponse:
properties:
agent_id:
type: string
title: Agent Id
description: 'Identifies the AI agent that produced the output checked by the /verify/schema calls this score is built from (for example, the seller): this record belongs to that producer.'
identity_verified:
type: boolean
title: Identity Verified
description: 'Whether agent_id is bound to a proven identity. Currently always false: agent_id is an unauthenticated label chosen by whoever called /verify/schema, so this history may include calls made by anyone under that id, and the score says nothing about who the AI agent really is. It flips to true only if and when identity is actually authenticated.'
trust_score:
anyOf:
- type: number
- type: 'null'
title: Trust Score
description: Recency-weighted pass rate as a raw float in [0.0, 1.0]; null when there is nothing meaningful to report (see `reason`). A statistical summary of structural verification history, not a measure of overall trustworthiness and not a prediction of future behavior.
sample_size:
type: integer
title: Sample Size
description: Total verification calls on record for this agent_id.
confidence_interval_95:
anyOf:
- items:
type: number
type: array
maxItems: 2
minItems: 2
- type: 'null'
title: Confidence Interval 95
description: '[lower, upper] 95% Wilson score interval around trust_score, computed from the effective (recency-discounted) sample size. Wide = little recent evidence.'
basis:
type: string
title: Basis
description: Names the formula behind trust_score. Its meaning never changes under the same name; a different formula gets a different basis.
reason:
anyOf:
- type: string
- type: 'null'
title: Reason
description: 'null when scored. ''no_data'': no verification history at all. ''insufficient_recent_data'': history exists but has decayed to under 0.05 effective observations.'
factors:
items:
$ref: '#/components/schemas/ScoreFactor'
type: array
title: Factors
description: The inputs behind trust_score. v1 has one entry; consumers should ignore entries they don't recognize, so factors can be added without breaking them.
verified_count:
type: integer
title: Verified Count
description: Raw number of /verify/schema calls on record for this agent_id (all history, no recency weighting). Equals sample_size.
passed_count:
type: integer
title: Passed Count
description: How many of those calls returned result 'pass'.
failed_count:
type: integer
title: Failed Count
description: How many of those calls returned result 'fail'.
last_seen:
anyOf:
- type: string
- type: 'null'
title: Last Seen
description: ISO-8601 UTC time of the most recent verification on record; null when there is none. Like agent_id itself, this reflects calls made under this label by anyone.
computed_at:
type: string
title: Computed At
description: ISO-8601 UTC time the score was computed; ages are measured from here.
verify:
$ref: '#/components/schemas/VerifySection'
description: 'How to verify this attestation and receipt without any other lookup than this response and public_key_url: canonicalization and hash algorithm, this service''s name, and its MCP endpoint.'
attestation:
$ref: '#/components/schemas/Attestation'
description: 'Ed25519 signature over this response (every field above) with the same key as /verify/schema, under the context ''json-schema-verifier/trust-score-attestation/v1''. Verification steps: /.well-known/agent-card.json.'
type: object
required:
- agent_id
- identity_verified
- trust_score
- sample_size
- confidence_interval_95
- basis
- reason
- factors
- verified_count
- passed_count
- failed_count
- last_seen
- computed_at
- verify
- attestation
title: TrustScoreResponse
Attestation:
properties:
algorithm:
type: string
const: Ed25519
title: Algorithm
key_version:
type: string
title: Key Version
timestamp:
type: string
title: Timestamp
signature:
type: string
title: Signature
type: object
required:
- algorithm
- key_version
- timestamp
- signature
title: Attestation