Staso Docs
Reference

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/ingest

Default 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/json

Workspace-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)

FieldTypeDefaultNotes
trace_idstring | nullAny UUID. The server generates one if omitted.
timestampstring (ISO 8601)Required. Must be within ±24h of server clock.
environmentstring"default"Logical env tag.
workspace_slugstring""Required for workspace-scoped API keys.
duration_msfloat0Total trace duration.
root_span_namestring""Usually the agent name.
statusstring"ok"ok | error | timeout.
modelstring""
input_tokensint0
output_tokensint0
total_tokensint0
attributesstring"{}"Stringified JSON blob.
session_idstring""
agent_idstring""
agent_namestring""
user_idstring""
spansSpanIngest[][]Ordered list of spans in the trace.

Span fields (SpanIngest)

FieldTypeDefaultNotes
span_idstring | nullAny UUID. Generated if omitted.
parent_span_idstring | nullnullnull for the root span.
timestampstring (ISO 8601)Required. Must be within ±24h of server clock.
duration_msfloat0
namestringRequired.
kindstring"llm"llm | tool | chain | retriever | agent | custom.
statusstring"ok"ok | error | timeout.
modelstring""
input_tokensint0
output_tokensint0
total_tokensint0
inputstring""Stringified JSON.
outputstring""Stringified JSON.
attributesstring"{}"Stringified JSON for span metadata.
error_messagestring | nullnull
session_idstring""
agent_idstring""
agent_namestring""
user_idstring""

A note on session_id. The wire schema uses session_id as 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. Send session_id on the wire; read about it as conversation_id.

See Data Model for the well-known keys you should put inside attributes.

Constraints

  • Between 1 and 500 traces per request.
  • Every timestamp must be within ±24 hours of the server clock.
  • kind must be one of llm | tool | chain | retriever | agent | custom.
  • status must be one of ok | error | timeout.
  • input, output, attributes are strings holding JSON — not nested objects.

Responses

StatusBodyMeaning
200{"accepted": N, "rejected": N, "errors": [...]}Batch accepted. errors lists per-trace validation failures.
400Malformed body or missing workspace_slug for a workspace-scoped key.
402Payment 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:

WindowHeader on limit
Per minuteRetry-After: 60
Per hour
Per day
Per monthRetry-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