Skip to main content

API Reference

The TraceAgent server exposes an HTTP API. This page lists the endpoints and details the ones used to send data and to follow a run live. The examples use the default address http://127.0.0.1:8000.

Endpoints​

MethodPathWhat it does
POST/api/runsCreate a run (agent name, goal, optional metadata)
GET/api/runsList runs, newest first
GET/api/runs/{id}One run: status, counters, provider, model, token usage, flags
POST/api/runs/{id}/tool-resultsRecord tool executions (also counts tools and errors)
POST/api/runs/{id}/observationsRecord other events: model calls, commands, file changes, framework events
POST/api/runs/{id}/finishMark the run as completed with its final output
POST/api/runs/{id}/failMark the run as failed with an error code and message
GET/api/runs/{id}/eventsRaw events of the run, in order
GET/api/runs/{id}/events/streamLive events as Server-Sent Events
GET/api/runs/{id}/timelineEvents grouped by step (agent run, model call, tool), with durations
GET/api/runs/{id}/graph?view=execution|decisionsNodes and edges ready for a graph viewer
GET/api/runs/{id}/explanationPlain-language narrative, the decisions taken and flags such as retries or loops
GET/api/runs/{id}/analyticsTool metrics, tool graph and similar runs
GET/api/runs/compare?left_run_id=…&right_run_id=…Difference between two runs, with the likely root cause
GET/api/dashboard?period=24h|7d|30dTotals, trends, top tools and top errors
POST/v1/tracesOpenTelemetry receiver (OTLP/HTTP, protobuf or JSON, gzip). Root path, not under /api
GET/healthHealth check

Run States​

A run is in one of these states: running, completed or failed. A finished run is completed (not "finished").

Decisions (tool choice, retry, recovery) and flags are derived when a run ends or fails, not while it runs. A running run shows none yet.

POST /v1/traces​

OTLP/HTTP receiver for OpenTelemetry traces. See OpenTelemetry Ingestion for how to set it up.

Content types: application/x-protobuf (what exporters send by default) and application/json. Content-Encoding: gzip and deflate are accepted.

Response: 200 OK with an empty ExportTraceServiceResponse ({} in JSON). If some spans could not be understood, a protobuf response reports them in partial_success.rejected_spans.

Example (JSON)​

POST /v1/traces
Content-Type: application/json
{
"resourceSpans": [{
"resource": {"attributes": [{"key": "service.name", "value": {"stringValue": "weather-service"}}]},
"scopeSpans": [{
"scope": {"name": "pydantic-ai"},
"spans": [
{"traceId": "5b8efff798038103d269b633813fc60c", "spanId": "eee19b7ec3c1b174", "name": "invoke_agent weather",
"startTimeUnixNano": "1792764785544708352", "endTimeUnixNano": "1792764788544708352",
"attributes": [
{"key": "gen_ai.operation.name", "value": {"stringValue": "invoke_agent"}},
{"key": "gen_ai.agent.name", "value": {"stringValue": "weather"}},
{"key": "gen_ai.agent.call.id", "value": {"stringValue": "call-1"}},
{"key": "final_result", "value": {"stringValue": "It is sunny in Madrid."}}]},
{"traceId": "5b8efff798038103d269b633813fc60c", "spanId": "eee19b7ec3c1b175", "parentSpanId": "eee19b7ec3c1b174",
"name": "chat gpt-4o", "startTimeUnixNano": "1792764785644708352", "endTimeUnixNano": "1792764787044708352",
"attributes": [
{"key": "gen_ai.operation.name", "value": {"stringValue": "chat"}},
{"key": "gen_ai.provider.name", "value": {"stringValue": "openai"}},
{"key": "gen_ai.request.model", "value": {"stringValue": "gpt-4o"}},
{"key": "gen_ai.usage.input_tokens", "value": {"intValue": "120"}},
{"key": "gen_ai.usage.output_tokens", "value": {"intValue": "24"}},
{"key": "gen_ai.agent.call.id", "value": {"stringValue": "call-1"}}]}
]
}]
}]
}

The server creates the run 5b8efff7-9803-8103-d269-b633813fc60c (the trace id written as a UUID) with agent weather, status completed, provider openai, model gpt-4o, 120 prompt tokens and 24 completion tokens.

OTLP/JSON encoding

In OTLP/JSON, ids are hexadecimal strings and 64-bit integers are strings ("intValue": "120").

Errors​

CaseResponse
Body that is not OTLP400 {"detail":"Invalid OTLP protobuf body: ..."}
Unsupported content type400 {"detail":"Unsupported Content-Type 'text/plain'."}
Corrupt gzip or unknown Content-Encoding400 {"detail":"Invalid gzip body: ..."} / {"detail":"Unsupported Content-Encoding 'br'."}
Body over TRACE_AGENT_OTLP_MAX_BODY_BYTES413
Receiver turned off (TRACE_AGENT_OTLP_ENABLED=false)404 {"detail":"The OTLP receiver is disabled."}

Span attributes read​

Only spans whose gen_ai.operation.name is invoke_agent, chat or execute_tool are used. The span id becomes the step id, the parent span id becomes the parent step, and times and durations come from the span start and end. A span status of UNSET or OK is a success; only ERROR is a failure, with the exception class name as error code and the exception message as error message.

invoke_agent → chain.started, chain.completed / chain.failed

AttributeUsed for
gen_ai.agent.nameAgent name. Names the run when it is created
gen_ai.agent.call.idIdentifies the agent call. The run ends when no call is left open
gen_ai.conversation.idStored in the event metadata
gen_ai.aggregated_usage.input_tokens / output_tokensEvent metadata
final_resultFinal output of the run (summarized)

chat → llm.started, llm.completed / llm.failed

