Docs/Observability/Opentelemetry
# OpenTelemetry (OTLP) Ingestion


ANTS Platform accepts OpenTelemetry data over **OTLP/HTTP**. Any OpenTelemetry
SDK, collector or tool that can export OTLP can send spans to ANTS Platform.
Spans become [traces and observations](/docs/observability/traces), with
model, token, cost, user and session details taken from standard attributes.

The [Python and JS/TS SDKs](/docs/getting-started/installation) use the same
endpoint under the hood, so everything on this page also applies to them.

<Callout type="info">
  Setting up Claude Code, GitHub Copilot CLI or OpenAI Codex CLI? Follow
  [Coding Agent Telemetry](/docs/integrations/coding-agent-telemetry). It has
  the exact environment variables for each tool.
</Callout>

## Endpoints

All endpoints are under `https://<your-ants-host>/api/public/otel`. Replace
`<your-ants-host>` with the host of your ANTS Platform deployment.


<div className="dv2-table-scroll"><table><thead><tr><th>Signal</th><th>Path</th><th>What happens to the data</th></tr></thead><tbody><tr><td>Traces</td><td><code>POST /api/public/otel/v1/traces</code></td><td>Every span is converted into a trace and observations.</td></tr><tr><td>Logs</td><td><code>POST /api/public/otel/v1/logs</code></td><td>Only log records from supported agents are stored: Claude Code, GitHub Copilot, OpenAI Codex and Claude Cowork. Other log records are rejected (see [Responses](#responses)).</td></tr><tr><td>Metrics</td><td><code>POST /api/public/otel/v1/metrics</code></td><td>Accepted and discarded. The endpoint answers <code>200</code> so exporters that also send metrics do not error. No metric data is stored.</td></tr></tbody></table></div>


Most OTLP exporters take the base URL and append `/v1/traces`, `/v1/logs` and
`/v1/metrics` themselves. Set the base to `https://<your-ants-host>/api/public/otel`.
If your exporter needs the full signal URL, use the paths above.

Only **OTLP over HTTP** is supported. OTLP over gRPC is not.

## Authentication

Use HTTP **Basic** authentication with a project key pair:

1. Create a key pair in **Project Settings > API Keys** (see
   [API Keys](/docs/administration/api-keys)). The public key starts with
   `pk-ap-` and the secret key with `sk-ap-`.
2. Join them with a colon and Base64-encode the result.
3. Send it in the `Authorization` header.

```bash
AUTH=$(printf '%s' "pk-ap-...:sk-ap-..." | base64)
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<your-ants-host>/api/public/otel"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic ${AUTH}"
```

Data lands in the project that owns the key. Discover AI project keys (from
**Project Settings > Deployment & Keys**) and enrolled devices are also
accepted on these endpoints. Organization-level API keys are not: they return
`401` because they do not belong to a project.

<Callout type="warning">
  The secret key grants write access to the project. Keep it in a secret store
  or managed configuration. Do not put it in source code or shared documents.
</Callout>

## Formats and compression


<div className="dv2-table-scroll"><table><thead><tr><th>Header</th><th>Accepted values</th></tr></thead><tbody><tr><td><code>Content-Type</code></td><td><code>application/x-protobuf</code> or <code>application/json</code></td></tr><tr><td><code>Content-Encoding</code></td><td><code>gzip</code> (optional)</td></tr></tbody></table></div>


Any other content type returns `400 Invalid content type`. A body that cannot be
decompressed or parsed returns `400`.

In standard OpenTelemetry exporters, `http/protobuf` sends protobuf and
`http/json` sends JSON. Both work.

## Send your first spans

<Tabs _items="Python (OTel SDK),Node.js (OTel SDK),OpenTelemetry Collector,curl">
  <Tabs.Tab>
    ```bash
    pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
    ```

    ```python
    import base64, os
    from opentelemetry import trace
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

    auth = base64.b64encode(
        f"{os.environ['ANTS_PLATFORM_PUBLIC_KEY']}:{os.environ['ANTS_PLATFORM_SECRET_KEY']}".encode()
    ).decode()

    provider = TracerProvider(
        resource=Resource.create({
            "service.name": "support-bot",
            "deployment.environment": "production",
        })
    )
    provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(
        endpoint="https://<your-ants-host>/api/public/otel/v1/traces",
        headers={"Authorization": f"Basic {auth}"},
    )))
    trace.set_tracer_provider(provider)

    tracer = trace.get_tracer("support-bot")
    with tracer.start_as_current_span("chat gpt-4o") as span:
        span.set_attribute("gen_ai.operation.name", "chat")
        span.set_attribute("gen_ai.request.model", "gpt-4o")
        span.set_attribute("gen_ai.usage.input_tokens", 42)
        span.set_attribute("gen_ai.usage.output_tokens", 128)
        span.set_attribute("user.id", "user-123")
        span.set_attribute("session.id", "session-456")

    provider.shutdown()
    ```
  </Tabs.Tab>
  <Tabs.Tab>
    ```bash
    npm install @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-proto
    ```

    ```typescript
    import { trace } from "@opentelemetry/api";
    import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
    import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";

    const auth = Buffer.from(
      `${process.env.ANTS_PLATFORM_PUBLIC_KEY}:${process.env.ANTS_PLATFORM_SECRET_KEY}`,
    ).toString("base64");

    const provider = new NodeTracerProvider({
      spanProcessors: [
        new BatchSpanProcessor(
          new OTLPTraceExporter({
            url: "https://<your-ants-host>/api/public/otel/v1/traces",
            headers: { Authorization: `Basic ${auth}` },
          }),
        ),
      ],
    });
    provider.register();

    const tracer = trace.getTracer("support-bot");
    tracer.startActiveSpan("chat gpt-4o", (span) => {
      span.setAttribute("gen_ai.operation.name", "chat");
      span.setAttribute("gen_ai.request.model", "gpt-4o");
      span.setAttribute("user.id", "user-123");
      span.end();
    });

    await provider.shutdown();
    ```
  </Tabs.Tab>
  <Tabs.Tab>
    Forward spans from an existing collector with the `otlphttp` exporter:

    ```yaml
    exporters:
      otlphttp/ants:
        endpoint: https://<your-ants-host>/api/public/otel
        headers:
          Authorization: "Basic ${env:ANTS_OTLP_AUTH}"
        compression: gzip

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [batch]
          exporters: [otlphttp/ants]
    ```

    Set `ANTS_OTLP_AUTH` to the Base64 value of `pk-ap-...:sk-ap-...`.
  </Tabs.Tab>
  <Tabs.Tab>
    ```bash
    curl -X POST "https://<your-ants-host>/api/public/otel/v1/traces" \
      -H "Authorization: Basic $AUTH" \
      -H "Content-Type: application/json" \
      -d '{
        "resourceSpans": [{
          "resource": { "attributes": [
            { "key": "service.name", "value": { "stringValue": "curl-test" } }
          ]},
          "scopeSpans": [{
            "scope": { "name": "manual" },
            "spans": [{
              "traceId": "5b8efff798038103d269b633813fc60c",
              "spanId": "eee19b7ec3c1b174",
              "name": "hello-ants",
              "kind": 1,
              "startTimeUnixNano": "1760000000000000000",
              "endTimeUnixNano": "1760000001000000000",
              "attributes": [
                { "key": "user.id", "value": { "stringValue": "user-123" } }
              ]
            }]
          }]
        }]
      }'
    ```
  </Tabs.Tab>
</Tabs>

Open **Tracing** in the sidebar to see the trace. Processing is asynchronous, so
allow a few seconds.

## How spans are mapped

Each OTel trace ID becomes an ANTS Platform trace. Each span becomes an
observation in that trace, nested by its parent span. The root span also sets
trace-level fields such as the trace name, input and output.

### Observation type

ANTS Platform stores three observation types: **span**, **generation** and
**event**. The type is chosen in this order:

1. `gen_ai.operation.name`:
   - `chat`, `text_completion`, `generate_content`, `embeddings` become a **generation**.
   - `invoke_agent`, `execute_tool`, `invoke_workflow`, `create_agent`, `retrieval` become a **span**.
2. Otherwise the span is a **generation** when `ants_platform.observation.type`
   (or `langfuse.observation.type`) is `generation`, when a model name is
   present, or when `openinference.span.kind` is `LLM`.
3. It is an **event** when `ants_platform.observation.type` is `event`.
4. Everything else is a **span**.

<Callout type="info">
  The SDKs let you mark observations as agent, tool, chain, retriever,
  evaluator, guardrail or embedding. These are stored as spans (or as
  generations when they carry a model). Use the observation name to tell them
  apart.
</Callout>

### OpenTelemetry GenAI semantic conventions


<div className="dv2-table-scroll"><table><thead><tr><th>Attribute</th><th>Maps to</th></tr></thead><tbody><tr><td><code>gen_ai.operation.name</code></td><td>Observation type (see above)</td></tr><tr><td><code>gen_ai.request.model</code>, <code>gen_ai.response.model</code></td><td>Model</td></tr><tr><td><code>gen_ai.request.temperature</code>, <code>top_p</code>, <code>top_k</code>, <code>max_tokens</code>, <code>frequency_penalty</code>, <code>presence_penalty</code>, <code>seed</code>, <code>stop_sequences</code>, <code>stream</code> (all under <code>gen_ai.request.</code>)</td><td>Model parameters</td></tr><tr><td><code>gen_ai.usage.input_tokens</code>, <code>gen_ai.usage.output_tokens</code></td><td>Token usage</td></tr><tr><td><code>gen_ai.usage.cache_read.input_tokens</code>, <code>gen_ai.usage.cache_creation.input_tokens</code>, <code>gen_ai.usage.reasoning.output_tokens</code></td><td>Detailed token usage</td></tr><tr><td><code>gen_ai.usage.prompt_tokens</code>, <code>gen_ai.usage.completion_tokens</code></td><td>Token usage (older names)</td></tr><tr><td><code>gen_ai.usage.cost</code></td><td>Cost</td></tr><tr><td><code>gen_ai.input.messages</code>, <code>gen_ai.system_instructions</code></td><td>Input</td></tr><tr><td><code>gen_ai.output.messages</code></td><td>Output</td></tr><tr><td><code>gen_ai.prompt.*</code>, <code>gen_ai.completion.*</code></td><td>Input and output (older indexed form)</td></tr><tr><td>Span events <code>gen_ai.user.message</code>, <code>gen_ai.system.message</code>, <code>gen_ai.assistant.message</code>, <code>gen_ai.tool.message</code>, <code>gen_ai.choice</code></td><td>Input and output (event form)</td></tr><tr><td><code>gen_ai.conversation.id</code></td><td>Session ID</td></tr></tbody></table></div>


Cost is calculated from token usage and your [model definitions](/docs/finops/models-pricing)
when the span does not carry a cost.

Attributes that are not mapped to a field are kept in the observation
metadata, together with the resource attributes and the instrumentation scope,
so you can still filter and search on them. Span events other than the GenAI
message events above (for example `exception` events) are not stored.

### Trace and observation attributes

Use these attributes to set fields that the GenAI conventions do not cover.
The `ants-platform.` (hyphen) spelling and the `langfuse.` prefix are also
accepted.


<div className="dv2-table-scroll"><table><thead><tr><th>Attribute</th><th>Sets</th></tr></thead><tbody><tr><td><code>user.id</code></td><td>Trace user (see [How users are resolved](#how-users-are-resolved))</td></tr><tr><td><code>session.id</code></td><td>Trace session</td></tr><tr><td><code>ants_platform.trace.name</code></td><td>Trace name</td></tr><tr><td><code>ants_platform.trace.tags</code></td><td>Trace tags (array, JSON array string or comma-separated string)</td></tr><tr><td><code>ants_platform.trace.metadata.&lt;key&gt;</code></td><td>One trace metadata entry per attribute</td></tr><tr><td><code>ants_platform.trace.input</code>, <code>ants_platform.trace.output</code></td><td>Trace input and output</td></tr><tr><td><code>langfuse.trace.public</code></td><td>Make the trace public (<code>true</code>)</td></tr><tr><td><code>ants_platform.observation.type</code></td><td><code>span</code>, <code>generation</code> or <code>event</code></td></tr><tr><td><code>ants_platform.observation.input</code>, <code>ants_platform.observation.output</code></td><td>Observation input and output</td></tr><tr><td><code>ants_platform.observation.metadata.&lt;key&gt;</code></td><td>One observation metadata entry per attribute</td></tr><tr><td><code>ants_platform.observation.level</code></td><td><code>DEBUG</code>, <code>DEFAULT</code>, <code>WARNING</code> or <code>ERROR</code></td></tr><tr><td><code>ants_platform.observation.status_message</code></td><td>Status message (otherwise the span status message)</td></tr><tr><td><code>ants_platform.observation.model.name</code></td><td>Model</td></tr><tr><td><code>ants_platform.observation.model.parameters</code></td><td>Model parameters, as a JSON-encoded string</td></tr><tr><td><code>ants_platform.observation.completion_start_time</code></td><td>Time to first token, as a JSON-encoded timestamp string</td></tr><tr><td><code>ants_platform.observation.prompt.name</code>, <code>ants_platform.observation.prompt.version</code></td><td>Linked prompt version</td></tr><tr><td><code>ants_platform.environment</code>, <code>deployment.environment.name</code>, <code>deployment.environment</code></td><td>Environment (default <code>default</code>), read from resource or span attributes</td></tr><tr><td><code>ants_platform.release</code></td><td>Release</td></tr><tr><td><code>ants_platform.version</code></td><td>Version (otherwise the resource <code>service.version</code>)</td></tr><tr><td><code>ants_platform.agent.name</code>, <code>ants_platform.agent.id</code>, <code>ants_platform.agent.display_name</code></td><td>Agent (see [How agents are resolved](#how-agents-are-resolved))</td></tr></tbody></table></div>


Span status `ERROR` sets the observation level to Error when no level
attribute is set.

<Callout type="warning">
  **Environment names must be lowercase.** The environment can only contain
  lowercase letters, digits, hyphens and underscores, up to 40 characters, and
  cannot start with `langfuse`. A value such as `Production` makes every event
  in the request fail validation. The endpoint still answers `200` and lists
  the failures in the `errors` array of the response.
</Callout>

`ants_platform.observation.usage_details` and `ants_platform.observation.cost_details`
are only read from spans produced by the ANTS Platform SDKs. From any other
exporter, send token counts with `gen_ai.usage.*` and cost with
`gen_ai.usage.cost`.

### Other instrumentation libraries

Spans from these libraries are mapped without extra configuration:


<div className="dv2-table-scroll"><table><thead><tr><th>Library</th><th>Attributes read</th></tr></thead><tbody><tr><td>OpenInference (Arize)</td><td><code>openinference.span.kind</code>, <code>input.value</code>, <code>output.value</code>, <code>llm.model_name</code>, <code>llm.invocation_parameters</code>, <code>llm.token_count.*</code></td></tr><tr><td>OpenLLMetry (Traceloop)</td><td><code>traceloop.entity.input</code>, <code>traceloop.entity.output</code>, plus GenAI attributes</td></tr><tr><td>Vercel AI SDK</td><td><code>ai.prompt.*</code>, <code>ai.response.*</code>, <code>ai.usage.tokens</code>, <code>ai.toolCall.*</code>, <code>ai.telemetry.metadata.*</code> (including <code>userId</code>, <code>sessionId</code>, <code>tags</code>)</td></tr><tr><td>Google Agent Development Kit</td><td><code>gcp.vertex.agent.llm_request</code>, <code>gcp.vertex.agent.llm_response</code></td></tr><tr><td>Pydantic Logfire</td><td><code>logfire.msg</code> (span name)</td></tr><tr><td>MLflow</td><td><code>mlflow.spanInputs</code>, <code>mlflow.spanOutputs</code></td></tr><tr><td>Langfuse SDK</td><td><code>langfuse.*</code> attributes</td></tr></tbody></table></div>


## Built-in agent parsers

Some agents export OTLP in their own formats. ANTS Platform detects them and
builds one trace per prompt or run, grouped into sessions.


<div className="dv2-table-scroll"><table><thead><tr><th>Agent</th><th>Detected by</th><th>Signals</th><th>Guide</th></tr></thead><tbody><tr><td><strong>Claude Code</strong></td><td><code>service.name</code> = <code>claude-code</code>, scope <code>com.anthropic.claude_code</code>, or <code>claude_code.*</code> event names</td><td>Traces (span tree) plus logs for request and response bodies</td><td>[Coding Agent Telemetry](/docs/integrations/coding-agent-telemetry)</td></tr><tr><td><strong>GitHub Copilot</strong> (CLI, VS Code chat, SDK)</td><td><code>service.name</code> containing <code>copilot</code>, scope names <code>github.copilot</code> or <code>copilot_chat</code>, or <code>gen_ai.agent.name</code> containing <code>copilot</code></td><td>Traces and <code>copilot_chat.*</code> logs</td><td>[Coding Agent Telemetry](/docs/integrations/coding-agent-telemetry)</td></tr><tr><td><strong>OpenAI Codex CLI</strong></td><td><code>service.name</code> such as <code>codex_cli_rs</code> or <code>codex</code>, scope names containing <code>codex</code>, or <code>codex.*</code> event names</td><td>Logs</td><td>[Coding Agent Telemetry](/docs/integrations/coding-agent-telemetry)</td></tr><tr><td><strong>Claude Cowork</strong></td><td><code>service.name</code> = <code>cowork</code></td><td>Logs, sent with protocol <code>http/json</code></td><td>[Claude Cowork](#claude-cowork)</td></tr><tr><td><strong>CrewAI</strong></td><td>Scope <code>crewai.enterprise</code>, <code>service.name</code> = <code>crewai</code>, or <code>crewai.*</code> span attributes</td><td>Traces</td><td>[CrewAI Native OTel](/docs/integrations/crewai/otel)</td></tr></tbody></table></div>


When an agent does not export a field, the trace shows a placeholder such as
`[<Field> not exported by <Tool>]` instead of an empty value.

<Callout type="warning">
  Detection is name based. If your own service has `copilot` in its
  `service.name` or agent name, or `codex` in its instrumentation scope name,
  its spans are parsed as GitHub Copilot or Codex data. Choose service and
  scope names that do not contain these words.
</Callout>

### Claude Cowork

In the Claude desktop app, an admin opens **Admin settings > Cowork >
Monitoring** and sets:


<div className="dv2-table-scroll"><table><thead><tr><th>Field</th><th>Value</th></tr></thead><tbody><tr><td>OTLP endpoint</td><td><code>https://&lt;your-ants-host&gt;/api/public/otel/v1/logs</code></td></tr><tr><td>OTLP protocol</td><td><code>http/json</code></td></tr><tr><td>OTLP headers</td><td><code>Authorization=Basic &lt;base64 of pk-ap-...:sk-ap-...&gt;</code></td></tr></tbody></table></div>


This needs a Claude Team or Enterprise plan. Settings load when a Cowork
session starts, so start a new session after saving. The same values are shown
in the in-app setup under the **Claude Cowork** tab.

## How users are resolved

For ordinary spans, the trace user is the first non-empty **span** attribute
in this list:

1. `langfuse.user.id`, then `user.id`
2. W3C baggage `baggage.user.id` or `baggage.langfuse.user.id`
3. `enduser.id`, then `enduser.pseudo.id`
4. `ants_platform.trace.metadata.user_id` (or the observation metadata
   equivalent), or `ai.telemetry.metadata.userId`

For ordinary spans, user and session are read from span attributes only. A
resource-level `user.id` is not used. Set `user.id` on the spans, or put it in
baggage so every child span carries it.

For Claude Code, GitHub Copilot, Codex, Claude Cowork and CrewAI, a
**resource** attribute `user.id`, `enduser.id` or `user.account_id` overrides
the identity the tool sends. A resource-level `user.email` is never used as an
override. See
[User attribution](/docs/integrations/coding-agent-telemetry#user-attribution).

Users appear under **Gauge AI > Users** (see [Users](/docs/observability/users)).

Sessions are resolved from `langfuse.session.id`, `session.id`,
`gen_ai.conversation.id`, or `session_id` in metadata.

## How agents are resolved

ANTS Platform keeps a list of agents per project. Agents power the
[AI Agent Center](/docs/aigovernance/command-center) and agent-level cost,
risk and compliance views.

- **Your own agents:** set `ants_platform.agent.name` (a stable identifier),
  `ants_platform.agent.id` and optionally `ants_platform.agent.display_name`.
  The agent ID must be 16 lowercase hex characters, and both the ID and the
  name must be present for the agent to be registered. The Python and JS/TS
  SDKs compute the ID for you from the agent name and project ID.
- **Coding agents:** Claude Code, GitHub Copilot, OpenAI Codex and Claude Cowork
  are registered automatically as one agent each per project.
- **CrewAI:** each CrewAI agent is registered from its `crewai.agent.role`.

`service.name` and `gen_ai.agent.name` do not create agents on their own.
Spans without the attributes above are still stored as traces, but they are
not linked to an agent.

## Choose which attributes are stored

Organizations can limit which OTel attributes are kept. Attributes that are not
selected are dropped **before** storage, on every OTel span, span event and log
record for every project in the organization.

<Steps>

### Open the setting

Go to **Organization Settings > Features > OTel Attribute Key Selection**. Only
roles with admin access to organization features can view or change it. Among
built-in roles these are Owner and Security & Compliance (see
[Members & Roles](/docs/administration/members-and-roles)).

### Turn on key-level filtering

Switch on **Key-level filtering**. When it is off, every attribute is stored.

### Pick the keys

Move keys between the available and selected lists, grouped by source (Core,
Generic GenAI, ANTS Platform SDK, Claude Code, Codex, Copilot, Claude Cowork,
CrewAI and others). Keys seen in your own traffic appear under **Discovered from
your traffic**. **Remove all free-text content** deselects every key that
carries prompt, response or instruction text in one step.

### Preview and save

Open **Sample output** to see what would be stored for each source. Save. The
selection applies to new data only.

</Steps>

Locked keys (IDs and routing attributes) are always kept, because the parser
needs them to build traces. Deselecting a content key removes the content but
never changes how a span is classified.

<Callout type="info">
  If the selection cannot be loaded at ingestion time, the request is refused
  with `503` rather than stored unfiltered. OTLP exporters retry, so the data
  arrives once the setting is available again.
</Callout>

To keep prompt text out of coding-agent data at the source, leave content
capture off in each tool. See
[Coding Agent Telemetry](/docs/integrations/coding-agent-telemetry).

## Responses

Responses are JSON, also for protobuf requests. The traces endpoint returns
`{"successes": [...], "errors": [...]}`. Check `errors` for events that failed
validation, because the status code is still `200`.


<div className="dv2-table-scroll"><table><thead><tr><th>Status</th><th>Meaning</th></tr></thead><tbody><tr><td><code>200</code></td><td>Accepted. Data is processed asynchronously. Validation failures, if any, are listed in <code>errors</code>.</td></tr><tr><td><code>200</code> with <code>partialSuccess</code></td><td>Logs only: some log records were not stored. <code>rejectedLogRecords</code> counts them and <code>errorMessage</code> says why (unsupported agent format, or a record that could not be processed).</td></tr><tr><td><code>400</code></td><td>Invalid content type, body that cannot be decompressed or parsed, or a logs request where no record was stored.</td></tr><tr><td><code>401</code></td><td>Missing or invalid credentials, an organization key, or a key type not allowed on this endpoint.</td></tr><tr><td><code>402</code></td><td>Ingestion is blocked for the organization, for example because credits are exhausted. See [Credits](/docs/core-concepts/credits).</td></tr><tr><td><code>403</code></td><td>The span was produced by an ANTS Platform SDK configured with a different project key than the one used to authenticate.</td></tr><tr><td><code>429</code></td><td>Rate limit exceeded. Retry with backoff.</td></tr><tr><td><code>503</code></td><td>The attribute selection could not be loaded. Retry.</td></tr></tbody></table></div>


A single span that cannot be parsed is dropped and the rest of the request is
stored.

## Limits and good practice

- **Rate limits** apply per organization on ANTS-hosted deployments and depend
  on your plan. Exceeding them returns `429`.
- **Credits:** OTel data is metered like any other ingestion. Each trace and
  each generation costs credits. See [Credits](/docs/core-concepts/credits).
- **Batching:** use a batch span processor and the exporter's default batch
  size. Enable gzip for large payloads.
- **Retention:** stored traces follow your organization's
  [data retention](/docs/administration/data-retention) period.
- **Compression:** only `gzip` is supported as a content encoding.
- **Sensitive data:** prompt and response text is stored as sent. Use
  attribute selection, masking in the SDK, or the tool's content settings to
  keep it out.

## Troubleshooting


<div className="dv2-table-scroll"><table><thead><tr><th>Symptom</th><th>Check</th></tr></thead><tbody><tr><td><code>401</code></td><td>The header must be <code>Authorization: Basic &lt;base64(pk:sk)&gt;</code> with a project key pair. With <code>OTEL_EXPORTER_OTLP_HEADERS</code>, write it as <code>Authorization=Basic &lt;value&gt;</code>.</td></tr><tr><td><code>400 Invalid content type</code></td><td>Use <code>http/protobuf</code> or <code>http/json</code>. gRPC is not supported.</td></tr><tr><td>Logs return <code>400</code> with "did not match a supported agent format"</td><td>Only Claude Code, Copilot, Codex and Claude Cowork logs are stored. Send other data as traces.</td></tr><tr><td>Nothing appears</td><td>Confirm the exporter flushes before your process exits (call <code>shutdown()</code> or <code>force_flush()</code>), and that you are viewing the right project and environment.</td></tr><tr><td><code>200</code> but nothing appears</td><td>Read the <code>errors</code> array in the response. A common cause is an environment name with uppercase letters.</td></tr><tr><td>Traces have no user</td><td>Set <code>user.id</code> on the spans or in baggage. A resource-level <code>user.id</code> only works for the coding-agent parsers.</td></tr></tbody></table></div>


## Related

<Cards>
  <Card title="Coding Agent Telemetry" href="/docs/integrations/coding-agent-telemetry" />
  <Card title="CrewAI Native OTel" href="/docs/integrations/crewai/otel" />
  <Card title="Sending Data In" href="/docs/observability/ingestion" />
  <Card title="Traces & Observations" href="/docs/observability/traces" />
</Cards>
© 2026 ANTS Platform, Inc.Docs v1.0 · Last updated June 2026