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

# Dry-run a contact import

> See exactly who a campaign would dial before you commit. It takes the same inputs as [`/contact-lists`](/api-reference/contacts/create-a-contact-list): a Google Sheet `ref`, or inline `data` holding CSV text or pasted "Name, +phone" lines. Nothing is stored or registered, so run it as often as you like. The one input it doesn't take is an uploaded file — [re-preview the upload](/api-reference/contacts/re-preview-an-upload-with-a-mapping) instead.

For sheets the read is live, and the sheet must be reachable: shared as anyone-with-the-link, or read through a Google `connectionId`. When it isn't, the 500 doesn't forward Google's reason — a wrong spreadsheet id, a missing tab, and an unshared file all look identical.

Already registered the source? [Re-check it](/api-reference/google-sheet-sources/re-check-a-registered-sheet) with the stored mapping instead. Column naming rules: [Special column names](/voice-agents/campaigns/contacts#special-column-names).



## OpenAPI

````yaml /voice-agents/api-reference/openapi.yaml post /contact-sources/preview
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:
  /contact-sources/preview:
    post:
      tags:
        - Contacts
      summary: Dry-run a contact import
      description: >-
        See exactly who a campaign would dial before you commit. It takes the
        same inputs as
        [`/contact-lists`](/api-reference/contacts/create-a-contact-list): a
        Google Sheet `ref`, or inline `data` holding CSV text or pasted "Name,
        +phone" lines. Nothing is stored or registered, so run it as often as
        you like. The one input it doesn't take is an uploaded file —
        [re-preview the
        upload](/api-reference/contacts/re-preview-an-upload-with-a-mapping)
        instead.


        For sheets the read is live, and the sheet must be reachable: shared as
        anyone-with-the-link, or read through a Google `connectionId`. When it
        isn't, the 500 doesn't forward Google's reason — a wrong spreadsheet id,
        a missing tab, and an unshared file all look identical.


        Already registered the source? [Re-check
        it](/api-reference/google-sheet-sources/re-check-a-registered-sheet)
        with the stored mapping instead. Column naming rules: [Special column
        names](/voice-agents/campaigns/contacts#special-column-names).
      operationId: previewContactSource
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreviewContactSourceRequest'
            examples:
              sheet:
                summary: Preview a Google Sheet
                value:
                  kind: google_sheets
                  ref: 1kQ7bP2xLd9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx!Contacts!A:E
              paste:
                summary: Preview pasted rows
                value:
                  kind: paste
                  data: |-
                    عائشہ صدیقی, +923001234567
                    بلال احمد, 03214567890
      responses:
        '200':
          description: >-
            The sheet as it stands right now. `willBeCalled` plus every count in
            `excluded` reconciles to `total`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactSourcePreview'
              examples:
                detected:
                  summary: Columns detected, two rows dropped
                  value:
                    summary:
                      willBeCalled: 3
                      excluded:
                        invalidPhone: 1
                        missingName: 0
                        duplicate: 1
                        dnc: 0
                        recentlyCalled: 0
                      errors:
                        - row: 4
                          issue: duplicate
                          value: '03001234567'
                        - row: 5
                          issue: invalidPhone
                          value: '0345'
                    detectedMapping:
                      nameColumn: Name
                      phoneColumn: Mobile No.
                      attributeColumns:
                        - City
                        - Policy Amount
                        - Due Date
                    sample:
                      - name: عائشہ صدیقی
                        phone: '+923001234567'
                        attributes:
                          City: لاہور
                          Policy Amount: Rs 18,000
                          Due Date: 25 اگست
                      - name: بلال احمد
                        phone: '03211234567'
                        attributes:
                          City: کراچی
                          Policy Amount: Rs 42,500
                          Due Date: 27 اگست
                      - name: حمزہ شیخ
                        phone: '+923339876543'
                        attributes:
                          City: ملتان
                          Policy Amount: Rs 30,000
                          Due Date: 30 اگست
                    total: 5
                    columns:
                      - Name
                      - Mobile No.
                      - City
                      - Policy Amount
                      - Due Date
                    mappingIssues: []
                phoneColumnMissing:
                  summary: Phone column renamed — nothing dialable
                  value:
                    summary:
                      willBeCalled: 0
                      excluded:
                        invalidPhone: 2
                        missingName: 0
                        duplicate: 0
                        dnc: 0
                        recentlyCalled: 0
                      errors:
                        - row: 2
                          issue: invalidPhone
                          value: ''
                        - row: 3
                          issue: invalidPhone
                          value: ''
                    detectedMapping:
                      nameColumn: Full Name
                      attributeColumns:
                        - Mobile Number Updated
                        - Branch
                    sample: []
                    total: 2
                    columns:
                      - Full Name
                      - Mobile Number Updated
                      - Branch
                    mappingIssues:
                      - phoneColumnNotFound
        '400':
          description: >-
            The request can't be resolved to rows — `ref` missing on a
            `google_sheets` preview, `data` missing on a `csv_upload` or
            `paste`, `connectionId` sent with a non-sheet kind, or `kind`
            outside the enum.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                message: google_sheets requires `ref`
                error: Bad Request
                statusCode: 400
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: No connection under that `connectionId` in this project.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                message: 'Connection not found: 6f3d9a52-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
                error: Not Found
                statusCode: 404
        '500':
          description: >-
            The sheet could not be read: wrong spreadsheet id, no such tab, or
            it isn't shared with the platform. Google's own error is not
            forwarded.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  statusCode:
                    type: integer
              example:
                statusCode: 500
                message: Internal server error
components:
  schemas:
    PreviewContactSourceRequest:
      oneOf:
        - title: Google Sheet
          type: object
          required:
            - kind
            - ref
          properties:
            kind:
              type: string
              enum:
                - google_sheets
            ref:
              type: string
              description: >-
                The spreadsheet id, optionally followed by `!` and a tab or A1
                range (`{id}!Contacts!A:E`). A bare id reads the first tab,
                columns A–ZZ.
            connectionId:
              type: string
              description: >-
                Reads through this Google connection instead of the public-link
                path.
            mapping:
              $ref: '#/components/schemas/ContactColumnMapping'
              description: Overrides column auto-detection.
        - title: Inline CSV or pasted rows
          type: object
          required:
            - kind
            - data
          properties:
            kind:
              type: string
              enum:
                - csv_upload
                - paste
              description: >-
                `csv_upload` for CSV text with a header row; `paste` for a grid
                or one-line-per-contact rows.
            data:
              type: string
              description: >-
                The rows themselves: CSV text for `csv_upload`; for `paste`, a
                tab/comma-separated grid or one `Name, +92…` per line.
            mapping:
              $ref: '#/components/schemas/ContactColumnMapping'
              description: Overrides column auto-detection.
    ContactSourcePreview:
      type: object
      properties:
        summary:
          $ref: '#/components/schemas/ImportSummary'
        detectedMapping:
          $ref: '#/components/schemas/ContactColumnMapping'
          description: >-
            The columns actually used, after applying `mapping`. When a name was
            joined from two columns, `nameColumn` reads `First Name + Last
            Name`.
        sample:
          type: array
          maxItems: 10
          description: The first ten contacts that would be dialed.
          items:
            $ref: '#/components/schemas/NormalizedContact'
        total:
          type: integer
          description: Rows read, before validation.
        columns:
          type: array
          items:
            type: string
          description: >-
            The header row as parsed — what a `mapping` override chooses from.
            Absent for free-form paste, which has no headers.
        mappingIssues:
          type: array
          items:
            type: string
            enum:
              - phoneColumnNotFound
              - nameColumnNotFound
          description: >-
            Non-empty means column detection failed. An unmapped phone column
            then shows up as every row `invalidPhone` with an empty value.
    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
    ContactColumnMapping:
      type: object
      description: >-
        Column overrides. Whatever you leave out is auto-detected from the
        headers.
      properties:
        nameColumn:
          type: string
          description: >-
            Header name (case-insensitive) or A1 column letter. An explicit
            value never falls back to auto-detection — if it matches no column,
            names read as empty and the preview reports it in `mappingIssues`.
          example: Name
        phoneColumn:
          type: string
          description: Header name or A1 column letter, resolved like `nameColumn`.
          example: B
        attributeColumns:
          type: array
          items:
            type: string
          description: >-
            Columns to carry as per-contact attributes, usable as
            personalization variables. Exact header names only — no A1 letters.
            Omit to keep every column that is not the name or phone column; send
            `[]` to keep none.
          example:
            - City
            - Appointment
    ImportSummary:
      type: object
      description: What the import kept and dropped.
      properties:
        willBeCalled:
          type: integer
          description: Rows that became contacts.
        excluded:
          type: object
          description: >-
            Dropped rows by reason. `dnc` and `recentlyCalled` are always `0` —
            those checks do not run yet.
          properties:
            invalidPhone:
              type: integer
            missingName:
              type: integer
            duplicate:
              type: integer
              description: >-
                Same number seen twice. Numbers are compared as E.164, so
                `03001234567` and `+923001234567` collapse to one contact,
                keeping the first spelling.
            dnc:
              type: integer
            recentlyCalled:
              type: integer
        errors:
          type: array
          description: The dropped rows, first 20 only.
          items:
            type: object
            properties:
              row:
                type: integer
                description: Line in the source file, counting the header as line 1.
              issue:
                type: string
                enum:
                  - invalidPhone
                  - missingName
                  - duplicate
                  - dnc
                  - recentlyCalled
              value:
                type: string
                description: >-
                  The row's phone number: raw source text for `invalidPhone` and
                  `missingName`, normalized for `duplicate`.
    NormalizedContact:
      type: object
      properties:
        name:
          type: string
        phone:
          type: string
          description: >-
            Kept in the format the sheet used, PK national (`03…`) or E.164
            (`+92…`), with spaces and dashes stripped.
        attributes:
          type: object
          additionalProperties:
            type: string
          description: >-
            The remaining columns, handed to the assistant as a contact record
            at dial time. Truncated at 1500 bytes per contact.
  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_…`).

````