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

# Create a campaign

> **Creating a campaign sends it.** There is no separate step that starts the send, and no way to call it back: whatever you create here goes out. `confirm_recipients` has to equal the `reachable` the preview returned for the same audience in the same channel; if it does not, the answer is 409 with both numbers and nothing is created.

**Do not repeat this call after a timeout.** A repeat creates a second campaign and sends a second time, and every WhatsApp message is billed. Reading the campaign back only helps if you already hold its `id`: there is no listing and no idempotency key, so a lost response cannot be resolved through this API — open the campaign dashboard in Cloud Chat to see whether it was created before you send again.

The channel follows from what you send. A campaign carrying `email_campaign_sender_id` is an e-mail campaign; otherwise the channel comes from the `channel_type` of the inbox behind `inbox_id`: `Channel::Whatsapp` sends a WhatsApp template, `Channel::WebWidget` sends a widget message. **Check `channel_type` first**, because nothing cross-checks your intent: a website inbox chosen while you meant to send WhatsApp delivers the template name to your contacts as the literal text of the message.

**This response is not a delivery confirmation.** Sending happens after it returns and can still fail; what actually reached your contacts is in the Cloud Chat campaign dashboard. Every WhatsApp message is billed and reaches a real person, so do not send one merely to rehearse. To check an audience without sending, use the preview endpoint.

export const CloudChatYourValues = () => {
  const STORAGE_KEY = "cloudchat-api-selected-account";
  const CREDENTIALS_HREF = "/api-reference/cloudchat/credentials";
  const [saved, setSaved] = useState(null);
  const [ready, setReady] = useState(false);
  useEffect(() => {
    let value = null;
    try {
      const raw = window.localStorage.getItem(STORAGE_KEY);
      if (raw) {
        const parsed = JSON.parse(raw);
        if (parsed && parsed.instance && parsed.account) {
          value = {
            instance: String(parsed.instance),
            account: String(parsed.account),
            name: typeof parsed.name === "string" && parsed.name ? parsed.name : null
          };
        }
      }
    } catch (error) {
      value = null;
    }
    setSaved(value);
    setReady(true);
  }, []);
  const shell = "not-prose rounded-xl border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5 px-4 py-3 mb-6";
  if (!ready || !saved) {
    return <div className={shell}>
        <p className="text-sm text-gray-600 dark:text-gray-400">
          Every request below needs your <code>cloudchat-instance</code> header and your account
          id.{" "}
          <a href={CREDENTIALS_HREF} className="underline underline-offset-2">
            Paste your token
          </a>{" "}
          and they will show up here, ready to copy.
        </p>
      </div>;
  }
  return <div className={shell}>
      <div className="flex flex-wrap items-baseline gap-x-6 gap-y-2">
        <div>
          <span className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
            cloudchat-instance
          </span>
          <span className="ml-2 font-mono text-sm text-gray-900 dark:text-gray-100">
            {saved.instance}
          </span>
        </div>
        <div>
          <span className="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
            accountId
          </span>
          <span className="ml-2 font-mono text-sm text-gray-900 dark:text-gray-100">
            {saved.account}
          </span>
        </div>
        {saved.name && <span className="text-sm text-gray-600 dark:text-gray-400">{saved.name}</span>}
        <a href={CREDENTIALS_HREF} className="text-sm text-gray-500 dark:text-gray-400 underline underline-offset-2">
          Change
        </a>
      </div>
    </div>;
};

<CloudChatYourValues />


## OpenAPI

````yaml api-reference/specs/cloudchat/v1.json POST /v1/accounts/{accountId}/campaigns
openapi: 3.0.1
info:
  title: Cloud Chat API
  version: v1
  description: >-
    The curated public surface of Cloud Chat. Every call needs two things: the
    Bearer token from `POST /auth/v1/signin`, and the `cloudchat-instance`
    header that says which Cloud Chat instance your company lives on.


    If you don't know your instance value, the [Find your
    credentials](/api-reference/cloudchat/credentials) page reads it out of your
    own token in the browser.
