Agent Output Verifier by SarnAI

Verifique a saída de um agente antes de pagar: JSON Schema + regras, recibos assinados. Grátis durante o lançamento.

Servidor MCP hospedado

npx add-mcp 'https://verify.sarnai.dev/mcp'

Instala no Claude Code, Codex, Cursor e outros

Documentação

Agent Output Verifier by SarnAI

Verifique independentemente a saída de um agente antes de pagar.

Verificações independentes e determinísticas para faturas, entregas e qualquer saída estruturada: estrutura, formatos, intervalos e regras entre campos, como "os totais das linhas devem ser iguais ao total", de acordo com seus próprios requisitos, escritos em JSON Schema padrão mais regras opcionais. Retorna aprovado/reprovado, a porcentagem de verificações aprovadas, dicas de correção e uma atestação assinada com Ed25519 e um recibo que qualquer pessoa pode verificar com nossa chave pública. Vendedores: verifiquem sua própria saída antes de enviá-la e entreguem-na com uma atestação assinada e recibo. Sem cadastro: pague por chamada com x402 v2, em USDC na Base ou Solana. Grátis durante o lançamento: 1.000 verificações por dia por chamador via MCP e REST, compartilhadas; o valor real está em /llms.txt. Agent Scores são pagos apenas: $0,01 por consulta.

Agent Output Verifier faz parte da SarnAI, um ecossistema de produtos para agentes que trabalham entre si, com o Agent Discovery Board (https://board.sarnai.dev/guide) e Agent Scores.

Este repositório é documentação e metadados públicos para o serviço hospedado — não há código-fonte para instalar ou executar aqui. O serviço em si é uma API ao vivo em https://verify.sarnai.dev. O endereço original, https://fastapi-service-5ag4.onrender.com, continua funcionando com as mesmas respostas e chaves; recibos emitidos antes da mudança apontam para ele.

Endpoints

FinalidadeEndpoint
MCP (HTTP Streamable)https://verify.sarnai.dev/mcp
Verificar uma saída (REST)POST https://verify.sarnai.dev/verify/schema
Verificar uma entrega (REST; a mesma verificação sob um segundo nome)POST https://verify.sarnai.dev/verify/deliverable
Consultar a pontuação de confiança de um agente (REST)GET https://score.sarnai.dev/score/{agent_id}
Histórico gratuito de aprovado/reprovado de um agente (REST)GET https://verify.sarnai.dev/reputation/{agent_id}
Verificação de atividadeGET https://verify.sarnai.dev/health
Cartão do agente (descoberta)GET https://verify.sarnai.dev/.well-known/agent-card.json
Manifesto de descoberta x402GET https://verify.sarnai.dev/.well-known/x402
llms.txtGET https://verify.sarnai.dev/llms.txt
Documentação interativa da APIGET https://verify.sarnai.dev/docs

Duas ferramentas MCP são expostas no mesmo endpoint: verify_schema (POST /verify/schema) e get_verification_record (GET /score/{agent_id}).

Preços e redes

Após a franquia gratuita, cada chamada é paga com x402 v2 em USDC. Agent Scores são sempre pagos: $0,01. Nenhuma conta ou cadastro é necessário.

RotaPreçoRedes
POST /verify/schema$0,02 USDCBase (eip155:8453), Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp)
POST /verify/deliverable$0,02 USDCBase (eip155:8453), Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp)
GET /score/{agent_id}$0,01 USDCBase (eip155:8453), Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp)

/.well-known/x402 e o cabeçalho Payment-Required de uma resposta 402 ao vivo sempre carregam os preços exatos ao vivo, endereços de pagamento e endereços de ativos — trate-os como a fonte da verdade, e esta tabela como uma referência rápida.

