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

# Criar campanha

> Cria a campanha, valida template e variáveis, reserva a cota do plano e
**debita o custo total da carteira** antes de enfileirar. Os envios rodam em
segundo plano. Contatos bloqueados no envio (opt-out, número inválido,
limite de frequência) são estornados um a um.

- `Idempotency-Key` é **obrigatório**. Repetir a chave devolve a campanha já
  criada, sem cobrar de novo. A unicidade da chave é global na Arara, não
  por organização: derive do identificador da campanha no seu sistema, com
  um prefixo só seu (ex. `acme-campanha-8842`).
- `scheduledAt` agenda o envio, entre 5 minutos e 30 dias à frente. A
  campanha nasce `SCHEDULED`, já cobrada. Fora da janela:
  `400 CAMPAIGN_SCHEDULE_TOO_SOON` ou `400 CAMPAIGN_SCHEDULE_TOO_FAR`.
- `abTest` cria o teste A/B junto com a campanha. `templateName` é a
  variante A.

Pré-requisitos: número dedicado conectado (`403 NO_DEDICATED_NUMBER`), saldo
para o total (`402 INSUFFICIENT_FUNDS`) e cota do plano
(`403 PLAN_LIMIT_REACHED`).

Permissão: `CAMPAIGNS_SEND`.




## OpenAPI

````yaml /openapi.yaml post /v1/campaigns
openapi: 3.0.3
info:
  title: API da Arara
  description: >
    API REST da Arara para WhatsApp Business: mensagens, templates, campanhas,

    contatos, conversas, formulários, automações, smart links e carteira.


    Esta especificação cobre só os endpoints que aceitam **chave de API**

    (`Authorization: Bearer ara_live_...`). Rotas usadas apenas pelo painel

    (gestão de chaves, configuração do webhook, pagamentos e perfil pessoal)

    ficam de fora.


    ## Autenticação


    Header `Authorization: Bearer ara_live_...`. Crie a chave em

    https://app.ararahq.com/dashboard/apikeys. As chaves são sempre LIVE:

    mensagens são entregues de verdade e o custo sai da carteira.


    ## Permissões da chave


    Chave criada sem permissões explícitas tem acesso total (`ADMIN`). Chave

    com permissões restritas só alcança:


    - `READ`: `GET` em `/v1/messages`, `/v1/campaigns`, `/v1/templates`,
      `/v1/flows`, `/v1/automations` e `/v1/organizations/me/numbers`.
    - `MESSAGES_SEND`: escrita em `/v1/messages/**`, `POST /v1/media/upload` e
      `POST /v1/flows/{id}/test`.
    - `TEMPLATES_WRITE`: escrita em `/v1/templates/**` e `/v1/flows/**`.

    - `CAMPAIGNS_SEND`: escrita em `/v1/campaigns/**`.

    - `CONTACTS_WRITE`: escrita em `/v1/contacts/**`.

    - `AUTOMATIONS_MANAGE`: escrita em `/v1/automations/**`.

    - `WEBHOOKS_MANAGE`: escrita em `/webhook/**`.


    Qualquer outra rota exige `ADMIN`. Fora do escopo, a resposta é `403`.


    ## Rate limit


    Por usuário da chave: 1.200 leituras (`GET`) por minuto e 3.000 escritas por

    minuto. Toda resposta traz `X-RateLimit-Remaining`. Acima do limite: `429`

    com `Retry-After` e `error.code = RATE_LIMIT_EXCEEDED`. O envio tem limites

    próprios (`SEND_RATE_LIMITED`, `BATCH_BUSY`,
    `MARKETING_FREQUENCY_EXCEEDED`).


    ## Formato de erro


    ```json

    { "error": { "code": "ERROR_CODE", "message": "Descrição", "details": {} } }

    ```


    Exceções: falha de autenticação (`401`, `403` e `503` do filtro de chave)

    volta no formato padrão do Spring (`timestamp`, `status`, `error`, `path`,

    sem `code`), e alguns `404` e `403` voltam sem corpo.
  version: 2.1.0
  contact:
    name: AraraHQ
    url: https://ararahq.com
    email: oi@ararahq.com
servers:
  - url: https://api.ararahq.com
    description: Produção
security:
  - bearerAuth: []