servers:
  - url: https://api.cloudhumans.com/cloudchat
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Canned Responses
    description: >-
      Canned responses — the shortcuts agents expand while replying. Each one
      belongs to a single account and is identified by its short code.
  - name: Conversations
    description: >-
      Read-only access to the conversations of an account. What you can see
      follows the same rule as the dashboard: an administrator token sees every
      inbox, an agent token only the inboxes it is a member of.
  - name: Help Center
    description: >-
      Portals, categories and articles of the help center — the public FAQ your
      customers read.
  - name: Uploads
    description: Store the images help center articles embed.
  - name: Labels
    description: >-
      Read-only access to the labels an account has defined. A label is a tag
      conversations and contacts carry; this endpoint only lists the label
      definitions themselves.
  - name: Inboxes
    description: >-
      Read-only access to the inboxes a token can see — every inbox of the
      account for an administrator, only its memberships for an agent. Never
      includes the channel's credentials.
  - name: Agents
    description: >-
      Read-only access to the human and AI agents of an account. A hidden admin
      (the platform's own support user) never appears, on the list or by id.
  - name: Teams
    description: >-
      Teams group agents for assignment. Listing and reading are open to any
      member; creating and updating a team, and managing its membership, require
      an administrator token. A system-managed team (provisioned by the
      platform) can be read like any other but never updated.
  - name: Macros
    description: >-
      Macros bundle a sequence of actions an agent runs against a conversation
      from the dashboard. This surface lets you list, read and write macro
      definitions — running one is not part of the v1 contract. A macro is
      either `global` (visible to the whole account) or `personal` (visible only
      to its author); an agent token can only create personal macros.
  - name: Automation Rules
    description: >-
      Read-only access to the account's automation rules — administrator only.
      An agent token gets a 403 on every operation in this group.
  - name: Availability Reasons
    description: >-
      The reasons an agent can go `busy` for, configured per account. Listing is
      open to any member; creating, updating and deleting require an
      administrator token. Deleting is a soft delete — the reason disappears
      from listings but agents' past availability history keeps referencing it.
  - name: Native Attribute Requirements
    description: >-
      Which native conversation attributes (priority, assignee, tag, team) the
      account requires agents to fill before resolving a conversation. Listing
      is open to any member; requiring and un-requiring need an administrator
      token. Deleting a requirement is reversible — require the same key again
      to restore it.
  - name: Campaigns
    description: Check who a campaign would reach, send it, and read the result back.
paths:
  /v1/accounts/{accountId}/campaigns:
    parameters:
      - $ref: '#/components/parameters/CloudChatInstance'
      - $ref: '#/components/parameters/AccountId'
    post:
      tags:
        - Campaigns
      summary: Create a campaign
      description: >-
        **Creating a campaign sends it.** There is no separate step that starts
        the send, and no way to call it back: whatever you create here goes out.
        `confirm_recipients` has to equal the `reachable` the preview returned
        for the same audience in the same channel; if it does not, the answer is
        409 with both numbers and nothing is created.


        **Do not repeat this call after a timeout.** A repeat creates a second
        campaign and sends a second time, and every WhatsApp message is billed.
        Reading the campaign back only helps if you already hold its `id`: there
        is no listing and no idempotency key, so a lost response cannot be
        resolved through this API — open the campaign dashboard in Cloud Chat to
        see whether it was created before you send again.


        The channel follows from what you send. A campaign carrying
        `email_campaign_sender_id` is an e-mail campaign; otherwise the channel
        comes from the `channel_type` of the inbox behind `inbox_id`:
        `Channel::Whatsapp` sends a WhatsApp template, `Channel::WebWidget`
        sends a widget message. **Check `channel_type` first**, because nothing
        cross-checks your intent: a website inbox chosen while you meant to send
        WhatsApp delivers the template name to your contacts as the literal text
        of the message.


        **This response is not a delivery confirmation.** Sending happens after
        it returns and can still fail; what actually reached your contacts is in
        the Cloud Chat campaign dashboard. Every WhatsApp message is billed and
        reaches a real person, so do not send one merely to rehearse. To check
        an audience without sending, use the preview endpoint.
      operationId: createCampaign
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignCreateRequest'
      responses:
        '201':
          description: >-
            Created, and already on its way. This is the only response that
            carries `reachable_count`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/CampaignForbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: >-
            `confirm_recipients` does not match the reachable count, and
            **nothing was created**. The message carries both numbers. Preview
            the same audience and channel again, then repeat the call with the
            number it returns.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              example:
                error:
                  code: conflict
                  message: The request payload is invalid.
                  details:
                    - field: confirm_recipients
                      code: mismatch
                      message: >-
                        You confirmed 800 recipients but this audience currently
                        reaches 1187. Preview the recipients again and resend
                        with the current number.
        '422':
          description: >-
            The campaign was refused and **nothing was created**.
            `details[].code` says why: `template_not_found` when `message` is
            not an approved template for the inbox (the message lists the valid
            names), `template_name_as_message` for the mirror case — `message`
            *is* the name of an approved WhatsApp template on one of your
            inboxes, but this campaign is not on a WhatsApp channel, so that
            text would reach your contacts verbatim, `unreachable` when the
            audience reaches nobody, `unsupported_type` or `too_many_entries`
            for the audience, `cap_exceeded` when it is larger than your account
            may put in one campaign, `invalid` for a `scheduled_at` that is not
            in the future, `conflicting_channel_fields` when `inbox_id` and
            `email_campaign_sender_id` travel together and `invalid` when the
            sender is not one of yours, `missing_channel` when neither is sent,
            `unsupported_channel` or `inbox_not_assigned` for an inbox that
            cannot run a campaign or is not yours, and `invalid_template_param`
            for a placeholder your account does not define.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              example:
                error:
                  code: validation_failed
                  message: The request payload is invalid.
                  details:
                    - field: message
                      code: template_not_found
                      message: >-
                        Template "reactivation" is not approved for this inbox.
                        Approved templates: reactivation_september,
                        welcome_back.
        '429':
          $ref: '#/components/responses/CampaignTooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  parameters:
    CloudChatInstance:
      name: cloudchat-instance
      in: header
      required: true
      description: >-
        Your Cloud Chat instance ID — an integer, fixed for your company, told
        at onboarding. [The API
        overview](/api-reference/cloudchat/overview#two-headers-every-call)
        explains how instances work, how to find yours, and the errors a wrong
        or missing value produces.
      schema:
        type: integer
        example: 1
    AccountId:
      name: accountId
      in: path
      required: true
      description: >-
        Your Cloud Chat account. It has to be an account your token grants
        membership on, and it has to live on the instance in the
        `cloudchat-instance` header — the two travel together. Account numbers
        are only unique **within** an instance, so the same number is a
        different company on another instance. Usually a mismatched pair fails
        closed with a 401, because your user does not exist on the other
        instance — but if your identity happens to exist on both, the call
        succeeds against the other company's data, silently. Read it and you are
        looking at the wrong help center; write it and you have stored into the
        wrong account. Send the two values that were given to you together, and
        never try a number to see what answers.
      schema:
        type: integer
        example: 1
  schemas:
    CampaignCreateRequest:
      type: object
      description: >-
        A campaign to create and send, together with the reach you are
        confirming.
      required:
        - campaign
        - confirm_recipients
      properties:
        campaign:
          $ref: '#/components/schemas/CampaignInput'
        confirm_recipients:
          type: integer
          description: >-
            The reachable count you are confirming. It has to equal the
            `reachable` the preview returned for this exact audience and
            channel; any other value answers 409 with both numbers and creates
            nothing. Leaving it out is a 409 too, not a default.
          example: 1187
        template_params:
          type: object
          description: >-
            Values for a WhatsApp template placeholders, keyed by the exact
            token between the braces: `"1"`, `"2"` and so on for a positional
            template, the variable name for a named one. Send exactly as many
            keys as the template `parameters` count. A value wrapped in double
            braces, such as `{{contact.name}}`, is resolved per recipient at
            send time; anything else is sent literally to everyone.
          additionalProperties:
            type: string
          example:
            '1': '{{contact.name}}'
            '2': 15%
        payload_params:
          type: object
          description: >-
            Values for placeholders inside the template buttons, keyed the same
            way. Only templates whose buttons carry a placeholder need this.
          additionalProperties:
            type: string
    Campaign:
      type: object
      description: >-
        A one-off campaign as stored. `reachable_count` is present only in the
        create response.
      required:
        - id
        - title
        - message
        - campaign_status
        - campaign_type
        - created_at
        - updated_at
      properties:
        id:
          type: integer
          description: >-
            The display id, sequential within the account. **Keep it** — no
            endpoint lists campaigns, so this is the only way back to it.
          example: 176
        reachable_count:
          type: integer
          description: >-
            The reach the campaign was confirmed against. Returned only when the
            campaign is created: reach is computed, never stored, so reading a
            campaign back omits the field entirely.
          example: 1187
        title:
          type: string
          description: Internal name of the campaign.
          example: September reactivation
        message:
          type: string
          description: >-
            The template name on a WhatsApp campaign, the message body on e-mail
            and widget.
          example: reactivation_september
        campaign_status:
          type: string
          description: >-
            Where the campaign is in its lifecycle — **not a delivery report**.
            `completed` means a worker took it over, not that anything arrived:
            on WhatsApp it is set before the audience is even resolved. `failed`
            means a precondition stopped the dispatch and it will not be
            retried. What actually reached your contacts is in the Cloud Chat
            campaign dashboard.


            The values in use are `active`, `completed`, `running`, `draft`,
            `partially_completed` and `failed`. The set is open and grows as new
            states are added, so read an unfamiliar value as a state you do not
            know yet rather than as an error.
          example: completed
        campaign_type:
          type: string
          enum:
            - ongoing
            - one_off
          description: >-
            Campaigns created through the API are always `one_off`. A campaign
            built in Cloud Chat can be `ongoing` and fire on a trigger instead.
          example: one_off
        subject:
          type: string
          nullable: true
          description: Subject line of an e-mail campaign; null on the other channels.
        inbox_id:
          type: integer
          nullable: true
          description: >-
            The inbox that sends. On an e-mail campaign it follows from the
            sender.
          example: 60
        email_campaign_sender_id:
          type: integer
          nullable: true
          description: >-
            The verified sender of an e-mail campaign; null on WhatsApp and
            widget.
        sender_id:
          type: integer
          nullable: true
          description: >-
            The user the campaign is attributed to. Through the API this is
            always the identity of the token that created it — authorship cannot
            be set in the payload.
          example: 895
        conversation_label_id:
          type: integer
          nullable: true
          description: >-
            Label applied to the conversations the campaign opens, when one was
            configured.
        team_id:
          type: integer
          nullable: true
          description: >-
            Team the resulting conversations are assigned to, when one was
            configured.
        recipients_count:
          type: integer
          nullable: true
          description: >-
            The **raw** audience size recorded on the campaign, before the
            reachability check. It is not what was delivered, and it is not what
            `confirm_recipients` is compared against.
          example: 1240
        scheduled_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            When the campaign is due out. A campaign created without one carries
            the moment it was created.
          example: '2026-09-19T17:34:12.881Z'
        started_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            When dispatch began. **Always null on a WhatsApp campaign** — only
            the e-mail path fills it — so its absence tells you nothing about
            whether the campaign ran.
        completed_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            When the campaign reached a terminal status; null while it is still
            open.
          example: '2026-09-19T17:34:15.002Z'
        created_at:
          type: string
          format: date-time
          description: When the campaign was created.
          example: '2026-09-19T17:34:12.881Z'
        updated_at:
          type: string
          format: date-time
          description: When it last changed.
          example: '2026-09-19T17:34:15.002Z'
    ValidationError:
      type: object
      description: A `validation_failed` error, which carries `details`.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - details
          properties:
            code:
              type: string
              enum:
                - validation_failed
              example: validation_failed
            message:
              type: string
              description: >-
                Localized to the account's language, like every other envelope
                message.
              example: The request payload is invalid.
            details:
              type: array
              description: >-
                One entry per rejected field. Unlike the envelope message above,
                the texts come from the model layer and are in English in
                practice (the model layer ships no translations) — match on code
                and field, never on the text.
              items:
                type: object
                required:
                  - field
                  - code
                  - message
                properties:
                  field:
                    type: string
                    description: >-
                      Which request field the rule was about — `short_code` or
                      `content`. A rule that is not tied to either is reported
                      as `base`, so treat `base` as "the payload as a whole"
                      rather than a field you can highlight.
                    enum:
                      - short_code
                      - content
                      - base
                    example: short_code
                  code:
                    type: string
                    description: >-
                      Which rule it broke — `blank` for a missing or empty
                      value, `taken` for a `short_code` already used on the
                      account.
                    example: taken
                  message:
                    type: string
                    example: has already been taken
    CampaignInput:
      type: object
      description: >-
        The campaign itself. Which fields apply depends on the channel, and the
        channel is never passed directly: it follows from
        `email_campaign_sender_id`, or from the type of the inbox.
      required:
        - title
        - message
        - audience
      properties:
        title:
          type: string
          description: >-
            Internal name, shown in the Cloud Chat campaign list. Your contacts
            never see it.
          example: September reactivation
        message:
          type: string
          description: >-
            What is sent, and it means two different things by channel. On
            WhatsApp it is **the name of an approved template**, spelled exactly
            as the template listing returns it — not the text you want to send.
            On e-mail and widget it is the message body. Required in every
            channel.
          example: reactivation_september
        audience:
          type: array
          description: >-
            Who receives it. At most 50 entries, each a label. Check the
            resulting reach with the preview endpoint first.
          items:
            $ref: '#/components/schemas/CampaignAudienceEntry'
        inbox_id:
          type: integer
          description: >-
            The inbox that sends, required for WhatsApp and widget campaigns.
            **It is what decides the channel**: `channel_type`
            `Channel::Whatsapp` sends a WhatsApp template, `Channel::WebWidget`
            sends a widget message, and any other channel is refused. Check
            `channel_type` before you send — nothing cross-checks your intent,
            so a website inbox chosen by mistake delivers your template name to
            contacts as plain text. Leave it out on an e-mail campaign.
          example: 60
        email_campaign_sender_id:
          type: integer
          description: >-
            The verified sender an e-mail campaign goes out from. Sending it is
            what makes the campaign an e-mail campaign, and the inbox follows
            from it. Cannot be combined with `inbox_id`. **No endpoint lists
            senders** — the id is configured in Cloud Chat, under the account
            e-mail campaign settings, so take it from whoever set the sender up.
            One belonging to another account is refused.
          example: 4
        subject:
          type: string
          description: >-
            Subject line, and **required on an e-mail campaign**: a campaign
            carrying `email_campaign_sender_id` without it is refused. The other
            channels carry no subject.
          example: We miss you
        scheduled_at:
          type: string
          format: date-time
          description: >-
            When to send, and it has to be in the future. **Leave it out to send
            immediately.** A timestamp in the past, or one that cannot be
            parsed, is refused rather than quietly treated as now.
          example: '2026-10-01T14:00:00Z'
    Error:
      type: object
      description: The error envelope every Cloud Chat API response uses.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: >-
                The stable, machine-readable reason. Branch on this, never on
                `message`.
              enum:
                - unauthorized
                - forbidden
                - not_found
                - validation_failed
                - bad_request
                - internal_error
              example: not_found
            message:
              type: string
              description: >-
                The reason in words, localized to your account's language
                (English, Spanish or Brazilian Portuguese; English when the
                account is set to anything else). Wording changes with the
                account and between releases, so never match on it.


                Two cases stay in English whatever the account is set to:
                `unauthorized`, decided before any account is known, and the
                account-level `not_found`, which must not reveal the account's
                language.
              example: Resource could not be found.
            details:
              type: array
              description: >-
                Present on some rejections — a 400 for a refused query parameter
                or upload mode, for example. One entry per offending field,
                localized to the account's language like `message` — match on
                `code` and `field`, never on the text.
              items:
                type: object
                required:
                  - field
                  - code
                  - message
                properties:
                  field:
                    type: string
                    description: Which request field the rule was about.
                  code:
                    type: string
                    description: Stable, machine-readable reason.
                  message:
                    type: string
                    description: The rule in words.
    GatewayError:
      type: object
      description: >-
        Rejected by the gateway before Cloud Chat saw it, so it does not use the
        `error` envelope.
      required:
        - message
      properties:
        message:
          type: string
          example: API rate limit exceeded
        request_id:
          type: string
          description: Gateway request id. Quote it when reporting a problem.
          example: f3f7567638d4b65a8003057d0c77d275
    CampaignAudienceEntry:
      type: object
      description: >-
        One entry of a campaign audience. Entries add up: the campaign targets
        the union of their contacts, each counted once.
      required:
        - type
        - id
      properties:
        type:
          type: string
          enum:
            - Label
          description: >-
            Only `Label` is accepted. Cloud Chat also builds audiences from
            saved filters and from uploaded files; neither is available through
            the API yet, and sending either is refused as `validation_failed`
            naming the type.
          example: Label
        id:
          type: integer
          description: >-
            The label whose contacts join the audience. Take the `id` from the
            label listing — the title is not accepted here.
          example: 20010
  responses:
    BadRequest:
      description: >-
        The request itself is malformed, before any value is validated:


        - a body that isn't valid JSON, or one with no `canned_response`
        wrapper,

        - a `canned_response` that isn't an object (`{"canned_response":
        "text"}`),

        - `page`, `per_page`, `search` or `sort` sent as an array or a nested
        object (`?page[]=1`, `?sort[x]=y`) instead of a plain value,

        - **the `cloudchat-instance` header missing or holding a value we don't
        route,**

        - an `accountId` that is not a number — including the literal
        `{accountId}` that some API clients send when the argument is left
        unset. This one answers with the normal envelope and `error.details`
        naming `accountId`, and it is deliberately **not** a 404: a 404 would
        mean the account cannot be reached, while this means the value was never
        an account id at all, so re-read the argument instead of trying other
        numbers.


        **Two different shapes answer with 400, and the difference will bite you
        on day one.** That last case is rejected before the request reaches the
        API, so `error` is a plain **string** instead of the usual object:


        ```json

        { "error": "<human-readable message>" }

        ```


        Every other 400 uses the normal envelope, where `error` is an object
        with `code` and `message`. Reading `error.code` without checking the
        type first throws on the missing-header case — which is the most likely
        400 of your first integration. Check `typeof error === "string"` first,
        or read `error?.code` defensively.


        A 400 always means fix the request shape; it never means a value was
        rejected. Rejected values are 422.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: bad_request
              message: The request is malformed.
    Unauthorized:
      description: >-
        No Bearer token, or one that is expired, malformed, or not a Cognito
        token. Sign in again for a fresh `id_token`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: unauthorized
              message: >-
                Authentication is required. Send a valid Bearer token in the
                Authorization header.
    CampaignForbidden:
      description: >-
        Refused. Either the identity lacks the role these operations need —
        creating a campaign, reading one back and both discovery listings all
        require an administrator, a supervisor or a CX engineer, and an agent is
        refused — or the account is suspended, which closes it even to its own
        members until it is reactivated.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: forbidden
              message: You do not have permission to perform this action.
    NotFound:
      description: >-
        The resource is not there for you. Three situations answer with this
        code, and the first two apply to **every** endpoint — the account is
        resolved before anything else runs, so a list or a create fails this way
        too:


        - the account does not exist on this instance,

        - the account exists but your token has no membership on it,

        - on an endpoint that names one, the resource it names (a canned
        response, a portal, an article) does not exist where the path says it
        does.


        The first two are deliberately indistinguishable — same status, same
        body, and the message stays in English in both, since translating it
        would reveal the account's configured language. So you cannot use this
        endpoint to find out which account ids exist. The third case is
        localized like every other error, which is safe because you only reach
        it once membership is established.


        Either way a 404 is not worth retrying. Check the `accountId`, the
        `cloudchat-instance` value, and that your user is a member of the
        account.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: not_found
              message: Resource could not be found.
    CampaignTooManyRequests:
      description: >-
        Rate limited, and nothing was created. Two ceilings apply and they
        answer differently: the gateway stops the request before it reaches the
        API and answers `{ "message": ... }`, while Cloud Chat applies its own
        ceiling to campaign previews and campaign creation per account and
        answers the usual `{ "error": ... }` envelope with code `rate_limited`.


        Don't hardcode either limit — read it from the response. `Retry-After`
        says how many seconds to wait, and `ratelimit-limit`,
        `ratelimit-remaining` and `ratelimit-reset` carry the current values.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/GatewayError'
              - $ref: '#/components/schemas/Error'
          example:
            error:
              code: rate_limited
              message: Too many requests. Try again in a moment.
    InternalError:
      description: >-
        Something failed on our side. The response never carries the underlying
        error, but it is reported to our monitoring automatically — no need to
        file anything for a one-off.


        Retrying is reasonable, with one caveat on `POST`: a 500 does not prove
        the write did not happen, so a retry can come back `422` with
        `short_code` already taken. That 422 means the first attempt succeeded —
        list the account and confirm before treating it as a failure.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: internal_error
              message: An unexpected error occurred. Please try again later.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        The `id_token` from `POST /auth/v1/signin`, sent as `Authorization:
        Bearer <id_token>`. Not the `access_token` — that one does not carry the
        identity Cloud Chat authorizes on.

````