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

# Create campaign

> Creates the campaign and queues the import of the recipients. The campaign starts in
`import:waiting` and moves on its own to `schedule`/`processing` when the import finishes
and the start date arrives.

Use `application/json` when the recipients come from the contact base
(`type: connecta_contacts`) and `multipart/form-data` when there is a file or an
attachment. In multipart, the fields that are objects or lists (`channel`, `queue`,
`template`, `messages`, `mapping`, `tags`, `contacts`, `add_tags`) go as JSON in text.



## OpenAPI

````yaml /api-reference/openapi.en.yaml post /campaigns
openapi: 3.0.3
info:
  title: Connecta DESK Public API
  version: 1.0.0
  description: >-
    Public API of Connecta DESK, available at `/api/v1`. Access uses a token
    generated in the tokens module. Each token has permissions per **module ×
    method**, and every list read is paginated.


    ## Authentication


    ```

    Authorization: Bearer sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

    ```


    The token belongs to a **company**, not to a user. The company used is
    always the token's company, so sending `companyId` in the body has no
    effect. A missing, invalid, revoked or expired token gets the same `401`
    response.


    ## Order of checks


    1. **token** → `401`

    2. **active company** → `403`

    3. **API enabled on the plan** → `403 ERR_API_NOT_ENABLED` (does not apply
    to on-premise)

    4. **company rate limit** → `429`

    5. **token permission** → `403 ERR_API_FORBIDDEN_SCOPE`


    ## User ids


    Every user id in the API is the user's id **in this company**, the same one
    `GET /users` returns. This applies to what the API returns (`user.id`,
    `assignedTo.id`, `userCreated.id`) and to what it accepts (`userId`,
    `userIds`, `assignedToUserId` and the `{id}` in user routes).


    The same person can work in several companies, with a different name and
    profile in each one, and has a different id in each company.


    An id that does not belong to this company returns `404`, with the same
    message as an id that does not exist.


    ## Pagination


    Every list `GET` is paginated. The `meta` contains only `page`, `perPage`
    and `hasMore`, without the total number of records. To go through
    everything, keep advancing `page` while `hasMore` is `true`.


    ## Idempotency


    Optional on `POST`/`PUT`, through the `Idempotency-Key` header. If you
    repeat the call with the same key and the same body, you get the stored
    response (with `Idempotent-Replay: true`). With the same key and a different
    body, the response is `409`.
servers:
  - url: https://api.connectadesk.com/api/v1
    description: Production
security:
  - apiToken: []
tags:
  - name: contacts
    description: Contact records, extra fields and marketing source.
  - name: tickets
    x-displayName: Tickets
    description: Opening, transferring, closing and wrap-up of tickets.
  - name: messages
    description: Asynchronous sending and reading of messages.
  - name: tags
    description: Tags and their link to tickets.
  - name: queues
    description: Service queues and their agents.
  - name: wrapups
    description: Wrap-up types.
  - name: users
    description: Agents and administrators.
  - name: schedules
    description: Scheduled messages.
  - name: quick-messages
    description: Quick replies.
  - name: campaigns
    description: Bulk sending.
  - name: crm
    description: Boards, stages, cards and metrics.
  - name: channels
    description: Channels (read only).
  - name: reports
    description: Reports and dashboards.
  - name: products
    description: Product catalog, categories and sales performance.
  - name: custom-fields
    description: Custom fields for contacts and cards, plus company-wide global variables.
  - name: profiles
    description: Company permission profiles (read only).
  - name: webchat
    description: >-
      Native webchat from your own system: open a visitor session, send and read
      messages. The token needs the `webchat` scope (GET and POST).
