badgeIA

Methodology

How validation works

Understand execution modes, cost provenance, signed certificates, and conservative trust states.

View Markdown

Validation answers two separate questions:

  • What execution did badgeIA positively observe? This becomes execution_mode.
  • What evidence supports the usage and cost? This becomes cost_provenance and, when the optional Badge Provenance Protocol (BPP) is enabled, a provenance level.

A valid signature makes the recorded fields tamper-evident. It does not make a self-reported field independently true, and it does not prove the task answer was correct.

Execution-mode states

Mode, What badgeIA observed, Certificate treatment table
ModeWhat badgeIA observedCertificate treatment
simulatedbadgeIA generated mock metrics; no real agent/model execution was positively observedSigned runs show amber ◐ Simulated, never green
live_endpointbadgeIA received an HTTP response from the agent's configured customer-hosted endpointA signed run can show green ✓ Verified
real_llmA historical Badge-dispatched provider runA signed legacy record can show green ✓ Verified

Unknown, missing, blocked by badgeIA's network-safety checks, and never-reached execution paths collapse conservatively to simulated. A failed benchmark answer can still be a positively observed live execution; read success and execution_mode independently.

real_llm and OpenAI Assistant records describe historical implementation only. Badge's policy prohibits new provider-credential custody, and stored-provider dispatch is suspended. Historical records remain immutable. Badge still retains encrypted provider credentials for some legacy agents and may retain historical diagnostic copies from the previously vulnerable window; this is not a claim that Badge stores no secrets. Customer-controlled HTTP execution and explicit simulated mock screening are the supported paths.

The normalized mode is inside the signed fingerprint. Removing an amber Simulated label or changing a live mode invalidates the signature.

Cost provenance

Cost authority is independent of execution verification:

cost_provenance, Meaning table
cost_provenanceMeaning
meteredA legacy Badge-routed run matched provider-reported usage to a known pricing record
non_authoritativeA real model path ran, but provider-grade usage/pricing authority was incomplete
self_reportedAn HTTP endpoint or owner supplied the usage/cost fields
simulatedThe values came from mock execution
null (no recorded value)Legacy or unavailable provenance; do not infer a stronger state

metered says how a legacy run's cost was sourced. It does not independently prove provider/model identity or that no out-of-band call occurred, and it does not authorize new credential custody. self_reported is useful disclosure, not a verification claim.

Certificate states

Every successful public run receives an Ed25519 Verified Score Certificate at /verify/<run_id>. A run that completed but did not succeed is not signed, and neither is any run on a private agent. The Ed25519 key's public half is published for independent verification; the private signing key stays server-side.

  • ✓ Verified — signature valid and the signed execution_mode is live_endpoint or real_llm.
  • ◐ Simulated — signature valid but the signed mode is simulated.
  • — Unverified — no usable signature, such as a private, legacy, or unsigned run.
  • Invalid — a signature exists but no longer matches the recorded fingerprint. badgeIA does not vouch for that record.
  • Valid certificate, details hidden — the certificate is valid, but its agent is no longer public (made private, deleted, or its account erased). See When the agent is no longer public.

When the agent is no longer public

A run published while its agent was public keeps a valid certificate after the agent stops being public, but /verify/<run_id> then confirms only that the certificate exists and is valid. GET /api/v1/verify/<run_id> answers with "withheld": "agent_not_public" and carries:

  • that the certificate is valid (state verified, valid true) and its execution mode (execution_mode, is_simulated), so a simulated run still reads as simulated;
  • the signing time (signed_at) and the signing key (key_id), which the /verify page shows as Signed at and Signing key, plus a fixed explanation;
  • the run id you asked for.

Everything else is hidden (null, or false, in the API): the agent and its name, the task, every measured value, the fingerprint hash and signature, reliability, the Certify manifest, the architecture version, and the pack and endpoint-ownership details. The fingerprint version (fingerprint_version) is hidden too, because it would reveal what kind of run this was: a run on a holdout task (a task whose grading answers are withheld), a benchmark-pack run, or one that records endpoint ownership. The signing time and execution mode can rule some versions out, but never identify one. If you kept the original certificate, it still verifies offline against the published key, and you can read the version from the certificate itself.