tags:
  - name: Mensagens
    description: Envio, consulta e histórico de mensagens de WhatsApp, e upload de mídia.
  - name: Conversas
    description: >-
      Conversas com cada telefone, resposta dentro da janela de 24h e situação
      da janela.
  - name: Templates
    description: Criação, listagem, status de aprovação e métricas de templates.
  - name: Campanhas
    description: >-
      Envio do mesmo template para muitos contatos, com estimativa, agendamento,
      cancelamento e teste A/B.
  - name: Contatos
    description: Base de contatos da organização e direitos do titular (LGPD).
  - name: Opt-out
    description: Registro e consulta de quem pediu para não receber mensagens.
  - name: Formulários
    description: >-
      Formulários nativos do WhatsApp (Flows) e as respostas recebidas.
      Disponíveis do plano Voo para cima.
  - name: Automações
    description: >-
      Sequências de passos que rodam sozinhas para cada contato que entra por um
      gatilho. Exigem plano com recuperação de receita.
  - name: Smart Links
    description: Links wa.me rastreáveis, com QR code.
  - name: Números
    description: >-
      Números de WhatsApp da organização e o perfil comercial exibido no
      WhatsApp.
  - name: Carteira
    description: Saldo, extrato, recarga automática e preços.
  - name: Consulta de telefone
    description: Consulta de tipo de linha e de WhatsApp de um número (cobrada).
  - name: Recuperação
    description: Endpoint de recuperação de receita (carrinho abandonado, Pix, boleto).
  - name: Conta
    description: Identidade da chave, plano da organização e status da API.
