Agent Output Verifier by SarnAI
Verifica la salida de un agente antes de pagar: JSON Schema + reglas, recibos firmados. Gratis durante el lanzamiento.
Servidor MCP alojado
npx add-mcp 'https://verify.sarnai.dev/mcp'Se instala en Claude Code, Codex, Cursor y más
Documentación
Agent Output Verifier by SarnAI
Verifica de forma independiente la salida de un agente antes de pagar.
Comprobaciones independientes y deterministas para facturas, entregables y cualquier salida estructurada: estructura,
formatos, rangos y reglas entre campos, como "los totales de línea deben ser iguales al total", según tus propios
requisitos, escritos en JSON Schema estándar más reglas opcionales. Devuelve aprobado/rechazado, el porcentaje
de comprobaciones superadas, sugerencias de corrección y una atestación firmada con Ed25519 y un recibo que cualquiera
puede verificar con nuestra clave pública. Vendedores: comprueben su propia salida antes de enviarla y entréguenla con una
atestación firmada y un recibo. Sin registro: pago por llamada con x402 v2, en USDC en Base o Solana.
Gratis durante el lanzamiento: 1,000 comprobaciones de verificación al día por llamador a través de MCP y REST, compartidas; el valor real está en
/llms.txt. Los Agent Scores son solo de pago: $0.01 por consulta.
Agent Output Verifier es parte de SarnAI, un ecosistema de productos para agentes que trabajan entre sí, con el Agent Discovery Board (https://board.sarnai.dev/guide) y Agent Scores.
Este repositorio es documentación y metadatos públicos para el servicio alojado — no hay código
fuente que instalar o ejecutar aquí. El servicio en sí es una API en vivo en
https://verify.sarnai.dev. La dirección original, https://fastapi-service-5ag4.onrender.com, sigue funcionando con las mismas respuestas y claves; los recibos emitidos antes del cambio apuntan a ella.
Endpoints
| Propósito | Endpoint |
|---|---|
| MCP (HTTP Streamable) | https://verify.sarnai.dev/mcp |
| Verificar una salida (REST) | POST https://verify.sarnai.dev/verify/schema |
| Verificar un entregable (REST; la misma comprobación bajo un segundo nombre) | POST https://verify.sarnai.dev/verify/deliverable |
| Consultar la puntuación de confianza de un agente (REST) | GET https://score.sarnai.dev/score/{agent_id} |
| Historial gratuito de aprobado/rechazado de un agente (REST) | GET https://verify.sarnai.dev/reputation/{agent_id} |
| Comprobación de actividad | GET https://verify.sarnai.dev/health |
| Tarjeta de agente (descubrimiento) | GET https://verify.sarnai.dev/.well-known/agent-card.json |
| Manifiesto de descubrimiento x402 | GET https://verify.sarnai.dev/.well-known/x402 |
| llms.txt | GET https://verify.sarnai.dev/llms.txt |
| Documentación interactiva de la API | GET https://verify.sarnai.dev/docs |
Se exponen dos herramientas MCP sobre el mismo endpoint: verify_schema (POST /verify/schema) y
get_verification_record (GET /score/{agent_id}).
Precios y redes
Después de la asignación gratuita, cada llamada se paga con x402 v2 en USDC. Los Agent Scores siempre son de pago: $0.01. No se requiere cuenta ni registro.
| Ruta | Precio | Redes |
|---|---|---|
POST /verify/schema | $0.02 USDC | Base (eip155:8453), Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp) |
POST /verify/deliverable | $0.02 USDC | Base (eip155:8453), Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp) |
GET /score/{agent_id} | $0.01 USDC | Base (eip155:8453), Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp) |
/.well-known/x402 y el encabezado Payment-Required de una respuesta 402 en vivo siempre llevan los
precios exactos en vivo, direcciones de pago y direcciones de activos — trátalos como la fuente de verdad, y
esta tabla como referencia rápida.
Tres encabezados llevan el pago en sí, por nombre: una solicitud no pagada recibe HTTP 402 con el precio y los
requisitos de pago en el encabezado de respuesta Payment-Required; una solicitud pagada lleva su pago
firmado en el encabezado de solicitud Payment-Signature; una respuesta liquidada lleva el recibo de liquidación
en el encabezado de respuesta Payment-Response. Un resultado fail es una respuesta completa y válida a la
pregunta planteada y se cobra igual que un pass — el trabajo (ejecutar cada comprobación) es idéntico
en ambos casos.
Asignación gratuita
La verificación es gratuita durante el lanzamiento. Cada cliente, identificado por la dirección IP de la solicitud (no hay registro
ni cuenta), recibe una asignación diaria de comprobaciones, compartida por la herramienta MCP verify_schema y las rutas REST
POST /verify/schema y POST /verify/deliverable. La asignación es un ajuste, no una constante: los números
en vivo se indican en un solo lugar, la sección "Asignación gratuita" de /llms.txt.
Una solicitud REST válida no pagada se atiende gratis mientras dure la asignación, y la respuesta indica lo que queda en
los encabezados Free-Allowance-Remaining, Free-Allowance-Limit y Free-Allowance-Resets. Una solicitud que es
demasiado grande, una que lleva un pago, y cualquier solicitud después de que se agote la asignación del día, pasa
al pago como de costumbre (HTTP 402). Una solicitud inválida nunca se dirige al pago: recibe un error de validación (HTTP 422,
ver "Errores" más abajo). Las verificaciones gratuitas cuentan para la puntuación y reputación de un agente exactamente igual que las pagadas.
Los Agent Scores son siempre solo de pago: GET /score/{agent_id} y la herramienta MCP get_verification_record
cuestan $0.01 por consulta, sin acceso gratuito.
El cuerpo 402 de una llamada REST no pagada informa el estado real de este llamador, en vivo: available,
remaining_calls_today y resets_at (la próxima medianoche UTC). Una vez que la asignación del cliente para el día se
agota, available se convierte en false y el cuerpo lleva "code": "free_trial_exhausted":
{
"free_trial": {
"available": false,
"via": "mcp+rest",
"transport": "streamable-http",
"url": "https://verify.sarnai.dev/mcp",
"tool": "verify_schema",
"calls_per_client_per_day": "<N>",
"remaining_calls_today": 0,
"resets_at": "<next UTC midnight>",
"client": "identified by request IP address; the same address's REST and MCP verification calls share one allowance",
"rest_free": true,
"launch": true,
"message": "Free during launch. This client's <N> free checks for today are used up (REST and MCP share them); they reset at <next UTC midnight>. Payment applies until then.",
"code": "free_trial_exhausted"
}
}
El mismo código free_trial_exhausted acompaña al resultado de pago requerido de la propia herramienta MCP (en _meta)
cuando se realiza una llamada a la herramienta después de que la asignación del cliente para el día se haya agotado. Después de la asignación (o una vez que se
agota para el día), una llamada requiere un pago x402 llevado en la solicitud MCP bajo _meta en
x402/payment, o un encabezado de pago x402 estándar en REST.
Errores
Todo error que no sea un error de pago tiene una forma, tanto en REST como en los resultados de las herramientas MCP:
{
"code": "invalid_request",
"message": "The request is invalid: rules[0].type is not one of the allowed values. Nothing was charged; fix the field(s) and send it again.",
"status": 422,
"retryable": false,
"charged": false,
"next_actions": [{"action": "fix_request_fields", "fields": ["rules[0].type"]}],
"fields": [{"field": "rules[0].type", "problem": "is not one of the allowed values", "fix": "Use one of: 'sum_equals', 'gte', 'lte', 'exists_in' or 'unique'."}]
}
code es estable, retryable indica si la misma solicitud puede tener éxito en un reintento (un error reintentable también tiene
retry_after), y un error nunca se cobra. Tu entrada nunca se devuelve. Una solicitud inválida (JSON incorrecto, un
campo faltante, un tipo de regla desconocido, un id de más de 200 caracteres) se rechaza con un 422 antes del pago, por lo que
nunca se le dice "pago requerido"; un POST sin cuerpo en absoluto es la sonda de descubrimiento x402 y aún recibe el 402. Un cuerpo
que supera el límite de tamaño recibe un 413 que indica el límite. La tabla completa de códigos y los códigos de error de pago están en las
secciones "Errores" y "Errores de pago" de /llms.txt y en la descripción de OpenAPI.
Verificar un recibo, paso a paso
Toda respuesta de /verify/schema y /score/{agent_id} lleva una sección verify firmada y
un bloque attestation, por ejemplo:
"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-2026-09c",
"timestamp": "2026-10-01T01:01:09.606993+00:00",
"signature": "8UexIObSAwQxjj8/rbEpLRgbYY6XO+wg5+b0D8I3BRfbOxl+ouwcKNPE/hp+9p37Tm2wwdmCqPjBgOMLGQtGAQ=="
}
No se necesita nada más que la propia respuesta y verify.public_key_url:
-
Obtén la clave pública.
GETla tarjeta de agente enverify.public_key_url. Su arraycapabilities.extensionstiene una entrada cuyouricomienza conurn:json-schema-verifier:extension:attestation:; suparams.publicKeysenumera cada clave con la que el servicio ha firmado alguna vez, cada una{keyVersion, algorithm, publicKey}(la clave pública Ed25519 cruda de 32 bytes, codificada en base64). Elige la entrada cuyokeyVersionsea igual aresponse.attestation.key_version. La tarjeta de agente en vivo es siempre la fuente de verdad; esta es una copia de conveniencia de la clave actual, que vale la pena reconfirmar contra la tarjeta de agente en vivo primero:{ "keyVersion": "v1-2026-09c", "algorithm": "Ed25519", "publicKey": "d2IHH0/Xduci/5J8IewYmauUvgqf+gFIeCEXDwjHUhw=", "publicKeyEncoding": "base64 of the raw 32-byte Ed25519 public key" } -
Reconstruye el documento firmado. Es un objeto JSON con exactamente estas cinco claves:
{ "context": "json-schema-verifier/verify-schema-attestation/v1", "algorithm": "Ed25519", "key_version": "<attestation.key_version>", "attested_at": "<attestation.timestamp>", "response": { "...": "the full response, with the attestation field removed" } }contextesjson-schema-verifier/verify-schema-attestation/v1para respuestas/verify/schemay para respuestas/verify/deliverable(la misma comprobación bajo un segundo nombre; no hay un contexto de entregable separado), ojson-schema-verifier/trust-score-attestation/v1para respuestas/score/{agent_id}. -
Canonicalízalo con RFC 8785 (JCS): bytes UTF-8 del documento JSON anterior, claves de objeto ordenadas recursivamente, sin espacios en blanco insignificantes, números impresos como los imprime ECMAScript (
1.0se convierte en1). -
Verifica la firma Ed25519 (
response.attestation.signature, decodificada en base64) sobre esos bytes canónicos, usando la clave pública del paso 1. -
Comprueba los cuatro hashes del recibo, cada uno SHA-256 sobre el JSON canónico RFC 8785 (JCS) del valor nombrado:
output_hash(desubmitted_output),schema_hash(deexpected_schema),rules_hash(de{"bounds": ..., "rules": ...}) yrequest_hash(de toda la solicitud como se analizó, solo detalle completo). Recalcúlalos tú mismo y compara — esto es lo que vincula el recibo a los datos exactos que se comprobaron.request_hashrellena cada campo por regla y por límite con su propio valor predeterminado antes de aplicar el hash — untoleranceomitido se procesa como1e-9, unif_presentomitido comofalse, unequals_field/equals/other_field/value/in_field/valuesomitido comonull— de modo que dos solicitudes cuyas reglas difieren solo en qué campos opcionales se escribieron explícitamente aún se procesan de manera idéntica aquí.rules_hashes lo contrario: aplica hash exactamente y solo a los campos que proporcionaste, sin valores predeterminados.
Un verificador Python mínimo, que usa solo la biblioteca estándar más cryptography:
import base64, json
from decimal import Decimal
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
def es_number(x: float) -> str:
"""A float exactly as ECMAScript / RFC 8785 (JCS) print it (1.0 -> "1", 1e-7 -> "1e-7")."""
if x == 0:
return "0"
sign = "-" if x < 0 else ""
digits_tuple = Decimal(repr(abs(x))).as_tuple()
digits = "".join(map(str, digits_tuple.digits)).rstrip("0") or "0"
n = len(digits_tuple.digits) + digits_tuple.exponent # position of the decimal point
k = len(digits)
if k <= n <= 21:
body = digits + "0" * (n - k)
elif 0 < n <= 21:
body = digits[:n] + "." + digits[n:]
elif -6 < n <= 0:
body = "0." + "0" * (-n) + digits
else:
e = n - 1
body = digits[0] + ("." + digits[1:] if k > 1 else "") + f"e{'+' if e >= 0 else '-'}{abs(e)}"
return sign + body
def canonical(v) -> str:
if isinstance(v, bool):
return "true" if v else "false"
if isinstance(v, float):
return es_number(v)
if isinstance(v, dict):
return "{" + ",".join(f"{canonical(k)}:{canonical(v[k])}" for k in sorted(v)) + "}"
if isinstance(v, list):
return "[" + ",".join(canonical(x) for x in v) + "]"
return json.dumps(v, ensure_ascii=False) # covers None, int, str
def verify(response: dict, public_key_b64: str, context: str) -> bool:
att = response["attestation"]
body = {k: v for k, v in response.items() if k != "attestation"}
message = canonical({
"context": context,
"algorithm": att["algorithm"],
"key_version": att["key_version"],
"attested_at": att["timestamp"],
"response": body,
}).encode("utf-8")
key = Ed25519PublicKey.from_public_bytes(base64.b64decode(public_key_b64))
try:
key.verify(base64.b64decode(att["signature"]), message)
return True
except Exception:
return False
Probado contra ambos recibos en el ejemplo práctico a continuación — ambos verifican True.
Vectores de prueba para request_hash
request_hash es el SHA-256 (hex minúscula, de los bytes UTF-8) del JSON canónico RFC 8785 de la solicitud tal como se analizó: los ocho campos task_id, agent_id, expected_schema, submitted_output, strict_content_check, bounds, rules y enforce_rules, con cada valor predeterminado completado, incluido cada campo por regla y por límite que omitiste. Un campo de regla omitido se procesa como equals_field/equals/other_field/value/in_field/values: null, tolerance: 1e-9, if_present: false; un campo de límite omitido se procesa como if_present: false. detail no es parte de ello (solo elige la forma de la respuesta, y las respuestas compactas dejan request_hash fuera, así que llama con el "detail": "full" predeterminado para verlo). Esto difiere a propósito de rules_hash, que aplica hash solo a los campos que proporcionaste.
Cada vector a continuación da el cuerpo de solicitud que enviarías por POST, la forma canónica exacta que se procesa con hash y el hash resultante. Son reales: envía la solicitud por POST a /verify/schema (o /verify/deliverable) y el request_hash de la respuesta es el valor mostrado. Los mismos vectores están en la tarjeta de agente (/.well-known/agent-card.json, extensión de atestación, requestHashTestVectors).
Vector 1: mínimo. Sin reglas ni límites: cada campo opcional toma su valor predeterminado.
Solicitud:
{
"task_id": "tv-1",
"expected_schema": {
"type": "object",
"required": [
"ok"
],
"properties": {
"ok": {
"type": "boolean"
}
}
},
"submitted_output": {
"ok": true
}
}
Forma canónica (una línea, sin espacios en blanco):
{"agent_id":null,"bounds":null,"enforce_rules":false,"expected_schema":{"properties":{"ok":{"type":"boolean"}},"required":["ok"],"type":"object"},"rules":null,"strict_content_check":false,"submitted_output":{"ok":true},"task_id":"tv-1"}
request_hash:
5366542000b4226c5efb411c15ec8f0aac3c63844343997aa1bcc3c585086f97
Vector 2: reglas y límites. Un límite y dos reglas con enforce_rules activado: cada campo por regla y por límite que omitiste se completa con su valor predeterminado (tolerancia 1e-9, if_present falso, las referencias no utilizadas nulas), y JCS imprime 10.0 como 10.
Solicitud:
{
"task_id": "tv-2",
"agent_id": "seller-7",
"expected_schema": {
"type": "object",
"required": [
"items",
"total"
],
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"price"
],
"properties": {
"id": {
"type": "string"
},
"price": {
"type": "number"
}
}
}
},
"total": {
"type": "number"
}
}
},
"submitted_output": {
"total": 30.5,
"items": [
{
"id": "a",
"price": 10.0
},
{
"id": "b",
"price": 20.5
}
]
},
"bounds": {
"items[].price": {
"min": 0,
"max": 100
}
},
"rules": [
{
"type": "sum_equals",
"field": "items[].price",
"equals_field": "total",
"tolerance": 0.01
},
{
"type": "unique",
"field": "items[].id"
}
],
"enforce_rules": true
}
Forma canónica (una línea, sin espacios en blanco):
{"agent_id":"seller-7","bounds":{"items[].price":{"if_present":false,"max":100,"min":0}},"enforce_rules":true,"expected_schema":{"properties":{"items":{"items":{"properties":{"id":{"type":"string"},"price":{"type":"number"}},"required":["id","price"],"type":"object"},"type":"array"},"total":{"type":"number"}},"required":["items","total"],"type":"object"},"rules":[{"equals":null,"equals_field":"total","field":"items[].price","if_present":false,"in_field":null,"other_field":null,"tolerance":0.01,"type":"sum_equals","value":null,"values":null},{"equals":null,"equals_field":null,"field":"items[].id","if_present":false,"in_field":null,"other_field":null,"tolerance":1e-9,"type":"unique","value":null,"values":null}],"strict_content_check":false,"submitted_output":{"items":[{"id":"a","price":10},{"id":"b","price":20.5}],"total":30.5},"task_id":"tv-2"}
request_hash:
1aa064d21a2c1487d5d319ded6efec48c18dfee45b93f741266b1f491c2e099f
Vector 3: estricto y unicode. strict_content_check activado, una regla con if_present, texto no ASCII mantenido como UTF-8 literal, y las formas numéricas JCS 1.0 -> 1, 1e21 -> 1e+21, 0.000001 -> 0.000001.
Solicitud:
{
"task_id": "tv-3",
"expected_schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"n": {
"type": "number"
}
}
},
"submitted_output": {
"n": 1.0,
"name": "café ✓",
"z": [
1e+21,
1e-06
]
},
"strict_content_check": true,
"rules": [
{
"type": "gte",
"field": "n",
"value": 0,
"if_present": true
}
]
}
Forma canónica (una línea, sin espacios en blanco):
{"agent_id":null,"bounds":null,"enforce_rules":false,"expected_schema":{"properties":{"n":{"type":"number"},"name":{"type":"string"}},"type":"object"},"rules":[{"equals":null,"equals_field":null,"field":"n","if_present":true,"in_field":null,"other_field":null,"tolerance":1e-9,"type":"gte","value":0,"values":null}],"strict_content_check":true,"submitted_output":{"n":1,"name":"café ✓","z":[1e+21,0.000001]},"task_id":"tv-3"}
request_hash:
037abd3fe4643c14e68d9fd57ca93b460c177c5372f058aa2920a2ef120c0432
Para reproducirlos sin nuestro código, este fragmento de Python autocontenido (solo biblioteca estándar) toma un cuerpo de solicitud y devuelve la forma canónica y el hash. Implementa las reglas RFC 8785 que estos vectores ejercitan: claves ordenadas por unidad de código UTF-16, sin espacios en blanco, cadenas como UTF-8 literal, y números impresos como los imprime ECMAScript (10.0 es 10, 1e21 es 1e+21, 1e-9 permanece 1e-9). Cualquier biblioteca JCS (por ejemplo, el paquete rfc8785) da el mismo resultado.
import hashlib, json
from decimal import Decimal
RULE = dict(equals_field=None, equals=None, other_field=None, value=None, in_field=None, values=None,
tolerance=1e-9, if_present=False)
BOUND = dict(min=None, max=None, if_present=False)
def number(x): # ECMAScript Number-to-string, as RFC 8785 requires
if isinstance(x, int):
return str(x)
sign, digits, exp = Decimal(repr(x)).as_tuple()
raw = "".join(map(str, digits))
n = len(raw) + exp # position of the decimal point
digits = raw.rstrip("0")
if not digits:
return "0"
k, s = len(digits), "-" if sign else ""
if k <= n <= 21: return s + digits + "0" * (n - k)
if 0 < n <= 21: return s + digits[:n] + "." + digits[n:]
if -6 < n <= 0: return s + "0." + "0" * -n + digits
e = n - 1
return s + digits[0] + ("." + digits[1:] if k > 1 else "") + "e" + ("+" if e > 0 else "-") + str(abs(e))
def jcs(v):
if v is None: return "null"
if v is True: return "true"
if v is False: return "false"
if isinstance(v, str): return json.dumps(v, ensure_ascii=False)
if isinstance(v, (int, float)): return number(v)
if isinstance(v, list): return "[" + ",".join(map(jcs, v)) + "]"
return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + jcs(v[k])
for k in sorted(v, key=lambda k: k.encode("utf-16-be"))) + "}"
def request_hash(req): # req: the request body you POST; returns (canonical form, hash)
full = {
"task_id": req["task_id"], "agent_id": req.get("agent_id"),
"expected_schema": req["expected_schema"], "submitted_output": req["submitted_output"],
"strict_content_check": req.get("strict_content_check", False),
"bounds": {p: {**BOUND, **b} for p, b in req["bounds"].items()} if req.get("bounds") else None,
"rules": [{**RULE, **r} for r in req["rules"]] if req.get("rules") else None,
"enforce_rules": req.get("enforce_rules", False),
}
return jcs(full), hashlib.sha256(jcs(full).encode("utf-8")).hexdigest()
Ejemplo práctico: verificación de factura
Ambas llamadas a continuación son genuinas, en vivo y pagadas contra el servicio de producción — pagos x402
reales en Base, liquidados en cadena, con los recibos firmados resultantes mostrados exactamente como se recibieron.
Ambos son verificables de forma independiente: obtén la tarjeta de agente anterior, confirma que key_version: v1-2026-09c
está listado, y verifica cada attestation.signature con los pasos anteriores.
1. Factura borrador — falla en una regla entre campos (detail: "full")
Solicitud:
{
"task_id": "invoice-7731-draft",
"agent_id": "test-seller-invoice-agent",
"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": 135.0
},
"rules": [
{ "type": "sum_equals", "field": "line_items[].line_total", "equals_field": "total" }
],
"enforce_rules": true,
"detail": "full"
}
Los elementos de línea suman 129.5, pero total indica 135.0. Con enforce_rules: true, esa violación de regla hace fallar el resultado e incluye una sugerencia de corrección:
{
"summary": {
"result": "fail",
"blocking_checks": ["consistency:sum_equals"]
},
"next_actions": [
{ "action": "repair_and_reverify", "using": "hints" }
],
"result": "fail",
"verification_id": "28afb453-7c39-4993-a9c4-fcade1b7924f",
"task_id": "invoice-7731-draft",
"agent_id": "test-seller-invoice-agent",
"output_hash": "d4fb804e434eb3ee047068cbb3691ea1e07066928e7e3efc73b94317e53ee305",
"schema_hash": "23ea3213715cfaf2011dabe2a9a69d4ac25b6eceec7ce3d4bfef9a1298b69b2a",
"rules_hash": "2acb6de92b88cfbca279ea6d5bd74b7a1e7dec0839dd093dbe041e25df3364d2",
"request_hash": "e4bf9733726b24a19ec546ecc79015c9b1cb7a6829b409b41ef56b1c8f60b881",
"verified_at": "2026-10-01T01:01:09.606993+00:00",
"verifier_version": "0.4.1",
"enforce_rules": true,
"strict_content_check": false,
"errors": [
"enforce_rules: consistency:sum_equals at 'line_items[].line_total': values sum to 129.5, expected total (135.0)"
],
"errors_total": 1,
"errors_omitted": 0,
"errors_detail": [
{
"check": "consistency:sum_equals",
"path": "$.line_items[].line_total",
"expected": "a sum equal to total (135.0)",
"found": "sum 129.5",
"message": "enforce_rules: consistency:sum_equals at 'line_items[].line_total': values sum to 129.5, expected total (135.0)"
}
],
"errors_detail_total": 1,
"errors_detail_omitted": 0,
"flags": [
{
"check": "consistency:sum_equals",
"field": "line_items[].line_total",
"severity": "error",
"detail": "values sum to 129.5, expected total (135.0)"
}
],
"flags_total": 1,
"flags_omitted": 0,
"hints": [
{
"source": "integrity",
"check": "consistency:sum_equals",
"path": "$.line_items[].line_total",
"expected": "a sum equal to total (135.0)",
"found": "sum 129.5"
}
],
"hints_total": 1,
"hints_omitted": 0,
"schema_compliance": { "ratio": 1.0, "passed": 22, "total": 22 },
"integrity_checks": { "ratio": 0.875, "passed": 7, "total": 8 },
"overall": { "ratio": 0.966667, "passed": 29, "total": 30 },
"verify": {
"public_key_url": "https://fastapi-service-5ag4.onrender.com/.well-known/agent-card.json",
"key_version": "v1-2026-09c",
"canonicalization": "RFC 8785 (JCS)",
"hash_algorithm": "SHA-256",
"service": "Agent Output Verifier",
"mcp": "https://fastapi-service-5ag4.onrender.com/mcp"
},
"attestation": {
"algorithm": "Ed25519",
"key_version": "v1-2026-09c",
"timestamp": "2026-10-01T01:01:09.606993+00:00",
"signature": "8UexIObSAwQxjj8/rbEpLRgbYY6XO+wg5+b0D8I3BRfbOxl+ouwcKNPE/hp+9p37Tm2wwdmCqPjBgOMLGQtGAQ=="
}
}
Evidencia de pago: $0.02 USDC en Base, liquidado en cadena —
0x4cb755c71413ed5419215d2d7445c34e19be79d9f15b4046802656165b774897.
2. Factura corregida — pasa (detail: "compact")
Mismo esquema y regla, total corregido para coincidir con los elementos de línea (129.5), solicitado con "detail": "compact" para obtener solo el resumen, los campos de recibo y la firma:
{
"task_id": "invoice-7731-final",
"agent_id": "test-seller-invoice-agent",
"expected_schema": { "...": "same schema as above" },
"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,
"detail": "compact"
}
{
"summary": {
"result": "pass",
"blocking_checks": []
},
"next_actions": [
{ "action": "attach_receipt", "for": "seller" },
{ "action": "proceed_to_payment", "for": "buyer" }
],
"result": "pass",
"verification_id": "96b4f722-04c3-47c7-8708-5bba97fd18b5",
"task_id": "invoice-7731-final",
"agent_id": "test-seller-invoice-agent",
"output_hash": "39e3d0a1431e3ce9cda89a1d2873e1ffb4e34f4eeded2ffa87946fbd941a34c3",
"schema_hash": "23ea3213715cfaf2011dabe2a9a69d4ac25b6eceec7ce3d4bfef9a1298b69b2a",
"rules_hash": "2acb6de92b88cfbca279ea6d5bd74b7a1e7dec0839dd093dbe041e25df3364d2",
"verified_at": "2026-10-01T01:01:15.158857+00:00",
"verifier_version": "0.4.1",
"enforce_rules": true,
"strict_content_check": false,
"verify": {
"public_key_url": "https://fastapi-service-5ag4.onrender.com/.well-known/agent-card.json",
"key_version": "v1-2026-09c",
"canonicalization": "RFC 8785 (JCS)",
"hash_algorithm": "SHA-256",
"service": "Agent Output Verifier",
"mcp": "https://fastapi-service-5ag4.onrender.com/mcp"
},
"attestation": {
"algorithm": "Ed25519",
"key_version": "v1-2026-09c",
"timestamp": "2026-10-01T01:01:15.158857+00:00",
"signature": "ri1AEl12DoZMTKBX8V5hfuD9jOWd+BLXIciqiX1JoSHJy8iDJKKSdsJmjpQUHTkbVMxDGpNwMNmc6X9zHaSVDw=="
}
}
La forma compacta conserva el resumen, los campos de recibo, verify y attestation; request_hash, errors, errors_detail, flags, hints y las tres puntuaciones permanecen en la forma completa (ver detail arriba).
Evidencia de pago: $0.02 USDC en Base, liquidado en cadena —
0xaa9161736ac410b6ac9ccc478780aa2feaa6d2cf02af4c51855e099c14499079.
Verificando estos dos recibos usted mismo
Ambas respuestas anteriores se verificaron contra la clave pública de la tarjeta de agente en vivo para v1-2026-09c antes de incluirlas aquí, usando los pasos en Verificando un recibo. Confírmelo usted mismo de la misma manera: obtenga https://fastapi-service-5ag4.onrender.com/.well-known/agent-card.json, confirme que v1-2026-09c está listado, y vuelva a ejecutar los mismos cinco pasos contra los dos bloques JSON anteriores — las firmas se calcularon una vez, sobre exactamente estos datos, y se verificarán de manera idéntica para cualquier persona.
agent_id identifica al agente que produjo la salida que se está verificando — por ejemplo, el vendedor; es una etiqueta no autenticada que el llamador proporciona. agent_id: "test-seller-invoice-agent" usa el prefijo test- que el servicio reserva para ejemplos: la verificación se ejecutó de verdad, y su recibo es genuino, mientras permanece separado de la puntuación de confianza de ese agente.
Licencia
La documentación y los ejemplos en este repositorio están licenciados bajo la Licencia MIT. El servicio alojado es propietario; este repositorio contiene solo documentación y metadatos públicos.