openapi: 3.1.0
info:
  title: Laminar API
  version: 1.0.0
  description: Public HTTP API for ingesting and managing data in a Laminar project.
  license:
    name: Apache 2.0
    identifier: Apache-2.0
servers:
  - url: https://api.lmnr.ai
    description: Laminar Cloud
security:
  - projectApiKey: []
tags:
  - name: Ingestion
    description: Send telemetry data to Laminar.
  - name: Traces
    description: Update trace data after ingestion.
  - name: SQL
    description: Query project data with read-only SQL.
  - name: Projects
    description: Resolve project information.
  - name: Datasets
    description: Create, read, update, and delete datasets and add datapoints.
  - name: Evaluations
    description: Create evaluation runs and record their results.
  - name: Labeling queues
    description: Add items to labeling queues.
  - name: Signals
    description: Manage automated evaluations of project traces.
  - name: LLM profiles
    description: Configure the workspace LLM credentials and models the playground and self-hosted Signals run on.
paths:
  /v1/traces:
    post:
      tags: [Ingestion]
      summary: Ingest OpenTelemetry traces
      description: Accepts an OTLP ExportTraceServiceRequest encoded as protobuf or OTLP JSON.
      operationId: ingestTraces
      security: [{ projectApiKey: [] }, { ingestApiKey: [] }]
      requestBody:
        required: true
        content:
          application/x-protobuf:
            schema: { type: string, contentMediaType: application/x-protobuf }
          application/json:
            schema: { type: object, additionalProperties: true }
      responses:
        "200": { description: Trace batch accepted. }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }
        "429": { description: Ingestion rate limit exceeded. }
  /v1/spans:
    post:
      tags: [Ingestion]
      summary: Create spans
      description: Ingests a batch of spans using Laminar's JSON span format.
      operationId: createSpans
      security: [{ projectApiKey: [] }, { ingestApiKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items: { $ref: "#/components/schemas/CreateSpanRequest" }
      responses:
        "200":
          description: Spans accepted.
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/CreateSpanResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }
  /v1/logs:
    post:
      tags: [Ingestion]
      summary: Ingest OpenTelemetry logs
      description: Accepts an OTLP ExportLogsServiceRequest encoded as protobuf.
      operationId: ingestLogs
      security: [{ projectApiKey: [] }, { ingestApiKey: [] }]
      requestBody:
        required: true
        content:
          application/x-protobuf:
            schema: { type: string, contentMediaType: application/x-protobuf }
      responses:
        "200": { description: Log batch accepted. }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }
  /v1/labeling_queues/{queue_id}/items:
    post:
      tags: [Labeling queues]
      summary: Add labeling queue items
      description: Adds items to a labeling queue, optionally deduplicating them with idempotency keys.
      operationId: createLabelingQueueItems
      security: [{ projectApiKey: [] }, { ingestApiKey: [] }]
      parameters:
        - { $ref: "#/components/parameters/QueueId" }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateLabelingQueueItemsRequest" }
      responses:
        "201":
          description: Items created.
          content:
            application/json:
              schema:
                { type: array, items: { $ref: "#/components/schemas/LabelingQueueItemResponse" } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/sql/query:
    post:
      tags: [SQL]
      summary: Query project data
      description: Executes a validated, read-only SQL query scoped to the authenticated project.
      operationId: executeSqlQuery
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SqlQueryRequest" }
      responses:
        "200":
          description: Query results.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SqlQueryResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { description: SQL query rate limit exceeded. }
  /v1/project:
    get:
      tags: [Projects]
      summary: Get the current project
      description: Returns the project ID associated with the supplied API key.
      operationId: getCurrentProject
      responses:
        "200":
          description: Current project.
          content:
            application/json:
              schema:
                type: object
                required: [projectId]
                properties:
                  projectId: { $ref: "#/components/schemas/Uuid" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /v1/datasets:
    get:
      tags: [Datasets]
      summary: List datasets
      description: Lists project datasets, optionally filtered by dataset ID or name.
      operationId: listDatasets
      parameters:
        - { name: id, in: query, schema: { $ref: "#/components/schemas/Uuid" } }
        - { name: name, in: query, schema: { type: string } }
      responses:
        "200":
          description: Matching datasets.
          content:
            application/json:
              schema: { type: array, items: { $ref: "#/components/schemas/Dataset" } }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Datasets]
      summary: Create a dataset
      description: Creates an empty dataset. Dataset names do not need to be unique.
      operationId: createDataset
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DatasetNameRequest" }
      responses:
        "201":
          description: Dataset created.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Dataset" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /v1/datasets/{dataset_id}:
    parameters:
      - { $ref: "#/components/parameters/DatasetId" }
    get:
      tags: [Datasets]
      summary: Get a dataset
      description: Returns a dataset by its canonical ID.
      operationId: getDataset
      responses:
        "200":
          description: Dataset found.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Dataset" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Datasets]
      summary: Update a dataset
      description: Renames a dataset. Dataset names do not need to be unique.
      operationId: updateDataset
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/DatasetNameRequest" }
      responses:
        "200":
          description: Dataset updated.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Dataset" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Datasets]
      summary: Delete a dataset
      description: Deletes a dataset and its datapoints.
      operationId: deleteDataset
      responses:
        "200":
          description: Dataset deleted. Returns the deleted dataset.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Dataset" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/datasets/datapoints:
    get:
      tags: [Datasets]
      summary: List dataset datapoints
      description: Returns a paginated page of datapoints from a dataset identified by ID or name.
      operationId: listDatasetDatapoints
      parameters:
        - { name: datasetId, in: query, schema: { $ref: "#/components/schemas/Uuid" } }
        - { name: datasetName, in: query, schema: { type: string } }
        - { name: limit, in: query, required: true, schema: { type: integer, format: int64, minimum: 1, maximum: 1000 } }
        - { name: offset, in: query, required: true, schema: { type: integer, format: int64, minimum: 0 } }
      responses:
        "200":
          description: A page of datapoints.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DatapointPage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    post:
      tags: [Datasets]
      summary: Create dataset datapoints
      description: Adds datapoints to a dataset and can optionally create the dataset when identified by name.
      operationId: createDatasetDatapoints
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateDatapointsRequest" }
      responses:
        "200": { $ref: "#/components/responses/DatapointsCreated" }
        "201": { $ref: "#/components/responses/DatapointsCreated" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { description: A dataset with the requested name already exists. }
  /v1/signals:
    post:
      tags: [Signals]
      summary: Create a Signal
      description: |
        Creates a Signal with the first server-managed configuration version.

        <Note>
          `llmProfileId` and `model` are available only on self-hosted deployments. Provide both fields together. Laminar Cloud rejects them.
        </Note>
      operationId: createSignal
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateSignalRequest" }
      responses:
        "201":
          description: Signal created.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Signal" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "409": { $ref: "#/components/responses/Conflict" }
    get:
      tags: [Signals]
      summary: List Signals
      description: Lists Signals in the project, optionally filtering by a name substring.
      operationId: listSignals
      parameters:
        - name: name
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Project Signals.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SignalList" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /v1/signals/{signal_id}:
    parameters:
      - { $ref: "#/components/parameters/SignalId" }
    get:
      tags: [Signals]
      summary: Get a Signal
      description: Returns a Signal in the authenticated project.
      operationId: getSignal
      responses:
        "200":
          description: Signal details.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Signal" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Signals]
      summary: Update a Signal
      description: |
        Updates a Signal. An update that changes the effective Signal configuration increments its server-managed version.

        <Note>
          `llmProfileId` and `model` are available only on self-hosted deployments. Provide both fields together to change the Signal's LLM route. Laminar Cloud rejects them.
        </Note>
      operationId: updateSignal
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateSignalRequest" }
      responses:
        "200":
          description: Signal updated.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Signal" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
    delete:
      tags: [Signals]
      summary: Delete a Signal
      description: |
        Deletes a Signal and returns its final state.

        <Warning>
          Deleting a Signal also deletes all of its alerts and events and is irreversible. We recommend disabling the Signal instead. You can disable it from the UI or through the [Update a Signal](/api-reference/signals/update-a-signal) endpoint by setting `disabled` to `true`.
        </Warning>
      operationId: deleteSignal
      responses:
        "200":
          description: Signal deleted.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Signal" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/llm-profiles:
    get:
      tags: [LLM profiles]
      summary: List LLM profiles
      description: |
        Lists every LLM profile in the authenticated project's workspace.
      operationId: listLlmProfiles
      responses:
        "200":
          description: Workspace LLM profiles. Secret values are masked.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/LlmProfileList" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    post:
      tags: [LLM profiles]
      summary: Create an LLM profile
      description: |
        Creates an LLM profile in the authenticated project's workspace.

        <Note>
          Secrets are accepted as plaintext over TLS, stored encrypted, and never returned unmasked.
        </Note>
      operationId: createLlmProfile
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateLlmProfileRequest" }
      responses:
        "200":
          description: LLM profile created. Secret values are masked.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/LlmProfile" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
  /v1/llm-profiles/{profile_id}:
    parameters:
      - { $ref: "#/components/parameters/LlmProfileId" }
    get:
      tags: [LLM profiles]
      summary: Get an LLM profile
      description: |
        Returns an LLM profile from the authenticated project's workspace. Secret values are masked.
      operationId: getLlmProfile
      responses:
        "200":
          description: LLM profile details.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/LlmProfile" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [LLM profiles]
      summary: Update an LLM profile
      description: |
        Updates an LLM profile. Omitted fields retain their stored values, `models` replaces the complete model list, a supplied `config` replaces the whole stored config object, and supplied secrets are merged with stored secrets.

        <Note>
          Removing a model used by a Signal returns `409 Conflict`.
        </Note>
      operationId: updateLlmProfile
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateLlmProfileRequest" }
      responses:
        "200":
          description: LLM profile updated. Secret values are masked.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/LlmProfile" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
    delete:
      tags: [LLM profiles]
      summary: Delete an LLM profile
      description: |
        Deletes an LLM profile and returns its final state.

        <Warning>
          A profile used by a Signal cannot be deleted. The API returns `409 Conflict` until every Signal stops using it.
        </Warning>
      operationId: deleteLlmProfile
      responses:
        "200":
          description: LLM profile deleted. Secret values are masked.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/LlmProfile" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
  /v1/evals:
    post:
      tags: [Evaluations]
      summary: Create an evaluation
      description: Creates an evaluation run, generating a name when none is supplied.
      operationId: createEvaluation
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateEvaluationRequest" }
      responses:
        "200":
          description: Evaluation created.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Evaluation" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /v1/evals/{eval_id}:
    post:
      tags: [Evaluations]
      summary: Update an evaluation
      description: Updates an evaluation's name and/or metadata; omitted fields remain unchanged.
      operationId: updateEvaluation
      parameters:
        - { $ref: "#/components/parameters/EvaluationId" }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateEvaluationRequest" }
      responses:
        "200":
          description: Evaluation updated.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Evaluation" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
  /v1/evals/{eval_id}/datapoints:
    post:
      tags: [Evaluations]
      summary: Add evaluation datapoints
      description: Adds result datapoints to an evaluation run.
      operationId: createEvaluationDatapoints
      parameters:
        - { $ref: "#/components/parameters/EvaluationId" }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateEvaluationDatapointsRequest" }
      responses:
        "200":
          description: Datapoints added; returns the evaluation ID.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Uuid" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /v1/evals/{eval_id}/datapoints/{datapoint_id}:
    post:
      tags: [Evaluations]
      summary: Update an evaluation datapoint
      description: Updates a datapoint's executor output, scores, and optional trace association.
      operationId: updateEvaluationDatapoint
      parameters:
        - { $ref: "#/components/parameters/EvaluationId" }
        - { $ref: "#/components/parameters/DatapointId" }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/UpdateEvaluationDatapointRequest" }
      responses:
        "200":
          description: Datapoint updated; returns the datapoint ID.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Uuid" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
components:
  securitySchemes:
    projectApiKey:
      type: http
      scheme: bearer
      bearerFormat: Project API key
      description: A Laminar project API key.
    ingestApiKey:
      type: http
      scheme: bearer
      bearerFormat: Ingest-only API key
      description: A project API key restricted to ingestion operations.
  parameters:
    DatasetId:
      name: dataset_id
      in: path
      required: true
      schema: { $ref: "#/components/schemas/Uuid" }
    QueueId:
      name: queue_id
      in: path
      required: true
      schema: { $ref: "#/components/schemas/Uuid" }
    EvaluationId:
      name: eval_id
      in: path
      required: true
      schema: { $ref: "#/components/schemas/Uuid" }
    DatapointId:
      name: datapoint_id
      in: path
      required: true
      schema: { $ref: "#/components/schemas/Uuid" }
    SignalId:
      name: signal_id
      in: path
      required: true
      schema: { $ref: "#/components/schemas/Uuid" }
    LlmProfileId:
      name: profile_id
      in: path
      required: true
      schema: { $ref: "#/components/schemas/Uuid" }
  responses:
    Success: { description: Request completed successfully. }
    BadRequest:
      description: Invalid request.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Unauthorized:
      description: Missing or invalid API key.
    NotFound:
      description: The requested resource was not found in this project.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    PayloadTooLarge: { description: The request exceeds the configured HTTP payload limit. }
    Conflict:
      description: A resource with the requested name already exists.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    DatapointsCreated:
      description: Datapoints created; status 201 means the dataset was also created.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/CreateDatapointsResponse" }
  schemas:
    Uuid: { type: string, format: uuid }
    JsonValue: {}
    Error:
      oneOf:
        - { type: object, properties: { error: { type: string } }, required: [error] }
        - { type: string }
    CreateSpanRequest:
      type: object
      required: [name, startTime, endTime, traceId, spanId]
      properties:
        name: { type: string }
        spanType:
          type: [string, "null"]
          description: "Note: The backend may still accept `PIPELINE` and `HUMAN_EVALUATOR`, but they are undocumented, and should not be used."
          enum: [DEFAULT, LLM, EXECUTOR, EVALUATOR, EVALUATION, TOOL, CACHED, null]
        startTime: { type: string, format: date-time }
        endTime: { type: string, format: date-time }
        input: { $ref: "#/components/schemas/JsonValue" }
        output: { $ref: "#/components/schemas/JsonValue" }
        attributes: { type: [object, "null"], additionalProperties: true }
        traceId: { $ref: "#/components/schemas/Uuid" }
        spanId: { $ref: "#/components/schemas/Uuid" }
        parentSpanId: { oneOf: [{ $ref: "#/components/schemas/Uuid" }, { type: "null" }] }
    CreateSpanResponse:
      type: object
      required: [spanId, traceId]
      properties:
        spanId: { $ref: "#/components/schemas/Uuid" }
        traceId: { $ref: "#/components/schemas/Uuid" }
    CreateLabelingQueueItemsRequest:
      type: object
      required: [items]
      properties:
        items:
          type: array
          minItems: 1
          items: { $ref: "#/components/schemas/LabelingQueueItemRequest" }
    LabelingQueueItemRequest:
      type: object
      required: [data, target]
      properties:
        data: { $ref: "#/components/schemas/JsonValue" }
        target: { $ref: "#/components/schemas/JsonValue" }
        metadata: { type: object, additionalProperties: true, default: {} }
        idempotencyKey: { type: [string, "null"] }
    LabelingQueueItemResponse:
      type: object
      required: [id, createdAt]
      properties:
        id: { $ref: "#/components/schemas/Uuid" }
        createdAt: { type: string, format: date-time }
    SqlQueryRequest:
      type: object
      required: [query]
      properties:
        query: { type: string }
        parameters: { type: object, additionalProperties: true, default: {} }
    SqlQueryResponse:
      type: object
      required: [data]
      properties:
        data: { type: array, items: { $ref: "#/components/schemas/JsonValue" } }
    DatasetNameRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
          minLength: 1
          description: Dataset name. Names are stored as supplied and do not need to be unique.
    Dataset:
      type: object
      required: [id, name, projectId, createdAt]
      properties:
        id: { $ref: "#/components/schemas/Uuid" }
        name: { type: string }
        projectId: { $ref: "#/components/schemas/Uuid" }
        createdAt: { type: string, format: date-time }
    Datapoint:
      type: object
      required: [id, datasetId, data, metadata, createdAt]
      properties:
        id: { $ref: "#/components/schemas/Uuid" }
        datasetId: { $ref: "#/components/schemas/Uuid" }
        data: { $ref: "#/components/schemas/JsonValue" }
        target: { $ref: "#/components/schemas/JsonValue" }
        metadata: { type: object, additionalProperties: true }
        createdAt: { type: string, format: date-time }
    DatapointPage:
      type: object
      required: [totalCount, items, anyInProject]
      properties:
        totalCount: { type: integer, format: int64 }
        items: { type: array, items: { $ref: "#/components/schemas/Datapoint" } }
        anyInProject: { type: boolean }
    NewDatapoint:
      type: object
      required: [data]
      properties:
        id: { oneOf: [{ $ref: "#/components/schemas/Uuid" }, { type: "null" }] }
        data: { $ref: "#/components/schemas/JsonValue" }
        target: { $ref: "#/components/schemas/JsonValue" }
        metadata: { type: object, additionalProperties: true, default: {} }
    CreateDatapointsRequest:
      type: object
      required: [datapoints]
      description: Supply exactly one of datasetId or datasetName.
      properties:
        datasetId: { $ref: "#/components/schemas/Uuid" }
        datasetName: { type: string }
        datapoints: { type: array, items: { $ref: "#/components/schemas/NewDatapoint" } }
        createDataset: { type: boolean, default: false }
      oneOf:
        - { required: [datasetId] }
        - { required: [datasetName] }
    CreateDatapointsResponse:
      type: object
      required: [message, datasetId, count, datapointInfo]
      properties:
        message: { type: string }
        datasetId: { $ref: "#/components/schemas/Uuid" }
        count: { type: integer }
        datapointInfo:
          type: array
          items:
            type: object
            required: [id, createdAt]
            properties:
              id: { $ref: "#/components/schemas/Uuid" }
              createdAt: { type: string, format: date-time }
    SignalTrigger:
      oneOf:
        - type: object
          required: [type]
          properties:
            type: { type: string, const: rootSpanFinished }
        - type: object
          required: [type, spanNames]
          properties:
            type: { type: string, const: spanName }
            spanNames:
              type: array
              minItems: 1
              items: { type: string }
    SignalFilter:
      type: object
      required: [column, operator, value]
      properties:
        column:
          type: string
          enum: [total_token_count, status, span_names, tags]
        operator:
          type: string
          enum: [eq, ne, gt, gte, lt, lte, includes, not_includes]
        value: { $ref: "#/components/schemas/JsonValue" }
      description: Operators and value shapes are validated for the selected column.
    SignalDefinitionFields:
      type: object
      properties:
        name: { type: string, maxLength: 255 }
        prompt: { type: string }
        structuredOutput:
          type: object
          additionalProperties: true
          description: JSON Schema for the Signal's structured result.
        sampleRate:
          type: [integer, "null"]
          minimum: 1
          maximum: 95
          description: Percentage of matching traces to sample; null clears sampling on update.
        disabled: { type: boolean }
        trigger: { $ref: "#/components/schemas/SignalTrigger" }
        filters:
          type: array
          items: { $ref: "#/components/schemas/SignalFilter" }
        mode: { type: string, enum: [batch, realtime] }
        llmProfileId:
          oneOf:
            - { $ref: "#/components/schemas/Uuid" }
            - { type: "null" }
          description: Self-hosted only. Workspace LLM profile used to evaluate the Signal. Provide with `model`; omit both fields on update to keep the existing route.
        model:
          type: [string, "null"]
          description: Self-hosted only. Model selected from `llmProfileId`. Provide both fields together.
    CreateSignalRequest:
      allOf:
        - { $ref: "#/components/schemas/SignalDefinitionFields" }
        - type: object
          required: [name, prompt, structuredOutput]
    UpdateSignalRequest:
      $ref: "#/components/schemas/SignalDefinitionFields"
    Signal:
      allOf:
        - { $ref: "#/components/schemas/SignalDefinitionFields" }
        - type: object
          required:
            [
              id,
              projectId,
              name,
              prompt,
              structuredOutput,
              sampleRate,
              disabled,
              createdAt,
              trigger,
              filters,
              mode,
              version,
              llmProfileId,
              llmProfileName,
              model,
            ]
          properties:
            id: { $ref: "#/components/schemas/Uuid" }
            projectId: { $ref: "#/components/schemas/Uuid" }
            createdAt: { type: string, format: date-time }
            version:
              type: integer
              format: int32
              minimum: 1
              readOnly: true
              description: Server-managed version of the Signal configuration.
            llmProfileName:
              type: [string, "null"]
              readOnly: true
              description: Display name of the selected workspace LLM profile.
    SignalList:
      type: object
      required: [signals]
      properties:
        signals:
          type: array
          items: { $ref: "#/components/schemas/Signal" }
    LlmProfileProvider:
      type: string
      enum:
        [
          openai_completions,
          openai_responses,
          anthropic,
          gemini,
          groq,
          mistral,
          bedrock,
          azure_chat_completions,
          azure_responses,
          azure_anthropic,
          custom,
          custom_responses,
        ]
    LlmProfileAuth:
      oneOf:
        - type: object
          required: [type]
          properties:
            type: { type: string, const: api_key }
        - type: object
          required: [type, accessKeyId]
          properties:
            type: { type: string, const: aws_keys }
            accessKeyId: { type: string, maxLength: 256 }
        - type: object
          required: [type]
          properties:
            type: { type: string, const: bearer_token }
    LlmProfileConfig:
      type: object
      properties:
        auth: { $ref: "#/components/schemas/LlmProfileAuth" }
        region: { type: string, maxLength: 64 }
        resourceId: { type: string, maxLength: 256 }
        baseUrl: { type: string, format: uri }
        apiVersion: { type: string, maxLength: 64 }
        headerNames:
          type: array
          maxItems: 32
          uniqueItems: true
          items: { type: string, maxLength: 256 }
      description: Provider-specific non-secret configuration. Azure requires exactly one of `resourceId` or `baseUrl`; Bedrock requires `region`; custom providers require `baseUrl`.
    LlmProfileSecretsInput:
      type: object
      writeOnly: true
      properties:
        apiKey: { type: string, format: password, maxLength: 8192 }
        secretAccessKey: { type: string, format: password, maxLength: 8192 }
        token: { type: string, format: password, maxLength: 8192 }
        headers:
          type: object
          additionalProperties: { type: string, format: password, maxLength: 8192 }
      description: Plaintext credentials. Required fields depend on the provider and authentication type.
    LlmProfileSecrets:
      type: object
      readOnly: true
      required: [apiKey, secretAccessKey, token, headers]
      properties:
        apiKey: { type: [string, "null"], description: Masked API key. }
        secretAccessKey: { type: [string, "null"], description: Masked AWS secret access key. }
        token: { type: [string, "null"], description: Masked bearer token. }
        headers:
          type: array
          items: { type: string }
          description: Names of stored custom headers; values are never returned.
    CreateLlmProfileRequest:
      type: object
      required: [name, provider, models]
      properties:
        name: { type: string, maxLength: 255 }
        provider: { $ref: "#/components/schemas/LlmProfileProvider" }
        config: { $ref: "#/components/schemas/LlmProfileConfig" }
        secrets: { $ref: "#/components/schemas/LlmProfileSecretsInput" }
        models:
          type: array
          minItems: 1
          maxItems: 64
          uniqueItems: true
          items: { type: string, maxLength: 256 }
    UpdateLlmProfileRequest:
      type: object
      properties:
        name: { type: string, maxLength: 255 }
        provider:
          allOf:
            - { $ref: "#/components/schemas/LlmProfileProvider" }
          description: When changing the provider, `config` is required.
        config: { $ref: "#/components/schemas/LlmProfileConfig" }
        secrets:
          allOf:
            - { $ref: "#/components/schemas/LlmProfileSecretsInput" }
          description: Supplied values replace matching secrets; omitted values remain unchanged.
        models:
          type: array
          minItems: 1
          maxItems: 64
          uniqueItems: true
          items: { type: string, maxLength: 256 }
          description: Replaces the complete model list when supplied.
    LlmProfile:
      type: object
      required: [id, workspaceId, name, provider, config, models, secrets, createdAt, updatedAt]
      properties:
        id: { $ref: "#/components/schemas/Uuid" }
        workspaceId: { $ref: "#/components/schemas/Uuid" }
        name: { type: string }
        provider: { $ref: "#/components/schemas/LlmProfileProvider" }
        config: { $ref: "#/components/schemas/LlmProfileConfig" }
        models:
          type: array
          items: { type: string }
        secrets: { $ref: "#/components/schemas/LlmProfileSecrets" }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    LlmProfileList:
      type: object
      required: [llmProfiles]
      properties:
        llmProfiles:
          type: array
          items: { $ref: "#/components/schemas/LlmProfile" }
    CreateEvaluationRequest:
      type: object
      properties:
        name: { type: [string, "null"] }
        groupName: { type: [string, "null"], default: default }
        metadata: { $ref: "#/components/schemas/JsonValue" }
    UpdateEvaluationRequest:
      type: object
      properties:
        name: { type: [string, "null"] }
        metadata:
          description: Replaces the complete evaluation metadata object when supplied.
          $ref: "#/components/schemas/JsonValue"
    Evaluation:
      type: object
      required: [id, createdAt, name, projectId, groupId]
      properties:
        id: { $ref: "#/components/schemas/Uuid" }
        createdAt: { type: string, format: date-time }
        name: { type: string }
        projectId: { $ref: "#/components/schemas/Uuid" }
        groupId: { type: string }
        metadata: { $ref: "#/components/schemas/JsonValue" }
    CreateEvaluationDatapointsRequest:
      type: object
      required: [points]
      properties:
        groupName: { type: [string, "null"], default: default }
        points:
          type: array
          items: { $ref: "#/components/schemas/EvaluationDatapoint" }
    EvaluationDatapoint:
      type: object
      required: [data]
      properties:
        data: { $ref: "#/components/schemas/JsonValue" }
        index: { type: integer, format: int32, default: 0 }
        id: { $ref: "#/components/schemas/Uuid" }
        target: { $ref: "#/components/schemas/JsonValue" }
        metadata: { type: [object, "null"], additionalProperties: true }
        executorOutput: { $ref: "#/components/schemas/JsonValue" }
        traceId: { $ref: "#/components/schemas/Uuid" }
        scores:
          type: object
          additionalProperties: { type: [number, "null"], format: double }
        datasetLink:
          oneOf:
            - { $ref: "#/components/schemas/EvaluationDatasetLink" }
            - { type: "null" }
    EvaluationDatasetLink:
      type: object
      required: [datasetId, datapointId, createdAt]
      properties:
        datasetId: { $ref: "#/components/schemas/Uuid" }
        datapointId: { $ref: "#/components/schemas/Uuid" }
        createdAt: { type: string, format: date-time }
    UpdateEvaluationDatapointRequest:
      type: object
      required: [scores]
      properties:
        executorOutput: { $ref: "#/components/schemas/JsonValue" }
        scores:
          type: object
          additionalProperties: { type: [number, "null"], format: double }
        traceId: { oneOf: [{ $ref: "#/components/schemas/Uuid" }, { type: "null" }] }