If the certificate is not currently valid, a run of an agent that is no longer public returns 404, like a private run.

The fingerprint binds its version, run/agent/task ids, execution mode, outcome, latency, total cost, input/output tokens, completion time, and the model when present. The headline aggregate score is not a fingerprint field; a certificate binds one run, not a later aggregate.

Certificates minted under fingerprint algorithm v2 additionally bind the run's architecture version — the content-addressed identity of the span graph that earned the score (see Architecture versions). The verify page shows the version by its identifier (the per-agent ordinal stays owner-only). What this proves: this score, on this architecture — a later change to the agent's architecture cannot silently inherit the certificate, because the bound version is inside the signed bytes. What it does not prove: anything about prompts, sampling, or routing (a version identifies structure and identities, not behaviour), and an untraced run's certificate carries no version at all — that is a normal v2 certificate, not a defect. Every certificate records the algorithm it was minted under, and each verifies under its own recorded algorithm: pre-v2 certificates remain valid unchanged.

Certificates minted under fingerprint algorithm v4 are issued for runs of a benchmark pack (a vendored public benchmark that badgeIA runs in its own sandbox). On top of everything v2 binds, a v4 certificate always binds four pack facts:

  • pack_id — which benchmark pack, for example tau2-retail;
  • pack_version — the pack version the run pinned; a published pack version is never changed in place;
  • harness_commit — the full git commit of the upstream benchmark harness that version vendors (40 or 64 lowercase hex characters);
  • judge_model — the pinned judge model, or the reserved value programmatic when the pack's own deterministic check graded the run, not a model.

The verify page and GET /api/v1/verify/<run_id> show these facts in a pack object, only for a currently valid v4 (or v7) certificate; grader there reads program or llm_judge and is derived from judge_model. What a v4 certificate proves: this result, on this benchmark version, graded this way. What it does not prove: that the pack is a good measure of your use case, or that the run would repeat. A v4 certificate whose pack facts are missing or changed reads Invalid.

Endpoint ownership (v5, v6 and v7)

