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

# List a campaign's calls

> One row per enrolled contact per campaign run, showing that contact's latest attempt — never-dialed contacts included. Earlier attempts collapse into the latest one, and a relaunch or recurrence can put the same `contactId` on the page twice. Rows come in no particular order, so don't read the top of the list as the most recent. Where `callId` is present it is the session id. Pass it to the [session detail](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes) for that call's transcript and recording.

**Note:** Scores, conversions, and sentiment are computed by LLMs — best effort, with room to be wrong. We are continuously closing the tracking gaps, but for sensitive workloads we recommend running your own analysis on the transcripts delivered over [webhooks](/voice-agents/webhooks/overview).



## OpenAPI

````yaml /voice-agents/api-reference/openapi.yaml get /campaigns/{campaignId}/calls
openapi: 3.1.0
info:
  title: UpliftAI Voice Agents API
  version: '1.0'
  description: >-
    Build voice assistants, start conversations on the web or the phone, run
    calling campaigns, and receive outcomes by webhook. All endpoints take a
    bearer API key unless noted otherwise.
servers:
  - url: https://api.upliftai.org/v1
security:
  - apiKey: []
tags:
  - name: Assistants
  - name: Authoring an assistant
  - name: Starting a conversation
  - name: Sessions & call records
  - name: Campaigns
  - name: Campaign outcomes
  - name: Contacts
  - name: Google Sheet sources
  - name: Integrations & delivery health
paths:
  /campaigns/{campaignId}/calls:
    get:
      tags:
        - Campaign outcomes
      summary: List a campaign's calls
      description: >-
        One row per enrolled contact per campaign run, showing that contact's
        latest attempt — never-dialed contacts included. Earlier attempts
        collapse into the latest one, and a relaunch or recurrence can put the
        same `contactId` on the page twice. Rows come in no particular order, so
        don't read the top of the list as the most recent. Where `callId` is
        present it is the session id. Pass it to the [session
        detail](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes)
        for that call's transcript and recording.


        **Note:** Scores, conversions, and sentiment are computed by LLMs — best
        effort, with room to be wrong. We are continuously closing the tracking
        gaps, but for sensitive workloads we recommend running your own analysis
        on the transcripts delivered over
        [webhooks](/voice-agents/webhooks/overview).
      operationId: listCampaignCalls
      parameters:
        - name: campaignId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          example: cb5401ef-98f7-49e7-8a6e-822fc54e697f
        - name: limit
          in: query
          required: false
          description: >-
            Rows per page. No maximum, though response size caps every page
            whether you set this or not.
          schema:
            type: integer
            minimum: 1
          example: 50
        - name: cursor
          in: query
          required: false
          description: The previous page's `nextCursor`, verbatim.
          schema:
            type: string
        - name: include_summary
          in: query
          required: false
          description: >-
            Adds each call's `summary` to its row, up to about 1 KB each. Must
            be exactly `true` or `false`, anything else is a 400. A row with no
            summary leaves the key out: a call that never connected, one not
            summarised yet, or one from before summaries.
          schema:
            type: boolean
            default: false
          example: true
      responses:
        '200':
          description: One page of the campaign's contacts and their latest call.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignCallList'
              examples:
                populated:
                  summary: >-
                    A graded call, a busy number waiting on its retry, and a
                    contact not yet dialed
                  value:
                    calls:
                      - callId: 7c1f9ab4-xxxx-xxxx-xxxx-xxxxxxxxxxxx#+923001234567#a1
                        contactId: '+923001234567'
                        name: عائشہ صدیقی
                        phone: '+923001234567'
                        state: done
                        attempts: 1
                        nextAttemptAt: 1787030462711
                        connected: true
                        startedAt: '2026-08-18T05:16:02.711Z'
                        durationSec: 74
                        score: 82
                        sentiment: happy
                      - callId: 7c1f9ab4-xxxx-xxxx-xxxx-xxxxxxxxxxxx#+923214455661#a2
                        contactId: '+923214455661'
                        name: بلال احمد
                        phone: '+923214455661'
                        state: retry_wait
                        attempts: 2
                        nextAttemptAt: 1787034062711
                        outcome: busy
                        connected: false
                        startedAt: '2026-08-18T05:31:44.902Z'
                        endReason: busy:486
                        failureReason: busy
                        failureReasonWithCode: busy:486
                      - contactId: '+923339988774'
                        name: فرحان قریشی
                        phone: '+923339988774'
                        state: queued
                        attempts: 0
                        nextAttemptAt: 1787030400000
                    nextCursor: eyJjYW1wYWlnbklkIjoiN2MxZjlhYjQtxxxx
                empty:
                  summary: A campaign with nothing enrolled yet
                  value:
                    calls: []
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: No such campaign in the API key's project.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                message: 'Campaign not found: 00000000-0000-4000-8000-000000000000'
                error: Not Found
                statusCode: 404