paths:
  /campaigns:
    post:
      tags:
        - campaigns
      summary: Create campaign
      description: >-
        Creates the campaign and queues the import of the recipients. The
        campaign starts in

        `import:waiting` and moves on its own to `schedule`/`processing` when
        the import finishes

        and the start date arrives.


        Use `application/json` when the recipients come from the contact base

        (`type: connecta_contacts`) and `multipart/form-data` when there is a
        file or an

        attachment. In multipart, the fields that are objects or lists
        (`channel`, `queue`,

        `template`, `messages`, `mapping`, `tags`, `contacts`, `add_tags`) go as
        JSON in text.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignInput'
            examples:
              base_por_tags:
                summary: >-
                  Recipients from the contact base, by tags, with a WhatsApp
                  template
                value:
                  send_name: Black Friday 2026
                  slug: black-friday-2026
                  channel:
                    id: 4
                  type: connecta_contacts
                  import_contacts_mode: tags
                  tags:
                    - 7
                    - 12
                  tags_mode: only
                  tags_operator: or
                  message_type: template
                  template:
                    name: promocao_black_friday
                    parameters:
                      body:
                        - Maria
                        - 30%
                  init_timestamp: '2026-11-24T09:00:00.000Z'
                  send_avg_time: 30
                  open_ticket: true
                  queue:
                    id: 7
                  add_tags:
                    - 19
              base_por_contatos:
                summary: Recipients picked one by one, with free text
                value:
                  send_name: Aviso de manutenção
                  slug: aviso-manutencao
                  channel:
                    id: 9
                  type: connecta_contacts
                  import_contacts_mode: contacts
                  contacts:
                    - 4821
                    - 4822
                  message_type: message
                  messages:
                    - Olá {{name}}, o sistema ficará indisponível hoje às 22h.
                  init_timestamp: '2026-10-01T12:00:00.000Z'
                  send_avg_time: 15
                  open_ticket: false
                  queue:
                    id: 3
          multipart/form-data:
            schema:
              allOf:
                - $ref: '#/components/schemas/CampaignInput'
                - type: object
                  properties:
                    file:
                      type: string
                      format: binary
                      description: >-
                        Spreadsheet with the recipients (CSV, XLS or XLSX),
                        required when

                        `type: file`. The CSV separator (`,` or `;`) is detected
                        from the header.

                        The maximum size comes from `FILE_MAX_SIZE` (5 MB by
                        default).
                    template_media:
                      type: string
                      format: binary
                      description: >-
                        Media for the template header (image, video, document or
                        audio). Only

                        makes sense when the approved template has a media
                        header.
                    message_attachment_0:
                      type: string
                      format: binary
                      description: >-
                        Attachment of the message in the same position in
                        `messages`

                        (`message_attachment_0` for the first one,
                        `message_attachment_1` for the

                        second, and so on). Applies only to `message_type:
                        message`.
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Campaign'
        '403':
          description: >-
            Error `ERR_CAMPAIGN_MODULE_DISABLED`, when the campaigns module is
            not enabled for the

            company. It is the same guard as the screen: without the module, the
            campaign would never be sent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
