POST /api/v1/traces
Batch ingestion endpoint for traces and spans. The Python SDK calls this automatically — use it directly for custom integrations in any language.
Request
POST /api/v1/traces
Authorization: Bearer ts_...
Content-Type: application/json
{
"batch": [
{
"id": "evt-001",
"type": "trace-create",
"body": {
"id": "trace-abc",
"name": "support-agent",
"metadata": {"version": "2.1"},
"tags": ["production"]
},
"timestamp": "2026-03-14T10:00:00Z"
},
{
"id": "evt-002",
"type": "span-create",
"body": {
"trace_id": "trace-abc",
"parent_span_id": null,
"name": "retrieve-docs",
"type": "RETRIEVAL",
"start_time": "2026-03-14T10:00:00.100Z",
"end_time": "2026-03-14T10:00:00.350Z",
"input": {"query": "refund policy"},
"output": {"docs": ["Refunds are processed within 5 days..."]},
"model": null,
"usage": null,
"cost": null,
"status": "OK",
"metadata": {}
},
"timestamp": "2026-03-14T10:00:00.350Z"
}
]
}curl Example
curl -X POST https://api.2signal.dev/api/v1/traces \
-H "Authorization: Bearer ts_your_api_key" \
-H "Content-Type: application/json" \
-d @trace.jsonResponse (201)
{ "accepted": 2 }The accepted count reflects how many events were successfully queued for processing.
Event Types
| Type | Description | Required Fields |
|---|---|---|
trace-create | Create a new trace | id, name |
span-create | Create a span within a trace | trace_id, name, type |
Trace Fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique trace ID (UUID recommended) |
name | string | Yes | Name of the trace (e.g., agent name) |
metadata | object | No | Arbitrary key-value metadata |
tags | string[] | No | Tags for filtering |
Span Fields
| Field | Type | Required | Description |
|---|---|---|---|
trace_id | string | Yes | Parent trace ID |
parent_span_id | string | No | Parent span ID (for nesting) |
name | string | Yes | Span name |
type | string | Yes | AGENT, LLM, TOOL, RETRIEVAL, CHAIN, CUSTOM |
start_time | ISO 8601 | No | When the span started |
end_time | ISO 8601 | No | When the span ended |
input | any | No | Input data (JSON-serializable) |
output | any | No | Output data (JSON-serializable) |
model | string | No | Model name (for LLM spans) |
usage | object | No | Token counts |
cost | number | No | Cost in USD |
status | string | No | OK or ERROR (default: OK) |
error_message | string | No | Error description |
metadata | object | No | Arbitrary metadata |
Nesting Spans
Use parent_span_id to create hierarchical span trees:
{
"batch": [
{
"type": "span-create",
"body": {
"trace_id": "trace-1",
"parent_span_id": null,
"name": "agent",
"type": "AGENT"
}
},
{
"type": "span-create",
"body": {
"trace_id": "trace-1",
"parent_span_id": "span-agent",
"name": "search",
"type": "TOOL"
}
}
]
}Usage Tracking
For LLM spans, include usage to track tokens and cost:
"usage": {
"prompt_tokens": 150,
"completion_tokens": 50,
"total_tokens": 200
},
"cost": 0.003Batch Limits
- Max 1,000 events per batch
- Max 5 MB total payload
- Each event must have a valid
type(trace-createorspan-create) - The
trace-createevent should come before itsspan-createevents in the batch
Error Responses
| Status | When |
|---|---|
400 | Missing batch field, invalid event type, malformed body |
401 | Invalid or missing API key |
413 | Payload exceeds 5 MB or 1,000 events |
429 | Rate limit exceeded |
Processing
Events are processed asynchronously. After accepting a batch, the API persists raw events to S3 and queues them for database writes. This means:
- A
201response means the events are queued, not yet in the database - Traces typically appear in the dashboard within 1–3 seconds
- Evaluators run after the trace is written to the database