components:
  schemas:
    CampaignCallList:
      type: object
      properties:
        calls:
          type: array
          items:
            $ref: '#/components/schemas/CampaignCallSummary'
        nextCursor:
          type: string
          description: >-
            Absent on the last page. Keep fetching while it is present — an
            intermediate page can come back empty without being the last.
    Error:
      type: object
      properties:
        message:
          description: >-
            One message per failed field for validation errors, a single string
            otherwise.
          oneOf:
            - type: string
            - type: array
              items:
                type: string
        error:
          type: string
        statusCode:
          type: integer
    CampaignCallSummary:
      type: object
      description: >-
        A contact's enrollment joined to its most recent call. Everything from
        `outcome` down is read off that call's session record, so all of it is
        absent until the contact has been dialed.
      properties:
        callId:
          type: string
          description: >-
            Session id of the latest attempt, `{campaignId}#{contactId}#a{n}`.
            Pass it to the [session
            detail](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes)
            for the transcript and recording. Absent while `attempts` is 0.
          example: 7c1f9ab4-xxxx-xxxx-xxxx-xxxxxxxxxxxx#+923001234567#a1
        contactId:
          type: string
          description: >-
            Identity within the campaign: the phone number as the contact list
            supplied it, or `evt#{externalRef}` for contacts appended under your
            own reference.
        name:
          type: string
        phone:
          type: string
          description: E.164, as dialed.
          example: '+923001234567'
        state:
          type: string
          description: >-
            What the dialer will do with this contact next — not how the last
            call went. `exhausted` means the retry budget ran out, `skipped`
            means the contact was excluded before dialing (do-not-call, a cap, a
            calling window, an unusable number).
          enum:
            - queued
            - dialing
            - retry_wait
            - done
            - exhausted
            - skipped
        skippedReason:
          type: string
          enum:
            - window_closed
            - org_cap
            - campaign_cap
            - no_credits
            - dnc
            - invalid_number
            - recently_called
          description: >-
            Only on `state: skipped`. The first four are about the moment the
            dialer looked, and can clear. `dnc`, `invalid_number` and
            `recently_called` are about the number itself.
        attempts:
          type: integer
          description: Dials placed so far. Never reset, so it also numbers the `callId`.
        nextAttemptAt:
          type: integer
          description: >-
            Epoch milliseconds at which this contact becomes dialable. Only
            meaningful while `state` is `queued` or `retry_wait`; terminal rows
            keep whatever it last held.
          example: 1787034062711
        outcome:
          type: string
          description: >-
            Why the last attempt did not become a conversation — the coarse
            bucket, with `failureReason` the finer one. Absent when the call
            connected. While `state` is `dialing` it reads `no_answer` as a
            placeholder, not a result.
          enum:
            - voicemail
            - busy
            - no_answer
            - unreachable
            - silent_pickup
            - failed
        connected:
          type: boolean
          description: >-
            Whether the last attempt reached a live conversation. Whether it
            converted is a separate verdict, in
            [`/campaigns/{campaignId}/results`](/api-reference/campaign-outcomes/list-a-campaigns-outcomes).
        startedAt:
          type: string
          format: date-time
          description: When the last attempt was placed.
        durationSec:
          type: integer
          description: >-
            Talk time, answer to hangup. Absent on calls that were never
            answered.
        score:
          type:
            - integer
            - 'null'
          minimum: 0
          maximum: 100
          description: >-
            Percent of the applicable points earned. `null` when every rule was
            N/A. The campaign scorecard's grade, present once post-call analysis
            has run. The per-criterion breakdown is on the [session
            detail](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes).
        failureReason:
          type: string
          description: >-
            The last attempt's failure, in the same vocabulary the session
            endpoints use. Absent when the call connected.
          enum:
            - wrong_number
            - busy
            - declined
            - no_answer
            - unreachable
            - voicemail
            - silent_pickup
            - network_error
            - call_failed
        failureReasonWithCode:
          type: string
          description: >-
            `failureReason` with the carrier's SIP code appended where there was
            one, as in `busy:486`.
          example: no_answer:408
        sentiment:
          type: string
          description: The grader's read on the caller. Present once graded.
          enum:
            - happy
            - neutral
            - annoyed
        assistantVersion:
          type: integer
          description: >-
            The assistant version the latest attempt ran. Explains a change in
            behaviour mid-run.
        assistantAlias:
          type: string
          enum:
            - prod
            - draft
          description: >-
            The alias the latest attempt was launched on. Absent when the
            campaign pins a version number.
        summary:
          allOf:
            - $ref: '#/components/schemas/ConversationSummary'
          description: >-
            Only with `include_summary=true`. Absent, not null, when there is
            none.
    ConversationSummary:
      type: object
      description: >-
        Call summary: how it went, in a headline and a few bullets, plus the
        facts that came up. In English. Not available for adhoc sessions.
      properties:
        headline:
          type: string
          description: One sentence, end state first.
          example: >-
            Farmer agreed to try one bottle of Cruiser on the next corn crop and
            asked for an expert callback.
        bullets:
          type: array
          description: Usually three to five, in call order. Events, not values.
          items:
            type: string
          example:
            - >-
              Agent confirmed the farmer and introduced Cruiser seed treatment
              for corn
            - Farmer agreed to try one bottle on the next sowing
            - >-
              Farmer asked about rice pollination temperature and wanted an
              expert to call
        facts:
          type: array
          description: >-
            One per distinct fact, in display order. The first is the person's
            Decision or the Next Step when one exists.
          items:
            $ref: '#/components/schemas/ConversationSummaryFact'
        language:
          type: string
          enum:
            - en
        promptVersion:
          type: string
          example: call-summary-v1
        model:
          type: string
          description: The model that wrote it.
          example: gpt-5.4-2026-03-05
        generatedAt:
          type: string
          format: date-time
      required:
        - headline
        - bullets
        - facts
        - language
        - promptVersion
        - model
        - generatedAt
    ConversationSummaryFact:
      type: object
      properties:
        label:
          type: string
          description: >-
            One to three words, Title Case, e.g. `Decision`, `Delivery Address`,
            `Complaint Ref`.
          example: Decision
        value:
          type: string
          description: Up to fifteen words, English, Latin script.
          example: Will try one bottle on the next corn crop
      required:
        - label
        - value
  responses:
    Unauthorized:
      description: A bearer key was sent but is not recognized.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: invalid authorization
            error: Unauthorized
            statusCode: 401
    Forbidden:
      description: No API key sent, or the key lacks permission for this resource.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: Forbidden resource
            error: Forbidden
            statusCode: 403
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: Project API key (`sk_api_…`).

````