{"openapi":"3.0.0","paths":{"/api/v1/workspaces/{workspaceId}/signals":{"get":{"description":"Returns signals defined for the workspace, optionally filtered by active status.","operationId":"WorkspaceSignalsController_findAll","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{"format":"uuid","type":"string"}},{"name":"isActive","required":false,"in":"query","description":"When provided, only returns signals matching this active state.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"The list of signals for the workspace.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SignalResponse"}}}}},"401":{"description":"Missing or invalid API key (or session token in the web app)."},"403":{"description":"No access to this workspace: an API key needs the signals:read scope and must belong to it."},"404":{"description":"No workspace exists with this ID."}},"security":[{"ApiKeyAuth":[]}],"summary":"List workspace signals","tags":["Read data"]}},"/api/v1/signals/{id}":{"get":{"description":"Returns a single signal by its UUID.","operationId":"SignalsController_findOne","parameters":[{"name":"id","required":true,"in":"path","description":"Signal UUID","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"The requested signal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalResponse"}}}},"401":{"description":"Missing or invalid API key (or session token in the web app)."},"403":{"description":"No access to this signal's workspace: an API key needs the signals:read scope and must belong to it."},"404":{"description":"No signal exists with this ID."}},"security":[{"ApiKeyAuth":[]}],"summary":"Get a signal by ID","tags":["Read data"]}},"/api/v1/workspaces/{workspaceId}/telemetry/query":{"post":{"description":"Powers both the live-tail inspector (a rolling recent window, polled by the client) and historical browsing (an arbitrary date/time range) over the same partitioned Parquet files, optionally filtered by signal, tag values, or a free-text tag search.","operationId":"WorkspaceTelemetryController_query","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelemetryQueryRequestDto"}}}},"responses":{"200":{"description":"The matching events, most recent first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelemetryQueryResponse"}}}},"401":{"description":"Missing or invalid API key (or session token in the web app)."},"403":{"description":"No access to this workspace: an API key needs the signals:read scope and must belong to it."}},"security":[{"ApiKeyAuth":[]}],"summary":"Query raw telemetry events","tags":["Read data"]}},"/api/v1/ingest":{"post":{"description":"Accepts a single event payload or a batch (array, up to 500 items). Each payload is validated against the resolved workspace's registered signal and tag schema before being queued for asynchronous Parquet storage. Batches are all-or-nothing: if any payload fails validation, the entire request is rejected and nothing is queued.","operationId":"IngestionController_ingest","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestRequestDto"}}}},"responses":{"201":{"description":"The event(s) were validated and queued for storage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResponse"}}}},"400":{"description":"One or more payloads targeted an unknown/inactive signal or failed tag schema validation."},"401":{"description":"Missing, invalid, or expired API key."},"403":{"description":"The API key is missing the signals:emit scope or is not scoped to a workspace."}},"security":[{"ApiKeyAuth":[]}],"summary":"Ingest one or more telemetry events","tags":["Send data"]}},"/api/v1/workspaces/{workspaceId}/reports/query":{"post":{"description":"Runs a time-bucketed aggregation (with an optional dimension breakdown, capped at the top 10 values plus an 'Other' bucket) over the workspace's partitioned Parquet files for the given signal and date range.","operationId":"ReportsController_query","parameters":[{"name":"workspaceId","required":true,"in":"path","description":"Workspace UUID","schema":{"format":"uuid","type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportQueryRequestDto"}}}},"responses":{"200":{"description":"The aggregated report result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportQueryResponse"}}}},"400":{"description":"startDate is not before endDate, or groupByDimension is not a registered tag on this signal."},"401":{"description":"Missing or invalid API key (or session token in the web app)."},"403":{"description":"No access to this workspace: an API key needs the signals:read scope and must belong to it."}},"security":[{"ApiKeyAuth":[]}],"summary":"Run a historical report query","tags":["Read data"]}},"/api/v1/otlp/v1/metrics":{"post":{"description":"Accepts a standard OTLP ExportMetricsServiceRequest (JSON body only — Protobuf is not yet supported). Gauge and Sum data points whose metric name matches an enabled mapping rule for the resolved workspace are transformed into Datius signal events and queued the same way as manual ingestion; unmatched metrics and unsupported point types (Histogram/Summary/ExponentialHistogram) are silently ignored, per OTLP's own tolerance for partial acceptance.","operationId":"OtlpMetricsController_exportMetrics","parameters":[],"responses":{"200":{"description":"The payload was structurally valid and any matching data points were queued for storage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtlpExportMetricsResponse"}}}},"400":{"description":"The request body isn't a well-formed OTLP metrics export."},"401":{"description":"Missing, invalid, or expired API key."},"403":{"description":"The API key is missing the signals:emit scope or is not scoped to a workspace."}},"security":[{"ApiKeyAuth":[]}],"summary":"OTLP/HTTP+JSON metrics ingestion","tags":["Send data"]}},"/api/v1/otlp/v1/traces":{"post":{"description":"Accepts a standard OTLP ExportTraceServiceRequest (JSON body only — Protobuf is not yet supported). A span's duration (endTimeUnixNano − startTimeUnixNano, in milliseconds) becomes the signal value for any span whose name matches an enabled trace mapping rule for the resolved workspace, timestamped at the span's end time; unmatched span names are silently ignored, per OTLP's own tolerance for partial acceptance.","operationId":"OtlpMetricsController_exportTraces","parameters":[],"responses":{"200":{"description":"The payload was structurally valid and any matching spans were queued for storage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtlpExportTracesResponse"}}}},"400":{"description":"The request body isn't a well-formed OTLP traces export."},"401":{"description":"Missing, invalid, or expired API key."},"403":{"description":"The API key is missing the signals:emit scope or is not scoped to a workspace."}},"security":[{"ApiKeyAuth":[]}],"summary":"OTLP/HTTP+JSON traces ingestion","tags":["Send data"]}}},"info":{"title":"Datius API","description":"Send events to Datius and read your data back. Every request is authenticated with a workspace API key, created in the app under API Keys.","version":"1.0.0","contact":{}},"tags":[{"name":"Send data","description":"Send events to Datius from your code, the Node.js SDK or an OpenTelemetry collector. Needs an API key with the signals:emit scope."},{"name":"Read data","description":"Read your signals, aggregated report numbers and raw events. Needs an API key with the signals:read scope, belonging to the workspace in the URL."}],"servers":[],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"A signals:emit-scoped API key. Authorization: Bearer <key> is also accepted."}},"schemas":{"SignalResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"workspaceId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"key":{"type":"string"},"displayName":{"type":"string"},"description":{"type":"array","items":{"type":"string"}},"dataType":{"type":"string","enum":["counter","numeric","currency","percentage","duration"]},"durationUnit":{"type":"string","enum":["ms","s","min"]},"tagSchema":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":100},"type":{"type":"string","enum":["string","number","boolean"]},"isRequired":{"type":"boolean"}},"required":["key","type","isRequired"],"additionalProperties":false}},"isActive":{"type":"boolean"},"hasData":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","workspaceId","key","displayName","description","dataType","durationUnit","tagSchema","isActive","hasData","createdAt","updatedAt"]},"TelemetryQueryRequestDto":{"type":"object","properties":{"mode":{"type":"string","enum":["live","historical"]},"signalKey":{"type":"string","minLength":1},"startTime":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"endTime":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"tagFilters":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1},"value":{"type":"string","minLength":1}},"required":["key","value"]}},"search":{"type":"array","items":{"type":"string","minLength":1}},"limit":{"default":50,"type":"integer","minimum":1,"maximum":500},"offset":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["mode","startTime","endTime"],"additionalProperties":false},"TelemetryEventResponse":{"type":"object","properties":{"eventId":{"type":"string","description":"Derived at query time from the event's signal key and its own microsecond timestamp — not a stored identifier.","example":"evt_checkout.gross_revenue_1789055641150000_1"},"signalKey":{"type":"string","example":"checkout.gross_revenue"},"value":{"type":"number","example":149.99},"timestamp":{"type":"string","description":"A UTC ISO-8601 timestamp.","example":"2026-09-10T13:42:01.5Z"},"tags":{"type":"object","description":"The event's tag dimensions.","example":{"store_id":"store-101"}},"parquetPath":{"type":"string","description":"The object storage key this event was read from, from DuckDB's filename metadata column.","example":"s3://datius-local-bucket/orgs/.../workspaces/.../signals/checkout.gross_revenue/year=2026/month=09/day=10/part-1789055641150-....parquet"}},"required":["eventId","signalKey","value","timestamp","tags","parquetPath"]},"TelemetryQueryResponse":{"type":"object","properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/TelemetryEventResponse"}}},"required":["events"]},"IngestRequestDto":{"anyOf":[{"type":"object","properties":{"signalKey":{"type":"string","minLength":1},"value":{"type":"number"},"timestamp":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"tags":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}}},"required":["signalKey","value"],"additionalProperties":false},{"minItems":1,"maxItems":500,"type":"array","items":{"type":"object","properties":{"signalKey":{"type":"string","minLength":1},"value":{"type":"number"},"timestamp":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"tags":{"type":"object","additionalProperties":{"type":["string","number","boolean"]}}},"required":["signalKey","value"],"additionalProperties":false}}]},"IngestResponse":{"type":"object","properties":{"accepted":{"type":"number","description":"Number of events accepted and queued for storage.","example":1}},"required":["accepted"]},"ReportQueryRequestDto":{"type":"object","properties":{"signalKey":{"type":"string","minLength":1},"startDate":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"endDate":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"granularity":{"type":"string","enum":["1h","1d","1w","1m"]},"aggregation":{"type":"string","enum":["count","sum","avg","min","max","p95","p99"]},"groupByDimension":{"type":"string","minLength":1},"tagFilters":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1},"value":{"type":"string","minLength":1}},"required":["key","value"]}},"timezoneOffsetMinutes":{"type":"integer","minimum":-840,"maximum":840}},"required":["signalKey","startDate","endDate","granularity","aggregation"],"additionalProperties":false},"ReportSeriesPointResponse":{"type":"object","properties":{"bucket":{"type":"string","example":"2026-09-10T00:00:00.000Z"},"dimensionValue":{"type":"object","example":"gold","nullable":true},"metricValue":{"type":"object","example":412500,"nullable":true},"eventCount":{"type":"number","example":14210}},"required":["bucket","dimensionValue","metricValue","eventCount"]},"ReportSummaryResponse":{"type":"object","properties":{"totalEvents":{"type":"number","example":25780},"aggregatedValue":{"type":"object","example":708800.5,"nullable":true},"peakBucket":{"type":"object","example":"2026-09-02T00:00:00.000Z","nullable":true},"peakValue":{"type":"object","example":48210,"nullable":true},"averagePerBucket":{"type":"object","example":23626.7,"nullable":true}},"required":["totalEvents","aggregatedValue","peakBucket","peakValue","averagePerBucket"]},"ReportBreakdownRowResponse":{"type":"object","properties":{"dimensionValue":{"type":"string","example":"gold"},"totalEvents":{"type":"number","example":14210},"aggregatedValue":{"type":"object","example":412500,"nullable":true},"percentShare":{"type":"number","example":58.2},"peakBucket":{"type":"object","example":"2026-09-02T00:00:00.000Z","nullable":true}},"required":["dimensionValue","totalEvents","aggregatedValue","percentShare","peakBucket"]},"ReportQueryResponse":{"type":"object","properties":{"status":{"type":"string","enum":["OK","SIGNAL_PURGED","INVALID_FILTER"],"description":"SIGNAL_PURGED means the requested signalKey no longer resolves to a signal in this workspace; INVALID_FILTER means a tag filter names a tag this signal does not declare — in both cases every other field is omitted."},"signalKey":{"type":"string","description":"Echoed back only when status is SIGNAL_PURGED or INVALID_FILTER.","example":"order_completed"},"invalidTags":{"description":"Only with status INVALID_FILTER: the filtered tag keys this signal does not declare.","example":["store_id"],"type":"array","items":{"type":"string"}},"series":{"type":"array","items":{"$ref":"#/components/schemas/ReportSeriesPointResponse"}},"summary":{"$ref":"#/components/schemas/ReportSummaryResponse"},"breakdown":{"type":"array","items":{"$ref":"#/components/schemas/ReportBreakdownRowResponse"}}},"required":["status","signalKey","invalidTags","series","summary","breakdown"]},"OtlpExportMetricsResponse":{"type":"object","properties":{}},"OtlpExportTracesResponse":{"type":"object","properties":{}}}}}