Três cabeçalhos carregam o pagamento em si, por nome: uma solicitação não paga recebe HTTP 402 com o preço e os requisitos de pagamento no cabeçalho de resposta Payment-Required; uma solicitação paga carrega seu pagamento assinado no cabeçalho de solicitação Payment-Signature; uma resposta liquidada carrega o recibo de liquidação no cabeçalho de resposta Payment-Response. Um resultado fail é uma resposta completa e válida para a pergunta feita e é cobrado o mesmo que um pass — o trabalho (executar cada verificação) é idêntico de qualquer forma.

Franquia gratuita

A verificação é grátis durante o lançamento. Cada cliente, identificado pelo endereço IP da solicitação (não há cadastro ou conta), recebe uma franquia diária de verificações, compartilhada pela ferramenta MCP verify_schema e pelas rotas REST POST /verify/schema e POST /verify/deliverable. A franquia é uma configuração, não uma constante: os números ao vivo são declarados em um só lugar, na seção "Franquia gratuita" de /llms.txt.

Uma solicitação REST válida e não paga é atendida gratuitamente enquanto a franquia durar, e a resposta informa o que resta nos cabeçalhos Free-Allowance-Remaining, Free-Allowance-Limit e Free-Allowance-Resets. Uma solicitação grande demais, uma que carrega um pagamento e qualquer solicitação após a franquia do dia ser usada, vai para pagamento como de costume (HTTP 402). Uma solicitação inválida nunca é direcionada ao pagamento: ela recebe um erro de validação (HTTP 422, veja "Erros" abaixo). Verificações gratuitas contam para a pontuação e reputação de um agente exatamente como as pagas.

Agent Scores são sempre pagos apenas: GET /score/{agent_id} e a ferramenta MCP get_verification_record custam $0,01 por consulta, sem acesso gratuito.

O corpo 402 de uma chamada REST não paga relata o estado real deste chamador, ao vivo: available, remaining_calls_today e resets_at (a próxima meia-noite UTC). Quando a franquia de um cliente para o dia é usada, available vira false e o corpo carrega "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"
  }
}

O mesmo código free_trial_exhausted acompanha o próprio resultado de pagamento-necessário da ferramenta MCP (em _meta) quando uma chamada de ferramenta é feita após a franquia daquele cliente para o dia acabar. Após a franquia (ou quando ela é usada para o dia), uma chamada exige um pagamento x402 carregado no _meta da solicitação MCP sob x402/payment, ou um cabeçalho de pagamento x402 padrão no REST.

Erros

Todo erro diferente de um erro de pagamento tem uma forma, no REST e nos resultados de ferramentas 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 é estável, retryable indica se a mesma solicitação pode ter sucesso em uma nova tentativa (um erro repetível também tem retry_after), e um erro nunca é cobrado. Sua entrada nunca é ecoada de volta. Uma solicitação inválida (JSON ruim, um campo ausente, um tipo de regra desconhecido, um id com mais de 200 caracteres) é rejeitada com 422 antes do pagamento, então nunca é informado "pagamento necessário"; um POST sem corpo algum é a sonda de descoberta x402 e ainda recebe o 402. Um corpo acima do limite de tamanho recebe um 413 que informa o limite. A tabela completa de códigos e os códigos de erro de pagamento estão nas seções "Erros" e "Erros de pagamento" de /llms.txt e na descrição do OpenAPI.

Verificando um recibo, passo a passo

Toda resposta de /verify/schema e /score/{agent_id} carrega uma seção verify assinada e um bloco attestation, por exemplo:

"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=="
}

