Staso Docs
Reference

HTTP ingest API

Use the raw HTTP ingest API when you can't use the Python SDK.

Endpoint

POST {base_url}/v1/traces/ingest

Default base_url is https://api.staso.ai.

Auth

X-API-Key: ak_live_...
Content-Type: application/json

Workspace-scoped 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,
          "input": "{\"query\":\"where is my order?\"}",
          "output": "{\"result\":\"...\"}",
          "attributes": "{\"tags\":[\"beta\"]}",
          "session_id": "conv-42",
          "agent_id": "b0b2...",
          "agent_name": "support-bot",
          "user_id": "user-alice"
        }
      ]
    }
  ]
}

Trace fields

FieldTypeDefaultNotes
trace_idstring | nullAny UUID. Server generates one if omitted.
timestampstring (ISO 8601)Required. Within ±24h of server clock.
environmentstring"default"
workspace_slugstring""Required for workspace-scoped keys.
duration_msfloat0
root_span_namestring""Usually the agent name.
statusstring"ok"ok | error | timeout.
modelstring""
input_tokens / output_tokens / total_tokensint0
attributesstring"{}"Stringified JSON.
session_idstring""
agent_id / agent_name / user_idstring""
spansSpanIngest[][]Ordered list.

Span fields

FieldTypeDefaultNotes
span_idstring | nullGenerated if omitted.
parent_span_idstring | nullnullnull for root.
timestampstring (ISO 8601)Required. ±24h of server clock.
duration_msfloat0
namestringRequired.
kindstring"llm"llm | tool | chain | retriever | agent | custom.
statusstring"ok"ok | error | timeout.
modelstring""
input_tokens / output_tokens / total_tokensint0
input / outputstring""Stringified JSON.
attributesstring"{}"Stringified JSON for span metadata.
error_messagestring | nullnull
session_id / agent_id / agent_name / user_idstring""

session_id vs conversation_id. The wire schema uses session_id to group traces into a conversation. Everywhere else — Python SDK, dashboard, docs — this is called a conversation. Send session_id on the wire; read about it as conversation_id.

Constraints

  • 1–500 traces per request.
  • Every timestamp within ±24h of server clock.
  • kind: llm | tool | chain | retriever | agent | custom.
  • status: 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 failures.
400Malformed body or missing workspace_slug for a workspace-scoped key.
402Payment required.
403{"detail": "Organization has no active plan..."}No active plan, or workspace not in key scope.
429{"detail": "Rate limit exceeded ..."}Honor Retry-After.

Rate limits

Counted per organization across four windows:

WindowHeader on limit
Per minuteRetry-After: 60
Per hour
Per day
Per monthRetry-After: 86400

Batched requests are atomic — if any window would overflow, the entire batch is rejected and no counter is incremented. See pricing for current limits.

curl

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