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

> Create a new project. `type` selects the payload shape: `broadcast` (the default) sends one message to the audience; `survey` runs a multi-question flow with branching.

**Phone numbers.** Assign one or more sending numbers via `phone_number_ids` (up to 49). The project spreads new conversations randomly across the assigned numbers, and once a recipient has been messaged from a given number every later message to that recipient comes from the same number (sticky sender). A single legacy `phone_number_id` is still accepted and is treated as a one-element `phone_number_ids`.

**Channel.** `channel` defaults to `10dlc`. For broadcast projects a `10dlc` channel requires `brand_id` + `campaign_id` (and `toll_free_verification_id` must be omitted), while `toll-free` requires `toll_free_verification_id` with matching toll-free `phone_number_ids` (`brand_id`/`campaign_id` omitted). Survey projects always require `campaign_id`, on both channels.

**Surveys.** `questions` defines the whole flow: an `intro` at sequence 1, then `multiple_choice`/`open_ended` questions, optionally ending with an `outro`. Multiple-choice options can branch with `next_sequence`, and `no_match_sequence` routes unmatched replies; both are forward-only. Each question can be SMS or MMS (`message_type` + `media_ids`). Question text supports merge tags and `{tracking_url}`. Surveys need at least 2 questions and 1 phone number.

When `link_tracking_enabled` is `true`, `link_tracking_destination_url` and `link_tracking_domain_id` become required. The destination URL may embed the selected `link_tracking_param_field` anywhere via a placeholder named after it, e.g. `https://test.com?utm_content=xyzd_{linkid}` redirects as `...utm_content=xyzd_ABC123` (no separate `&linkid=` pair is appended). Without a placeholder the field is appended as its own query pair. `link_tracking_destination_url` may also be exactly `https://{<link_tracking_param_field>}`, e.g. `https://{custom_url}`; each recipient's tracking link then redirects to the URL stored in that contact field instead (`short_id` is still appended as a query param, but no `<field>` query param is). In this mode `link_tracking_fallback_url` is required, and recipients whose field is empty or not a valid URL go there instead. `phone` cannot be used as a whole-URL destination, and a `{field}` token in the host position must be the entire URL (`https://{custom_url}/path` is rejected). A placeholder that does not match the selected field, or an invalid whole-URL form, is rejected with a 400 (`INVALID_LINK_PLACEHOLDER`).

**Opt-out footer.** Broadcast messages automatically carry the `STOP=END` opt-out footer. Set `opt_out_footer_enabled` to `false` to disable it (defaults to `true`); surveys never carry the footer.

**Drafts.** `contact_list_ids` is optional. With at least one list the created project starts in `awaiting_test` status (send a test, then schedule). Without it the project is created as a `draft`; attach lists later via `PATCH /projects/{id}` and the project moves to `awaiting_test` automatically. Drafts cannot be tested or scheduled. The response's `completeness` block shows what is still missing.



## OpenAPI