AttributeUsed for
gen_ai.provider.name (fallback gen_ai.system)Provider of the event and of the run
gen_ai.response.model (preferred) / gen_ai.request.modelModel of the event and of the run
gen_ai.usage.input_tokens / gen_ai.usage.output_tokensPrompt and completion tokens, added to the run totals
gen_ai.response.finish_reasonsEvent metadata
gen_ai.input.messagesGoal of the run (first user text, when the run is created)
gen_ai.output.messagesNames of the requested tools

execute_tool → tool.started, tool.succeeded / tool.failed

AttributeUsed for
gen_ai.tool.nameTool name
gen_ai.tool.call.idCall id
gen_ai.tool.call.argumentsArguments (long values are truncated)
gen_ai.tool.call.resultOutput summary

Attributes that configure TraceAgent

AttributeUsed for
service.name (resource)Fallback agent name
trace_agent.run_id (resource, or on every span)Attach the spans to an existing run

POST /api/runs/{id}/tool-results​

Reports tool executions of a run. Each result records both tool.started and tool.succeeded / tool.failed, and updates the run's counters.

{
"results": [{
"call_id": "c1",
"name": "weather",
"step_id": "tool-1",
"parent_step_id": "llm-1",
"status": "succeeded",
"args": {"city": "Madrid"},
"output_summary": "Sunny",
"duration_ms": 12
}]
}

parent_step_id is optional. It hangs the tool from its parent step in the timeline.

Report a tool once

Report a tool through this endpoint or through a tool_call observation, never both. Doing both duplicates the events and counts errors twice.

GET /api/runs/{id}/events/stream​

Server-Sent Events stream to follow a run live. The response is 200 with content-type: text/event-stream.

data: {"type": "connected", "run_id": "ad76681d-6a6a-4666-be32-377ae8544420"}

data: {"id": 29, "seq": 3, "type": "tool.started", "actor": "sdk", "summary": "Started weather.", "status": "running", "step_id": "tool-1", "tool_name": "weather", "timestamp": "2026-10-23T14:13:12.435113+00:00"}

data: {"id": 30, "seq": 4, "type": "tool.succeeded", "actor": "sdk", "summary": "Sunny", "status": "succeeded", "step_id": "tool-1", "tool_name": "weather", "timestamp": "2026-10-23T14:13:12.439109+00:00"}

data: {"id": 31, "seq": 5, "type": "run.completed", "actor": "proxy", "summary": "It is sunny in Madrid.", "status": "completed", "step_id": null, "tool_name": null, "timestamp": "2026-10-23T14:13:12.487165+00:00"}
  • The first message is {"type": "connected"}.
  • A {"type": "heartbeat"} message is sent after every 15 seconds without events.
  • Every other message is one recorded event, and its type is the event type itself (tool.succeeded, run.completed...).

GET /api/runs/{id}/timeline​

Returns the events of a run grouped by step. This is what the UI timeline is built from.

{
"run_id": "ce67fce7-8cbe-4df8-b3c0-deef2070afad",
"groups": [
{
"step_id": "run-08b5ddf944d3",
"label": "chain.completed",
"status": "succeeded",
"parent_step_id": null,
"event_count": 2,
"duration_ms": 21,
"events": [
{ "seq": 2, "type": "chain.started", "summary": "Pydantic AI run 'support-agent' started", "status": "running" },
{ "seq": 13, "type": "chain.completed", "summary": "Pydantic AI run 'support-agent' completed", "status": "succeeded", "duration_ms": 21 }
]
},
{
"step_id": "tool-a61c92ca8864",
"label": "lookup_order",
"status": "failed",
"tool_name": "lookup_order",
"parent_step_id": "run-08b5ddf944d3",
"event_count": 2,
"duration_ms": 0,
"events": [
{ "seq": 5, "type": "tool.started", "summary": "Started lookup_order." },
{ "seq": 6, "type": "tool.failed", "summary": "The orders service timed out, try again", "status": "failed" }
]
}
]
}

parent_step_id is what nests each step under its parent: model calls and tools hang from the agent step. Single events such as run.started and run.completed have no parent.

API labels vs UI labels

The UI shows readable names ("Agent run", "Model call"). In the API, label stays the technical event type (chain.completed, llm.completed) or the tool name.

GET /api/runs/compare​

Compares two runs: GET /api/runs/compare?left_run_id=<id>&right_run_id=<id>. The UI shows the same data in its Compare view, here with the token and duration difference between two runs:

Compare view showing the token and duration difference between two runs

The response has these top-level keys:

KeyContent
left_run, right_runThe overview of each run, in the same format as GET /api/runs/{id}
overview_diffDifferences between the two runs as signed numbers (right minus left): tool, command, error, retry and artifact counts, total tokens and duration in milliseconds. A positive number means the right run has more. duration_ms_delta is null if either run has not finished
divergenceThe first point where the two runs stop doing the same thing: the kind of step that differs, the event ids on each side and a one-line summary. It is null when the runs never diverge
aligned_timelineThe steps of both runs paired up in order (see the pair format below)
tool_diff, command_diff, file_diff, artifact_diff, decision_diffThe same pair format, limited to tools, commands, file changes, artifacts or decisions
root_causeThe most likely reason the runs differ: a label, a summary, the first event on each side that caused the difference, the events that support it (supporting_event_ids), the impact and a confidence of high, medium or low. If the runs don't diverge, the label is no_observable_root_cause
evidenceThe full events of both runs, keyed by event id under left and right, so every event id mentioned above can be looked up

Each pair in aligned_timeline and in the *_diff lists has a relation (shared, left_only, right_only or different), a kind, a summary, and a left and right side. Each side has its label, status, summary, event_ids, step_id, duration_ms and metadata.

💬 Comments