---
title: OpenTelemetry ingestion reference
description: "Reference for Amplitude's OTLP endpoint: which span attributes set the session, user, and agent, how GenAI spans map to [Agent] events, request limits, and response codes."
product: general
lang: en
last_updated: 2026-10-15
token_estimate: 1904
---
# OpenTelemetry ingestion reference

> For AI agents: a documentation index is available at [/docs/llms.txt](/docs/llms.txt). Append `.md` to any page URL for markdown, or send `Accept: text/markdown`.

This page is the reference for how Amplitude's OTLP endpoint translates OpenTelemetry GenAI spans into Agent Analytics `[Agent]` events. For setup steps, refer to [Send OpenTelemetry traces to Agent Analytics](https://amplitude.com/docs/amplitude-ai/agent-analytics/instrument-opentelemetry).

## Endpoints and protocol

| Region | Endpoint |
| --- | --- |
| US | `https://api.amplitude.com/otlp/v1/traces` |
| EU | `https://api.eu.amplitude.com/otlp/v1/traces` |

- **Protocol**: OTLP over HTTP only, as `http/protobuf` or `http/json`, with optional gzip. Amplitude doesn't support OTLP/gRPC.
- **Authentication**: The project API key as a `Bearer` token or as an `api_key` query 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 |
| 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` |
| 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](https://amplitude.com/docs/amplitude-ai/agent-analytics/taxonomy#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](https://amplitude.com/docs/sdks/agent-analytics/sdk#opentelemetry-attribute-mapping) in the SDK reference.

## 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](https://amplitude.com/docs/amplitude-ai/agent-analytics/instrument-opentelemetry#send-claude-platform-traces).

## 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 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.

