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

# Upload a contact file

> Step one of the file flow. The API detects CSV vs `.xlsx` from the contents, reads only the first sheet of a workbook, parses the rows and stores them — no contact list yet. Pass the returned `uploadId` to [`/contact-lists`](/api-reference/contacts/create-a-contact-list) to create one, or re-preview first if the detected mapping needs correcting. A missing name or phone column is not an upload error: it lands in `mappingIssues` and those rows count as excluded. Column naming rules: [Special column names](/voice-agents/campaigns/contacts#special-column-names).



## OpenAPI

````yaml /voice-agents/api-reference/openapi.yaml post /contact-lists/uploads
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-lists/uploads:
    post:
      tags:
        - Contacts
      summary: Upload a contact file
      description: >-
        Step one of the file flow. The API detects CSV vs `.xlsx` from the
        contents, reads only the first sheet of a workbook, parses the rows and
        stores them — no contact list yet. Pass the returned `uploadId` to
        [`/contact-lists`](/api-reference/contacts/create-a-contact-list) to
        create one, or re-preview first if the detected mapping needs
        correcting. A missing name or phone column is not an upload error: it
        lands in `mappingIssues` and those rows count as excluded. Column naming
        rules: [Special column
        names](/voice-agents/campaigns/contacts#special-column-names).
      operationId: uploadContactFile
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: >-
                    The contact file, up to 10 MB. CSV or `.xlsx`, detected from
                    the contents rather than the filename. Only the first sheet
                    of a workbook is read.
              required:
                - file
      responses:
        '201':
          description: >-
            The stored upload and what parsing it produced. A missing name or
            phone column is not an error — it lands in `mappingIssues` and the
            affected rows count as excluded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactUploadPreview'
              example:
                uploadId: dac00330-xxxx-xxxx-xxxx-xxxxxxxxxxxx
                summary:
                  willBeCalled: 2
                  excluded:
                    invalidPhone: 0
                    missingName: 0
                    duplicate: 0
                    dnc: 0
                    recentlyCalled: 0
                  errors: []
                detectedMapping:
                  nameColumn: Name
                  phoneColumn: Phone
                  attributeColumns:
                    - City
                sample:
                  - name: عائشہ صدیقی
                    phone: '+923000000001'
                    attributes:
                      City: لاہور
                  - name: بلال احمد
                    phone: '+923000000002'
                    attributes:
                      City: کراچی
                total: 2
                columns:
                  - Name
                  - Phone
                  - City
                mappingIssues: []
        '400':
          description: >-
            No `file` part was sent — an empty one counts as none — or it is
            neither CSV nor a readable `.xlsx`. Legacy `.xls` must be re-saved
            as `.xlsx` or CSV.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                message: multipart `file` is required
                error: Bad Request
                statusCode: 400
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '413':
          description: The file is larger than 10 MB. Nothing is stored.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                message: File too large
                error: Payload Too Large
                statusCode: 413
components:
  schemas:
    ContactUploadPreview:
      allOf:
        - $ref: '#/components/schemas/ContactPreview'
        - type: object
          properties:
            uploadId:
              type: string
              format: uuid
              description: >-
                Handle for the stored rows. Uploads are scoped to the API key's
                project and cannot be deleted.
    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
    ContactPreview:
      type: object
      properties:
        summary:
          $ref: '#/components/schemas/ContactImportSummary'
        detectedMapping:
          allOf:
            - $ref: '#/components/schemas/ContactColumnMapping'
          description: >-
            The mapping applied. Overrides you sent are resolved to header names
            — an A1 letter comes back as its header — and the rest is
            auto-detected.
        sample:
          type: array
          items:
            $ref: '#/components/schemas/PreviewContact'
          description: First 10 contacts that would be called, after normalization.
        total:
          type: integer
          description: >-
            Data rows read, excluding the header. `summary.willBeCalled` and the
            `summary.excluded` counts add up to this.
        columns:
          type: array
          items:
            type: string
          description: >-
            Headers as parsed, in file order — the choices for a mapping
            override.
        mappingIssues:
          type: array
          items:
            type: string
            enum:
              - phoneColumnNotFound
              - nameColumnNotFound
          description: >-
            Columns that resolved to nothing. Check this first when everything
            is excluded — an unmapped phone column makes every number look
            invalid.
    ContactImportSummary:
      type: object
      properties:
        willBeCalled:
          type: integer
        excluded:
          type: object
          description: Row counts per reason for exclusion.
          properties:
            invalidPhone:
              type: integer
              description: Missing, or not dialable as an E.164 number.
            missingName:
              type: integer
            duplicate:
              type: integer
              description: A number already counted earlier in the file.
            dnc:
              type: integer
              description: Always `0` — no do-not-call suppression is applied here.
            recentlyCalled:
              type: integer
              description: Always `0` — no recent-call suppression is applied here.
        errors:
          type: array
          description: Per-row detail for excluded rows, capped at the first 20.
          items:
            type: object
            properties:
              row:
                type: integer
                description: >-
                  Row number in the file, counting the header — so data starts
                  at 2.
              issue:
                type: string
                enum:
                  - invalidPhone
                  - missingName
                  - duplicate
                  - dnc
                  - recentlyCalled
              value:
                type: string
                description: The offending cell, empty when the column was absent.
    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
    PreviewContact:
      type: object
      description: One contact as it would be dialed.
      properties:
        name:
          type: string
        phone:
          type: string
          description: >-
            Dialable number, cleaned up but kept in the format the file used — a
            Pakistani national `03…` stays `03…`. Two spellings of the same
            number still count as one.
        attributes:
          type: object
          additionalProperties:
            type: string
          description: Attribute columns for this row; empty cells are dropped.
  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_…`).

````