> ## Documentation Index
> Fetch the complete documentation index at: https://docs.signalmd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Compare two versions

> What changed in the configuration between any two versions, not only neighbours, and what production did across them by the health board's metrics and verdicts (`result`), with the stored comparison when one exists for that pair.



## OpenAPI

````yaml /api-reference/openapi.json get /v1/agents/{agentId}/versions/compare
openapi: 3.1.0
info:
  title: SignalMD API
  version: 1.0.0
  description: >-
    SignalMD reads a voice agent's production calls and says what to fix, then
    tests whether the fix worked.

    This API exposes the same data the web app shows: agents, calls and their
    traces, signals, issues,

    versions and their comparisons, tests, and test runs.


    Authenticate with `Authorization: Bearer sig_...` (an API key from the API
    keys page). Read keys may

    call every `read` operation; write keys may also call `write` operations,
    within their monthly budget.

    Each operation's scope is in `x-signal-scope`. Errors share one envelope
    (`Error`), lists share one

    cursor scheme (`limit`, `cursor`, `hasMore`, `nextCursor`), and every
    response carries `Request-Id`

    and `RateLimit-*` headers.
servers:
  - url: https://api.signalmd.dev
    description: Production
  - url: http://localhost:43124
    description: Local development
security:
  - bearerAuth: []
tags:
  - name: Account
    description: Who the token belongs to and what it may do.
  - name: Agents
    description: Voice agents connected to the workspace.
  - name: Calls
    description: Production calls, their traces and per-call signals.
  - name: Signals
    description: What SignalMD noticed across calls.
  - name: Issues
    description: 'Problems worth fixing: where, why, and what to change.'
  - name: Versions
    description: Agent versions and how each one did.
  - name: Comparisons
    description: Stored comparisons between two versions.
  - name: Tests
    description: Tests, test sets, personas and coverage.
  - name: Runs
    description: Test runs and the attempts inside them.
paths:
  /v1/agents/{agentId}/versions/compare:
    get:
      tags:
        - Versions
      summary: Compare two versions
      description: >-
        What changed in the configuration between any two versions, not only
        neighbours, and what production did across them by the health board's
        metrics and verdicts (`result`), with the stored comparison when one
        exists for that pair.
      operationId: compareVersions
      parameters:
        - schema:
            type: string
            format: uuid
            pattern: >-
              ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$
            example: 3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f
            description: The agent's id.
          required: true
          description: The agent's id.
          name: agentId
          in: path
        - schema:
            type: integer
            minimum: 0
            description: The older version, usually.
            example: 8
          required: true
          description: The older version, usually.
          name: from
          in: query
        - schema:
            type: integer
            minimum: 0
            description: The newer version, usually.
            example: 8
          required: true
          description: The newer version, usually.
          name: to
          in: query
      responses:
        '200':
          description: The diff, the comparison and the stored comparison.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VersionDiff'
        '400':
          description: 'Invalid request: a parameter or the body failed validation.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing, invalid or revoked credentials.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Not permitted for this principal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: >-
            The agent, or a resource under it, does not exist or is not visible
            to this key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limited. See Retry-After.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Server error. The message is generic; quote the request id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      x-codeSamples:
        - lang: bash
          label: curl
          source: >-
            curl
            "https://api.signalmd.dev/v1/agents/6f1d2c3b-4a5e-4f60-8b7a-9c0d1e2f3a4b/versions/compare?from=7&to=8"
            \
              -H "Authorization: Bearer $SIGNAL_API_KEY"
