curl -X POST http://localhost:4700/api/v1/ingest/traces \
-H "x-api-key: agtx_local_0f3c9a17d2b84e6a5c01b9f4e7d8a2c6431b5f97a0e2d4c8" \
-H "Content-Type: application/json" \
-d '{
"name": "customer-support-agent",
"framework": "langchain",
"model": "gpt-4o",
"input": "How do I reset my password?",
"output": "Click Forgot Password on the login page.",
"latencyMs": 1340,
"inputTokens": 150,
"outputTokens": 45,
"toolCalls": [
{ "name": "search_kb", "input": "password reset", "output": "...", "success": true }
],
"sessionId": "user_session_abc123"
}'
from agentx import AgentX
client = AgentX.from_env()
@client.tracer.trace("customer-support-agent", framework="langchain", model="gpt-4o")
def handle(query: str) -> str:
return chain.invoke(query)
handle("How do I reset my password?")
from agentx import AgentX
client = AgentX.from_env()
# sync=True blocks until the trace is ingested, so trace_id is available as soon as the
# `with` block exits. The decorator form above is fire-and-forget and never returns an id.
with client.tracer.trace(
"customer-support-agent", framework="langchain", model="gpt-4o", sync=True
) as span:
span.output = chain.invoke("How do I reset my password?")
print(span.trace_id) # "mJ3vQ8pTr2LqYw6bZk9Xd"
{
"trace_id": "mJ3vQ8pTr2LqYw6bZk9Xd",
"traceId": "mJ3vQ8pTr2LqYw6bZk9Xd",
"deduped": false
}
{
"error": "Invalid trace payload",
"details": {
"fieldErrors": { "name": ["Required"] },
"formErrors": []
}
}
{
"error": "Ingest queue is full - retry with backoff (Retry-After: 1s)."
}
{
"error": "Trace storage is unavailable - the span was not stored; retry."
}
Tracing
Submit Trace
Ingest a single span or agent run into the engine’s trace store
POST
/
api
/
v1
/
ingest
/
traces
curl -X POST http://localhost:4700/api/v1/ingest/traces \
-H "x-api-key: agtx_local_0f3c9a17d2b84e6a5c01b9f4e7d8a2c6431b5f97a0e2d4c8" \
-H "Content-Type: application/json" \
-d '{
"name": "customer-support-agent",
"framework": "langchain",
"model": "gpt-4o",
"input": "How do I reset my password?",
"output": "Click Forgot Password on the login page.",
"latencyMs": 1340,
"inputTokens": 150,
"outputTokens": 45,
"toolCalls": [
{ "name": "search_kb", "input": "password reset", "output": "...", "success": true }
],
"sessionId": "user_session_abc123"
}'
from agentx import AgentX
client = AgentX.from_env()
@client.tracer.trace("customer-support-agent", framework="langchain", model="gpt-4o")
def handle(query: str) -> str:
return chain.invoke(query)
handle("How do I reset my password?")
from agentx import AgentX
client = AgentX.from_env()
# sync=True blocks until the trace is ingested, so trace_id is available as soon as the
# `with` block exits. The decorator form above is fire-and-forget and never returns an id.
with client.tracer.trace(
"customer-support-agent", framework="langchain", model="gpt-4o", sync=True
) as span:
span.output = chain.invoke("How do I reset my password?")
print(span.trace_id) # "mJ3vQ8pTr2LqYw6bZk9Xd"
{
"trace_id": "mJ3vQ8pTr2LqYw6bZk9Xd",
"traceId": "mJ3vQ8pTr2LqYw6bZk9Xd",
"deduped": false
}
{
"error": "Invalid trace payload",
"details": {
"fieldErrors": { "name": ["Required"] },
"formErrors": []
}
}
{
"error": "Ingest queue is full - retry with backoff (Retry-After: 1s)."
}
{
"error": "Trace storage is unavailable - the span was not stored; retry."
}
Records one span (an agent run, LLM call, tool call, or retrieval step) in the project selected
by your API key. This is the endpoint the Python SDK’s
Retries are safe: send a
client.tracer.trace(...) posts to, and
the one to call directly from any language the SDK doesn’t cover. The response returns the
stored trace’s id, which you can pass to
Evaluate Trace or link to an evaluation result.
Casing: camelCase keys (
latencyMs, sessionId, …) are the canonical wire form, and
the snake_case spellings (latency_ms, session_id, …) are accepted as legacy aliases on
this endpoint only, for existing SDKs. If a request carries both spellings of a key, the
snake_case value wins. New integrations should send camelCase.Authentication
string
required
Project API key. The key alone selects the project; no project id is sent.
Body
string
required
Agent or operation label. For a root span, the name resolves to a registered agent
(auto-registering one on first use) unless
agentId says otherwise.any
Agent input: string, JSON object, array, etc.
any
Agent output.
string
Error message if the run failed.
number
End-to-end wall-clock latency in milliseconds. Legacy alias:
latency_ms.string
Platform label - an SDK integration literal (
"langchain", "crewai", "openai-agents",
"anthropic", …), an OTel scope name, or any custom string for platforms AgentX has no
integration for. Trimmed, folded to lowercase, and capped at 64 characters on ingest, so
"LangChain" and "langchain" chart and filter as one platform. Unlabeled traces bucket as
“other” in the dashboard. See Platform detection.string
LLM model used, e.g.
"gpt-4o". Must match a pricing-catalog model id for cost estimation.array
Tool calls made during the run, as an array of objects (any keys;
name, input, output,
and success are what the monitor and judges read). Legacy alias: tool_calls.string
What kind of step this span is:
"agent", "llm", "tool", "retrieval", "chain",
"embedding", "reranker", "guardrail", "evaluator", "prompt", or "memory". Other vocabularies
(OpenInference, OTel GenAI, Langfuse, MLflow) are folded onto these on ingest; unrecognized
values are stored as null. Legacy alias: span_kind. See
Span kinds.string
Where the trace came from.
"eval-run" marks traffic produced inside an offline evaluation
run; monitoring and the Live Traces production filter exclude it. Unknown values are stored
as null.string
Groups spans from the same user session, conversation thread, or OTel trace. Legacy alias:
session_id.object
Arbitrary key-value metadata, stored on the trace and passed to online evaluators.
object
Arbitrary performance breakdown object, stored verbatim. Legacy alias:
performance_summary.number
Input token count. Legacy alias:
input_tokens.number
Output token count. Legacy alias:
output_tokens.number
Prompt-cache read tokens - a subset of
inputTokens, not additional tokens. Priced at the
model’s cache-read rate when one is configured. Legacy alias: cache_read_tokens.number
Prompt-cache write tokens - also a subset of
inputTokens. Legacy alias:
cache_write_tokens.string
Stable client-supplied span identity. Makes ingest idempotent: replaying the same
spanId
stores nothing new, triggers no duplicate monitoring or judging, and returns the original
trace id with deduped: true. Legacy alias: span_id.string
The parent span’s
spanId, for a real span hierarchy (OTel ingestion sends this
unconditionally; the Python SDK sends it with span_tree=True). Child spans skip agent
resolution, the daily trace quota, and the monitoring pipeline. Legacy alias:
parent_span_id.string
Span start time as a Unix-epoch nanosecond count, sent as a string (the value exceeds
safe-integer precision as a JSON number). When present it also becomes the trace’s
createdAt, so historical imports land in the right time window. Legacy alias:
started_at_unix_nano.string
Explicit agent to attribute this root span to - a registered agent id from
Register Agent, or an agent name. Omit to resolve from
name, identical to the pre-registry behavior. Legacy alias: agent_id.boolean
false skips every ingest-time check (pattern detection, online and custom evaluators,
topics) for this trace - eval harnesses send this. true opts eval-run traffic back into
monitoring, and combined with patternIds restricts detection to those patterns. Omitted:
the default sweep runs on every root span.array
Custom pattern ids to restrict detection to. Only meaningful alongside
monitor: true.
Legacy alias: pattern_ids.string
Accepted for wire compatibility with the hosted SaaS payload shape; ignored by the self-host
engine (the API key already selects the project).
Payload caps:
input, output, toolCalls, and metadata are each capped at 100,000
characters serialized (AGENTX_INGEST_MAX_FIELD_CHARS). Oversized fields are stored
truncated with an explicit agentx.truncated marker, never rejected.Response
Returns200 OK once the span is durably accepted (monitoring and evaluator checks run in the
background after the response).
string
ID of the stored trace. Canonical key.
string
The same id under its legacy snake_case key, kept for existing SDKs. Both keys are always
present and always equal.
boolean
true when a span with the same spanId was already stored; the returned id is the
original trace’s, and no monitoring or judging re-ran.Errors
| Status | Body | Meaning |
|---|---|---|
422 | { "error": "Invalid trace payload", "details": ... } | Schema validation failed (e.g. missing name) |
429 | { "error": "Ingest queue is full - retry with backoff (Retry-After: 1s)." } | Backpressure; retry after the Retry-After header (1s). Nothing was stored |
429 | { "error": "Daily trace quota reached (...)" } | AGENTX_QUOTA_TRACES_PER_DAY hit; resets at midnight. Child spans are exempt |
503 | { "error": "Trace storage is unavailable - the span was not stored; retry." } | Storage flush failed; retry after the Retry-After header (2s) |
spanId and a redelivered span dedupes instead of duplicating.
curl -X POST http://localhost:4700/api/v1/ingest/traces \
-H "x-api-key: agtx_local_0f3c9a17d2b84e6a5c01b9f4e7d8a2c6431b5f97a0e2d4c8" \
-H "Content-Type: application/json" \
-d '{
"name": "customer-support-agent",
"framework": "langchain",
"model": "gpt-4o",
"input": "How do I reset my password?",
"output": "Click Forgot Password on the login page.",
"latencyMs": 1340,
"inputTokens": 150,
"outputTokens": 45,
"toolCalls": [
{ "name": "search_kb", "input": "password reset", "output": "...", "success": true }
],
"sessionId": "user_session_abc123"
}'
from agentx import AgentX
client = AgentX.from_env()
@client.tracer.trace("customer-support-agent", framework="langchain", model="gpt-4o")
def handle(query: str) -> str:
return chain.invoke(query)
handle("How do I reset my password?")
from agentx import AgentX
client = AgentX.from_env()
# sync=True blocks until the trace is ingested, so trace_id is available as soon as the
# `with` block exits. The decorator form above is fire-and-forget and never returns an id.
with client.tracer.trace(
"customer-support-agent", framework="langchain", model="gpt-4o", sync=True
) as span:
span.output = chain.invoke("How do I reset my password?")
print(span.trace_id) # "mJ3vQ8pTr2LqYw6bZk9Xd"
{
"trace_id": "mJ3vQ8pTr2LqYw6bZk9Xd",
"traceId": "mJ3vQ8pTr2LqYw6bZk9Xd",
"deduped": false
}
{
"error": "Invalid trace payload",
"details": {
"fieldErrors": { "name": ["Required"] },
"formErrors": []
}
}
{
"error": "Ingest queue is full - retry with backoff (Retry-After: 1s)."
}
{
"error": "Trace storage is unavailable - the span was not stored; retry."
}

