OpenTelemetry ingestion reference
This page is the reference for how Amplitude's OTLP endpoints translate OpenTelemetry GenAI spans and log records into Agent Analytics [Agent] events. For setup steps, refer to Send OpenTelemetry traces to Agent Analytics.
Endpoints and protocol
| Region | Traces | Logs |
|---|---|---|
| US | https://api.amplitude.com/otlp/v1/traces | https://api.amplitude.com/otlp/v1/logs |
| EU | https://api.eu.amplitude.com/otlp/v1/traces | https://api.eu.amplitude.com/otlp/v1/logs |
- Protocol: OTLP over HTTP only, as
http/protobuforhttp/json, with optional gzip. Amplitude doesn't support OTLP/gRPC. Both endpoints use the same authentication, limits, and response codes. - Authentication: The project API key as a
Bearertoken or as anapi_keyquery parameter. Amplitude doesn't require a per-project enablement request. - Supported instrumentation: OpenTelemetry GenAI semantic conventions, OpenInference (
llm.*), OpenLLMetry and Traceloop (traceloop.*), OpenLIT, and LiteLLM. Amplitude ignores non-GenAI spans.
Attribute resolution order
Amplitude reads the first attribute present, in this order:
| Signal | Attributes Amplitude reads, in order |
|---|---|
| Session | gen_ai.conversation.id, session.id, traceloop.association.properties.session (or chat, thread, conversation), gen_ai.session.id, then resource session.id and gen_ai.conversation.id. With none of these, Amplitude falls back to the trace ID and marks the session degraded. Claude-native spans are the exception: anthropic.session.id is the session, and a different gen_ai.conversation.id is kept as the thread. |
| User | enduser.id, gen_ai.request.user, gen_ai.user, user.id, traceloop.association.properties.user, resource enduser.id and user.id, then LiteLLM's metadata.user_api_key_end_user_id. With none of these, the user resolves to unknown. |
| Agent | gen_ai.agent.id, else resource service.name. Claude-native spans don't fall back to service.name, so claude-platform isn't stored as the agent. |
| Parent agent | Span attribute amplitude.parent.agent.id only. A parent span ID and resource attributes aren't a parent agent. |
| Environment | deployment.environment, on the span or the resource. |
What maps to what
| OpenTelemetry source | [Agent] result |
|---|---|
gen_ai.operation.name of chat, text_completion, or generate_content | [Agent] AI Response, plus an [Agent] User Message synthesized from the new input messages |
gen_ai.operation.name of execute_tool | [Agent] Tool Call, with gen_ai.tool.name, gen_ai.tool.call.id, arguments, and result. A commit_sha event property is added from the [branch sha] line of git commit output, or from the sha in a JSON result when the tool name or arguments name a commit, pull request, or file push as a whole word. git log, git show, and other hex strings are never read. |
Any other operation, such as invoke_agent, retrieval, plan, or the memory operations | [Agent] Span |
gen_ai.request.model, gen_ai.response.model | [Agent] Model Name |
gen_ai.provider.name, or the deprecated gen_ai.system | [Agent] Provider |
gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, and the reasoning and cache token attributes | Token properties |
gen_ai.usage.cost | [Agent] Cost USD. This is an Amplitude extension: OpenTelemetry doesn't standardize cost, so most instrumentation omits it. |
gen_ai.response.finish_reasons | [Agent] Finish Reason |
gen_ai.input.messages, gen_ai.output.messages | $llm_message |
gen_ai.system_instructions, or a system-role message | [Agent] System Prompt on the AI Response |
Span status of ERROR | [Agent] Is Error, [Agent] Error Message |
trace_id, span_id, parent_span_id, start time, duration | [Agent] Trace ID, [Agent] Span ID, [Agent] Parent Span ID, event timestamp, [Agent] Latency Ms |
amplitude.parent.agent.id | [Agent] Parent Agent ID |
The seven agent context attributes, such as amplitude.peer.session.id | The matching agent context properties. Refer to Agent context attributes. |
| Derived on the synthesized user message | [Agent] Message Source: explicit amplitude.message.source wins; otherwise agent when a parent agent is present, else user |
Amplitude doesn't calculate a cost when gen_ai.usage.cost is absent. Refer to How cost is calculated.
A chat call re-sends the whole prompt history, so only the user messages after the last assistant message become a new [Agent] User Message. Amplitude doesn't re-emit replayed history, and a tool-loop continuation whose new input is a tool result doesn't produce a user turn.
The AI SDK's in-process OpenTelemetry exporter uses its own mapping. Refer to OpenTelemetry attribute mapping in the SDK reference.
Agent context attributes
These span attributes record how your agent handled a call from another agent. Amplitude maps each one to its [Agent] property. For what the values mean, refer to Agent context properties.
| Span attribute | [Agent] property |
|---|---|
amplitude.peer.agent.id | [Agent] Peer Agent ID |
amplitude.peer.session.id | [Agent] Peer Session ID |
amplitude.peer.trace.id | [Agent] Peer Trace ID |
amplitude.context.trust | [Agent] Context Trust |
amplitude.context.verify.result | [Agent] Context Verify Result |
amplitude.actor.type | [Agent] Actor Type |
amplitude.identity.type | [Agent] Identity Type |
Amplitude trusts span attributes from any exporter that holds your project API key, the same as events. It doesn't check agent context signatures on spans. Set these attributes from your own verification logic, such as the AI SDK's, and never copy them from an inbound request.
[Agent] Context Trust follows amplitude.context.verify.result: ok records trusted, and any other result records untrusted, whatever amplitude.context.trust says. Without a verify result, Amplitude keeps amplitude.context.trust only when it's none.
Don't copy inbound baggage into identity attributes
OpenTelemetry baggage span processors copy inbound W3C baggage entries into span attributes. A caller from outside your trust boundary can then set your session, user, or agent context. Don't copy inbound baggage keys for session.id, user.id, enduser.id, gen_ai.conversation.id, or any amplitude.* attribute from untrusted callers. Allowlist only the baggage keys you set yourself.
Feature flag attributes
Amplitude reads OpenTelemetry feature flag evaluations from GenAI spans and records each one as an experiment. It reads every feature_flag.evaluation span event first, then the span's own feature_flag.* attributes. Resource attributes don't count.
| OpenTelemetry source | Experiment field |
|---|---|
feature_flag.key | Key |
feature_flag.result.variant, or the older feature_flag.variant | Variant |
feature_flag.provider.name, or the older feature_flag.provider_name | Provider |
amplitude.experiment.unit of agent_session or turn | Unit. Without it, or with any other value, the unit is span. |
Every event the span produces carries [Agent] Experiments, plus [Agent] Experiment Key, Variant, Unit, and Provider when exactly one evaluation remains. An evaluation without a variant, such as one that returned an error, adds nothing. When two evaluations share a key, the later one wins, so a span attribute overrides a span event with the same key. The validation rules and the 10-experiment limit match the AI SDK. Refer to Record experiment variants in Agent Analytics.
Claude Platform spans
Amplitude applies these rules only when the span name starts with anthropic. and the span also carries gen_ai.provider.name=anthropic, a com.anthropic.claude-platform or com.anthropic.sdk instrumentation scope, or resource service.name=claude-platform. A generic invoke_agent span, and an Anthropic chat span with any other name, stay on the mapping above.
| Claude span | [Agent] result |
|---|---|
anthropic.messages.create | User message and AI response. This span keeps the token total. |
anthropic.model_request under Messages API | [Agent] Span with usage removed, so the parent total isn't counted again. |
anthropic.session.turn with message text | User message and AI response, with no usage copied onto those events. |
anthropic.session.turn with no message text | [Agent] Span, with usage removed. |
anthropic.model_request with gen_ai.operation.name=chat | [Agent] Span that keeps model, tokens, cache, latency, and error. Several model requests each keep their own usage. |
Span name starting with anthropic.tool_use, anthropic.mcp_tool_use, or anthropic.custom_tool_use | [Agent] Tool Call. anthropic.tool_result.content is the tool output. anthropic.mcp.server.name is kept as an extra property. |
anthropic.test | Accepted and dropped. It doesn't appear in Live Events. |
anthropic.message.stop_reason, then anthropic.session.turn.stop_reason, becomes [Agent] Finish Reason on the AI Response when gen_ai.response.finish_reasons is absent. Amplitude doesn't invent enduser.id or gen_ai.usage.cost. Parent span IDs stay hierarchy. They aren't [Agent] Parent Agent ID.
To add the destination, refer to Send Claude Platform traces.
Log records
The logs endpoint turns an allowlist of agent log events into [Agent] events. Amplitude accepts every other log record and drops it, so you can point a mixed logs pipeline at Amplitude.
| Log event | [Agent] result |
|---|---|
claude_code.user_prompt | [Agent] User Message |
claude_code.api_request | [Agent] AI Response with model, tokens, cost, and latency |
claude_code.assistant_response | Reply text on the [Agent] AI Response with the same request_id |
claude_code.tool_result | [Agent] Tool Call. A record that carries vcs.ref.head.revision adds a commit_sha event property. Claude Code v2.1.269 or later sends it only with OTEL_LOG_TOOL_DETAILS=1, for a successful git commit run by the Bash or PowerShell tool. |
claude_code.subagent_completed | [Agent] Span |
gen_ai.user.message | User message text for the span with the same trace and span ID |
gen_ai.assistant.message, gen_ai.choice | Reply text for the span with the same trace and span ID |
gen_ai.tool.message | Tool content for the span with the same trace and span ID |
Some sources split one model call across two records: one with tokens and cost, and one with the text. Amplitude merges them into one turn in either arrival order. Tokens, cost, and turn counts are counted once, and a resent record changes nothing.
Log identity
Amplitude reads the session, user, and agent from the log record and its resource. A record without a session ID, or without a user ID that can join your data, is skipped rather than stored under a trace ID or as unknown.
| Signal | Claude Code (service.name=claude-code) | Other sources |
|---|---|---|
| Session | session.id | The attribute resolution order |
| Trace | prompt.id | The record's trace ID |
| User | enduser.id, then user.email, then user.account_uuid. user.id only when identity.source=gateway-oidc. | Resource enduser.id, then the attribute resolution order |
| Agent | agent_id, else claude-code | gen_ai.agent.id, then gen_ai.agent.name, then resource service.name |
| Parent agent | parent_agent_id | amplitude.parent.agent.id |
Set OTEL_RESOURCE_ATTRIBUTES=enduser.id=<user ID> to choose the user explicitly. It overrides every other rule.
One canonical signal per source
Each source has one signal that carries its turns, so nothing counts twice when you export both traces and logs.
- Claude Code: Logs carry the turn. Exported
claude_code.*spans become[Agent] Spanevents without tokens or cost. - Every other source: Spans carry the turn. GenAI log events only add text to the turn the matching span produced.
Delivery, limits, and responses
Delivery is at least once. Run a Collector with a persistent sending queue and keep retries enabled. An in-memory queue loses buffered spans and log records on restart.
| Limit | Behavior |
|---|---|
| 4 MB decompressed request body | 413 |
| 2,000 translated events per export | 413. Lower your Collector batch size. |
| Response | Meaning |
|---|---|
200 | Accepted. A partial_success body reports any events that ingestion rejected. Watch for it, because a 200 tells your Collector to delete the batch. |
400 | Malformed request body or OTLP payload. Not retryable. |
401 | Missing or invalid API key. |
415 | A content type other than application/x-protobuf or application/json. |
503 | Temporary, including downstream ingestion outages. Your Collector should retry from its queue. |
The OTLP receiver never returns 403. Treat a 403 from another hop, such as a CDN or WAF, as a configuration error, not a missing Amplitude enablement.
Was this helpful?
For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.