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

# Create a campaign

> A campaign is created in `draft`. It cannot be started: see PATCH.



## OpenAPI

````yaml /api-reference/openapi-public.json post /campaigns
openapi: 3.0.3
info:
  description: >-
    The API your own systems use: place and follow calls, have an AI agent call
    someone, run campaigns and get their results, keep contacts in step with
    your CRM, and receive signed webhooks. Authenticate with an integration key
    (Authorization: Bearer ft_...), used only from the IP addresses it allows.
  title: FireTone API
  version: 0.1.0
servers:
  - description: Your platform's API host
    url: https://{host}/api/v1
    variables:
      host:
        default: api.firet.one
security:
  - bearerAuth: []
tags:
  - name: Auth
  - description: >-
      Live calls and what can be done to them: hang up, hold, transfer, park,
      merge, monitor, whisper; the Desk's own call.
    name: Calls
  - description: 'Outbound campaigns: contacts, attempts, outcomes.'
    name: Campaigns
  - description: 'Customers: who called, what is known about them, and their memory.'
    name: Contacts
  - description: What was said on an AI call, and the review of it.
    name: Conversations
  - description: >-
      Your own systems: HTTP connections an IVR calls mid-call, and webhooks for
      call events. Tenant URLs must be public https addresses.
    name: Integrations
  - name: Live
  - name: Provisioning
  - name: Reporting
  - description: Tickets raised by people, agents and the API.
    name: Tickets
  - description: Messages left for an extension or a queue.
    name: Voicemail