paths:
  /v1/campaigns:
    post:
      tags:
        - Campanhas
      summary: Criar campanha
      description: >
        Cria a campanha, valida template e variáveis, reserva a cota do plano e

        **debita o custo total da carteira** antes de enfileirar. Os envios
        rodam em

        segundo plano. Contatos bloqueados no envio (opt-out, número inválido,

        limite de frequência) são estornados um a um.


        - `Idempotency-Key` é **obrigatório**. Repetir a chave devolve a
        campanha já
          criada, sem cobrar de novo. A unicidade da chave é global na Arara, não
          por organização: derive do identificador da campanha no seu sistema, com
          um prefixo só seu (ex. `acme-campanha-8842`).
        - `scheduledAt` agenda o envio, entre 5 minutos e 30 dias à frente. A
          campanha nasce `SCHEDULED`, já cobrada. Fora da janela:
          `400 CAMPAIGN_SCHEDULE_TOO_SOON` ou `400 CAMPAIGN_SCHEDULE_TOO_FAR`.
        - `abTest` cria o teste A/B junto com a campanha. `templateName` é a
          variante A.

        Pré-requisitos: número dedicado conectado (`403 NO_DEDICATED_NUMBER`),
        saldo

        para o total (`402 INSUFFICIENT_FUNDS`) e cota do plano

        (`403 PLAN_LIMIT_REACHED`).


        Permissão: `CAMPAIGNS_SEND`.
      operationId: createCampaign
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          schema:
            type: string
            maxLength: 255
          description: >-
            Identificador único da campanha. Derive do identificador da campanha
            no seu sistema (ex. `acme-campanha-8842`) e reuse a mesma chave nas
            retentativas. A unicidade é global na Arara.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignRequest'
            examples:
              simples:
                summary: Envio imediato
                value:
                  name: Black Friday 2026
                  templateName: bf_oferta_geral
                  contacts:
                    - to: '+5511999999999'
                      variables:
                        - João
                        - '30'
                    - to: '+5511888888888'
                      variables:
                        - Maria
                        - '25'
              agendada:
                summary: Agendada
                description: >-
                  Troque `scheduledAt` por uma data entre 5 minutos e 30 dias à
                  frente.
                value:
                  name: Lembrete sexta
                  templateName: lembrete_evento
                  scheduledAt: '2027-01-15T12:00:00-03:00'
                  contacts:
                    - to: '+5511999999999'
                      variables:
                        - João
              testeAB:
                summary: Com teste A/B
                value:
                  name: Reativação
                  templateName: reativacao_copy_a
                  contacts:
                    - to: '+5511999999999'
                      variables:
                        - João
                  abTest:
                    variantBTemplateName: reativacao_copy_b
                    metric: CLICKED
                    samplePct: 20
                    splitPct: 50
                    decisionWindowMinutes: 240
                    autopilot: true
      responses:
        '200':
          description: Campanha criada (ou existente, se a chave já foi usada)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignResponse'
        '400':
          description: >-
            `VALIDATION_ERROR` (`name`, `templateName` ou `contacts` ausente ou
            vazio), `CAMPAIGN_SCHEDULE_TOO_SOON` ou `CAMPAIGN_SCHEDULE_TOO_FAR`
            (`scheduledAt` fora da janela de 5 minutos a 30 dias).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: CAMPAIGN_SCHEDULE_TOO_SOON
                  message: Agende para pelo menos 5 minutos à frente.
                  details: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: >-
            `UNPROCESSABLE_ENTITY` ou `ararahq-*`: template não encontrado,
            variáveis incompatíveis ou `sender` inválido.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: UNPROCESSABLE_ENTITY
                  message: Template não encontrado.
                  details: {}
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          description: >-
            Requisição sem `Idempotency-Key` responde hoje `500
            INTERNAL_SERVER_ERROR`, e não `400`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    CampaignRequest:
      type: object
      required:
        - name
        - templateName
        - contacts
      properties:
        name:
          type: string
          description: Nome interno da campanha.
        templateName:
          type: string
          description: Nome ou ID do template aprovado. Com `abTest`, é a variante A.
        sender:
          type: string
          nullable: true
          description: Número que envia. Omitido, usa o padrão da organização.
        contacts:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/CampaignContactRequest'
          description: Destinatários, no mínimo 1.
        abTest:
          $ref: '#/components/schemas/CampaignAbConfig'
        scheduledAt:
          type: string
          format: date-time
          nullable: true
          description: Agenda o envio (ISO 8601), entre 5 minutos e 30 dias no futuro.
    CampaignResponse:
      type: object
      required:
        - id
        - name
        - status
        - totalMessages
        - totalCost
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        status:
          $ref: '#/components/schemas/CampaignStatus'
        totalMessages:
          type: integer
        totalCost:
          type: number
          description: >-
            Valor já debitado da carteira, em reais. Os valores dos exemplos são
            ilustrativos.
        scheduledAt:
          type: string
          format: date-time
          nullable: true
      example:
        id: 7b3789fb-4c5d-6789-abcd-ef0123456789
        name: Black Friday 2026
        status: INGESTING
        totalMessages: 2
        totalCost: 0.767
        scheduledAt: null
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              example: INSUFFICIENT_FUNDS
              description: >-
                Código estável do erro. Use este campo no seu tratamento, não a
                mensagem.
            message:
              type: string
              nullable: true
              description: Descrição legível, em português. Pode mudar.
            details:
              type: object
              additionalProperties: true
              description: Dados extras do erro. Vazio na maioria dos casos.
      example:
        error:
          code: INSUFFICIENT_FUNDS
          message: Saldo insuficiente.
          details: {}
    CampaignContactRequest:
      type: object
      required:
        - to
      properties:
        to:
          type: string
          description: Telefone E.164 com `+`.
          example: '+5511999999999'
        variables:
          type: array
          default: []
          items:
            type: string
          description: >-
            Variáveis posicionais do template, em **array** (`["João", "30"]`),
            não em objeto.
    CampaignAbConfig:
      type: object
      required:
        - variantBTemplateName
      properties:
        variantBTemplateName:
          type: string
          description: Template da variante B.
        metric:
          type: string
          enum:
            - DELIVERED
            - READ
            - CLICKED
            - CONVERTED
          default: CLICKED
          description: Métrica que decide a vencedora.
        samplePct:
          type: integer
          default: 20
          description: >-
            Porcentagem dos contatos que entra no teste. O restante espera a
            vencedora.
        splitPct:
          type: integer
          minimum: 1
          maximum: 99
          default: 50
          description: Divisão entre A e B dentro da amostra, de 1 a 99.
        decisionWindowMinutes:
          type: integer
          default: 240
          description: Minutos até decidir a vencedora.
        autopilot:
          type: boolean
          default: true
          description: Envia o restante pela vencedora automaticamente.
    CampaignStatus:
      type: string
      enum:
        - SCHEDULED
        - INGESTING
        - AB_TESTING
        - COMPLETED
        - COMPLETED_WITH_ERRORS
        - FAILED
        - CANCELED
      description: |
        - `SCHEDULED`: agendada, aguardando o horário.
        - `INGESTING`: processando contatos e enfileirando envios.
        - `AB_TESTING`: teste A/B em andamento.
        - `COMPLETED`: todos os contatos processados.
        - `COMPLETED_WITH_ERRORS`: processada, com contatos bloqueados.
        - `FAILED`: falha geral no processamento.
        - `CANCELED`: cancelada.
    SpringError:
      type: object
      description: >-
        Corpo padrão do Spring nas falhas de autenticação (`401`, `403` e `503`
        do filtro de chave). Não tem `error.code`.
      properties:
        timestamp:
          type: string
          format: date-time
        status:
          type: integer
          example: 401
        error:
          type: string
          example: Unauthorized
        path:
          type: string
          example: /v1/messages
  responses:
    Unauthorized:
      description: >
        Chave inexistente, revogada, expirada, com prefixo errado (`ara_` sem

        `ara_live_`) ou vinda de IP fora da allowlist. O corpo é o JSON padrão
        do

        Spring, sem `error.code`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SpringError'
          example:
            timestamp: '2026-09-15T14:30:00.000+00:00'
            status: 401
            error: Unauthorized
            path: /v1/messages
    PaymentRequired:
      description: >-
        `INSUFFICIENT_FUNDS` (saldo insuficiente na carteira) ou
        `PLAN_REQUIRED`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: INSUFFICIENT_FUNDS
              message: Saldo insuficiente.
              details: {}
    Forbidden:
      description: >
        Dois casos, com corpos diferentes:


        - Autenticação: header `Authorization` ausente, Bearer que não começa
        com
          `ara_`, chave sem a permissão da rota ou rota que não aceita chave de
          API. Corpo no formato padrão do Spring, sem `error.code`.
        - Regra de negócio, no formato `{ "error": { ... } }`:
          `PLAN_FEATURE_LOCKED`, `PLAN_LIMIT_REACHED`, `ACCOUNT_NOT_ACTIVATED`,
          `NO_DEDICATED_NUMBER`, `ORGANIZATION_SUSPENDED`, `NUMBER_BLOCKED`,
          `FRAUD_SUSPECTED`, `RESOURCE_FORBIDDEN`, `VIEWER_READ_ONLY`.
      content:
        application/json:
          schema:
            anyOf:
              - $ref: '#/components/schemas/Error'
              - $ref: '#/components/schemas/SpringError'
          examples:
            regraDeNegocio:
              summary: Regra de negócio
              value:
                error:
                  code: PLAN_FEATURE_LOCKED
                  message: Seu plano não inclui este recurso.
                  details: {}
            autenticacao:
              summary: Sem header ou sem permissão
              value:
                timestamp: '2026-09-15T14:30:00.000+00:00'
                status: 403
                error: Forbidden
                path: /v1/messages
    TooManyRequests:
      description: >
        Limite atingido:


        - `RATE_LIMIT_EXCEEDED`: limite geral da API. Traz `Retry-After` e
          `X-RateLimit-Remaining`.
        - `SEND_RATE_LIMITED`: teto de envio, inclusive o limite diário da
        chave.
          Traz `Retry-After`.
        - `BATCH_BUSY`: já existe um lote em processamento. Traz `Retry-After`.

        - `MARKETING_FREQUENCY_EXCEEDED`: o contato já recebeu marketing demais
          na janela. **Não** traz `Retry-After`.
      headers:
        Retry-After:
          schema:
            type: integer
          description: >-
            Segundos a aguardar antes de repetir a chamada, com a mesma
            `Idempotency-Key`. Ausente em `MARKETING_FREQUENCY_EXCEEDED`.
        X-RateLimit-Remaining:
          schema:
            type: integer
          description: >-
            Requisições restantes na janela atual. Presente em
            `RATE_LIMIT_EXCEEDED`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: SEND_RATE_LIMITED
              message: Limite de envio atingido. Tente de novo em instantes.
              details: {}
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: ara_live_...
      description: >-
        Chave de API da Arara (`ara_live_...`), enviada no header
        `Authorization: Bearer ara_live_...`. Crie e gerencie as chaves no
        painel: https://app.ararahq.com/dashboard/apikeys. O valor completo
        aparece uma única vez, na criação.

````

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