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

# Import Contacts From A URL

> Import contacts into a list from a CSV you host.

The file is fetched over HTTPS through the same SSRF-guarded fetcher `POST /contact-lists/import` uses: private and link-local addresses are refused and re-checked on every redirect, and the 50 MB cap is enforced while streaming rather than trusted from `Content-Length`. Fetching and staging happen before this call returns; the rows are written afterwards, which is why the status is `202`. Poll `GET /email/lists/imports/{id}`.

`mapping` is optional. Omit it and the platform uses the mapping it recognizes from the export's own headers, which is what a caller exporting from a common ESP wants. When neither your mapping nor the recognizer finds an email column the request returns `400 VALIDATION_ERROR` with `details.headers` listing the headers that were read, so the retry can name the right column instead of guessing.

**Early access.** This endpoint returns `403 EMAIL_EARLY_ACCESS` until the email product reaches general availability. The contract below is stable and safe to build against.



## OpenAPI

````yaml /api-reference/openapi.json post /email/lists/import
openapi: 3.1.0
info:
  title: Political Comms API
  summary: >-
    Direct-to-carrier political texting API for campaigns, PACs, advocacy
    organizations, fundraisers, and elected officials.
  description: >-
    Public REST API for the Political Comms platform. Surfaces include Projects
    (compose, test, schedule, send), Conversations (read inbound threads and
    reply inside them), Contact Lists (S3 import and analysis), Media Files,
    Organizations and hierarchy, Brands, Campaigns, Tracking Domains, Phone
    Numbers, Analytics, and Billing.


    Authentication is an API key passed in the `X-API-Key` header. Keys are
    generated from the dashboard at Admin → API and are prefixed `pc_live_`. All
    POST and PATCH endpoints that mutate state are designed to be safe to retry,
    with optional `Idempotency-Key` headers for stronger guarantees. Rate limit
    is 100 requests per minute per key (60-second sliding window; write-only
    keys 60/min, delete-only keys 30/min; the `X-RateLimit-Limit` header reports
    your key's exact limit).


    Webhooks emit `message.sent`, `message.delivered`, `message.failed`,
    `message.replied`, and `link.clicked` events. Payloads are HMAC-signed;
    validate the signature before trusting any payload.


    A Model Context Protocol (MCP) server is available at
    https://docs.politicalcomms.com/mcp for AI agents that need to search the
    documentation programmatically. The developer hub at
    https://politicalcomms.com/developers/ has quickstart examples in cURL, raw
    HTTP, and Python.
  version: 1.4.0
  termsOfService: https://politicalcomms.com/terms/
  contact:
    name: Political Comms Support
    email: support@politicalcomms.com
    url: https://docs.politicalcomms.com
  license:
    name: Proprietary
    url: https://politicalcomms.com/terms/
  x-logo:
    url: https://politicalcomms.com/images/brand/pcomms-logo-left-of-text.png
    altText: Political Comms
    backgroundColor: '#ffffff'
    href: https://politicalcomms.com/
  x-mcp:
    url: https://docs.politicalcomms.com/mcp
    discovery_url: https://docs.politicalcomms.com/.well-known/mcp
    transport: http
    auth: none
    tools:
      - search_political_comms
      - query_docs_filesystem_political_comms
      - submit_feedback
servers:
  - url: https://api.politicalcomms.com/v1
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: Projects
    description: Create, edit, test, schedule, and inspect projects.
  - name: Conversations
    description: >-
      Read inbound threads and reply inside them from the same number.
      Conversations are created by project sends; the API never starts one.
  - name: Phone Numbers
    description: List phone numbers across 10DLC campaigns and toll-free verifications.
  - name: Toll-Free Verifications
    description: >-
      List toll-free verifications (carrier registrations) across your
      organization hierarchy.
  - name: Brands
    description: List brands across your organization hierarchy.
  - name: 10DLC Campaigns
    description: >-
      List 10DLC campaigns (carrier registrations) across your organization
      hierarchy.
  - name: Email Campaigns
    description: >-
      Create, schedule, and report on email campaigns. Pause, resume, and test
      sends are deliverability decisions a human makes while watching a send,
      and live in the dashboard. **Early access:** this endpoint answers `403
      EMAIL_EARLY_ACCESS` until the email product is generally available.
  - name: Email Templates
    description: >-
      Create and read saved email templates. **Early access:** this endpoint
      answers `403 EMAIL_EARLY_ACCESS` until the email product is generally
      available.
  - name: Email Senders
    description: >-
      Read the sender identities (From addresses) on your verified domains.
      Identities carry the physical mailing address and paid-for-by disclaimer,
      so they are created and edited in the dashboard. **Early access:** this
      endpoint answers `403 EMAIL_EARLY_ACCESS` until the email product is
      generally available.
  - name: Email Suppressions
    description: >-
      Read, add, and lift suppressions in bulk. This is the primitive for
      keeping your own opt-out record in sync with ours. **Early access:** this
      endpoint answers `403 EMAIL_EARLY_ACCESS` until the email product is
      generally available.
  - name: Contact Lists
    description: List, import, and analyze contact lists.
  - name: Email Lists
    description: >-
      Create email lists, read their contacts, and bulk-upsert or import
      addresses. To stop mailing someone use Email Suppressions, which survives
      a re-import. **Early access:** this endpoint answers `403
      EMAIL_EARLY_ACCESS` until the email product is generally available.
  - name: Media Files
    description: List, import, and fetch media files.
  - name: Sending Domains
    description: >-
      Read the sending domains in your organization and the DNS records to
      publish. Domains are added in the dashboard: DNS is published by hand, so
      creation is not part of the API. **Early access:** this endpoint answers
      `403 EMAIL_EARLY_ACCESS` until the email product is generally available.
  - name: Tracking Domains
    description: >-
      List active link-tracking domains. Use the returned ids as
      `link_tracking_domain_id` on project create/update.
  - name: Organizations
    description: List descendant organizations and hierarchy.
  - name: Billing
    description: Usage and billing data across your organization.
  - name: Analytics
    description: Message statistics and delivery performance.
paths:
  /email/lists/import:
    post:
      tags:
        - Email Lists
      summary: Import Contacts From A URL
      description: >-
        Import contacts into a list from a CSV you host.


        The file is fetched over HTTPS through the same SSRF-guarded fetcher
        `POST /contact-lists/import` uses: private and link-local addresses are
        refused and re-checked on every redirect, and the 50 MB cap is enforced
        while streaming rather than trusted from `Content-Length`. Fetching and
        staging happen before this call returns; the rows are written
        afterwards, which is why the status is `202`. Poll `GET
        /email/lists/imports/{id}`.


        `mapping` is optional. Omit it and the platform uses the mapping it
        recognizes from the export's own headers, which is what a caller
        exporting from a common ESP wants. When neither your mapping nor the
        recognizer finds an email column the request returns `400
        VALIDATION_ERROR` with `details.headers` listing the headers that were
        read, so the retry can name the right column instead of guessing.


        **Early access.** This endpoint returns `403 EMAIL_EARLY_ACCESS` until
        the email product reaches general availability. The contract below is
        stable and safe to build against.
      operationId: importEmailListContacts
      parameters:
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                source_url:
                  type: string
                  format: uri
                  description: HTTPS URL of the CSV. Maximum 50 MB.
                name:
                  type: string
                  maxLength: 255
                  description: >-
                    Name for the list this file becomes. Defaults to the file
                    name. The uploaded file IS the list: an import creates one
                    rather than adding to an existing list.
                email_domain_id:
                  type: string
                  format: uuid
                  description: >-
                    Scopes the list to one sending domain. Omit for an
                    organization-wide list any campaign can use.
                acquired:
                  type: boolean
                  description: >-
                    The addresses were purchased or rented. An acquired list
                    must pass paid validation before it can be sent to.
                mapping:
                  type: object
                  additionalProperties:
                    type: string
                    maxLength: 64
                  description: >-
                    CSV header to contact field, for example `{"Email Address":
                    "email", "First": "first_name"}`. Exactly one header must
                    map to `email`.
                consent:
                  type: object
                  properties:
                    source:
                      type: string
                      enum:
                        - donation_form
                        - petition
                        - signup_form
                        - event
                        - purchased
                        - rented
                        - other
                      description: >-
                        How these people agreed to hear from you. Answer
                        honestly: `purchased` and `rented` change how the list
                        is treated, and an undeclared acquired list is the usual
                        cause of a mid-send deliverability pause.
                    note:
                      type: string
                      maxLength: 1000
                  required:
                    - source
                  additionalProperties: false
                options:
                  type: object
                  properties:
                    allow_role:
                      type: boolean
                      description: >-
                        Import role addresses such as info@ and sales@. Off by
                        default: they complain at a far higher rate than
                        personal addresses.
                  additionalProperties: false
              required:
                - source_url
                - consent
              additionalProperties: false
            example:
              source_url: https://files.example.com/exports/august-donors.csv
              name: August donors
              consent:
                source: donation_form
                note: Donate page opt-in checkbox
      responses:
        '202':
          description: Import accepted. Poll the import for progress.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/EmailListImport'
                required:
                  - success
                  - data
                additionalProperties: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  parameters:
    IdempotencyKeyHeader:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        minLength: 16
        maxLength: 200
      description: >-
        Optional idempotency key: a unique string of 16-200 printable ASCII
        characters (a UUID is recommended). Retrying the write with the same key
        within 24 hours returns the stored response of the first call with an
        `X-Idempotent-Replayed: true` response header instead of executing it
        again. Reusing a key with a different request body returns `422`
        (`IDEMPOTENCY_MISMATCH`); a duplicate sent while the first call is still
        running returns `409` with a `Retry-After` header. Keys are scoped per
        endpoint and organization.
  schemas:
    EmailListImport:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          description: >-
            Import lifecycle state. Terminal states are `completed` and
            `failed`.
        email_list_id:
          type:
            - string
            - 'null'
          format: uuid
        file_name:
          type:
            - string
            - 'null'
          description: Derived from the last path segment of `source_url`.
        file_size:
          type: integer
          description: Bytes fetched.
        headers:
          type: array
          items:
            type: string
          description: >-
            The CSV's header row as read. Echoed back so a caller that omitted
            `mapping` can see what the recognizer worked from.
        mapping:
          type: object
          additionalProperties:
            type: string
          description: >-
            CSV header to contact field. The mapping actually used, whether you
            sent it or the recognizer produced it.
        recognized_provider:
          type:
            - string
            - 'null'
          description: The ESP whose export format was recognized, when one was.
        summary:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: Row counts once the import finishes.
        error_message:
          type:
            - string
            - 'null'
        created_at:
          type: string
          format: date-time
        started_at:
          type:
            - string
            - 'null'
          format: date-time
        completed_at:
          type:
            - string
            - 'null'
          format: date-time
      required:
        - id
        - status
      description: One import of a caller-hosted CSV into an email list.
      additionalProperties: true
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message.
        code:
          type: string
          description: >-
            Machine-readable error code, e.g. VALIDATION_ERROR,
            ORG_ACCESS_DENIED, PROJECT_NOT_FOUND.
        correlationId:
          type: string
          description: Request correlation ID; include it in support requests.
        details:
          type:
            - array
            - object
          description: >-
            Present on some errors. An array of field-level issues for
            VALIDATION_ERROR; an object for other codes (e.g.
            insufficient-balance shortfall).
      required:
        - error
        - code
      additionalProperties: true
      description: >-
        Standard error body produced by the API error handler for 4xx/5xx
        responses.
    AuthErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Short error summary.
        message:
          type: string
          description: Human-readable detail.
      required:
        - error
        - message
      additionalProperties: true
      description: >-
        Error body returned by the API-key authentication layer (401 and
        permission 403s).
    RateLimitErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          type: string
        message:
          type: string
        code:
          type: string
          enum:
            - RATE_LIMIT_EXCEEDED
        statusCode:
          type: integer
          enum:
            - 429
        retryAfter:
          type: integer
          description: >-
            Seconds until the rate-limit window resets. Also sent as the
            Retry-After header.
      required:
        - success
        - error
        - code
        - statusCode
        - retryAfter
      additionalProperties: true
  responses:
    BadRequest:
      description: >-
        Invalid request (validation failure, malformed parameters, or a request
        the resource state does not allow).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Validation failed
            code: VALIDATION_ERROR
            correlationId: 030bf857-922f-4b82-8fca-c5b0769c1590
            details:
              - code: invalid_format
                format: uuid
                path:
                  - organization_id
                message: Invalid UUID
    Unauthorized:
      description: Missing, malformed, revoked, or expired API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AuthErrorResponse'
          example:
            error: API key required
            message: Please provide an API key in the X-API-Key header
    Forbidden:
      description: >-
        The API key lacks the required permission scope, or the requested
        resource belongs to an organization outside the key's hierarchy.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/AuthErrorResponse'
              - $ref: '#/components/schemas/ErrorResponse'
          examples:
            missingScope:
              summary: Key lacks the required permission scope
              value:
                error: Insufficient permissions
                message: >-
                  This API key does not have the required permissions:
                  public:read
            orgAccessDenied:
              summary: Resource outside the key's organization hierarchy
              value:
                error: >-
                  Access denied: You do not have permission to access this
                  organization's data
                code: ORG_ACCESS_DENIED
                correlationId: de038205-7c9c-4d4a-99bc-d7275a52f07e
    NotFound:
      description: Resource not found or not visible to this API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Project not found
            code: PROJECT_NOT_FOUND
            correlationId: de038205-7c9c-4d4a-99bc-d7275a52f07e
    RateLimited:
      description: Rate limit exceeded. Check the Retry-After header.
      headers:
        Retry-After:
          description: Seconds until the window resets.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RateLimitErrorResponse'
          example:
            success: false
            error: Rate limit exceeded
            message: Maximum 100 requests per 60 seconds
            code: RATE_LIMIT_EXCEEDED
            statusCode: 429
            retryAfter: 1800
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: An unexpected error occurred. Please try again later.
            code: INTERNAL_ERROR
            correlationId: de038205-7c9c-4d4a-99bc-d7275a52f07e
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: >-
        Authenticate every request by passing your API key in the X-API-Key
        header. Keys are scoped to your organization hierarchy.

````