openapi: 3.0.0
paths:
  /api/v1/workspaces/{workspaceId}/business-context:
    get:
      description: >-
        What the workspace's owners wrote about the business for AI assistants
        (Settings > Workspace > General): background such as what the stores are
        and what a good day looks like. Null when none is set. Treat it as
        background, not instructions.
      operationId: WorkspaceBusinessContextController_find
      parameters:
        - name: workspaceId
          required: true
          in: path
          description: Workspace UUID
          schema:
            format: uuid
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceBusinessContextResponse'
        '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: Get the workspace's business context
      tags:
        - Read data
  /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/api-key:
    get:
      description: >-
        Returns the API key making the request: its name, scopes and expiry, and
        the workspace and organization it belongs to. Works with any valid key,
        whatever its scopes. Use it to find the workspace ID for the read API.
      operationId: ApiKeyIdentityController_findSelf
      parameters: []
      responses:
        '200':
          description: The key making the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyIdentityResponse'
        '401':
          description: The API key is missing, invalid or expired.
      security:
        - ApiKeyAuth: []
      summary: Describe this API key
      tags:
        - Your API key
  /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.
        '402':
          description: >-
            The organization has used its plan's monthly event allowance.
            Nothing was stored. Not worth retrying until the plan changes or the
            month turns.
        '403':
          description: >-
            The API key is missing the signals:emit scope or is not scoped to a
            workspace.
        '413':
          description: The request body is over the size limit.
        '429':
          description: >-
            Too many requests for this API key. Nothing was stored; retry after
            Retry-After.
          headers:
            Retry-After:
              description: Seconds to wait before sending the same request again.
              schema:
                type: integer
        '503':
          description: >-
            Datius is catching up or briefly unavailable. Nothing was stored;
            send the same request again after Retry-After.
          headers:
            Retry-After:
              description: Seconds to wait before sending the same request again.
              schema:
                type: integer
      security:
        - ApiKeyAuth: []
      summary: Ingest one or more telemetry events
      tags:
        - Send data
  /api/v1/workspaces/{workspaceId}/derived-signals:
    get:
      description: >-
        Saved calculations across the workspace’s signals, each with its formula
        in words and the tags it can be grouped by.
      operationId: DerivedSignalsController_list
      parameters:
        - name: workspaceId
          required: true
          in: path
          description: Workspace UUID
          schema:
            format: uuid
            type: string
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DerivedSignalResponse'
        '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), or derived signals aren't on this
            organization's plan.
      security:
        - ApiKeyAuth: []
      summary: List derived signals
      tags:
        - Read 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/workspaces/{workspaceId}/insights/explain:
    post:
      description: >-
        Compares a signal over a window with a baseline (by default the same
        hours averaged over the previous 4 weeks, falling back to last week,
        then yesterday) and ranks, per tag, the values that account for the
        change. Counters and currency are compared as totals; numbers,
        percentages and durations as averages, split into performance (a value
        itself got higher or lower) and mix (there's more or less of it).
        Identifier tags are skipped. The headline sums it up in a sentence.
      operationId: InsightsController_explain
      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/ExplainChangeRequestDto'
      responses:
        '200':
          description: >-
            OK with the result; NOT_ENOUGH_DATA when there is too little data to
            compare; SIGNAL_PURGED or INVALID_FILTER as in reports.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExplainChangeResponseDto'
        '400':
          description: >-
            from is not before to, a window is longer than 400 days, or the
            chosen baseline is shorter than the window.
        '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), or the plan does not include insights.
      security:
        - ApiKeyAuth: []
      summary: Explain what changed
      tags:
        - Read data
  /api/v1/workspaces/{workspaceId}/tags/{key}/values:
    get:
      description: >-
        Distinct values seen for this tag over the last 7 days, optionally
        narrowed by a case-insensitive substring, capped at 100.
      operationId: TagsController_listValues
      parameters:
        - name: workspaceId
          required: true
          in: path
          description: Workspace UUID
          schema:
            format: uuid
            type: string
        - name: key
          required: true
          in: path
          description: Tag key
          schema:
            type: string
        - name: q
          required: false
          in: query
          schema:
            type: string
            maxLength: 100
          description: Case-insensitive substring to match values against.
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TagValuesResponseDto'
        '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: List recent values for one tag key
      tags:
        - Read data
  /api/v1/otlp/v1/metrics:
    post:
      description: >-
        Accepts a standard OTLP ExportMetricsServiceRequest as JSON
        (application/json) or Protobuf (application/x-protobuf), optionally
        gzip- or deflate-compressed. Data points whose metric name matches an
        enabled mapping rule for the resolved workspace become Datius signal
        events, queued the same way as manual ingestion: Gauge and Sum points
        through a scalar rule, Histogram and Summary points through a fan-out
        rule (one event per chosen component). Unmatched metrics and
        ExponentialHistogram points are ignored, per OTLP's tolerance for
        partial acceptance; the response's partialSuccess counts what was
        dropped.
      operationId: OtlpMetricsController_exportMetrics
      parameters: []
      responses:
        '202':
          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.
        '402':
          description: >-
            The organization has used its plan's monthly event allowance.
            Nothing was stored. Not worth retrying until the plan changes or the
            month turns.
        '403':
          description: >-
            The API key is missing the signals:emit scope or is not scoped to a
            workspace, or the organization's plan doesn't include OpenTelemetry
            ingest.
        '413':
          description: The request body is over the size limit.
        '429':
          description: >-
            Too many requests for this API key. Nothing was stored; retry after
            Retry-After.
          headers:
            Retry-After:
              description: Seconds to wait before sending the same request again.
              schema:
                type: integer
        '503':
          description: >-
            Datius is catching up or briefly unavailable. Nothing was stored;
            send the same request again after Retry-After.
          headers:
            Retry-After:
              description: Seconds to wait before sending the same request again.
              schema:
                type: integer
      security:
        - ApiKeyAuth: []
      summary: OTLP/HTTP metrics ingestion
      tags:
        - Send data
  /api/v1/otlp/v1/traces:
    post:
      description: >-
        Accepts a standard OTLP ExportTraceServiceRequest as JSON
        (application/json) or Protobuf (application/x-protobuf), optionally
        gzip- or deflate-compressed. 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:
        '202':
          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.
        '402':
          description: >-
            The organization has used its plan's monthly event allowance.
            Nothing was stored. Not worth retrying until the plan changes or the
            month turns.
        '403':
          description: >-
            The API key is missing the signals:emit scope or is not scoped to a
            workspace, or the organization's plan doesn't include OpenTelemetry
            ingest.
        '413':
          description: The request body is over the size limit.
        '429':
          description: >-
            Too many requests for this API key. Nothing was stored; retry after
            Retry-After.
          headers:
            Retry-After:
              description: Seconds to wait before sending the same request again.
              schema:
                type: integer
        '503':
          description: >-
            Datius is catching up or briefly unavailable. Nothing was stored;
            send the same request again after Retry-After.
          headers:
            Retry-After:
              description: Seconds to wait before sending the same request again.
              schema:
                type: integer
      security:
        - ApiKeyAuth: []
      summary: OTLP/HTTP 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.
  - name: Your API key
    description: >-
      Check which workspace an API key belongs to, its scopes and when it
      expires. Works with any valid key.
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:
    WorkspaceBusinessContextResponse:
      type: object
      properties:
        businessContext:
          nullable: true
          type: string
      required:
        - businessContext
      additionalProperties: false
    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:
          nullable: true
          type: string
        dataType:
          type: string
          enum:
            - counter
            - numeric
            - currency
            - percentage
            - duration
        durationUnit:
          type: string
          enum:
            - ms
            - s
            - min
        direction:
          type: string
          enum:
            - higher_is_better
            - higher_is_worse
            - neutral
        tagSchema:
          type: array
          items:
            type: object
            properties:
              key:
                type: string
                minLength: 1
                maxLength: 100
              type:
                type: string
                enum:
                  - string
                  - number
                  - boolean
              isRequired:
                type: boolean
              description:
                type: string
                maxLength: 500
              isIdentifier:
                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
        - direction
        - tagSchema
        - isActive
        - hasData
        - createdAt
        - updatedAt
      additionalProperties: false
    ApiKeyIdentityResponse:
      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)$
        name:
          type: string
        keyPrefix:
          type: string
        scopes:
          type: array
          items:
            type: string
            enum:
              - signals:emit
              - signals:read
        expiresAt:
          nullable: true
          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))$
        workspace:
          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)$
            name:
              type: string
          required:
            - id
            - name
          additionalProperties: false
        organization:
          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)$
            name:
              type: string
          required:
            - id
            - name
          additionalProperties: false
      required:
        - id
        - name
        - keyPrefix
        - scopes
        - expiresAt
        - workspace
        - organization
      additionalProperties: false
    TelemetryQueryRequestDto:
      type: object
      properties:
        mode:
          type: string
          enum:
            - live
            - historical
        signalKey:
          type: string
          minLength: 1
          maxLength: 100
        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:
          maxItems: 20
          type: array
          items:
            type: object
            properties:
              key:
                type: string
                minLength: 1
                maxLength: 100
              value:
                type: string
                minLength: 1
                maxLength: 1000
            required:
              - key
              - value
        search:
          maxItems: 10
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 200
        limit:
          default: 50
          type: integer
          minimum: 1
          maximum: 500
        offset:
          default: 0
          type: integer
          minimum: 0
          maximum: 10000
      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 Parquet file this event was read from, as a path within the
            organization's storage (no bucket name).
          example: 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
              maxLength: 100
            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:
                anyOf:
                  - type: string
                  - type: number
                  - type: boolean
          required:
            - signalKey
            - value
          additionalProperties: false
        - minItems: 1
          maxItems: 500
          type: array
          items:
            type: object
            properties:
              signalKey:
                type: string
                minLength: 1
                maxLength: 100
              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:
                  anyOf:
                    - type: string
                    - type: number
                    - type: 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
    DerivedSignalResponse:
      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:
          nullable: true
          type: string
        dataType:
          type: string
          enum:
            - numeric
            - currency
            - percentage
            - duration
        durationUnit:
          type: string
          enum:
            - ms
            - s
            - min
        direction:
          type: string
          enum:
            - higher_is_better
            - higher_is_worse
            - neutral
        definition:
          type: object
          properties:
            operator:
              type: string
              enum:
                - add
                - subtract
                - multiply
                - divide
            left:
              oneOf:
                - type: object
                  properties:
                    kind:
                      type: string
                      enum:
                        - signal
                    signalKey:
                      type: string
                      minLength: 1
                    aggregation:
                      type: string
                      enum:
                        - SUM
                        - AVG
                        - COUNT
                        - MIN
                        - MAX
                    tagFilters:
                      maxItems: 20
                      type: array
                      items:
                        type: object
                        properties:
                          key:
                            type: string
                            minLength: 1
                            maxLength: 100
                          value:
                            type: string
                            minLength: 1
                            maxLength: 1000
                        required:
                          - key
                          - value
                        additionalProperties: false
                  required:
                    - kind
                    - signalKey
                    - aggregation
                  additionalProperties: false
                - type: object
                  properties:
                    kind:
                      type: string
                      enum:
                        - constant
                    value:
                      type: number
                  required:
                    - kind
                    - value
                  additionalProperties: false
            right:
              oneOf:
                - type: object
                  properties:
                    kind:
                      type: string
                      enum:
                        - signal
                    signalKey:
                      type: string
                      minLength: 1
                    aggregation:
                      type: string
                      enum:
                        - SUM
                        - AVG
                        - COUNT
                        - MIN
                        - MAX
                    tagFilters:
                      maxItems: 20
                      type: array
                      items:
                        type: object
                        properties:
                          key:
                            type: string
                            minLength: 1
                            maxLength: 100
                          value:
                            type: string
                            minLength: 1
                            maxLength: 1000
                        required:
                          - key
                          - value
                        additionalProperties: false
                  required:
                    - kind
                    - signalKey
                    - aggregation
                  additionalProperties: false
                - type: object
                  properties:
                    kind:
                      type: string
                      enum:
                        - constant
                    value:
                      type: number
                  required:
                    - kind
                    - value
                  additionalProperties: false
          required:
            - operator
            - left
            - right
          additionalProperties: false
        formulaText:
          type: string
        groupableTags:
          type: array
          items:
            type: string
        inactiveSources:
          type: array
          items:
            type: string
        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
        - direction
        - definition
        - formulaText
        - groupableTags
        - inactiveSources
        - createdAt
        - updatedAt
      additionalProperties: false
    ReportQueryRequestDto:
      type: object
      properties:
        signalKey:
          type: string
          minLength: 1
          maxLength: 100
        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
          maxLength: 100
        tagFilters:
          maxItems: 20
          type: array
          items:
            type: object
            properties:
              key:
                type: string
                minLength: 1
                maxLength: 100
              value:
                type: string
                minLength: 1
                maxLength: 1000
            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
    ExplainChangeRequestDto:
      type: object
      properties:
        signalKey:
          type: string
          minLength: 1
          maxLength: 100
        from:
          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))$
        to:
          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))$
        baseline:
          type: string
          enum:
            - auto
            - four_week_average
            - last_week
            - yesterday
            - previous_period
            - custom
        compareWith:
          type: object
          properties:
            from:
              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))$
            to:
              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))$
            label:
              type: string
              minLength: 1
              maxLength: 120
            windows:
              minItems: 1
              maxItems: 400
              type: array
              items:
                type: object
                properties:
                  from:
                    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))$
                  to:
                    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:
                  - from
                  - to
                additionalProperties: false
          required:
            - from
            - to
          additionalProperties: false
        tagFilters:
          maxItems: 20
          type: array
          items:
            type: object
            properties:
              key:
                type: string
                minLength: 1
                maxLength: 100
              value:
                type: string
                minLength: 1
                maxLength: 1000
            required:
              - key
              - value
        aggregation:
          type: string
          enum:
            - sum
            - avg
            - count
        focusTag:
          type: string
          minLength: 1
          maxLength: 100
        timeZone:
          type: string
          minLength: 1
          maxLength: 64
      required:
        - signalKey
        - from
        - to
      additionalProperties: false
    ExplainChangeResponseDto:
      type: object
      properties:
        status:
          type: string
          enum:
            - OK
            - NOT_ENOUGH_DATA
            - SIGNAL_PURGED
            - INVALID_FILTER
        signalKey:
          type: string
        result:
          type: object
          properties:
            signalKey:
              type: string
            signalName:
              type: string
            method:
              type: string
              enum:
                - additive
                - average
            window:
              type: object
              properties:
                from:
                  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))$
                to:
                  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:
                - from
                - to
              additionalProperties: false
            baseline:
              type: object
              properties:
                kind:
                  type: string
                  enum:
                    - four_week_average
                    - last_week
                    - yesterday
                    - previous_period
                    - custom
                windows:
                  type: array
                  items:
                    type: object
                    properties:
                      from:
                        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))$
                      to:
                        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:
                      - from
                      - to
                    additionalProperties: false
              required:
                - kind
                - windows
              additionalProperties: false
            current:
              type: object
              properties:
                value:
                  nullable: true
                  type: number
                events:
                  type: number
              required:
                - value
                - events
              additionalProperties: false
            baselineTotal:
              type: object
              properties:
                value:
                  nullable: true
                  type: number
                events:
                  type: number
              required:
                - value
                - events
              additionalProperties: false
            change:
              type: object
              properties:
                absolute:
                  type: number
                percent:
                  nullable: true
                  type: number
              required:
                - absolute
                - percent
              additionalProperties: false
            tone:
              type: string
              enum:
                - good
                - bad
                - neutral
            headline:
              type: string
            finding:
              nullable: true
              type: string
            headlineTag:
              nullable: true
              type: string
            tags:
              type: array
              items:
                type: object
                properties:
                  key:
                    type: string
                  description:
                    nullable: true
                    type: string
                  concentration:
                    type: number
                  spreadEvenly:
                    type: boolean
                  time:
                    type: string
                    enum:
                      - hour
                      - day
                  values:
                    type: array
                    items:
                      type: object
                      properties:
                        value:
                          type: string
                        isOther:
                          type: boolean
                        current:
                          type: object
                          properties:
                            value:
                              nullable: true
                              type: number
                            events:
                              type: number
                          required:
                            - value
                            - events
                          additionalProperties: false
                        baseline:
                          type: object
                          properties:
                            value:
                              nullable: true
                              type: number
                            events:
                              type: number
                          required:
                            - value
                            - events
                          additionalProperties: false
                        contribution:
                          type: number
                        shareOfMovement:
                          type: number
                        shareOfVolume:
                          type: number
                        percent:
                          nullable: true
                          type: number
                        standout:
                          type: string
                          enum:
                            - led
                            - opposite
                            - lagged
                        performance:
                          type: number
                        mix:
                          type: number
                        lowVolume:
                          type: boolean
                        order:
                          type: integer
                          minimum: -9007199254740991
                          maximum: 9007199254740991
                      required:
                        - value
                        - isOther
                        - current
                        - baseline
                        - contribution
                        - shareOfMovement
                        - shareOfVolume
                        - lowVolume
                      additionalProperties: false
                required:
                  - key
                  - description
                  - concentration
                  - spreadEvenly
                  - values
                additionalProperties: false
            skippedTags:
              type: array
              items:
                type: string
          required:
            - signalKey
            - signalName
            - method
            - window
            - baseline
            - current
            - baselineTotal
            - change
            - tone
            - headline
            - finding
            - headlineTag
            - tags
            - skippedTags
          additionalProperties: false
        reason:
          type: string
        emptyWindow:
          type: object
          properties:
            from:
              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))$
            to:
              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:
            - from
            - to
          additionalProperties: false
        invalidTags:
          type: array
          items:
            type: string
      required:
        - status
        - signalKey
      additionalProperties: false
    TagValuesResponseDto:
      type: object
      properties:
        key:
          type: string
          example: store_id
        values:
          example:
            - store_001
            - store_002
          type: array
          items:
            type: string
      required:
        - key
        - values
    OtlpExportMetricsResponse:
      type: object
      properties: {}
    OtlpExportTracesResponse:
      type: object
      properties: {}