paths:
  /campaigns:
    post:
      tags:
        - Campaigns
      summary: Create a campaign
      description: 'A campaign is created in `draft`. It cannot be started: see PATCH.'
      operationId: postCampaigns
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCampaignRequest'
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Campaign'
          description: Created
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
components:
  schemas:
    CreateCampaignRequest:
      properties:
        agent_kind:
          default: human
          enum:
            - human
            - virtual
          type: string
        ai_assist:
          default: false
          description: >-
            Stream this campaign's answered calls to an AI that writes live
            suggestions to the agent's screen. Off by default. It costs per
            minute of every answered call and streams the customer's
            conversation to the provider for the whole call, which in several
            jurisdictions is a recording and consent question rather than only a
            cost one. The AI is never heard: an assist session's audio is
            dropped at the single point all audio leaves, so it cannot speak
            into the call even if a provider ignores the text-only instruction.
            Implemented for OpenAI only; other providers refuse rather than
            speaking.
          type: boolean
        caller_id:
          type: string
        kind:
          default: voice_broadcast
          enum:
            - voice_broadcast
            - auto_dial
          type: string
        max_abandon_pct:
          default: '3.00'
          description: >-
            The compliance ceiling, as an exact decimal string. Meaningful only
            for an automatic dialler: a broadcast cannot abandon a call.
          type: string
        max_lines:
          default: 10
          type: integer
        message_asset_id:
          format: uuid
          type: string
        message_text:
          description: >-
            What the call says, read out by a machine. May carry {{field}}
            placeholders filled from that contact's own data. Exclusive with
            message_asset_id: send one or the other, because the runtime prefers
            the recording and an operator who wrote a message without clearing
            an old recording would send last month's audio.
          type: string
        name:
          type: string
        organisation_id:
          format: uuid
          type: string
        pacing:
          default: balanced
          enum:
            - balanced
            - aggressive
          type: string
        reference:
          description: Your own id for the campaign, on every result.
          maxLength: 128
          type: string
        result_callback_url:
          description: >-
            Each contact's final outcome (campaign.contact.completed) and the
            campaign's end (campaign.completed) are POSTed here, signed with the
            organisation's callback secret; needs the secret to exist (409
            no_callback_secret).
          type: string
        schedule_days:
          description: >-
            0=Sunday to 6=Saturday. Omitted or empty means Monday to Friday: an
            empty array would otherwise mean 'dial on no day at all', which is
            not what sending nothing means.
          items:
            type: integer
          type: array
        schedule_end:
          description: HH:MM.
          type: string
        schedule_start:
          description: >-
            HH:MM. The daily calling window, settable at creation -- it used to
            be reachable only by a follow-up PATCH, so a form had to make two
            requests and could leave a campaign with the wrong hours if the
            second failed.
          type: string
        start_at:
          description: >-
            Earliest moment this campaign may dial. Null means as soon as it is
            running. A campaign whose start is in the future is 'running' and
            dialling nothing -- there is deliberately no separate status for it.
            The daily calling window still applies on top.
          format: date-time
          nullable: true
          type: string
        team_id:
          format: uuid
          type: string
        timezone:
          default: Australia/Sydney
          type: string
        trunk_group_id:
          format: uuid
          type: string
        tts_voice:
          type: string
        virtual_agent_profile_id:
          description: Required when agent_kind is virtual.
          format: uuid
          type: string
        workflow_id:
          format: uuid
          type: string
      required:
        - name
      type: object
    Campaign:
      properties:
        abandoned:
          description: >-
            Answered with no agent free to take it. Read-only, and the figure
            max_abandon_pct governs.
          format: int64
          type: integer
        agent_kind:
          enum:
            - human
            - virtual
          type: string
        ai_assist:
          description: >-
            Stream this campaign's answered calls to an AI that writes live
            suggestions to the agent's screen. Off by default. It costs per
            minute of every answered call and streams the customer's
            conversation to the provider for the whole call, which in several
            jurisdictions is a recording and consent question rather than only a
            cost one. The AI is never heard: an assist session's audio is
            dropped at the single point all audio leaves, so it cannot speak
            into the call even if a provider ignores the text-only instruction.
            Implemented for OpenAI only; other providers refuse rather than
            speaking.
          type: boolean
        answered:
          format: int64
          type: integer
        caller_id:
          type: string
        completed_at:
          description: 'When it completed: nobody left to dial, or stopped.'
          format: date-time
          nullable: true
          type: string
        contact_count:
          type: integer
        created_at:
          format: date-time
          type: string
        dialed:
          description: Dialer output. Read-only.
          format: int64
          type: integer
        id:
          format: uuid
          type: string
        kind:
          description: >-
            voice_broadcast plays a message to whoever answers. auto_dial
            connects the answered contact to an agent.
          enum:
            - voice_broadcast
            - auto_dial
          type: string
        max_abandon_pct:
          description: >-
            Exact decimal string. A COMPLIANCE setting: abandonment caps are law
            in most jurisdictions and 3% is the common statutory figure.
          type: string
        max_attempts:
          type: integer
        max_lines:
          description: >-
            Hard ceiling on simultaneous calls. Not a tuning knob: the node
            allows 30 new sessions per second in total, shared with inbound
            traffic.
          type: integer
        message_asset_id:
          description: A recording to play. Works today.
          format: uuid
          type: string
        message_text:
          description: >-
            What the call says, read out by a machine. May carry {{field}}
            placeholders filled from that contact's own data -- the columns
            their CSV row had -- so each person hears their own details.


            Rendered to speech once into a media asset and cached on the exact
            text and voice, so a plain message is spoken once for ten thousand
            calls and a personalised one is not re-rendered when a busy contact
            is redialled. Generated at ANSWER, inside the pause the broadcast
            already takes listening for a voicemail beep, so nothing is spent on
            calls nobody picks up and the caller hears no extra delay.


            A contact whose row is missing a field the message needs is NOT
            CALLED: the call sheet records missing_field:<name>. A sentence with
            a hole in it would go out to everybody whose row was short a column,
            sound broken, and leave nothing saying which calls were affected.
          type: string
        name:
          type: string
        organisation_id:
          format: uuid
          type: string
        pacing:
          description: >-
            balanced opens one line per free agent and abandons nobody.
            aggressive dials ahead on the measured answer rate, capped by
            max_abandon_pct.
          enum:
            - balanced
            - aggressive
          type: string
        reference:
          description: Your own id for the campaign, on every result.
          maxLength: 128
          type: string
        result_callback_url:
          description: >-
            Each contact's final outcome (campaign.contact.completed) and the
            campaign's end (campaign.completed) are POSTed here, signed with the
            organisation's callback secret; needs the secret to exist (409
            no_callback_secret).
          type: string
        schedule_days:
          description: >-
            0=Sunday to 6=Saturday, matching Go's time.Weekday. NOT the ISO
            1=Monday convention.
          items:
            type: integer
          type: array
        schedule_end:
          type: string
        schedule_start:
          description: HH:MM in the campaign's timezone.
          type: string
        script:
          description: A structured brief for a virtual agent.
          type: object
        start_at:
          description: >-
            Earliest moment this campaign may dial. Null means as soon as it is
            running. A campaign whose start is in the future is 'running' and
            dialling nothing -- there is deliberately no separate status for it.
            The daily calling window still applies on top.
          format: date-time
          nullable: true
          type: string
        status:
          description: running means the dialer is working this campaign.
          enum:
            - draft
            - running
            - paused
            - completed
          type: string
        team_id:
          format: uuid
          type: string
        timezone:
          type: string
        trunk_group_id:
          format: uuid
          type: string
        tts_voice:
          type: string
        virtual_agent_profile_id:
          format: uuid
          type: string
        workflow_id:
          description: Must be an OUTBOUND workflow.
          format: uuid
          type: string
      type: object
    Error:
      properties:
        error:
          properties:
            code:
              enum:
                - invalid_request
                - invalid_credentials
                - unauthenticated
                - forbidden
                - not_found
                - conflict
                - internal
              type: string
            message:
              type: string
          required:
            - code
            - message
          type: object
      required:
        - error
      type: object
  responses:
    BadRequest:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
      description: The request is not valid. The message names every field that is wrong.
    Unauthorized:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
      description: Missing, invalid or expired token
    Forbidden:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
      description: Outside the caller's scope, or insufficient role
    Conflict:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
      description: >-
        The row is still referenced, or a unique value is taken. The message
        names what still points at it.
  securitySchemes:
    bearerAuth:
      bearerFormat: JWT or API key
      description: >-
        Every request sends `Authorization: Bearer <token>`. The token is either
        a panel session (a JWT from /auth/login, 12 hours) or an API key
        `ft_<id>_<secret>`. An API key is accepted only from an address on its
        IP allowlist (403 ip_not_allowed otherwise; 403 ip_allowlist_required
        for an old key that has none), is limited to its rate per minute (429
        rate_limited with Retry-After; X-RateLimit-Limit/Remaining/Reset on
        every response), and at most 60 call placements a minute.
      scheme: bearer
      type: http

````