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

# Register a Google Sheet as a source

> Register a Google Sheet as a reusable contact source. The source is a pointer to the live sheet, not a copy: a campaign reads whatever rows the sheet holds at that moment, so to change the contacts you edit the sheet. The registration itself never changes — to point at a different sheet or use a different column mapping, register a new source.

Nothing is checked at registration today (this may change): a wrong spreadsheet id, an unshared sheet, or an unknown `connectionId` all register fine and only fail later — so [preview the source](/api-reference/google-sheet-sources/re-check-a-registered-sheet) right after creating it.

Only a Google Sheet can be registered here. For a CSV or pasted rows, [create a contact list](/api-reference/contacts/create-a-contact-list) instead — a one-time import rather than a live source. Column naming rules: [Special column names](/voice-agents/campaigns/contacts#special-column-names).



## OpenAPI

````yaml /voice-agents/api-reference/openapi.yaml post /contact-sources
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:
    post:
      tags:
        - Google Sheet sources
      summary: Register a Google Sheet as a source
      description: >-
        Register a Google Sheet as a reusable contact source. The source is a
        pointer to the live sheet, not a copy: a campaign reads whatever rows
        the sheet holds at that moment, so to change the contacts you edit the
        sheet. The registration itself never changes — to point at a different
        sheet or use a different column mapping, register a new source.


        Nothing is checked at registration today (this may change): a wrong
        spreadsheet id, an unshared sheet, or an unknown `connectionId` all
        register fine and only fail later — so [preview the
        source](/api-reference/google-sheet-sources/re-check-a-registered-sheet)
        right after creating it.


        Only a Google Sheet can be registered here. For a CSV or pasted rows,
        [create a contact list](/api-reference/contacts/create-a-contact-list)
        instead — a one-time import rather than a live source. Column naming
        rules: [Special column
        names](/voice-agents/campaigns/contacts#special-column-names).
      operationId: createContactSource
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateContactSourceRequest'
            examples:
              publicSheet:
                summary: Public link-shared sheet
                value:
                  name: Edversity admissions leads
                  kind: google_sheets
                  ref: 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms!Admissions
                  mapping:
                    nameColumn: طالب علم کا نام
                    phoneColumn: موبائل
                    attributeColumns:
                      - پروگرام
                      - شہر
              privateSheet:
                summary: Private sheet via Google connection
                value:
                  name: Clinic patient roster
                  kind: google_sheets
                  ref: 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms!Patients
                  connectionId: 7d21c9b4-xxxx-xxxx-xxxx-xxxxxxxxxxxx
      responses:
        '201':
          description: The registered source.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactSource'
              example:
                sourceId: c616feca-xxxx-xxxx-xxxx-xxxxxxxxxxxx
                organizationId: a12b7e74-xxxx-xxxx-xxxx-xxxxxxxxxxxx
                projectId: c782a4ec-xxxx-xxxx-xxxx-xxxxxxxxxxxx
                name: Edversity admissions leads
                kind: google_sheets
                ref: 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms!Admissions
                mapping:
                  nameColumn: طالب علم کا نام
                  phoneColumn: موبائل
                  attributeColumns:
                    - پروگرام
                    - شہر
                createdAt: '2026-08-18T05:19:03.311Z'
                updatedAt: '2026-08-18T05:19:03.311Z'
                version: 1
        '400':
          description: Validation failed, or `kind` was not `google_sheets`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                message:
                  - name must be shorter than or equal to 120 characters
                  - name should not be empty
                  - name must be a string
                  - >-
                    Only 'google_sheets' connected sources can be registered;
                    use the preview endpoint for csv/paste uploads
                  - ref should not be empty
                  - ref must be a string
                error: Bad Request
                statusCode: 400
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
components:
  schemas:
    CreateContactSourceRequest:
      oneOf:
        - title: Public link-shared sheet
          type: object
          required:
            - name
            - kind
            - ref
          properties:
            name:
              type: string
              minLength: 1
              maxLength: 120
              description: Label for the source.
            kind:
              type: string
              enum:
                - google_sheets
            ref:
              type: string
              minLength: 1
              description: >-
                Spreadsheet id from the sheet URL, optionally `!` plus an A1
                range or tab name (`{id}!Leads`, `{id}!A:C`). A bare id reads
                columns A–ZZ of the first tab. The sheet must be shared as
                anyone-with-the-link; it is read with the platform's own
                credential.
            mapping:
              $ref: '#/components/schemas/ContactColumnMapping'
        - title: Private sheet via Google connection
          type: object
          required:
            - name
            - kind
            - ref
            - connectionId
          properties:
            name:
              type: string
              minLength: 1
              maxLength: 120
              description: Label for the source.
            kind:
              type: string
              enum:
                - google_sheets
            ref:
              type: string
              minLength: 1
              description: >-
                Spreadsheet id from the sheet URL, optionally `!` plus an A1
                range or tab name (`{id}!Leads`, `{id}!A:C`). A bare id reads
                columns A–ZZ of the first tab.
            connectionId:
              type: string
              format: uuid
              description: >-
                The Google Sheets connection you added on the
                [portal](https://upliftai.org/app/calling/webhooks), whose
                account can read the sheet. No link-sharing needed.
            mapping:
              $ref: '#/components/schemas/ContactColumnMapping'
    ContactSource:
      type: object
      properties:
        sourceId:
          type: string
          format: uuid
        organizationId:
          type: string
          format: uuid
        projectId:
          type: string
          format: uuid
        name:
          type: string
        kind:
          type: string
          enum:
            - google_sheets
        ref:
          type: string
        connectionId:
          type: string
          format: uuid
        mapping:
          $ref: '#/components/schemas/ContactColumnMapping'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        version:
          type: integer
          description: Sources cannot be edited, so this stays `1`.
      required:
        - sourceId
        - organizationId
        - projectId
        - name
        - kind
        - ref
        - createdAt
        - updatedAt
        - version
    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
  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_…`).

````