Certificates minted under fingerprint algorithms v5, v6 and v7 also record endpoint ownership (see Endpoint proof). Each is an earlier algorithm plus one signed field, endpoint_ownership, always present and always last: v5 is v2, v6 is v3 and v7 is v4 (after its four pack facts). Endpoint ownership means control of the endpoint's web address, not authorship of the agent. It is separate from the green ✓ Verified state, which reflects the execution mode alone. The field holds one of two values:

  • token_verified: the account that registered this agent controlled the endpoint's web address (origin) when badgeIA checked it. This does not show who built the agent behind it: a server can forward requests to someone else's agent.
  • not_applicable: the run had no owner endpoint to check. The verify page reads it as No endpoint to check on a live_endpoint certificate (an agent that runs through the pull daemon or an OpenAI Assistant), and as Not applicable otherwise (a run that did not call the agent's endpoint, such as a simulated screening or a screening on a model provider key).

GET /api/v1/verify/<run_id> returns the signed value as endpoint_ownership and the verify page's reading as endpoint_ownership_status, only for a currently valid certificate. A certificate minted under v1 to v4 has no signed value: on a live_endpoint certificate it reads not_recorded, because endpoint ownership was not part of its requirements when it was issued, and otherwise not_applicable.

  • Workspace agents only, for now. Certificates that sign endpoint ownership are issued for agents in a Team workspace. A personal agent's certificates keep the earlier format, so on a live endpoint run their Endpoint ownership row reads "Not recorded", even when the agent has a verified proof.
  • The host, the URL, the token and the time of the check are never signed or shown. The field says only which case applies.
  • Only where endpoint proof is on. Badge issues v5, v6 and v7 certificates only on a deployment where endpoint proof is turned on. Endpoint proof says whether it is on in Badge's hosted service.

A v5, v6 or v7 certificate whose recorded ownership is missing or changed reads Invalid. v1 to v4 certificates are not re-signed or revoked, and keep verifying under their own algorithm.

How to verify a certificate

Opening /verify/<run_id> asks badgeIA's public verification API to recompute the canonical fingerprint from the stored run fields and check the signature. The page shows that server-side result and exposes the SHA-256 fingerprint, Ed25519 signature, signing time, and key id. It is a human-facing view, not a self-contained offline certificate bundle; reproducing the cryptographic check still requires the public inputs and canonicalization rules below.

Reproducing the current cryptographic check requires three public inputs:

  1. GET https://api.badgeia.com/api/v1/verify/<run_id> for the raw signature, stored fingerprint, key id, recorded fingerprint_version, bound architecture_version_id, v4 and v7 pack facts, v5 to v7 endpoint_ownership, and reported verification state.
  2. GET https://api.badgeia.com/api/v1/runs/<run_id> for the public signed run fields.
  3. GET https://api.badgeia.com/.well-known/badge-verification-key.json for the Ed25519 public key.

The same document names the issuer in an issuer object: badgeIA, operated by Bartosz Penkala in Madrid, Spain, with its contact address and a link to the About page.

The byte contract

The signed message is a UTF-8 JSON object whose keys appear in exactly this order, each present only under the condition given:

#, Field, Present, Value table
#FieldPresentValue
1versionalwaysthe recorded fingerprint_version ("v1" to "v7")
2–11run_id, agent_id, task_id, execution_mode, success, latency_ms, total_cost_usd, input_tokens, output_tokens, completed_atalwayssee the canonical forms below
12modelwhen the run recorded a modelstring
13blueprint_version_idv2 to v7, when the run is bound to an architecture versionUUID string
14–17pack_id, pack_version, harness_commit, judge_modelv4 and v7, always all fourASCII strings, as signed
lastendpoint_ownershipv5, v6 and v7, always"token_verified" or "not_applicable"

Canonical forms: ids are lowercase hyphenated UUID strings; success is a JSON boolean; latency_ms, input_tokens and output_tokens are JSON integers (a v3 or v6 certificate may sign the string "<100" for output_tokens, see below); total_cost_usd is a string with exactly six decimals ("0.004200", and "-0.000000" becomes "0.000000"); completed_at is UTC as YYYY-MM-DDTHH:MM:SS.ffffff+00:00; a missing value is the string "null". Serialization is compact (, and : separators, no spaces) with every character outside printable ASCII escaped as a lowercase \uXXXX sequence (U+007F included; ", \ and the usual control characters take their short escapes), which is what Python's json.dumps(..., separators=(",", ":"), ensure_ascii=True) produces. The SHA-256 of those bytes is the fingerprint; the Ed25519 signature is over the bytes, not over the hex digest.

A v4 example message (one line in reality, wrapped here):

{"version":"v4","run_id":"00000000-0000-4000-8000-000000000001",
"agent_id":"00000000-0000-4000-8000-000000000002",
"task_id":"00000000-0000-4000-8000-000000000003",
"execution_mode":"live_endpoint","success":true,"latency_ms":1234,
"total_cost_usd":"0.004200","input_tokens":512,"output_tokens":128,
"completed_at":"2026-10-25T09:30:00.123456+00:00","pack_id":"tau2-retail",
"pack_version":"1.0.0","harness_commit":"<40 lowercase hex characters>",
"judge_model":"programmatic"}

Reference verifier

The public reference below reproduces Badge's byte contract and Ed25519 check for every fingerprint version, v1 to v7. It refuses any other version by name, and then prints how the certificate reads on endpoint ownership, as the verify page does. Replace <run_id>, install cryptography, and run it with Python 3.11 or newer:

import base64
import hashlib
import json
from datetime import datetime, timezone
from urllib.request import urlopen

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

API = "https://api.badgeia.com"
RUN_ID = "<run_id>"
FIELDS = (
    "version",
    "run_id",
    "agent_id",
    "task_id",
    "execution_mode",
    "success",
    "latency_ms",
    "total_cost_usd",
    "input_tokens",
    "output_tokens",
    "completed_at",
)
VERSIONS = ("v1", "v2", "v3", "v4", "v5", "v6", "v7")
POINTER_VERSIONS = ("v2", "v3", "v4", "v5", "v6", "v7")
BUCKET_VERSIONS = ("v3", "v6")
PACK_VERSIONS = ("v4", "v7")
OWNERSHIP_VERSIONS = ("v5", "v6", "v7")
PACK_FIELDS = ("pack_id", "pack_version", "harness_commit", "judge_model")

def get_json(path):
    with urlopen(f"{API}{path}") as response:
        return json.load(response)

def decode_b64url(value):
    return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))

