Use the raw HTTP ingest API when you can't use the Python SDK — same schema, same auth, same rate limits.
Endpoint
POST {base_url}/v1/traces/ingestDefault base_url is https://api.staso.ai. Override for self-hosted installs.
Auth
Send your workspace or org API key in the X-API-Key header:
X-API-Key: ak_live_...
Content-Type: application/jsonWorkspace-scoped API keys require every trace in the batch to set workspace_slug.
Request body
{
"traces": [
{
"trace_id": "4b2f1e0a-7c36-4b9e-8c12-93a2b8d4e0f1",
"timestamp": "2026-04-15T12:00:00.000Z",
"environment": "prod",
"workspace_slug": "default",
"root_span_name": "agent-run",
"status": "ok",
"model": "claude-sonnet-4",
"input_tokens": 120,
"output_tokens": 345,
"total_tokens": 465,
"duration_ms": 1820.5,
"attributes": "{}",
"session_id": "conv-42",
"agent_id": "b0b2...",
"agent_name": "support-bot",
"user_id": "user-alice",
"spans": [
{
"span_id": "1f77...",
"parent_span_id": null,
"name": "support-bot",
"kind": "agent",
"status": "ok",
"timestamp": "2026-04-15T12:00:00.000Z",
"duration_ms": 1820.5,
"model": "",
"input_tokens": 0,
"output_tokens": 0,
"total_tokens": 0,
"input": "{\"query\":\"where is my order?\"}",
"output": "{\"result\":\"...\"}",
"attributes": "{\"tags\":[\"beta\"]}",
"error_message": null,
"session_id": "conv-42",
"agent_id": "b0b2...",
"agent_name": "support-bot",
"user_id": "user-alice"
}
]
}
]
}Trace fields (TraceIngest)
| Field | Type | Default | Notes |
|---|---|---|---|
trace_id | string | null | — | Any UUID. The server generates one if omitted. |
timestamp | string (ISO 8601) | — | Required. Must be within ±24h of server clock. |
environment | string | "default" | Logical env tag. |
workspace_slug | string | "" | Required for workspace-scoped API keys. |
duration_ms | float | 0 | Total trace duration. |
root_span_name | string | "" | Usually the agent name. |
status | string | "ok" | ok | error | timeout. |
model | string | "" | |
input_tokens | int | 0 | |
output_tokens | int | 0 | |
total_tokens | int | 0 | |
attributes | string | "{}" | Stringified JSON blob. |
session_id | string | "" | |
agent_id | string | "" | |
agent_name | string | "" | |
user_id | string | "" | |
spans | SpanIngest[] | [] | Ordered list of spans in the trace. |
Span fields (SpanIngest)
| Field | Type | Default | Notes |
|---|---|---|---|
span_id | string | null | — | Any UUID. Generated if omitted. |
parent_span_id | string | null | null | null for the root span. |
timestamp | string (ISO 8601) | — | Required. Must be within ±24h of server clock. |
duration_ms | float | 0 | |
name | string | — | Required. |
kind | string | "llm" | llm | tool | chain | retriever | agent | custom. |
status | string | "ok" | ok | error | timeout. |
model | string | "" | |
input_tokens | int | 0 | |
output_tokens | int | 0 | |
total_tokens | int | 0 | |
input | string | "" | Stringified JSON. |
output | string | "" | Stringified JSON. |
attributes | string | "{}" | Stringified JSON for span metadata. |
error_message | string | null | null | |
session_id | string | "" | |
agent_id | string | "" | |
agent_name | string | "" | |
user_id | string | "" |
A note on
session_id. The wire schema usessession_idas the field name that groups traces into a conversation. Everywhere else in Staso — the Python SDK (st.conversation,conversation_id), the dashboard, and the docs — this is called a conversation. Sendsession_idon the wire; read about it asconversation_id.
See Data Model for the well-known keys you should put inside attributes.
Constraints
- Between 1 and 500 traces per request.
- Every
timestampmust be within ±24 hours of the server clock. kindmust be one ofllm | tool | chain | retriever | agent | custom.statusmust be one ofok | error | timeout.input,output,attributesare strings holding JSON — not nested objects.
Responses
| Status | Body | Meaning |
|---|---|---|
200 | {"accepted": N, "rejected": N, "errors": [...]} | Batch accepted. errors lists per-trace validation failures. |
400 | — | Malformed body or missing workspace_slug for a workspace-scoped key. |
402 | — | Payment required — billing issue on the organization. |
403 | {"detail": "Organization has no active plan..."} or "workspace_slug ... not in API key scope" | No active plan, or the key can't reach the target workspace. |
429 | {"detail": "Rate limit exceeded ..."} | Rate-limited. Honor the Retry-After header. |
Rate limits
Every ingest call is counted against four fixed-window counters per organization:
| Window | Header on limit |
|---|---|
| Per minute | Retry-After: 60 |
| Per hour | — |
| Per day | — |
| Per month | Retry-After: 86400 |
Per-plan quotas are published on the pricing page. Batched requests are atomic: if any window would overflow, the entire batch is rejected and no counter is incremented.
curl example
curl -X POST https://api.staso.ai/v1/traces/ingest \
-H "X-API-Key: $STASO_API_KEY" \
-H "Content-Type: application/json" \
-d @trace.json