OTLP endpoint
Reference for the OTLP/HTTP endpoint that receives traces, its responses, limits and the attributes it reads.
Endpoint
POST https://api.divergentlabs.xyz/api/otlp/v1/traces
The endpoint receives OTLP/HTTP trace exports. It does not receive gRPC, metrics or logs. Set the base URL, and your exporter adds /v1/traces:
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.divergentlabs.xyz/api/otlp
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20$DIVERGENT_API_KEY"
If you also set OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, it must be the full URL, ending in /v1/traces.
Request
| Header | Values |
|---|---|
Authorization |
Bearer <key>. The key needs the traces:write scope. |
Content-Type |
application/x-protobuf or application/json (OTLP/JSON, with hex trace and span ids). |
Content-Encoding |
gzip, or none. |
The body is an ExportTraceServiceRequest, at most 4 MiB as sent and 16 MiB uncompressed. A request with no spans succeeds.
Responses
Every response is encoded like the request: protobuf for a protobuf request, JSON for a JSON request. Errors are a google.rpc.Status whose message is the error code, a colon and what to do.
| Status | Code | Meaning | Exporter retries |
|---|---|---|---|
200 |
Every span was stored, or had been stored before. | No | |
200 |
Some spans were refused. partial_success has their count and the reason. See Refused spans. |
No | |
400 |
invalid_otlp |
The body is not an ExportTraceServiceRequest in its content type and encoding. |
No |
401 |
unauthorized |
The key is missing or invalid. | No |
403 |
tracing_not_enabled |
The org is not in the tracing beta. Request access on the Tracing page in the dashboard. | No |
403 |
insufficient_scope |
The key lacks traces:write. Run div login, or add the scope to the key under Settings. |
No |
413 |
payload_too_large |
Over 4 MiB as sent or 16 MiB uncompressed. Set OTEL_BSP_MAX_EXPORT_BATCH_SIZE lower and OTEL_EXPORTER_OTLP_COMPRESSION=gzip. |
No |
415 |
unsupported_media_type |
A content type other than protobuf or JSON, or an encoding other than gzip. | No |
503 |
tracing_unavailable |
Nothing from the request was stored. Sent with Retry-After. |
Yes |
Sending a span again is safe. A span with a trace id and span id that tracing has already stored succeeds and is not stored or counted twice.
Refused spans
A refused span is counted in partial_success.rejected_spans, and partial_success.error_message says why, for example:
Rejected 312 spans: this org has kept 1,000,000 spans in October 2026, the most tracing keeps during the beta. Spans are accepted again on 2026-11-01.
| Reason | Message |
|---|---|
| Bad id | <n> had a trace or span id that is missing, all zeros or the wrong length. |
| Bad time | <n> had a start or end time past 2262. |
| Trace full | <n> would have taken a trace past 10,000 spans. |
| Deleted trace | <n> belong to traces deleted in the last 24 hours. |
| Monthly spans | this org has kept 1,000,000 spans in <month>, the most tracing keeps during the beta. Spans are accepted again on <date>. |
| Monthly data | this org has kept 5 GiB of spans in <month>, the most tracing keeps during the beta. Spans are accepted again on <date>. |
Within one request, spans are accepted in the order they were sent until a limit is reached.
Limits
| Limit | Value |
|---|---|
| Request body | 4 MiB as sent, 16 MiB uncompressed |
| Spans per org per UTC month | 1,000,000 |
| Stored span data per org per UTC month | 5 GiB |
| Spans per trace | 10,000 |
| Span name | 256 bytes |
| Input, output | 128 KiB each, encoded as JSON. The longest strings are cut first, and input_truncated or output_truncated is set. |
| Other string values | 8 KiB |
| Attributes per span or event | 128 |
| Events per span | 128 |
| Links per span | 32 |
| Resource attributes | 4 KiB |
| Span, in all | 384 KiB |
| Retention | 30 days from when tracing first received a span of the trace. Spans that arrive later are deleted with the trace. |
A longer string is cut at a character boundary and ends …[truncated <n> bytes].
Attributes tracing reads
Tracing reads these attributes into a span’s kind, model, tokens, input, output, session and user. The first attribute in each list that a span has is used. Attributes that become input or output are removed from the span’s attributes, so they are stored once. Other attributes are stored as they were sent.
Kind
| Kind | Read from |
|---|---|
generation |
openinference.span.kind LLM or EMBEDDING; gen_ai.operation.name chat, text_completion, generate_content or embeddings; AI SDK ai.operationId ending .doGenerate, .doStream or .doEmbed; llm.request.type chat, completion or embedding |
tool |
openinference.span.kind TOOL; gen_ai.operation.name execute_tool; ai.operationId ai.toolCall; traceloop.span.kind tool |
agent |
openinference.span.kind AGENT or CHAIN; gen_ai.operation.name invoke_agent or create_agent; any other ai.operationId; traceloop.span.kind agent or workflow |
retrieval |
openinference.span.kind RETRIEVER or RERANKER |
span |
anything else |
A trace’s tokens are the sum over its generation spans. Agent spans repeat the usage of the calls inside them and are not added again.
Model and tokens
| Field | Read from |
|---|---|
| Model | gen_ai.response.model, gen_ai.request.model, llm.model_name, ai.response.model, ai.model.id |
| Input tokens | gen_ai.usage.input_tokens, gen_ai.usage.prompt_tokens, llm.token_count.prompt, ai.usage.inputTokens, ai.usage.promptTokens |
| Output tokens | gen_ai.usage.output_tokens, gen_ai.usage.completion_tokens, llm.token_count.completion, ai.usage.outputTokens, ai.usage.completionTokens |
Input and output
| Convention | Attributes |
|---|---|
| GenAI | gen_ai.input.messages, gen_ai.output.messages, gen_ai.system_instructions, or the same on a gen_ai.client.inference.operation.details event |
| GenAI, indexed | gen_ai.prompt.<n>.role, gen_ai.prompt.<n>.content, gen_ai.completion.<n>.*, including tool_calls |
| GenAI tool calls | gen_ai.tool.call.arguments, gen_ai.tool.call.result |
| AI SDK | ai.prompt.messages, ai.prompt, ai.response.text, ai.response.toolCalls, ai.toolCall.args, ai.toolCall.result |
| OpenInference | llm.input_messages.<n>.message.*, llm.output_messages.<n>.message.*, input.value, output.value |
| Traceloop | traceloop.entity.input, traceloop.entity.output |
Messages are stored as {"messages": [{"role": ..., "parts": [...]}]}, with text, tool_call and tool_call_response parts. Other input and output is stored as {"value": ...}, parsed as JSON when it is JSON.
Session, user, service and environment
| Field | Read from |
|---|---|
| Session | session.id, gen_ai.conversation.id, langfuse.session.id, ai.telemetry.metadata.sessionId, ai.settings.context.sessionId, traceloop.association.properties.session_id |
| User | user.id, enduser.id, langfuse.user.id, ai.telemetry.metadata.userId, ai.settings.context.userId, traceloop.association.properties.user_id |
| Service | resource service.name |
| Environment | resource deployment.environment.name, deployment.environment |
A trace takes each of these from its root span, or from its earliest span that has one.
Removed before storing
Tracing removes credentials from every span before storing it. See What tracing stores. A span’s redacted_count is the number of values removed.