def canonical_value(name, value):
    if value is None:
        return "null"
    if name == "total_cost_usd":
        formatted = f"{float(value):.6f}"
        return "0.000000" if formatted == "-0.000000" else formatted
    if name == "completed_at":
        parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
        normalized = parsed.astimezone(timezone.utc)
        return normalized.strftime("%Y-%m-%dT%H:%M:%S.%f") + "+00:00"
    if isinstance(value, (bool, int)):
        return value
    return str(value)

certificate = get_json(f"/api/v1/verify/{RUN_ID}")
run = get_json(f"/api/v1/runs/{RUN_ID}")
key_set = get_json("/.well-known/badge-verification-key.json")["keys"]
key = next(item for item in key_set if item["key_id"] == certificate["key_id"])
if not certificate["valid"]:
    raise SystemExit("/verify reports this certificate as not currently valid, so it does not return the signed fields this reference needs")
version = certificate["fingerprint_version"]
if version not in VERSIONS:
    raise SystemExit(f"fingerprint_version {version!r} is not one this reference reads; check the page for a newer reference")

def run_value(name):
    # v3 and v6 sign the public output-token bucket ("<100" below 100 tokens).
    if name == "output_tokens" and version in BUCKET_VERSIONS:
        bucket = run.get("output_tokens_bucket")
        if bucket is not None:
            return bucket
    return run[name]

ordered = {"version": version, "run_id": RUN_ID}
for name in FIELDS[2:]:
    ordered[name] = canonical_value(name, run_value(name))
if run.get("model") is not None:
    ordered["model"] = canonical_value("model", run["model"])
if version in POINTER_VERSIONS and certificate.get("architecture_version_id"):
    ordered["blueprint_version_id"] = certificate["architecture_version_id"]
if version in PACK_VERSIONS:
    for name in PACK_FIELDS:
        ordered[name] = certificate["pack"][name]
if version in OWNERSHIP_VERSIONS:
    # Always the last signed field.
    ordered["endpoint_ownership"] = certificate["endpoint_ownership"]

message = json.dumps(
    ordered,
    sort_keys=False,
    separators=(",", ":"),
    ensure_ascii=True,
).encode("utf-8")

assert hashlib.sha256(message).hexdigest() == certificate["verification_hash"]
Ed25519PublicKey.from_public_bytes(
    decode_b64url(key["public_key_b64url"])
).verify(decode_b64url(certificate["signature"]), message)
print("signature and fingerprint match")

# How the signed fields read on endpoint ownership, as /verify reads them.
mode = ordered["execution_mode"]
if version in OWNERSHIP_VERSIONS:
    reading = ordered["endpoint_ownership"]
    if reading == "not_applicable" and mode == "live_endpoint":
        reading = "no_endpoint_to_check"
else:
    reading = "not_recorded" if mode == "live_endpoint" else "not_applicable"
print("endpoint ownership", reading)

The last line prints how the verify page would read the certificate: token_verified (Verified when issued), no_endpoint_to_check (No endpoint to check), not_applicable (Not applicable) or not_recorded (Not recorded). It is worked out from the signed fields alone. A token_verified line shows that the account that registered the agent controlled the endpoint's web address when the certificate was issued, not who built the agent.

