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

# Create Campaign

> Create a draft campaign.

**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/campaigns
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/campaigns:
    post:
      tags:
        - Email Campaigns
      summary: Create Campaign
      description: >-
        Create a draft campaign.


        **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: createEmailCampaign
      parameters:
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 255
                sender_identity_id:
                  type: string
                  format: uuid
                list_ids:
                  type: array
                  minItems: 1
                  maxItems: 50
                  items:
                    type: string
                    format: uuid
                suppression_list_ids:
                  type: array
                  maxItems: 50
                  items:
                    type: string
                    format: uuid
                template_id:
                  type: string
                  format: uuid
                subject:
                  type: string
                  maxLength: 255
                preheader:
                  type: string
                  maxLength: 255
                html:
                  type: string
                  maxLength: 1000000
                source_code:
                  type: string
                  maxLength: 255
                refcode:
                  type: string
                  maxLength: 255
                append_utm:
                  type: boolean
                  description: Append UTM parameters to outbound links for attribution.
                is_repermission:
                  type: boolean
                  description: Mark the send as a re-permission (re-consent) message.
                tracking_domain_id:
                  type: string
                  format: uuid
                  nullable: true
                  description: >-
                    Tracking domain serving this campaign's tracked links, open
                    pixel, unsubscribe page and browser view, so recipients see
                    your own `links.` host instead of the platform one. Must be
                    an active tracking domain your organization owns or inherits
                    from a parent. Omit to let the platform pick the obvious
                    default (the tracking domain matching your sending domain's
                    root, or your only one); send `null` to force the platform
                    link host.
                require_approval:
                  type: boolean
                  description: >-
                    Require an approval after the test send before this campaign
                    can schedule or send. Off by default; every campaign still
                    requires a successful test send regardless of this setting.
                recipient_policy:
                  type: string
                  enum:
                    - max_reach
                    - max_deliverability
                  default: max_reach
                  description: >-
                    Which subscribed contacts on the campaign's lists actually
                    receive it. `max_reach` (default) sends to every subscribed
                    contact. `max_deliverability` sends only to contacts whose
                    current validation verdict is deliverable; contacts that
                    have never been validated are skipped.
              required:
                - name
                - sender_identity_id
                - list_ids
              additionalProperties: true
            example:
              name: August appeal
              sender_identity_id: 3f7a9c1e-8b24-4d6f-a2e5-9c7b3f1a8d42
              list_ids:
                - 7c1de5f0-9a42-4b6d-8e33-2f5a9c7b1e64
              subject: Can you help before Friday?
              append_utm: true
      responses:
        '201':
          description: Campaign created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/EmailCampaign'
                required:
                  - success
                  - data
                additionalProperties: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          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
                onboardingIncomplete:
                  summary: >-
                    Organization has not completed required onboarding (business
                    profile or funding)
                  value:
                    error: Complete your account setup to continue
                    code: ONBOARDING_INCOMPLETE
                    details:
                      missingSteps:
                        - profile
                        - funding
                      onboardingUrl: /onboarding
        '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:
    EmailCampaign:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        status:
          type: string
          enum:
            - draft
            - awaiting_test
            - awaiting_approval
            - ready
            - scheduled
            - compiling
            - sending
            - paused
            - completed
            - deleted
        sender_identity_id:
          type: string
          format: uuid
        list_ids:
          type: array
          items:
            type: string
            format: uuid
        suppression_list_ids:
          type: array
          items:
            type: string
            format: uuid
        tracking_domain_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            Tracking domain serving this campaign's `/e/*` URLs. `null` means
            the platform link host.
        tracking_domain:
          type: object
          nullable: true
          description: >-
            The resolved tracking domain, returned on the single-campaign read.
            Links are branded only while `status` is `active`; any other status
            falls back to the platform link host for the send.
          properties:
            id:
              type: string
              format: uuid
            domain:
              type: string
            status:
              type: string
        require_approval:
          type: boolean
          description: >-
            Require an approval after the test send before this campaign can
            schedule or send. Off by default; every campaign still requires a
            successful test send regardless of this setting.
        recipient_policy:
          type: string
          enum:
            - max_reach
            - max_deliverability
          default: max_reach
          description: >-
            Which subscribed contacts on the campaign's lists actually receive
            it. `max_reach` (default) sends to every subscribed contact.
            `max_deliverability` sends only to contacts whose current validation
            verdict is deliverable; contacts that have never been validated are
            skipped.
        approval_status:
          type: string
          enum:
            - not_required
            - pending
            - approved
          description: >-
            `not_required` when `require_approval` is off. `pending` once a
            current test send exists and approval is outstanding. `approved`
            once granted. Editing the campaign's content, sender, lists,
            suppression lists, or template resets a `pending` or `approved`
            status back to `pending` behind a new required test.
        last_tested_at:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            When the campaign's current test send completed. Null until a test
            send succeeds, and cleared whenever a later edit invalidates it.
        refcode:
          type:
            - string
            - 'null'
          description: >-
            Fundraising refcode appended to donation links for attribution.
            Generated as `e-` plus eight characters when omitted.
        source_code:
          type:
            - string
            - 'null'
        scheduled_at:
          type:
            - string
            - 'null'
          format: date-time
        timezone:
          type:
            - string
            - 'null'
        audience_count:
          type:
            - integer
            - 'null'
          description: Deduplicated, suppression-filtered recipient count.
        counts:
          $ref: '#/components/schemas/EmailCampaignCounts'
        pause_reason:
          type:
            - string
            - 'null'
          description: >-
            Why the campaign is paused. A deliverability breaker sets this
            automatically.
        blocked:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              message:
                type: string
            additionalProperties: true
          description: >-
            Returned by the single-campaign read only: the machine-readable
            reasons this campaign will not schedule yet. Empty when it is ready.
        started_at:
          type:
            - string
            - 'null'
          format: date-time
        completed_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - name
        - status
        - sender_identity_id
      additionalProperties: true
    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).
    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.
    EmailCampaignCounts:
      type: object
      properties:
        queued:
          type: integer
        sent:
          type: integer
        delivered:
          type: integer
        bounced:
          type: integer
        complained:
          type: integer
        unsubscribed:
          type: integer
        opened:
          type: integer
        clicked:
          type: integer
        failed:
          type: integer
      additionalProperties: true
    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
    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.

````