Raw OTLP / curl
The lowest-level way to send telemetry: POST an OTLP payload directly to the ingest endpoint. This is ideal for testing your setup, understanding the exact contract, or emitting spans from an environment without an OpenTelemetry SDK.
The endpoint
POST /otel/v1/traces (also accepts /v1/traces)
Content-Type: application/json (OTLP/JSON — protobuf is also accepted)A successful ingest returns 200 with { "partialSuccess": {} }.
A complete trace: agent → chat → tool
The payload below is one trace with three spans that share a traceId: an invoke_agent parent, a nested chat (with tokens), and a nested execute_tool. The child spans set parentSpanId to the agent span's id.
curl -X POST http://localhost:3010/otel/v1/traces \
-H "Content-Type: application/json" \
-d '{
"resourceSpans": [{
"resource": { "attributes": [
{ "key": "service.name", "value": { "stringValue": "support-agent" } }
]},
"scopeSpans": [{
"spans": [
{
"traceId": "5b8efff798038103d269b633813fc60c",
"spanId": "aaaa000000000001",
"name": "invoke_agent support-agent",
"startTimeUnixNano": "1710000000000000000",
"endTimeUnixNano": "1710000002000000000",
"attributes": [
{ "key": "gen_ai.operation.name", "value": { "stringValue": "invoke_agent" } },
{ "key": "gen_ai.agent.name", "value": { "stringValue": "support-agent" } },
{ "key": "gen_ai.conversation.id","value": { "stringValue": "conv-123" } }
],
"status": { "code": 1 }
},
{
"traceId": "5b8efff798038103d269b633813fc60c",
"spanId": "aaaa000000000002",
"parentSpanId": "aaaa000000000001",
"name": "chat",
"startTimeUnixNano": "1710000000100000000",
"endTimeUnixNano": "1710000001200000000",
"attributes": [
{ "key": "gen_ai.operation.name", "value": { "stringValue": "chat" } },
{ "key": "gen_ai.request.model", "value": { "stringValue": "gpt-4o" } },
{ "key": "gen_ai.usage.input_tokens", "value": { "intValue": "1200" } },
{ "key": "gen_ai.usage.output_tokens", "value": { "intValue": "340" } }
],
"status": { "code": 1 }
},
{
"traceId": "5b8efff798038103d269b633813fc60c",
"spanId": "aaaa000000000003",
"parentSpanId": "aaaa000000000001",
"name": "execute_tool get_incident",
"startTimeUnixNano": "1710000001300000000",
"endTimeUnixNano": "1710000001600000000",
"attributes": [
{ "key": "gen_ai.operation.name", "value": { "stringValue": "execute_tool" } },
{ "key": "gen_ai.tool.name", "value": { "stringValue": "get_incident" } },
{ "key": "gen_ai.tool.type", "value": { "stringValue": "mcp" } }
],
"status": { "code": 1 }
}
]
}]
}]
}'Field notes
| Field | Notes |
|---|---|
traceId | 32 hex chars (16 bytes). Shared by all spans in one trace. |
spanId | 16 hex chars (8 bytes). Unique per span. |
parentSpanId | The parent span's id — nests chat/tool under the agent. |
startTimeUnixNano / endTimeUnixNano | Nanoseconds since the Unix epoch (strings). Their difference is the span's duration. |
status.code | 1 = OK, 2 = ERROR. |
| token values | Use intValue as a string in OTLP/JSON. |
Use recent timestamps
The example uses fixed timestamps for reproducibility. In practice, use a current time (nanoseconds) so spans land inside the dashboard's default window — otherwise widen the date range in the UI to find them.
Simulating live traffic
To watch the Live tab move, loop the request in a small script, varying traceId / spanId and timestamps each iteration, and post every second or so. About 5–15% of spans with status.code = 2 gives a realistic error rate.
See the OTLP attributes reference for every attribute Boidra Platform recognizes.
