---
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
token_estimate: 1284
---
# 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. |
| 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`. |
| 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 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.

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