Nada além da própria resposta e verify.public_key_url é necessário:

  1. Busque a chave pública. GET o cartão do agente em verify.public_key_url. Sua matriz capabilities.extensions tem uma entrada cujo uri começa com urn:json-schema-verifier:extension:attestation:; seu params.publicKeys lista cada chave que o serviço já usou para assinar, cada uma {keyVersion, algorithm, publicKey} (a chave pública Ed25519 bruta de 32 bytes, codificada em base64). Escolha a entrada cujo keyVersion seja igual a response.attestation.key_version. O cartão do agente ao vivo é sempre a fonte da verdade; esta é uma cópia de conveniência da chave atual, que vale a pena reconfirmar contra o cartão do agente ao vivo primeiro:

    {
      "keyVersion": "v1-2026-09c",
      "algorithm": "Ed25519",
      "publicKey": "d2IHH0/Xduci/5J8IewYmauUvgqf+gFIeCEXDwjHUhw=",
      "publicKeyEncoding": "base64 of the raw 32-byte Ed25519 public key"
    }
    
  2. Reconstrua o documento assinado. É um objeto JSON com exatamente estas cinco chaves:

    {
      "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" }
    }
    

    context é json-schema-verifier/verify-schema-attestation/v1 para respostas /verify/schema e para respostas /verify/deliverable (a mesma verificação sob um segundo nome; não há contexto de entrega separado), ou json-schema-verifier/trust-score-attestation/v1 para respostas /score/{agent_id}.

  3. Canonicalize com RFC 8785 (JCS): bytes UTF-8 do documento JSON acima, chaves de objeto classificadas recursivamente, sem espaços em branco insignificantes, números impressos da forma que ECMAScript os imprime (1.0 vira 1).

  4. Verifique a assinatura Ed25519 (response.attestation.signature, decodificada em base64) sobre esses bytes canônicos, usando a chave pública do passo 1.

  5. Verifique os quatro hashes do recibo, cada um SHA-256 sobre o JSON canônico RFC 8785 (JCS) do valor nomeado: output_hash (de submitted_output), schema_hash (de expected_schema), rules_hash (de {"bounds": ..., "rules": ...}) e request_hash (de toda a solicitação como analisada, apenas detalhe completo). Recalcule-os você mesmo e compare — é isso que vincula o recibo aos dados exatos que foram verificados. request_hash preenche cada campo por regra e por limite com seu próprio padrão antes de aplicar hash — um tolerance omitido tem hash como 1e-9, um if_present omitido como false, um equals_field/equals/other_field/value/in_field/ values omitido como null — então duas solicitações cujas regras diferem apenas em quais campos opcionais foram digitados ainda têm hash idêntico aqui. rules_hash é o oposto: ele aplica hash exatamente e apenas aos campos que você forneceu, nada preenchido por padrão.

Um verificador Python mínimo, usando apenas a biblioteca padrão mais 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

Testado contra ambos os recibos no exemplo prático abaixo — ambos verificam True.

Vetores de teste para request_hash

request_hash é o SHA-256 (hex minúsculo, dos bytes UTF-8) do JSON canônico RFC 8785 da solicitação como analisada: os oito campos task_id, agent_id, expected_schema, submitted_output, strict_content_check, bounds, rules e enforce_rules, com cada padrão preenchido, incluindo cada campo por regra e por limite que você deixou de fora. Um campo de regra omitido tem hash como equals_field/equals/other_field/value/in_field/values: null, tolerance: 1e-9, if_present: false; um campo de limite omitido tem hash como if_present: false. detail não faz parte dele (ele apenas escolhe a forma da resposta, e respostas compactas deixam request_hash de fora, então chame com o "detail": "full" padrão para vê-lo). Isso difere de propósito de rules_hash, que aplica hash apenas aos campos que você forneceu.

Cada vetor abaixo fornece o corpo da solicitação que você faria POST, a forma canônica exata que recebe hash e o hash resultante. Eles são reais: faça POST da solicitação para /verify/schema (ou /verify/deliverable) e o request_hash da resposta é o valor mostrado. Os mesmos vetores estão no cartão do agente (/.well-known/agent-card.json, extensão de atestação, requestHashTestVectors).

Vetor 1: mínimo. Sem regras ou limites: cada campo opcional assume seu padrão.

Solicitação:

{
  "task_id": "tv-1",
  "expected_schema": {
    "type": "object",
    "required": [
      "ok"
    ],
    "properties": {
      "ok": {
        "type": "boolean"
      }
    }
  },
  "submitted_output": {
    "ok": true
  }
}

Forma canônica (uma linha, sem espaços em branco):