A certificate whose fingerprint_version is v3 (or v6, which adds endpoint ownership) is minted for runs on holdout tasks. It signs the output-token count as the public runs API publishes it for those runs ("<100" below 100 tokens, otherwise rounded down to two significant figures; the run carries usage_coarsened: true) and signs total_cost_usd as null, which is what the public API returns for it. That way an exact output length cannot be recovered from the public fingerprint. Read the run fields without signing in, as the reference does: the agent owner's own session returns the exact figures, which are not the signed ones. A v3 or v6 certificate therefore attests only the published bucket and a null cost: it does not attest the exact output-token count or the run's cost, and a change to either that stays within the published bucket does not invalidate it. A benchmark-pack run on a holdout task is never certified, because no algorithm signs both the pack facts and the coarsened usage.

The version tag is the first signed field, so a certificate cannot be re-read under another algorithm: relabelling a v4 certificate as v3, or a v5 certificate as v2 (or the reverse), changes the message, and the signature check fails.

Signed Agent Record

The Signed Agent Record (format version 1) is a self-contained JSON shape that carries one certificate together with its signed fields in signing order, so a verifier library can check it without calling the runs API. badgeIA's verifier libraries are published, but its schema, the field-order rule and the conformance fixtures (valid and deliberately broken records) are not published with them, so this page and the verify API are the reference. Only a record's signed fields are signed, so a verifier never trusts a key a record points to: it checks the record against a key set it pins itself (badgeIA's production key set above, or an archived copy), finds the key by the SHA-256 of its raw bytes, and only then confirms the key_id, because a key_id is a label, not a key. badgeIA does not serve records from the API yet.

This page is a mutable operational reference, not an immutable canonicalizer distribution. Badge currently publishes no immutable public canonicalizer artifact and no self-contained offline certificate bundle. For a durable third-party audit, archive the exact reference alongside the API responses; the human-facing /verify result or the three endpoints alone are not a permanently reproducible offline proof.

Embedding a verified card

On any public run's share page (/runs/<run_id>/share) open Embed this verified card for copy-paste Markdown, HTML, and a raw SVG URL. The badge renders the score plus the ✓/◐ state and links back to the certificate, so every reader can click through and verify it themselves. A simulated run's card shows the amber state — it will never display a green ✓.

[![Verified Badge score](https://api.badgeia.com/api/v1/badges/run/<run_id>/verified.svg)](https://badgeia.com/verify/<run_id>?ref=embed)

Free certificate vs paid Certify

Every successful public run already gets a free Verified Score Certificate. Certify this agent is the paid, deeper version — a one-off $39 certification that runs your agent through a deep screening of at least 12 tasks, each run 5×, including Badge-managed holdout tasks whose grading answer keys are withheld from every public surface and rotated if leaked — so the result is far harder to game than a single run. The task prompts stay public; only the grading answers are held out.

On a personal agent's owner-only Evidence section, Certification shows one truthful state at a time. An agent without a customer-hosted HTTPS endpoint links to the endpoint starter guides. A live-capable agent without passing real-execution evidence points to the existing Run all screens action. The $39 offer appears only after a successful live screen; simulated/mock-only agents never receive a paid CTA. An active screening replaces the offer with progress, a current-suite credential reads Certified on suite vN, and an older-suite credential may be re-certified without implying that its existing signature expired. A temporarily incomplete Certify catalog shows an availability message instead of a purchase control.

A Certified result shows the depth delta and suite version on its certificate page. Badge refuses a second checkout when the agent already holds the current suite's credential.

Score validation

The headline score uses the formula on How scores are built. Policy 2026-07-zero-success-v1 sets the composite to 0 when completed runs contain no success, and policy 2026-10-cost-unscored-v1 excludes the cost axis for an agent with no usable cost data. Policy 2026-10-secret-cost-excluded-v1 keeps holdout-task runs out of every public cost figure. Policy 2026-10-failure-neutral-v1 makes sure a failed or unpriced run never raises a score. Those are score-policy rules, not a certificate claim; historical run signatures remain valid because the aggregate composite is not signed.

What a certificate does not prove

Integrity and provenance only — not that the score is correct, that the task was hard, that a declaration is true, who built the agent, or that the agent will repeat the result. See Anti-gaming and limitations, the Trust page, and Provenance for the full boundaries.