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

# Per-model drill-down — usage, reliability, quality, and cost-vs-quality frontier

> The follow-up question to the Models table: everything about ONE model over the selected range. Backs the Model detail dashboard page reached by clicking a model row.

Quality figures are computed directly from `evaluation_scores` grouped by `model`, not through the `llm_events`↔`evaluation_scores` span join the Cost vs Quality page uses — there is no fan-out to correct for when both sides are grouped by model directly.

`frontier.points` lists every OTHER model called in the project over the same range with its own cost and quality, each flagged `cheaper` against this model and carrying a `qualityDelta` (`null` when either side has no quality score yet) — the data behind "should I switch this workload?".

Returns `found: false` with empty panels (200, not 404) for a model with zero calls in range, rather than erroring — a model can be renamed, retired, or simply unused in the chosen window.

**Authenticated with a dashboard session (browser cookie), not `x-api-key`**, and requires the `dashboard:read` permission.




## OpenAPI

````yaml /api-reference/openapi.yaml get /v1/projects/{id}/models/{model}
openapi: 3.1.0
info:
  title: Zespan Public API
  version: 1.0.0
  description: >
    The Zespan Public API covers the endpoints that customers call directly or
    through the Zespan SDKs: trace ingestion (native and OpenTelemetry), prompt
    management, datasets and dataset runs, and the runtime guardrails check.

    All endpoints authenticate with a project API key sent in the `x-api-key`
    header. Create and manage API keys from the project settings in the Zespan
    dashboard.
servers:
  - url: https://api.zespan.com
    description: Zespan production API
security:
  - ApiKeyAuth: []
