> ## 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.

# Report business outcomes attributed to traces

> Report a batch of business outcomes (a deflected ticket, an avoided refund, an SLA met) attributed to traces your SDK already emitted. This is a direct, synchronous write to ClickHouse — not routed through the same async queue as `POST /v1/ingest` — since it's a single low-volume out-of-band call rather than a batched stream of trace events.

The trace an outcome names does not need to exist in Zespan yet. Outcomes and traces are joined at query time (`GET /v1/projects/{id}/outcomes/summary`), not at write time, so an outcome that arrives before its trace has finished ingesting is stored and joined correctly once the trace shows up. This is the expected shape, not an edge case: an outcome is usually known minutes or hours after the trace that produced it has already finished, from a customer's own backend (a support-desk webhook, a billing reconciliation job) — not from the same process that ran the agent.

Re-reporting the same `(kind, traceId)` pair is how you correct an earlier outcome — for example, a ticket that reopens after you'd already reported `ticket_deflected: true`. It is not an update to the earlier row; it's a new one, and the most recently ingested value is what reads (including the summary endpoint below) return.

**Authenticated with `x-api-key`**, the same as the rest of ingestion — this route is never reachable with a dashboard session.




## OpenAPI

````yaml /api-reference/openapi.yaml post /v1/ingest/outcomes
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`.
paths:
  /v1/ingest/outcomes:
    post:
      tags:
        - Outcomes
      summary: Report business outcomes attributed to traces
      description: >
        Report a batch of business outcomes (a deflected ticket, an avoided
        refund, an SLA met) attributed to traces your SDK already emitted. This
        is a direct, synchronous write to ClickHouse — not routed through the
        same async queue as `POST /v1/ingest` — since it's a single low-volume
        out-of-band call rather than a batched stream of trace events.


        The trace an outcome names does not need to exist in Zespan yet.
        Outcomes and traces are joined at query time (`GET
        /v1/projects/{id}/outcomes/summary`), not at write time, so an outcome
        that arrives before its trace has finished ingesting is stored and
        joined correctly once the trace shows up. This is the expected shape,
        not an edge case: an outcome is usually known minutes or hours after the
        trace that produced it has already finished, from a customer's own
        backend (a support-desk webhook, a billing reconciliation job) — not
        from the same process that ran the agent.


        Re-reporting the same `(kind, traceId)` pair is how you correct an
        earlier outcome — for example, a ticket that reopens after you'd already
        reported `ticket_deflected: true`. It is not an update to the earlier
        row; it's a new one, and the most recently ingested value is what reads
        (including the summary endpoint below) return.


        **Authenticated with `x-api-key`**, the same as the rest of ingestion —
        this route is never reachable with a dashboard session.
      operationId: ingestOutcomes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                outcomes:
                  type: array
                  minItems: 1
                  maxItems: 100
                  description: >
                    Up to 100 outcomes per request — the same per-batch cap
                    `POST /v1/ingest` uses for native SDK events, reused rather
                    than inventing a second limit for a second ingest endpoint.
                  items:
                    $ref: '#/components/schemas/Outcome'
              required:
                - outcomes
      responses:
        '201':
          description: Batch accepted and written.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutcomeIngestResponse'
        '400':
          description: >
            Validation error — e.g. more than 100 outcomes in one batch, an
            empty `outcomes` array, or an item failing its own field validation
            (`kind` over 100 characters, missing `traceId`, etc.).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: >
            Missing or invalid API key. Unlike the generic `Error` shape used
            elsewhere on this page, this route's 401 body is `{ "message": "API
            key required" }`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: API key required
components:
  schemas:
    Outcome:
      type: object
      description: >
        A single business outcome to report, attributed to one trace. Sent as an
        array wrapped in `{ outcomes: [...] }` — a batch of 1 to 100 per
        request.
      properties:
        kind:
          type: string
          minLength: 1
          maxLength: 100
          description: >
            A free-form label for what this outcome measures — no fixed enum.
            Whatever strings you report show up as filterable dimensions on the
            Value dashboard page.
          example: ticket_deflected
        success:
          type: boolean
          description: Whether this outcome was actually achieved.
        valueUsd:
          type: number
          minimum: 0
          description: >
            Dollar value of this outcome, if known. Omit entirely for "unknown"
            rather than sending `0`.
          example: 12.5
        attributes:
          type: object
          additionalProperties:
            oneOf:
              - type: string
              - type: number
              - type: boolean
          description: >
            Free-form metadata, stored as given. The TypeScript SDK's
            `outcome()` coerces every value to a string before sending, to match
            how every other Zespan SDK surface treats attributes; the raw ingest
            API itself accepts string, number, or boolean.
        traceId:
          type: string
          minLength: 1
          description: >
            The trace this outcome is attributed to. The trace does not need to
            exist in Zespan yet — outcomes and traces are joined at query time,
            not write time.
        sessionId:
          type: string
        agentName:
          type: string
          description: >
            The agent that produced the trace. Populating this is what lets the
            Value dashboard's "By agent" breakdown group correctly.
        environment:
          type: string
          maxLength: 50
          description: >
            Environment slug (e.g. `prod`, `staging`). Historical free-text
            values are aliased the same way the rest of ingestion aliases them
            (`production` -> `prod`, etc.).
        occurredAt:
          type: string
          format: date-time
          description: >
            When this outcome actually occurred. Defaults to the time the
            request is received.
      required:
        - kind
        - success
        - traceId
    OutcomeIngestResponse:
      type: object
      properties:
        accepted:
          type: integer
          description: Number of outcomes accepted from the batch.
          example: 1
      required:
        - accepted
    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
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Project API key. Manage keys in the Zespan dashboard under project
        settings.

````