Documentation menu

Traces

Ingest distributed traces (native or OTLP), and list/inspect sampled traces and their spans.

Traces are ingested as spans grouped by trace_id. Only a subset of ingested traces are kept for querying — see the sampling note below. There is no separate /traces/service-map endpoint.

POST /api/traces

Ingest a batch of spans in ScryWatch’s native format (up to 50 per request).

Auth: API key (Bearer) · Content-Type: application/json

Request body

fieldtyperequireddescription
spansarrayyes1–50 span objects
spans[].trace_idstringyesGroups spans into one trace
spans[].span_idstringyesUnique span ID
spans[].namestringyesSpan name
spans[].servicestringyesService name
spans[].start_timenumberyesEpoch ms
spans[].end_timenumberyesEpoch ms
spans[].parent_span_idstringnoParent span ID; omit/null for a root span
spans[].statusstringnook | error. Defaults to ok
spans[].attributesobjectnoArbitrary JSON. http.method, http.url, http.status_code on the root span populate the trace’s HTTP fields
spans[].eventsarraynoArbitrary JSON, stored as-is
spans[].user_idstringnoUser identifier (read from the root span)
spans[].session_idstringnoSession identifier (read from the root span)
spans[].environmentstringnoe.g. production (read from the root span)

The root span is the one with no parent_span_id (falls back to the first span in the batch if every span has a parent). Its fields populate the parent trace row; duration_ms on both the trace and each span is computed server-side as end_time - start_time. A trace’s status is error if any of its spans has status: "error".

Response 202

{ "status": 202, "inserted": 4 }

Errors: 400 validation errors, shape { "status": 400, "error": "<message>" }:

  • "spans array is required and must not be empty"
  • "batch size must not exceed 50 spans"
  • "each span requires trace_id, span_id, name, service, start_time, end_time"

Common auth responses (401) apply — see Authentication. Unlike /api/ingest, this endpoint does not enforce usage limits or rate limiting, so 402 and 429 don’t apply despite being API-key authenticated.

POST /api/traces/otlp

Ingest spans in OpenTelemetry OTLP/JSON format (resourceSpansscopeSpansspans). The payload is normalized into the same span shape as POST /api/traces and passed through the same ingest path — same response and error shapes as above, same 401-only auth behavior.

Auth: API key (Bearer) · Content-Type: application/json

Request body: a standard OTLP JSON traces payload. service.name is read from each resourceSpans[].resource.attributes; span status.code === 2 maps to status: "error", anything else to "ok". startTimeUnixNano/endTimeUnixNano are converted to epoch ms.

Response 202 — same shape as POST /api/traces.

GET /api/traces

List sampled traces. Also available at /api/orgs/:orgSlug/projects/:projectSlug/traces.

Auth: API key (Bearer) or session cookie

Query params

paramtypedescription
servicestringExact match on service
statusstringExact match on status (ok | error)
environmentstringExact match on environment
fromnumberEpoch ms, matched against trace start_time
tonumberEpoch ms, matched against trace start_time
min_durationnumberMinimum duration_ms
max_durationnumberMaximum duration_ms
limitnumberMax results. Default 50
offsetnumberPagination offset. Default 0

Response 200

{
  "traces": [
    {
      "id": "trace_01hxyz",
      "project_id": "proj_01abc",
      "name": "POST /checkout",
      "service": "checkout-api",
      "start_time": 1720392000000,
      "end_time": 1720392000240,
      "duration_ms": 240,
      "status": "ok",
      "http_method": "POST",
      "http_url": "/checkout",
      "http_status_code": 200,
      "user_id": "user_42",
      "session_id": "sess_abc123",
      "environment": "production",
      "sampled": 1,
      "created_at": 1720392000300
    }
  ],
  "total": 87
}

Sampling note: ingested traces are written unsampled (sampled: 0) and are not returned by this endpoint until a background job evaluates them (roughly 60 seconds after ingest, to allow all of a trace’s spans to arrive). A trace is kept (sampled: 1) if any of its spans is status: "error", or if its duration is at least 2 seconds; otherwise it’s purged. limit/offset are not capped server-side.

GET /api/traces/:traceId

Fetch one trace and all of its spans. Also available at /api/orgs/:orgSlug/projects/:projectSlug/traces/:traceId.

Auth: API key (Bearer) or session cookie

Response 200

{
  "trace": { "id": "trace_01hxyz", "...": "same shape as one item in GET /api/traces" },
  "spans": [
    {
      "id": "span_01",
      "trace_id": "trace_01hxyz",
      "project_id": "proj_01abc",
      "parent_span_id": null,
      "name": "POST /checkout",
      "service": "checkout-api",
      "start_time": 1720392000000,
      "end_time": 1720392000240,
      "duration_ms": 240,
      "status": "ok",
      "attributes": "{\"http.method\":\"POST\"}",
      "events": null,
      "has_error": 0
    }
  ]
}

Spans are returned ordered by start_time ascending. attributes/events are stored and returned as JSON-encoded strings (not parsed objects). This lookup is not restricted to sampled traces — an unsampled or not-yet-evaluated trace can still be fetched directly by ID.

Errors: 404{ "error": "Trace not found" }


Common responses (401; 402 and 429 don’t apply) — see Authentication.