tags:
  - name: Ingestion
    description: Send traces and events to Zespan.
  - name: OpenTelemetry
    description: OTLP-compatible ingestion endpoints.
  - name: Prompts
    description: Manage versioned prompts and their labels, tags, and folders.
  - name: Datasets
    description: Read datasets and manage dataset runs used for experiments and scoring.
  - name: Guardrails
    description: Runtime guardrail evaluation.
  - name: SDK / CLI support
    description: >
      Small support endpoints consumed by the SDKs and @zespan/cli rather than
      called directly by application code.
  - name: Blast Radius
    description: >
      The prompt/agent/model/policy/evaluator/alert dependency graph backing the
      pre-release impact check and the evaluator-delete gate in the dashboard.
      Session-authenticated (dashboard cookie), not `x-api-key`.
  - name: Outcomes
    description: >
      Report business outcomes (a deflected ticket, an avoided refund, an SLA
      met) attributed to a trace, and read them back summarized by agent or
      model, joined to real trace cost. Backs the Value dashboard page. The
      ingest endpoint is `x-api-key`-authenticated like the rest of ingestion;
      the two read endpoints are session-authenticated (dashboard cookie) like
      Blast Radius.
  - name: Compliance
    description: >
      Generate audit-ready evidence documents (a per-agent Compliance Card, or
      SOC 2 control evidence) from recorded platform data, and re-verify a
      generated document's citations against live data. Session-authenticated
      (dashboard cookie), not `x-api-key`, gated by `compliance:read` /
      `compliance:generate` permissions and the Pro plan or above (the framework
      listing is the one exception — no project scope and no plan gate, since a
      customer deciding whether to upgrade needs to see what they'd get).
  - name: Models
    description: >
      Per-model usage, cost, latency, and error-rate rollups for a project,
      including the lifecycle overlay described under the Model Lifecycle tag.
      Session-authenticated (dashboard cookie), not `x-api-key`, gated by
      `dashboard:read`.
  - name: Model Lifecycle
    description: >
      Findings from the daily model deprecation scan, which matches models a
      project actually calls against a curated, bundled catalogue of
      provider-announced deprecation and retirement dates. Every figure on a
      finding (call volume, cost, affected agents/prompts, cost comparison
      against a named successor) is measured from real trace data — there is no
      quality-delta or regression-comparison endpoint, because nothing in this
      API invokes a model on the caller's behalf. Session-authenticated
      (dashboard cookie), not `x-api-key`: reading findings and the catalogue
      requires `dashboard:read`, dismissing a finding requires `alerts:manage`.
  - name: Policies
    description: >
      The organisation-wide policy rollup backing the top-level **Policies**
      entry in the org admin sidebar: every policy id across every project,
      grouped by id, plus the org's recent apply history. These endpoints report
      what is deployed — they never declare a project non-compliant, because no
      standard exists yet for a project to be measured against. `orgId` in the
      path selects the scope: it accepts either the organisation's slug or its
      id, and both routes 403 if the caller is not a member of it.
      Session-authenticated (dashboard cookie), not `x-api-key`, gated by
      `policy:read`, which every role has.
paths:
  /v1/projects/{id}/models/{model}:
    get:
      tags:
        - Models
      summary: >-
        Per-model drill-down — usage, reliability, quality, and cost-vs-quality
        frontier
      description: >
        The follow-up question to the Models table: everything about ONE model
        over the selected range. Backs the Model detail dashboard page reached
        by clicking a model row.


        Quality figures are computed directly from `evaluation_scores` grouped
        by `model`, not through the `llm_events`↔`evaluation_scores` span join
        the Cost vs Quality page uses — there is no fan-out to correct for when
        both sides are grouped by model directly.


        `frontier.points` lists every OTHER model called in the project over the
        same range with its own cost and quality, each flagged `cheaper` against
        this model and carrying a `qualityDelta` (`null` when either side has no
        quality score yet) — the data behind "should I switch this workload?".


        Returns `found: false` with empty panels (200, not 404) for a model with
        zero calls in range, rather than erroring — a model can be renamed,
        retired, or simply unused in the chosen window.


        **Authenticated with a dashboard session (browser cookie), not
        `x-api-key`**, and requires the `dashboard:read` permission.
      operationId: getModelDetail
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Project id.
        - name: model
          in: path
          required: true
          schema:
            type: string
          description: >
            Model id exactly as recorded on `llm_events.model` (e.g.
            `gpt-5.6-luna`, `claude-opus-5`). URL-encode the segment — model ids
            commonly contain dots and sometimes slashes (`provider/model`).
        - name: range
          in: query
          schema:
            type: string
            enum:
              - 7d
              - 30d
              - 90d
            default: 30d
      responses:
        '200':
          description: >-
            Model detail, or an empty `found:false` shell if the model had zero
            calls in range.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModelDetailResponse'
        '403':
          description: >-
            Not authenticated with a dashboard session, or lacks
            `dashboard:read`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security: []
components:
  schemas:
    ModelDetailResponse:
      type: object
      description: Response of `GET /v1/projects/{id}/models/{model}`.
      properties:
        model:
          type: string
        found:
          type: boolean
          description: >-
            `false` when this model had zero calls in the selected range — every
            other field below is then `null`/empty rather than the request
            erroring.
        summary:
          $ref: '#/components/schemas/ModelDetailSummary'
          nullable: true
        timeline:
          type: array
          items:
            $ref: '#/components/schemas/ModelTimelinePoint'
          description: One row per day with at least one call, ascending.
        errorBreakdown:
          type: array
          items:
            type: object
            properties:
              error_code:
                type: string
              count:
                type: integer
            required:
              - error_code
              - count
        usage:
          type: object
          properties:
            agents:
              type: array
              items:
                $ref: '#/components/schemas/ModelUsageBreakdownRow'
            operations:
              type: array
              items:
                $ref: '#/components/schemas/ModelUsageBreakdownRow'
          required:
            - agents
            - operations
        quality:
          $ref: '#/components/schemas/ModelQualityBlock'
        frontier:
          type: object
          properties:
            thisModel:
              $ref: '#/components/schemas/ModelFrontierPoint'
              nullable: true
            points:
              type: array
              items:
                $ref: '#/components/schemas/ModelFrontierPoint'
              description: >-
                Every OTHER model called in the project over the same range,
                each carrying `cheaper`/`costDeltaPct`/`qualityDelta` relative
                to `thisModel`.
          required:
            - thisModel
            - points
      required:
        - model
        - found
        - summary
        - timeline
        - errorBreakdown
        - usage
        - quality
        - frontier
    Error:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message.
          example: Unauthorized
        code:
          type: string
          description: Machine-readable error code, when present.
          example: rate_limit_exceeded
      required:
        - error
    ModelDetailSummary:
      type: object
      properties:
        model:
          type: string
        provider:
          type: string
          nullable: true
        total_calls:
          type: integer
        total_cost:
          type: number
        avg_cost_per_call:
          type: number
        avg_cost_per_success:
          type: number
          nullable: true
          description: '`null` when every call in range errored.'
        p50_latency:
          type: number
        p95_latency:
          type: number
        p99_latency:
          type: number
        avg_ttft:
          type: number
          nullable: true
          description: '`null` when no call in range reported a time-to-first-token.'
        error_count:
          type: integer
        error_rate:
          type: number
        total_input_tokens:
          type: integer
        total_output_tokens:
          type: integer
        total_cached_tokens:
          type: integer
        total_reasoning_tokens:
          type: integer
        cache_hit_rate:
          type: number
          nullable: true
          description: '`null` when `total_input_tokens` is zero.'
        trace_count:
          type: integer
        unique_agents:
          type: integer
        unique_operations:
          type: integer
        first_seen:
          type: string
          nullable: true
        last_seen:
          type: string
          nullable: true
        share_of_calls:
          type: number
          nullable: true
          description: >-
            This model's calls as a fraction of every call in the project over
            the same range.
        share_of_cost:
          type: number
          nullable: true
      required:
        - model
        - provider
        - total_calls
        - total_cost
        - avg_cost_per_call
        - p50_latency
        - p95_latency
        - p99_latency
        - error_count
        - error_rate
        - total_input_tokens
        - total_output_tokens
        - total_cached_tokens
        - total_reasoning_tokens
        - trace_count
        - unique_agents
        - unique_operations
    ModelTimelinePoint:
      type: object
      properties:
        day:
          type: string
        calls:
          type: integer
        cost:
          type: number
        avg_latency:
          type: number
        avg_ttft:
          type: number
          nullable: true
        errors:
          type: integer
        error_rate:
          type: number
        cached_tokens:
          type: integer
        input_tokens:
          type: integer
        output_tokens:
          type: integer
      required:
        - day
        - calls
        - cost
        - avg_latency
        - errors
        - error_rate
        - cached_tokens
        - input_tokens
        - output_tokens
    ModelUsageBreakdownRow:
      type: object
      description: One row of the "Called by agents" or "Called by operation" usage tables.
      properties:
        agent_name:
          type: string
        operation:
          type: string
        calls:
          type: integer
        cost:
          type: number
        avg_latency:
          type: number
        error_rate:
          type: number
      required:
        - calls
        - cost
        - avg_latency
        - error_rate
    ModelQualityBlock:
      type: object
      properties:
        hasData:
          type: boolean
        overall:
          nullable: true
          type: object
          properties:
            avg_score:
              type: number
            eval_count:
              type: integer
            pass_count:
              type: integer
            fail_count:
              type: integer
            pass_rate:
              type: number
              nullable: true
          required:
            - avg_score
            - eval_count
            - pass_count
            - fail_count
        byEvaluator:
          type: array
          items:
            type: object
            properties:
              evaluator_slug:
                type: string
              eval_count:
                type: integer
              avg_score:
                type: number
              pass_count:
                type: integer
              fail_count:
                type: integer
            required:
              - evaluator_slug
              - eval_count
              - avg_score
              - pass_count
              - fail_count
        trend:
          type: array
          items:
            type: object
            properties:
              day:
                type: string
              eval_count:
                type: integer
              avg_score:
                type: number
            required:
              - day
              - eval_count
              - avg_score
      required:
        - hasData
        - overall
        - byEvaluator
        - trend
    ModelFrontierPoint:
      type: object
      properties:
        model:
          type: string
        callCount:
          type: integer
        avgCostUsd:
          type: number
        totalCost:
          type: number
        avgEvalScore:
          type: number
          nullable: true
        evalCount:
          type: integer
        cheaper:
          type: boolean
          description: >-
            Present only on entries in `frontier.points` — whether this model's
            avg cost/call is below `thisModel`'s.
        costDeltaPct:
          type: number
          nullable: true
          description: >-
            `(thisAvgCost - referenceAvgCost) / referenceAvgCost`; `null` if the
            reference model's avg cost is zero.
        qualityDelta:
          type: number
          nullable: true
          description: >-
            This point's avg eval score minus `thisModel`'s; `null` if either
            side has no quality score yet.
      required:
        - model
        - callCount
        - avgCostUsd
        - totalCost
        - avgEvalScore
        - evalCount
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Project API key. Manage keys in the Zespan dashboard under project
        settings.

````