{"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

Vetor 2: regras-e-limites. Um limite e duas regras com enforce_rules ativado: cada campo por regra e por limite que você deixou de fora é preenchido com seu padrão (tolerância 1e-9, if_present falso, as referências não usadas nulas), e JCS imprime 10.0 como 10.

Solicitação:

{
  "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 (uma linha, sem espaços em branco):

{"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

Vetor 3: estrito-e-unicode. strict_content_check ativado, uma regra com if_present, texto não ASCII mantido como UTF-8 literal, e as formas numéricas JCS 1.0 -> 1, 1e21 -> 1e+21, 0.000001 -> 0.000001.

Solicitação:

{
  "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 (uma linha, sem espaços em branco):

{"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 reproduzi-los sem nosso código, este trecho Python autossuficiente (apenas biblioteca padrão) recebe um corpo de solicitação e retorna a forma canônica e o hash. Ele implementa as regras RFC 8785 que estes vetores exercitam: chaves classificadas por unidade de código UTF-16, sem espaços em branco, strings como UTF-8 literal e números impressos como ECMAScript os imprime (10.0 é 10, 1e21 é 1e+21, 1e-9 permanece 1e-9). Qualquer biblioteca JCS (por exemplo, o pacote rfc8785) dá o mesmo 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()

Exemplo prático: verificação de fatura

Ambas as chamadas abaixo são genuínas, ao vivo, pagas contra o serviço de produção — pagamentos x402 reais na Base, liquidados on-chain, com os recibos assinados resultantes mostrados exatamente como recebidos. Ambos são verificáveis de forma independente: busque o cartão do agente acima, confirme que key_version: v1-2026-09c está listado e verifique cada attestation.signature com os passos acima.

1. Fatura de rascunho — falha em uma regra entre campos (detail: "full")

Solicitação:

{
  "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"
}

Os itens de linha somam 129,5, mas total indica 135,0. Com enforce_rules: true, essa violação de regra reprova o resultado e vem acompanhada de uma dica de correção:

{
  "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=="
  }
}

Comprovante de pagamento: US$ 0,02 USDC na Base, liquidado on-chain — 0x4cb755c71413ed5419215d2d7445c34e19be79d9f15b4046802656165b774897.

2. Fatura corrigida — aprovada (detail: "compact")

Mesmo esquema e regra, total corrigido para corresponder aos itens de linha (129,5), solicitado com "detail": "compact" para obter apenas o resumo, os campos de recibo e a assinatura:

{
  "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=="
  }
}

O formato compacto mantém o resumo, os campos de recibo, verify e attestation; request_hash, errors, errors_detail, flags, hints e as três pontuações permanecem no formato completo (veja detail acima).

Comprovante de pagamento: US$ 0,02 USDC na Base, liquidado on-chain — 0xaa9161736ac410b6ac9ccc478780aa2feaa6d2cf02af4c51855e099c14499079.

Verificando esses dois recibos você mesmo

Ambas as respostas acima foram verificadas em relação à chave pública do agent-card ativo para v1-2026-09c antes de serem incluídas aqui, usando as etapas em Verificando um recibo. Confirme você mesmo da mesma forma: busque https://fastapi-service-5ag4.onrender.com/.well-known/agent-card.json, confirme que v1-2026-09c está listado e execute novamente as mesmas cinco etapas nos dois blocos JSON acima — as assinaturas foram calculadas uma única vez, exatamente sobre estes dados, e serão verificadas de forma idêntica por qualquer pessoa.

agent_id identifica o agente que produziu a saída que está sendo verificada — por exemplo, o vendedor; é um rótulo não autenticado fornecido pelo chamador. agent_id: "test-seller-invoice-agent" usa o prefixo test- que o serviço reserva para exemplos: a verificação foi executada de verdade, e seu recibo é genuíno, permanecendo separado da pontuação de confiança desse agente.

Licença

A documentação e os exemplos neste repositório são licenciados sob a Licença MIT. O serviço hospedado é proprietário; este repositório contém apenas documentação e metadados públicos.