````yaml /api-reference/openapi.json post /projects
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 600 requests per minute per key (bursts up to 600, refilling at 10 per
    second, the same for every scope; the `X-RateLimit-Limit` header reports
    your key's exact limit, and `Retry-After` on a 429 is the seconds until your
    next request is allowed).


    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.1
  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.
  - name: Email Templates
    description: Create and read saved email templates.
  - 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.
  - 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.
  - 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.
  - 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.
  - 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:
  /projects:
    post:
      tags:
        - Projects
      summary: Create Project
      description: >-
        Create a new project. `type` selects the payload shape: `broadcast` (the
        default) sends one message to the audience; `survey` runs a
        multi-question flow with branching.


        **Phone numbers.** Assign one or more sending numbers via
        `phone_number_ids` (up to 49). The project spreads new conversations
        randomly across the assigned numbers, and once a recipient has been
        messaged from a given number every later message to that recipient comes
        from the same number (sticky sender). A single legacy `phone_number_id`
        is still accepted and is treated as a one-element `phone_number_ids`.


        **Channel.** `channel` defaults to `10dlc`. For broadcast projects a
        `10dlc` channel requires `brand_id` + `campaign_id` (and
        `toll_free_verification_id` must be omitted), while `toll-free` requires
        `toll_free_verification_id` with matching toll-free `phone_number_ids`
        (`brand_id`/`campaign_id` omitted). Survey projects always require
        `campaign_id`, on both channels.


        **Surveys.** `questions` defines the whole flow: an `intro` at sequence
        1, then `multiple_choice`/`open_ended` questions, optionally ending with
        an `outro`. Multiple-choice options can branch with `next_sequence`, and
        `no_match_sequence` routes unmatched replies; both are forward-only.
        Each question can be SMS or MMS (`message_type` + `media_ids`). Question
        text supports merge tags and `{tracking_url}`. Surveys need at least 2
        questions and 1 phone number.


        When `link_tracking_enabled` is `true`, `link_tracking_destination_url`
        and `link_tracking_domain_id` become required. The destination URL may
        embed the selected `link_tracking_param_field` anywhere via a
        placeholder named after it, e.g.
        `https://test.com?utm_content=xyzd_{linkid}` redirects as
        `...utm_content=xyzd_ABC123` (no separate `&linkid=` pair is appended).
        Without a placeholder the field is appended as its own query pair.
        `link_tracking_destination_url` may also be exactly
        `https://{<link_tracking_param_field>}`, e.g. `https://{custom_url}`;
        each recipient's tracking link then redirects to the URL stored in that
        contact field instead (`short_id` is still appended as a query param,
        but no `<field>` query param is). In this mode
        `link_tracking_fallback_url` is required, and recipients whose field is
        empty or not a valid URL go there instead. `phone` cannot be used as a
        whole-URL destination, and a `{field}` token in the host position must
        be the entire URL (`https://{custom_url}/path` is rejected). A
        placeholder that does not match the selected field, or an invalid
        whole-URL form, is rejected with a 400 (`INVALID_LINK_PLACEHOLDER`).


        **Opt-out footer.** Broadcast messages automatically carry the
        `STOP=END` opt-out footer. Set `opt_out_footer_enabled` to `false` to
        disable it (defaults to `true`); surveys never carry the footer.


        **Drafts.** `contact_list_ids` is optional. With at least one list the
        created project starts in `awaiting_test` status (send a test, then
        schedule). Without it the project is created as a `draft`; attach lists
        later via `PATCH /projects/{id}` and the project moves to
        `awaiting_test` automatically. Drafts cannot be tested or scheduled. The
        response's `completeness` block shows what is still missing.
      operationId: createProject
      parameters:
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: object
                  required:
                    - organization_id
                    - name
                    - protocol
                    - message_text
                  properties:
                    type:
                      type: string
                      enum:
                        - broadcast
                      description: Project type. Optional for broadcast (the default).
                    organization_id:
                      type: string
                    channel:
                      type: string
                      enum:
                        - 10dlc
                        - toll-free
                      default: 10dlc
                      description: Messaging channel. Defaults to 10dlc.
                    brand_id:
                      type: string
                      description: >-
                        Required when channel=10dlc; omit when
                        channel=toll-free.
                    campaign_id:
                      type: string
                      description: >-
                        Required when channel=10dlc; omit when
                        channel=toll-free.
                    toll_free_verification_id:
                      type: string
                      description: >-
                        Required when channel=toll-free; omit when
                        channel=10dlc. Must match the verification behind the
                        chosen phone_number_ids.
                    phone_number_ids:
                      type: array
                      items:
                        type: string
                      minItems: 1
                      maxItems: 49
                      description: >-
                        Sending phone number IDs (1-49). At least one of
                        phone_number_ids or phone_number_id is required. New
                        conversations are spread randomly across the numbers;
                        each recipient is then pinned to one number.
                    phone_number_id:
                      type: string
                      deprecated: true
                      description: >-
                        Deprecated. Use phone_number_ids. A single id is
                        accepted and treated as a one-element phone_number_ids.
                        Provide one of phone_number_ids or phone_number_id.
                    name:
                      type: string
                    protocol:
                      type: string
                      enum:
                        - sms
                        - mms
                    contact_list_ids:
                      type: array
                      items:
                        type: string
                      minItems: 1
                      description: >-
                        Optional. Omit to create the project as a draft with no
                        audience; attach lists later via PATCH /projects/{id}.
                        When present, must contain at least one list id.
                    suppression_list_ids:
                      type: array
                      items:
                        type: string
                    message_text:
                      type: string
                    media_ids:
                      type: array
                      items:
                        type: string
                    link_tracking_enabled:
                      type: boolean
                    link_tracking_destination_url:
                      type: string
                      format: uri
                      description: >-
                        Where tracking links redirect. May embed the selected
                        link parameter anywhere via a placeholder named after
                        it, e.g. 'https://test.com?utm_content=xyzd_{linkid}'
                        redirects as '...utm_content=xyzd_ABC123' (URL-encoded
                        value, empty when the contact has none; the parameter is
                        then not appended separately). A placeholder that does
                        not match link_tracking_param_field is rejected with a
                        400 (INVALID_LINK_PLACEHOLDER). Without a placeholder
                        the parameter is appended as its own query pair. May
                        also be exactly 'https://{<link_tracking_param_field>}'
                        to send each recipient to the URL in that contact field;
                        then link_tracking_fallback_url is required. '{phone}'
                        is not allowed as a whole URL.
                    link_tracking_domain_id:
                      type: string
                    link_tracking_param_field:
                      type: string
                      maxLength: 64
                      description: >-
                        Contact field carried on tracking-link redirects. Use
                        'phone', a contact custom-field name, or omit for no
                        param (default). By default the field's name becomes an
                        appended query-param key with the contact's value; a
                        matching {field} placeholder in
                        link_tracking_destination_url embeds the value there
                        instead.
                    link_tracking_fallback_url:
                      type: string
                      format: uri
                      description: >-
                        Redirect used when a recipient's
                        link_tracking_param_field value is empty or not a valid
                        URL. Required when link_tracking_destination_url is a
                        whole-URL placeholder (e.g. 'https://{custom_url}');
                        rejected with a 400 (INVALID_LINK_PLACEHOLDER)
                        otherwise.
                    opt_out_footer_enabled:
                      type: boolean
                      default: true
                      description: >-
                        Whether the 'STOP=END' opt-out footer is appended to
                        every outbound message. Defaults to true. Broadcast
                        projects only; surveys never carry the footer.
                  title: Broadcast project
                  description: >-
                    Unknown body properties are rejected with a 400 (strict
                    validation).
                - title: Survey project
                  type: object
                  required:
                    - type
                    - organization_id
                    - campaign_id
                    - name
                    - questions
                  properties:
                    type:
                      type: string
                      enum:
                        - survey
                      description: Must be `survey`.
                    organization_id:
                      type: string
                    campaign_id:
                      type: string
                      description: >-
                        Always required for surveys, on both channels (surveys
                        route through a brand + campaign; there is no toll-free
                        verification path for surveys).
                    name:
                      type: string
                    channel:
                      type: string
                      enum:
                        - 10dlc
                        - toll-free
                      default: 10dlc
                      description: Messaging channel. Defaults to 10dlc.
                    phone_number_ids:
                      type: array
                      items:
                        type: string
                      minItems: 1
                      maxItems: 49
                      description: >-
                        Sending phone number IDs (1-49). At least one of
                        phone_number_ids or phone_number_id is required. New
                        conversations are spread randomly across the numbers;
                        each recipient is then pinned to one number.
                    phone_number_id:
                      type: string
                      deprecated: true
                      description: >-
                        Deprecated. Use phone_number_ids. A single id is
                        accepted and treated as a one-element phone_number_ids.
                    contact_list_ids:
                      type: array
                      items:
                        type: string
                      minItems: 1
                      description: >-
                        Optional. Omit to create the survey as a draft with no
                        audience; attach lists later via PATCH /projects/{id}.
                        When present, must contain at least one list id.
                    suppression_list_ids:
                      type: array
                      items:
                        type: string
                    questions:
                      type: array
                      minItems: 2
                      maxItems: 32
                      description: >-
                        The full question flow, at least an intro plus one
                        question (max 32 total). Only recipients who reply
                        advance to later questions.
                      items:
                        type: object
                        required:
                          - sequence
                          - question_type
                          - question_text
                        properties:
                          sequence:
                            type: integer
                            minimum: 1
                            maximum: 32
                            description: >-
                              Position in the flow. Sequences must be exactly
                              1..N with no gaps; sequence 1 must be the intro.
                          question_type:
                            type: string
                            enum:
                              - intro
                              - multiple_choice
                              - open_ended
                              - outro
                            description: >-
                              intro: the opening message (must be sequence 1).
                              multiple_choice: 2-9 numbered response_options.
                              open_ended: free-text answer, no response_options.
                              outro: closing message, expects no reply.
                          question_text:
                            type: string
                            minLength: 1
                            maxLength: 1600
                            description: >-
                              Message text. Supports merge tags
                              (`{first_name|there}`, contact custom fields) and
                              `{tracking_url}` when link tracking is enabled on
                              the project.
                          message_type:
                            type: string
                            enum:
                              - sms
                              - mms
                            default: sms
                            description: >-
                              Per-question delivery type. MMS questions require
                              media_ids.
                          media_ids:
                            type: array
                            items:
                              type: string
                            maxItems: 10
                            description: >-
                              Media file IDs attached to this question (see
                              Media Files). Required (at least one) when
                              message_type=mms; not allowed when
                              message_type=sms.
                          response_options:
                            type: array
                            minItems: 2
                            maxItems: 9
                            description: >-
                              Numbered answer options. Required for
                              multiple_choice; not allowed for open_ended.
                            items:
                              type: object
                              required:
                                - value
                                - label
                              properties:
                                value:
                                  type: integer
                                  minimum: 1
                                  maximum: 9
                                  description: >-
                                    The digit a recipient texts to pick this
                                    option.
                                label:
                                  type: string
                                  maxLength: 200
                                  description: >-
                                    Descriptive label used for matching and
                                    reporting.
                                next_sequence:
                                  type: integer
                                  minimum: 1
                                  maximum: 32
                                  description: >-
                                    Branch target when this option is picked.
                                    Must point to a later question
                                    (forward-only, no loops). Omit for linear
                                    progression to sequence + 1.
                          no_match_sequence:
                            type: integer
                            minimum: 1
                            maximum: 32
                            description: >-
                              Branch target when a reply matches none of the
                              response_options. Only valid on questions with
                              response_options; forward-only. Omit to fall
                              through to sequence + 1.
                          is_required:
                            type: boolean
                            default: true
                    link_tracking_enabled:
                      type: boolean
                    link_tracking_destination_url:
                      type: string
                      format: uri
                      description: >-
                        Where tracking links redirect. May embed the selected
                        link_tracking_param_field via a {field} placeholder (see
                        POST /projects). May also be exactly
                        'https://{<link_tracking_param_field>}' to send each
                        recipient to the URL in that contact field; then
                        link_tracking_fallback_url is required. '{phone}' is not
                        allowed as a whole URL.
                    link_tracking_domain_id:
                      type: string
                    link_tracking_param_field:
                      type: string
                      maxLength: 64
                      description: >-
                        Contact field appended as a redirect query param on
                        tracking links. Use 'phone', a contact custom-field
                        name, or omit for no param (default).
                    link_tracking_fallback_url:
                      type: string
                      format: uri
                      description: >-
                        Redirect used when a recipient's
                        link_tracking_param_field value is empty or not a valid
                        URL. Required when link_tracking_destination_url is a
                        whole-URL placeholder (e.g. 'https://{custom_url}');
                        rejected with a 400 (INVALID_LINK_PLACEHOLDER)
                        otherwise.
                    ai_survey_analysis_enabled:
                      type: boolean
                      default: false
                      description: >-
                        When true, replies that fail exact option matching are
                        classified by AI toward the option they clearly express
                        (including that option's branching). Billed per analyzed
                        reply; exact matches stay free.
                  description: >-
                    Unknown body properties are rejected with a 400 (strict
                    validation).
            examples:
              10dlc:
                summary: 10DLC project (brand + campaign)
                value:
                  organization_id: 01HX0000000000000000000000
                  channel: 10dlc
                  brand_id: 01HX0000000000000000000001
                  campaign_id: 01HX0000000000000000000002
                  phone_number_ids:
                    - 01HX0000000000000000000003
                    - 01HX0000000000000000000006
                  name: Spring Outreach
                  protocol: sms
                  contact_list_ids:
                    - 01HX0000000000000000000004
                  suppression_list_ids: []
                  message_text: >-
                    Hi {first_name}, early voting starts Monday. More info:
                    {link}
                  media_ids: []
                  link_tracking_enabled: true
                  link_tracking_destination_url: https://example.com/vote
                  link_tracking_domain_id: 01HX0000000000000000000005
                  link_tracking_param_field: voter_id
                  link_tracking_fallback_url: null
                  opt_out_footer_enabled: true
              toll-free:
                summary: Toll-free project (verification)
                value:
                  organization_id: 01HX0000000000000000000000
                  channel: toll-free
                  toll_free_verification_id: 01HX00000000000000000000T0
                  phone_number_ids:
                    - 01HX00000000000000000000T1
                  name: Spring Outreach (TFN)
                  protocol: sms
                  contact_list_ids:
                    - 01HX0000000000000000000004
                  suppression_list_ids: []
                  message_text: Hi {first_name}, early voting starts Monday.
                  media_ids: []
              survey:
                summary: Survey project (branching + AI analysis)
                value:
                  type: survey
                  organization_id: 01HX0000000000000000000000
                  campaign_id: 01HX0000000000000000000002
                  name: Primary Intent Survey
                  channel: 10dlc
                  phone_number_ids:
                    - 01HX0000000000000000000003
                  contact_list_ids:
                    - 01HX0000000000000000000004
                  questions:
                    - sequence: 1
                      question_type: intro
                      question_text: >-
                        Hi {first_name|there}, it's Smith PAC. Mind answering 2
                        quick questions? Reply STOP to opt out.
                    - sequence: 2
                      question_type: multiple_choice
                      question_text: >-
                        Do you plan to vote in the primary? Reply 1 for Yes, 2
                        for No, 3 for Undecided.
                      response_options:
                        - value: 1
                          label: 'Yes'
                          next_sequence: 4
                        - value: 2
                          label: 'No'
                        - value: 3
                          label: Undecided
                      no_match_sequence: 3
                    - sequence: 3
                      question_type: open_ended
                      question_text: What issue matters most to you this year?
                    - sequence: 4
                      question_type: outro
                      question_text: 'Thanks for your time! Learn more: {tracking_url}'
                  link_tracking_enabled: true
                  link_tracking_destination_url: https://example.com/volunteer
                  link_tracking_domain_id: 01HX0000000000000000000005
                  ai_survey_analysis_enabled: true
      responses:
        '201':
          description: >-
            Project created. Status reflects completeness (`awaiting_test` when
            ready to test).
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - data
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      project_id:
                        type: string
                      name:
                        type: string
                      type:
                        type: string
                        enum:
                          - broadcast
                          - survey
                      status:
                        type: string
                      channel:
                        type: string
                      created_via_api:
                        type: boolean
                      question_count:
                        type: integer
                        description: Survey projects only.
                      estimated_cost_cents:
                        type: integer
                      total_recipients:
                        type: integer
                      completeness:
                        type: object
                        description: >-
                          Broadcast reports has_message; surveys report
                          has_questions.
                        additionalProperties: true
                    additionalProperties: true
              examples:
                broadcast:
                  summary: Broadcast created
                  value:
                    success: true
                    data:
                      project_id: 01HX000000000000000000P001
                      name: Spring Outreach
                      type: broadcast
                      status: awaiting_test
                      channel: 10dlc
                      created_via_api: true
                      estimated_cost_cents: 4250
                      total_recipients: 500
                      completeness:
                        has_list: true
                        has_message: true
                        has_phone_number: true
                        ready_to_test: true
                survey:
                  summary: Survey created
                  value:
                    success: true
                    data:
                      project_id: 01HX000000000000000000P002
                      name: Primary Intent Survey
                      type: survey
                      status: awaiting_test
                      channel: 10dlc
                      created_via_api: true
                      question_count: 4
                      estimated_cost_cents: 1276
                      total_recipients: 500
                      completeness:
                        has_list: true
                        has_questions: true
                        has_phone_number: true
                        ready_to_test: 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
        '409':
          description: >-
            Sending is paused for the organization or platform-wide (error code
            SENDING_PAUSED), or a contact list on the project is still being
            analyzed (error code LIST_ANALYSIS_IN_PROGRESS; nothing is changed,
            retry as-is once `analysis.status` is `complete`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                listAnalysisInProgress:
                  summary: A contact list on the project is still being analyzed
                  value:
                    error: >-
                      Contact list "October donors" is still being analyzed.
                      Wait for analysis to complete, then try again.
                    code: LIST_ANALYSIS_IN_PROGRESS
                    details:
                      lists:
                        - id: 0195f3a2-7c1e-7d4b-9a3e-5b6c7d8e9f01
                          name: October donors
                          status: processing
                organizationPaused:
                  summary: Sending is paused for this organization
                  value:
                    error: Sending is currently paused for this organization
                    code: SENDING_PAUSED
                    details:
                      scope: organization
                platformPaused:
                  summary: Sending is paused platform-wide
                  value:
                    error: Sending is currently paused
                    code: SENDING_PAUSED
                    details:
                      scope: platform
        '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.
  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:
              - field: organization_id
                message: Invalid UUID
                code: invalid_format
    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
    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 600 requests per 60 seconds
            code: RATE_LIMIT_EXCEEDED
            statusCode: 429
            retryAfter: 3
    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
  schemas:
    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. For VALIDATION_ERROR, an array of { field,
            message, code } with one entry per rejected field (field is the
            dotted path); 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.
    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
  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.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.