components:
  schemas:
    CampaignInput:
      type: object
      description: >-
        Campaign settings. Which fields are required depends on `type` (where
        the recipients

        come from) and `message_type` (what will be sent).
      required:
        - send_name
        - slug
        - channel
        - queue
        - type
        - message_type
        - init_timestamp
        - send_avg_time
      properties:
        send_name:
          type: string
          description: Campaign name, as it appears in the listing.
          example: Black Friday 2026
        slug:
          type: string
          description: Short campaign identifier, used in reports.
          example: black-friday-2026
        channel:
          type: object
          description: Outbound channel. See the available ones in `GET /channels`.
          required:
            - id
          properties:
            id:
              type: integer
              example: 4
        type:
          type: string
          description: >-
            Where the recipients come from:

            `file` (spreadsheet sent in the `file` field) or `connecta_contacts`
            (your contact base).
          enum:
            - file
            - connecta_contacts
          example: connecta_contacts
        import_contacts_mode:
          type: string
          description: >-
            Required with `type: connecta_contacts`. Picks contacts one by one
            or by tags.
          enum:
            - contacts
            - tags
          example: tags
        contacts:
          type: array
          description: >-
            Contact ids, with `import_contacts_mode: contacts`. No file is
            needed:

            the list becomes the campaign's recipient base.


            Every id must belong to this company — an id from outside returns
            `422`

            (`ERR_API_CAMPAIGN_IDS_NOT_FOUND`) with the list of what does not
            match.
          items:
            type: integer
          example:
            - 4821
            - 4822
        tags:
          type: array
          description: >-
            Tag ids, with `import_contacts_mode: tags`. The recipients are the
            contacts that

            match the tags, according to `tags_mode` and `tags_operator`. No
            file is needed

            here either, and the ids must belong to this company.
          items:
            type: integer
          example:
            - 7
            - 12
        tags_mode:
          type: string
          description: >-
            How the tags filter the base: `only` (only those who have them),
            `except` (those who

            do not) or `all` (the whole base, ignoring the tags).
          enum:
            - all
            - except
            - only
          example: only
        tags_operator:
          type: string
          description: >-
            With more than one tag: `and` requires all of them, `or` accepts any
            one of them.
          enum:
            - and
            - or
          example: or
        mapping:
          type: array
          description: >-
            Mapping between the spreadsheet columns and the system fields, with
            `type: file`.


            **Can be omitted.** Without it, the mapping is built automatically
            from the header:

            each column is compared (ignoring accents and case) with the known
            names of each

            field — for example `telefone`, `celular`, `whatsapp`, `fone`,
            `numero` and `phone`

            become `contact_number`; `nome`, `contato` and `cliente` become
            `contact_name`;

            `cpf`, `cnpj` and `documento` become `contact_cpfcnpj`. For custom
            fields, the header

            is compared with the `key` and with the field name. A column that
            matches nothing

            comes in as `dont_import`, and the first column wins when two would
            point to the

            same field.


            The phone and the name are required. If the automatic mapping does
            not recognize one

            of these columns, or if the `mapping` sent does not include
            `contact_number` and

            `contact_name`, creation returns `422`
            (`ERR_API_CAMPAIGN_MAPPING_PHONE_REQUIRED` or

            `ERR_API_CAMPAIGN_MAPPING_NAME_REQUIRED`) instead of creating a
            campaign that would fail

            on import.
          items:
            $ref: '#/components/schemas/CampaignMappingItem'
        message_type:
          type: string
          description: >-
            What will be sent: `message` (free text, unofficial channels only)
            or `template`

            (approved official WhatsApp template). Any other value returns `422`

            (`ERR_API_CAMPAIGN_MESSAGE_TYPE_INVALID`).
          enum:
            - message
            - template
          example: template
        messages:
          type: array
          description: >-
            Campaign texts, with `message_type: message`. More than one text
            makes the system

            alternate between them on each send. Accepts the contact variables
            (`{{name}}`,

            `{{email}}`…). The attachment of each message goes in
            `message_attachment_<position>`.
          items:
            type: string
          example:
            - Olá {{name}}, temos uma novidade para você!
        template:
          type: object
          description: >-
            Approved template, with `message_type: template`. Send only the name
            and the

            variables — the system builds the rest and checks it against the
            REAL templates of

            the channel, so a wrong name or a missing variable shows up here and
            not on the day

            of the send. See the available ones in `GET
            /channels/{id}/templates`.
          properties:
            name:
              type: string
              example: promocao_black_friday
            language:
              type: string
              description: Only when the template exists in more than one language.
              example: pt_BR
            parameters:
              type: object
              description: Variable values, in the order they appear in the template.
              properties:
                header:
                  type: array
                  items:
                    type: string
                body:
                  type: array
                  items:
                    type: string
                  example:
                    - Maria
                    - 30%
        init_timestamp:
          type: string
          format: date-time
          description: >-
            When the sending starts. It must be in the future.


            **Without a time zone, your company's time zone applies.**
            `2026-11-24T09:00:00` starts at

            nine in the morning in the time zone set on the company — the same
            one you see in the

            panel. If you prefer to be explicit, send it with an offset

            (`2026-11-24T09:00:00-03:00`) or in UTC
            (`2026-11-24T12:00:00.000Z`), and the value is

            then respected as it is.
          example: '2026-11-24T09:00:00-03:00'
        pause_timestamp:
          type: string
          format: date-time
          description: >-
            Start of the daily pause window (for example, to avoid sending in
            the middle of the night).

            Same time zone rule as `init_timestamp`.
          example: '2026-11-24T21:00:00-03:00'
        restart_timestamp:
          type: string
          format: date-time
          description: |-
            End of the pause window. Required when `pause_timestamp` is given.
            Same time zone rule as `init_timestamp`.
          example: '2026-11-25T09:00:00-03:00'
        send_avg_time:
          type: integer
          description: >-
            Average interval between sends, in seconds. The longer it is, the
            slower and the safer for the number.
          example: 30
        open_ticket:
          type: boolean
          description: >-
            Opens the ticket at the moment of the send: it comes in as `pending`
            in the `queue`

            queue and shows up for the agents right away. Without it the send
            goes out with the

            ticket closed, and the conversation only opens if the contact
            replies.
          example: true
        queue:
          type: object
          description: >-
            Queue of the ticket the send creates. **Required.** It applies
            either way: with

            `open_ticket: true` the conversation opens already in that queue;
            without it, the ticket

            stays closed, but in the queue.


            It is the `queueId` that decides where the ticket lands. Without a
            queue it would be

            created loose, showing up for no team — and you would only find out
            after the campaign

            ran. That is why creating or editing without `queue` responds `422`

            (`ERR_API_CAMPAIGN_QUEUE_REQUIRED`). See the queues in `GET
            /queues`.
          required:
            - id
          properties:
            id:
              type: integer
              example: 7
        add_tags:
          type: array
          description: Tags applied to every contact the campaign reaches.
          items:
            type: integer
          example:
            - 19
    Campaign:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
          description: Display name.
        status:
          type: string
          enum:
            - import:waiting
            - import:start
            - import:fail_retry
            - import:failed
            - waiting
            - schedule
            - processing
            - failed
            - completed
            - canceled
            - pause:manual
            - pause:automatic
            - pause:no_balance
            - pause:active_contacts_limit
        scheduledAt:
          type: string
          format: date-time
          nullable: true
        completedAt:
          type: string
          format: date-time
          nullable: true
        contactListId:
          type: integer
          nullable: true
        channelId:
          type: integer
          nullable: true
          example: 4
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      example:
        id: 77
        name: Promoção de inverno
        status: processing
        scheduledAt: '2026-08-19T12:00:00.000Z'
        completedAt: null
        contactListId: 12
        channelId: 11
        createdAt: '2026-08-17T18:20:00.000Z'
        updatedAt: '2026-08-19T12:00:03.221Z'
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Fixed error code, so your system can handle each case.
              example: ERR_API_FORBIDDEN_SCOPE
            message:
              type: string
            details: {}
    CampaignMappingItem:
      type: object
      description: One spreadsheet column and the system field it fills.
      required:
        - header
        - value
      properties:
        header:
          type: string
          description: Column header, exactly as written in the file.
          example: Telefone
        value:
          type: string
          description: >-
            Field that receives the column contents:


            | Value | Fills |

            |---|---|

            | `contact_number` | Phone number with country code. **Required** —
            it is the recipient of the send |

            | `contact_name` | Name. **Required** |

            | `contact_email` | Email |

            | `contact_cpfcnpj` | CPF/CNPJ (the mask is applied on import) |

            | `contact_code` | Contact code in your system |

            | `contact_origin` | Origin |

            | `contact_instagram` | Instagram handle (the `@` is removed) |

            | `contact_lid` | WhatsApp LID |

            | `contact_source_url` | Source page |

            | `contact_source_id` | Source id |

            | `contact_headline` | Ad headline |

            | `contact_media_type` | Source media type |

            | `contact_media_url` | Source media URL |

            | `contact_utm_source` | utm_source |

            | `contact_utm_medium` | utm_medium |

            | `contact_utm_campaign` | utm_campaign |

            | `contact_utm_id` | utm_id |

            | `contact_fbclid` | fbclid |

            | `contact_gclid` | gclid |

            | `contact_ctwa_clid` | ctwa_clid |

            | `custom_field:<id>` | Contact custom field — the `<id>` comes from
            `GET /custom-fields?application=contact` |

            | `dont_import` | Skips the column |


            A blank column never overwrites data already stored on the contact.
            A system custom

            field (`isSystem`) is read-only and is ignored.


            A row with a blank phone or name is not imported: it is recorded in
            the import

            failures and the other rows continue normally.
          example: contact_number
  responses:
    UnprocessableEntity:
      description: >-
        The body is well formed, but a required field is missing or a value is
        not accepted. For example, `application` other than `contact`/`card`, an
        empty `orderedIds` or a blank `name`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: ERR_API_VALIDATION
              message: >-
                O parâmetro `application` é obrigatório e aceita "contact" ou
                "card".
  securitySchemes:
    apiToken:
      type: http
      scheme: bearer
      description: API token in the `sk_live_…` format.

````

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