components:
  schemas:
    VersionDiff:
      type: object
      properties:
        object:
          type: string
          enum:
            - version_diff
        from:
          type: object
          properties:
            version:
              type: integer
            configCaptured:
              type: boolean
            configHash:
              type:
                - string
                - 'null'
              description: >-
                `sha256:<hex>` over the configuration (agent, flow, components
                and LLM, keys sorted). Two versions with the same hash are
                configured the same. Null when the configuration was not
                captured.
              example: sha256:9f2c...
          required:
            - version
            - configCaptured
            - configHash
        to:
          type: object
          properties:
            version:
              type: integer
            configCaptured:
              type: boolean
            configHash:
              type:
                - string
                - 'null'
              description: >-
                `sha256:<hex>` over the configuration (agent, flow, components
                and LLM, keys sorted). Two versions with the same hash are
                configured the same. Null when the configuration was not
                captured.
              example: sha256:9f2c...
          required:
            - version
            - configCaptured
            - configHash
        changes:
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/ConfigChange'
          description: >-
            What changed in the configuration from `from` to `to`, computed now
            from the captured configurations. Null when either was not captured:
            it cannot be diffed, which is not the same as nothing changed.
        result:
          $ref: '#/components/schemas/ComparisonResult'
        comparison:
          $ref: '#/components/schemas/Comparison'
      required:
        - object
        - from
        - to
        - changes
        - result
        - comparison
      description: >-
        The configuration diff between two versions and what production did
        across them, with the stored comparison when SignalMD recorded one for
        that pair.
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            type:
              $ref: '#/components/schemas/ErrorType'
            code:
              type: string
              example: agent_not_found
              description: The specific reason; stable within v1.
            message:
              type: string
              example: Agent not found.
            param:
              type:
                - string
                - 'null'
              description: The request field at fault, for a validation error.
            requestId:
              type:
                - string
                - 'null'
              example: req_8c1f0e2b4a6d9e3f7a5c1b0d
            quota:
              type: object
              properties:
                metric:
                  type: string
                  example: credits
                  description: >-
                    credits when the workspace's monthly credits are used up, or
                    agents when the plan's agent limit is reached.
                activity:
                  type: string
                  enum:
                    - observation
                    - testing
                    - chat
                  example: testing
                  description: >-
                    With metric credits: what the request would have spent them
                    on. observation is analysing calls, testing is test runs,
                    chat is the copilot.
                requested:
                  type: integer
                  example: 18
                  description: >-
                    With metric credits: the credits the request needed to
                    start.
                limit:
                  type: integer
                  description: 'The plan''s limit: the month''s credits, or its agents.'
                used:
                  type: integer
                  description: How much of the limit is used this month.
                plan:
                  type: string
                  example: free_org
                  description: The workspace's plan slug.
                upgradePath:
                  type: string
                  example: /billing
                  description: Where in the web app the plan is changed.
              required:
                - metric
                - limit
                - used
                - plan
                - upgradePath
              description: >-
                Present when code is quota_exceeded: the plan limit the request
                would pass.
            proposalId:
              type: string
              format: uuid
              description: >-
                Present when code is criteria_locked: the pending criteria
                proposal the change was filed as. A person approves or rejects
                it.
          required:
            - type
            - code
            - message
            - param
            - requestId
      required:
        - error
      example:
        error:
          type: not_found_error
          code: agent_not_found
          message: Agent not found.
          param: null
          requestId: req_8c1f0e2b4a6d9e3f7a5c1b0d
    ConfigChange:
      type: object
      properties:
        kind:
          type: string
          enum:
            - node_added
            - node_removed
            - prompt_changed
            - edge_added
            - edge_removed
            - tool_added
            - tool_removed
            - tool_changed
            - model_changed
            - voice_changed
            - setting_changed
        location:
          $ref: '#/components/schemas/Location'
        detail:
          type: string
          description: What changed, in a sentence.
        before:
          type:
            - string
            - 'null'
          description: >-
            The old value or prompt, shortened. Null when there was none or it
            was not recorded.
        after:
          type:
            - string
            - 'null'
          description: The new value or prompt, shortened.
      required:
        - kind
        - location
        - detail
        - before
        - after
      description: One difference between two captured configurations.
    ComparisonResult:
      type: object
      properties:
        conclusion:
          $ref: '#/components/schemas/ComparisonConclusion'
        conclusionLabel:
          type: string
          description: The conclusion as the web app words it.
          example: Not enough calls
        callsBefore:
          type: integer
          description: Calls on the older version.
        callsAfter:
          type: integer
          description: Calls on the newer version.
        metrics:
          type: array
          items:
            $ref: '#/components/schemas/MetricComparison'
          description: >-
            Got it done first. A metric with nothing on either side (no tools,
            no transfers) is left out.
        issueMoves:
          type: array
          items:
            $ref: '#/components/schemas/IssueMove'
          description: >-
            Open issues on the board whose calls fall on either version, with
            how their rate moved. Those that changed come first. Lower is
            better.
        narrative:
          type: string
          description: >-
            The comparison in words, worded as an observation rather than a
            cause. Names the issues that moved.
        rule:
          type: string
          description: >-
            What the comparison settled, in plain words: whether there are
            enough calls on each version yet, and whether the difference is real
            and which way it went. Kept under its old name: it no longer states
            how a change is decided.
          example: Not enough calls on each version yet.
        isRandomized:
          type: boolean
          description: >-
            Always false: versions are compared before and after, never in a
            controlled experiment.
      required:
        - conclusion
        - conclusionLabel
        - callsBefore
        - callsAfter
        - metrics
        - issueMoves
        - narrative
        - rule
        - isRandomized
      description: >-
        What production did from `from` to `to`, read the same way as a stored
        comparison and the web app's version page, so its Got it done on each
        side equals that version's `gotItDone` in the version list. Any pair,
        recorded or not.
    Comparison:
      type:
        - object
        - 'null'
      properties:
        object:
          type: string
          enum:
            - comparison
        id:
          type: string
          format: uuid
          pattern: >-
            ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$
          example: 3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f
        fromVersion:
          type: integer
        toVersion:
          type: integer
        configCaptured:
          type: object
          properties:
            from:
              type: boolean
            to:
              type: boolean
          required:
            - from
            - to
          description: >-
            Whether each version's configuration was captured. When either was
            not, `changes` is empty because nothing could be diffed, not because
            nothing changed.
        changes:
          type: array
          items:
            $ref: '#/components/schemas/ConfigChange'
          description: >-
            What changed in the configuration between the two versions, as
            recorded.
        result:
          allOf:
            - $ref: '#/components/schemas/ComparisonResult'
            - description: >-
                What production did on each version, computed on request from
                the calls stored now, by the health board's own metrics and
                verdicts.
        createdAt:
          type: string
          format: date-time
          example: '2026-10-02T14:03:07.120Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-10-02T14:03:07.120Z'
      required:
        - object
        - id
        - fromVersion
        - toVersion
        - configCaptured
        - changes
        - result
        - createdAt
        - updatedAt
      description: >-
        The stored comparison for exactly this pair, in this order, when
        SignalMD recorded one (its id is what get_comparison takes). Null
        otherwise; most pairs that are not consecutive versions have none. Its
        result reads the same calls as `result`.
    ErrorType:
      type: string
      enum:
        - invalid_request_error
        - authentication_error
        - budget_error
        - permission_error
        - not_found_error
        - conflict_error
        - rate_limit_error
        - api_error
    Location:
      type:
        - object
        - 'null'
      properties:
        kind:
          type: string
          enum:
            - node
            - edge
            - tool
            - global
          description: >-
            "node" is a step of the flow, "edge" a transition between two steps,
            "tool" a function the agent calls, "global" the whole call (no one
            place).
        id:
          type: string
          description: >-
            The step id, `from->to` for a transition, the tool's name, or
            `global`.
        label:
          type: string
          description: >-
            How it reads to a person: the step's name, `From → To`, the tool's
            name, or `Whole call`.
      required:
        - kind
        - id
        - label
      description: Where it happens.
    ComparisonConclusion:
      type: string
      enum:
        - insufficient_evidence
        - not_comparable
        - no_observed_change
        - observed_improvement
        - observed_regression
        - mixed_result
      description: >-
        What the before and after numbers say, and no more. Versions ship to all
        traffic at once, so none of these establishes that the change caused the
        movement: something else may have changed at the same time.


        "insufficient_evidence": too few calls on a side to say. Nothing is
        established either way; do not read it as "no change".


        "not_comparable": the versions could not be compared the same way.


        "no_observed_change": nothing got better or worse. A metric with no
        direction (handed_off) may still have moved; the narrative says so.


        "observed_improvement": an overall improvement.


        "observed_regression": an overall regression.


        "mixed_result": changes in both directions.
    MetricComparison:
      type: object
      properties:
        metric:
          type: string
          enum:
            - got_it_done
            - went_wrong
            - hung_up_mid_task
            - handed_off
            - tool_failed
            - reply_time
          description: >-
            A health board catalogue id, with the board's label, denominator and
            direction, the same numbers the board and the web app's version page
            show. Each side's `n` is the metric's denominator on that version.


            "got_it_done" (Got it done, the headline): share of calls where the
            caller spoke and the call has been read whose caller got what they
            came for. Higher is better. The same numerator and denominator as a
            version's `gotItDone` in the version list.


            "went_wrong" (Calls with a bad outcome): share of spoke calls with
            an outcome reading that ended with at least one bad outcome for the
            caller or the company. Lower is better.


            "hung_up_mid_task" (Hung up mid-task): share of spoke calls with an
            outcome reading where the caller left while something was still
            under way. Lower is better.


            "handed_off" (Handed to a person): share of spoke calls the platform
            ended in a transfer. No better direction: a handoff can be by
            design, so it is shown and never judged.


            "tool_failed" (Calls with a failed tool): share of calls where a
            tool ran and at least one failed (an error, a timeout, an empty or
            rejected answer). Lower is better.


            "reply_time" (Reply time): median seconds from the end of the
            caller's last word to the start of the agent's reply, over every
            reply on the version; `n` counts calls with a timed reply. Lower is
            better.
        label:
          type: string
          example: Got it done
        unit:
          type: string
          enum:
            - share
            - seconds
        before:
          type: object
          properties:
            value:
              type:
                - number
                - 'null'
              description: >-
                In the metric's unit: a share is 0 to 1, a time is seconds. Null
                when nothing is in the denominator.
            'n':
              type: integer
              description: Calls in the metric's denominator.
          required:
            - value
            - 'n'
          description: On the older version.
        after:
          type: object
          properties:
            value:
              type:
                - number
                - 'null'
              description: >-
                In the metric's unit: a share is 0 to 1, a time is seconds. Null
                when nothing is in the denominator.
            'n':
              type: integer
              description: Calls in the metric's denominator.
          required:
            - value
            - 'n'
          description: On the newer version.
        delta:
          type:
            - number
            - 'null'
          description: >-
            after minus before, in the metric's unit. A share's 0.12 is 12
            points.
        better:
          type:
            - string
            - 'null'
          enum:
            - higher
            - lower
            - null
          description: >-
            Which way is better. Null for "handed_off": a handoff can be by
            design, so a move either way is shown and never judged.
        verdict:
          type:
            - string
            - 'null'
          enum:
            - changed
            - too_small
            - within_noise
            - too_few
            - not_comparable
            - null
          description: >-
            The board's verdict on this row. "changed": a real movement, beyond
            chance and large enough to count; only this verdict is a movement.
            "too_small": real but too small to count. "within_noise": could be
            chance, which is not the same as no change. "too_few": too few calls
            on a side, so nothing is established either way. "not_comparable":
            the two sides were not measured the same way, or the reading is
            missing. Null when the board could not judge the row at all.
        direction:
          type:
            - string
            - 'null'
          enum:
            - better
            - worse
            - null
          description: >-
            Set only when the verdict is "changed" and the metric has a better
            direction.
        minimum:
          type: string
          description: >-
            This row's verdict in plain words, such as `The difference is real.`
            or `Not enough calls on each version yet.` Kept under its old name:
            it no longer gives a size.
          example: The difference is real.
        notRead:
          type: integer
          description: >-
            Calls left out on either side because the reading this metric needs
            is not stored yet.
      required:
        - metric
        - label
        - unit
        - before
        - after
        - delta
        - better
        - verdict
        - direction
        - minimum
        - notRead
    IssueMove:
      type: object
      properties:
        issueId:
          type: string
          description: The issue, as get_issue reads it.
        title:
          type: string
        place:
          type:
            - string
            - 'null'
          description: The step or tool it happens at.
        before:
          type: object
          properties:
            value:
              type:
                - number
                - 'null'
              description: >-
                In the metric's unit: a share is 0 to 1, a time is seconds. Null
                when nothing is in the denominator.
            'n':
              type: integer
              description: Calls in the metric's denominator.
          required:
            - value
            - 'n'
          description: >-
            Share of the older version's calls (caller spoke, call read) that
            the issue holds.
        after:
          type: object
          properties:
            value:
              type:
                - number
                - 'null'
              description: >-
                In the metric's unit: a share is 0 to 1, a time is seconds. Null
                when nothing is in the denominator.
            'n':
              type: integer
              description: Calls in the metric's denominator.
          required:
            - value
            - 'n'
          description: The same on the newer version.
        callsBefore:
          type: integer
        callsAfter:
          type: integer
        delta:
          type:
            - number
            - 'null'
          description: after minus before, as a share.
        verdict:
          type: string
          enum:
            - changed
            - too_small
            - within_noise
            - too_few
            - not_comparable
          description: The same verdicts as a metric row (see MetricComparison).
        direction:
          type:
            - string
            - 'null'
          enum:
            - better
            - worse
            - null
          description: >-
            Lower is better for an issue. Set only when the verdict is
            "changed".
        atChangedPlace:
          type: boolean
          description: >-
            The issue's step or tool is named in the configuration change. A
            place match, not evidence the change caused the movement.
      required:
        - issueId
        - title
        - place
        - before
        - after
        - callsBefore
        - callsAfter
        - delta
        - verdict
        - direction
        - atChangedPlace
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: An API key (`sig_...`), or a session token from the web app.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.