Integrations
OpenTelemetry
Understand Badge's gated OTLP receiver contract and current integration readiness.
Release preparation only. Customer setup is not supported yet. The agent-scoped credential, run-correlation backend, and BPP-gated create/rotate/revoke card have shipped, as has a real multi-node evidence run — published end to end in Screen a local Ollama multi-agent app, which is the one worked path that exists today. Version-pinned framework validation has not, so the readiness table below governs every framework that guide does not cover. This page documents the receiver contract without presenting an unverified quickstart.
Whether a given environment accepts OTLP at all is a server-side setting that an operator can change. Check it rather than assuming — the public, no-credential probe
GET /api/v1/provenance/statusreturns{"enabled":true}or{"enabled":false}.
OpenTelemetry traces can describe the model and tool calls an HTTP agent reports, their parent-child relationships, token counts, latency, and self-reported or estimated cost.
Evidence boundary: a Blueprint is self-reported telemetry — consistent with claims, not proof. Badge cross-checks bounded metadata against its registered run and trace context, but the agent still controls the spans it emits. Estimated cost is not provider billing.
Receiver contract
- Endpoint:
POST https://api.badgeia.com/api/v1/provenance/otlp - Authentication header:
Authorization: Bearer <agent OTLP credential> - Transport: OTLP/HTTP with
application/x-protobuforapplication/json; OTLP/gRPC is not accepted. On the JSON path you do not need to do anything special about ID encoding — Badge accepts both the hextraceId/spanIdthe OTel spec mandates (what the JS SDK and other conforminghttp/jsonexporters send) and the base64 form protobuf's canonical JSON mapping produces. Both are stored identically. - Bounds: 1 MiB per request and 200 stored spans per run.
- Feature gate: when Blueprint/provenance processing is disabled, the
receiver returns
404and this page is excluded from navigation,llms.txt, and the sitemap.
The owner/editor-only backend routes
POST /api/v1/agents/{agent_id}/otlp-token and
DELETE /api/v1/agents/{agent_id}/otlp-token create, rotate, and revoke a
single-reveal credential. The credential is reusable for that agent until
rotated or revoked. The agent-detail card masks and single-reveals the value
for eligible editors. Customer publication remains blocked until the exact
staging trace and compatibility matrix pass.
Every batch authenticated with an agent credential must assign
badge.run_id to each effective span partition. The run must belong to that
agent, and every trace ID must match a trace Badge registered before
dispatch. Badge sends the run ID in X-Badge-Run-Id and W3C trace context in
traceparent to an eligible HTTP agent.
Use the exact trace endpoint above with your SDK's trace-specific OTLP HTTP setting. Send the credential only in the authorization header. Never put the credential, a Badge API key, prompts, completions, tool arguments, or tool results in span attributes, URLs, logs, screenshots, or support tickets.
Framework readiness
The SDK's built-in tracing does not become OTLP when standard exporter variables are set.
- What Badge reads
- Nothing through the built-in tracing path. A separate adapter would need to emit Badge's supported GenAI attributes.
- Evidence still required
- A selected, security-reviewed, version-pinned adapter exercised against staging.
Partial and reference-only rows are deliberately not copy-paste examples. They become supported only after exact package versions, content-capture controls, accepted-span results, expected Blueprint nodes, and privacy-safe staging receipts are recorded.
Attribute mapping
Badge applies a strict allowlist before persistence. Unknown attributes are discarded.
| Exported metadata | Badge interpretation | Notes |
|---|---|---|
Span trace_id, span_id, parent_span_id | Sequence and invocation edges | Standard OTLP fields, not attributes |
| Span start/end timestamps | Per-node latency | An end before start is clamped to start and contributes 0ms |
gen_ai.response.model | Model node | Preferred over request model when both are present |
gen_ai.request.model | Model node | Used when response model is absent |
gen_ai.usage.input_tokens | Input tokens | Current GenAI semantic convention |
gen_ai.usage.output_tokens | Output tokens | Current GenAI semantic convention |
gen_ai.usage.prompt_tokens | Input tokens | Accepted legacy alias |
gen_ai.usage.completion_tokens | Output tokens | Accepted legacy alias |
gen_ai.usage.cost | Self-reported USD cost | Badge extension; numeric, bounded, and labeled |
gen_ai.tool.name | Tool node | Use a stable non-secret identifier; arguments and results are ignored |
gen_ai.agent.name | Agent/role node (multi-agent hierarchy) | Stored as a bounded agent:<display>~<hash> projection. The name must start with a letter and contain only letters, digits, _ or - — a name that doesn't is dropped, not cleaned up (see "Agent nodes do not appear" under Troubleshooting). At most 16 stored characters (the public read view cuts this to 12 — see "What Badge stores"); at most 8 distinct roles per run; supervisor→subagent edges come from real parent span structure |
Span name retrieval | Retrieval node | One of exactly seven accepted names: retrieve, retrieval, retriever, vector_search, vector.search, semantic_search, semantic.search. Anything else becomes unknown_operation |
badge.response_sha256 | Answer-binding evidence | SHA-256 hex digest only; never the answer body |
badge.run_id | Agent-credential correlation | Required for every effective partition using an agent credential |
Badge reads OpenInference, OpenLLMetry, LangSmith, Vercel, or other
framework output only when it also emits the supported gen_ai.* keys.
Framework-native prompt, message, content, llm.*, and
ai.toolCall.name attributes are not remapped.
Unknown models are unpriced, never guessed as zero. A valid
gen_ai.usage.cost remains self-reported telemetry. Badge estimates cost
only when the exact model ID has a price effective on the run date and both
token counts are valid nonnegative integers.
What Badge stores
Badge stores a bounded metadata projection: safe span, parent, and trace IDs; operation kind; model identifier; token counts; timestamps; safe tool identifier; optional numeric cost; run correlation; and answer hash. It does not persist prompt or completion content from OTLP.
What is stored is not what is published. A second, narrower projection is applied at read time for anyone who cannot edit the agent:
- Model and tool digests are replaced by per-response ordinals (
model:1,tool:1). Model IDs on Badge's vendored price allowlist stay verbatim. - An agent identity keeps its readable role name — case-folded and cut to
12 characters — with an ordinal in place of the digest:
agent:researcher~2. This is the only caller-authored text on the public surface. It is bounded, not anonymised. Name nodes for the role, never for the customer, tenant, or internal system. - Token counts are floored to two significant figures; latencies and costs are rounded to nearest at two significant figures, so a published latency or cost can exceed the measured one. Owners get exact token counts and latencies; every stored cost is quantised to four significant figures.
Blueprint architecture and cost remain labeled self-reported or estimated. They do not change the run's composite score or fitness radar formula.
Architecture versions (where enabled)
Whether versioning is enabled is a server-side setting, exposed by the same
public probe as "blueprint_versions": GET /api/v1/provenance/status.
On environments with versioning enabled, Badge assigns every traced run a
content-addressed architecture version: two runs whose span graphs have
the same structure and identities — the same roles, model identities, tool
set, and call relations — share one version; a structural change mints the
next one (v1, v2, …). Volatile signals (token counts, latency, cost,
timestamps, retries of an existing path) never change the version. A version
identifies structure and identities, not behaviour: prompts, sampling
settings, and routing logic are invisible to it. Blueprint responses carry
the version as blueprint_version (id, version_number,
canonicalization_version); the field is null where versioning is
disabled or the run predates it.
Reading the timeline. The agent's Evidence page lists its versions most
recently seen first — so the version currently running sits at the top, even
after a rollback to an older architecture (a rollback re-uses the existing
version; it never mints a new one). Each entry shows when that version was
first and last seen, how many traced runs are bound to it, and a summary of
what changed against the version before it. Dates and counts cover the runs
you can see: a run the owner keeps private moves nothing on the public
timeline. The summary names a model only where the identity is one Badge
recognises (gpt-4o-mini → gpt-5-mini); everywhere else it says model
identity changed, because the comparison is over identifiers that were
privacy-projected before storage — Badge can see that a model changed, not
which model. A change to an agent node reads role renamed or replaced: the
comparison cannot tell a rename from a replacement. An entry marked hashing
rules updated is not an architecture change at all — it is the same
architecture re-recorded after Badge changed how it canonicalises graphs.
Signed out, or on someone else's agent, the same list appears without version
numbers: versions are identified by their id, because the counter is minted
across private runs too.
For agents with a signed manifest, the Evidence page's comparison panel also records declared vs observed against the architecture version it was computed on — reported, never enforced, and visible to the agent's owner and editors only. What it does and does not prove is covered in the Provenance FAQ.
Troubleshooting
404 from the OTLP endpoint
Blueprint/provenance processing is disabled. Check
GET /api/v1/provenance/status or ask the environment operator.
Exporter sends to an added /v1/traces path
The generic OTLP endpoint setting was used. Configure the trace-specific setting with the complete Badge endpoint exactly as shown above.
Connection error on port 4317
The exporter is using OTLP/gRPC. Select OTLP/HTTP with protobuf or JSON.
401 Invalid or missing OTLP ingest token
Use the single-reveal credential created for the same agent. It remains usable until rotated, revoked, or made ineligible. Do not send a Badge API key to the receiver.
415 or 422
For 415, use application/x-protobuf or application/json. For
422 Malformed OTLP payload, make the body encoding and declared content
type agree. Agent-credential batches also return 422 when a run ID is
missing or a trace does not match the run's registered dispatch.
200 with accepted_spans: 0
The span IDs were already ingested, or the run reached its storage cap. Retries are idempotent.
Run completes but no Blueprint appears
Force-flush the exporter, confirm every partition carries the correct
badge.run_id, and confirm at least one span has a supported model, tool, or
retrieval signal.
Spans arrive but model or token data is absent
Inspect the exported attribute names. Framework-native llm.* fields are not
automatically treated as gen_ai.*.
Tool names become opaque
This is unconditional and not a misconfiguration. Every accepted
gen_ai.tool.name is stored as tool:<sha256[:16]>; there is no
readable-tool allowlist and no identifier you can choose that stays readable.
An anonymous reader sees less still — a per-response ordinal (tool:1),
which is positional and not a stable key across responses.
Tool names disappear entirely
The value failed the ingest guard rather than being hashed. Use a short
stable identifier, not arguments, URLs, credentials, or natural-language
content. Vercel's ai.toolCall.name is not mapped today.
Agent nodes do not appear
The value failed the ingest guard rather than being shortened.
gen_ai.agent.name must start with a letter and, in its first 16
characters, contain only letters, digits, _ or -. Dots, /, :, @,
or a leading digit drop the name entirely and the span keeps its non-agent
classification — research.agent yields no node, research_agent does.
Internal spaces are fine (Math Tutor → Math_Tutor). At most 8 distinct
roles per run are kept.
Cost shows unpriced
The exact model ID had no price effective on the run date, or one of the token counts was missing, invalid, or negative. Badge does not infer aliases or missing usage.
Trace is accepted but provenance stays unbound
Confirm that the trace ID matches the dispatched traceparent, the root span
has the exact canonical response hash, and the agent echoes
X-Badge-Trace-Id. Accepted late spans trigger recomputation; arrival after
the initial evaluation does not by itself leave a run permanently unbound.