{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/hellou-ai/json-schema/hellou-ai-voice-campaign-create-request-schema.json",
  "title": "voice_campaign_create_request",
  "x-generated": "2026-11-11",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/hellou-ai-openapi.json#/components/schemas/voice_campaign_create_request",
  "type": "object",
  "required": [
    "voice_campaign"
  ],
  "properties": {
    "consent_attested": {
      "type": "boolean",
      "description": "AI voice campaigns. The administrator's statement that every contact gave prior express consent to calls from an AI agent, stamped with who and when. Send it here for a campaign scheduled for later, or on launch."
    },
    "voice_campaign": {
      "type": "object",
      "required": [
        "title"
      ],
      "properties": {
        "title": {
          "type": "string"
        },
        "test": {
          "type": "boolean",
          "default": false,
          "description": "Mark the campaign as a test. This is a label only: the campaign places real calls like any other, and the label only keeps it out of the default lists unless `include_test` is set. It can be changed later only while the campaign is a draft."
        },
        "description": {
          "type": "string"
        },
        "handler_type": {
          "type": "string",
          "enum": [
            "voice_human",
            "voice_ai"
          ],
          "default": "voice_human"
        },
        "direction": {
          "type": "string",
          "enum": [
            "outbound",
            "inbound"
          ],
          "default": "outbound",
          "description": "`inbound` campaigns have no contact list; they need `inbound_queue_ids` and `voice_human`."
        },
        "inbound_queue_ids": {
          "type": "array",
          "description": "PBX hunt-group ids the agents are logged in to (inbound campaigns).",
          "items": {
            "type": "string"
          }
        },
        "executor_type": {
          "type": "string",
          "enum": [
            "team",
            "agent",
            "agents",
            "ai_agent"
          ],
          "description": "A team, one agent, a hand-picked list of agents (`agents`, with `agent_user_ids`), or `ai_agent` for an AI voice campaign."
        },
        "voice_line_id": {
          "type": "integer",
          "description": "For an AI voice campaign, the AI phone line whose AI agent places the calls."
        },
        "ai_settings": {
          "$ref": "#/$defs/voice_campaign_ai_settings"
        },
        "target_concurrency_limit": {
          "type": "integer",
          "minimum": 1,
          "description": "For an AI voice campaign, how many of its calls may be live at once (the account's own cap also applies)."
        },
        "executor_id": {
          "type": "integer",
          "description": "The team or agent that works the campaign. Not used with `agents`."
        },
        "agent_user_ids": {
          "type": "array",
          "description": "Agents of the account that work the campaign (`executor_type` `agents`; between 1 and 100). On update of a draft or scheduled campaign it replaces the list; on a running or paused one it is accepted only with a switch to `agents` (a list change on an `agents` campaign uses `POST` and `DELETE .../agents`, otherwise 422 `use_agents_endpoints`). Sent with any other `executor_type`, or with an id that is not an agent of the account, it answers 422.",
          "items": {
            "type": "integer"
          }
        },
        "dialer_mode": {
          "type": "string",
          "enum": [
            "preview",
            "progressive",
            "power"
          ]
        },
        "lines_per_agent": {
          "type": "number",
          "minimum": 1,
          "maximum": 3,
          "default": 1,
          "description": "Calls placed per free agent on a human outbound power campaign. Only 1 is accepted until multi-line dialing ships; any other value answers 422 (`attributes` lists `lines_per_agent`)."
        },
        "retry_after_voicemail": {
          "type": "boolean",
          "default": true,
          "description": "Whether a contact is called again after a voicemail (`true` by default). With `false`, a voicemail completes the contact with no more attempts and the `voicemail` disposition (or the agent's voicemail disposition). Only a real voicemail signal counts: answering-machine detection, or a voicemail disposition from the agent or the AI agent; a billed call without one is retried as before."
        },
        "max_attempts": {
          "type": "integer",
          "description": "Calls to one contact before it is given up. Once the campaign has calls it can only be raised (422 `attempts_lowered`)."
        },
        "retry_delay_mins": {
          "type": "integer",
          "description": "Minutes before a contact who was not reached is called again."
        },
        "after_call_work_time": {
          "type": "integer",
          "minimum": 10,
          "maximum": 3600,
          "description": "Seconds an agent has for wrap-up after each call, from 10 to 3600. A value outside that range answers 422 (`attributes` lists `after_call_work_time`)."
        },
        "scheduled_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "When the campaign starts. A value without an offset is read in the campaign's `timezone`; one that carries an offset (or a `Z`) is honoured as sent. It must be in the future, or the request answers 422 `start_time_in_past` (an unreadable value answers 422 `start_time_invalid`). `null` means launch as soon as the campaign is launched."
        },
        "ends_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "When the campaign ends. A value without an offset is read in the campaign's `timezone`; one that carries an offset (or a `Z`) is honoured as sent. It must be in the future (422 `ends_at_in_past`; an unreadable value answers 422 `ends_at_invalid`) and after `scheduled_at` (a validation 422). `null` clears it. The campaign completes when it passes, and a scheduled campaign past it never launches. A running or paused campaign may change it."
        },
        "timezone": {
          "type": "string"
        },
        "program_id": {
          "type": [
            "integer",
            "null"
          ],
          "description": "A voice campaign program of this account (another account's, or one that does not exist, answers 422). `null` unlinks it. A paused campaign may still change it."
        },
        "batch_label": {
          "type": [
            "string",
            "null"
          ],
          "maxLength": 100,
          "description": "The batch of the program this campaign is part of. A paused campaign may still change it."
        },
        "sweep_number": {
          "type": [
            "integer",
            "null"
          ],
          "minimum": 1,
          "description": "Which sweep of the batch this campaign is. A paused campaign may still change it."
        }
      }
    }
  },
  "$defs": {
    "voice_campaign_ai_settings": {
      "type": "object",
      "description": "The settings of an AI voice campaign. Texts may name the call's data: `{{contact.name}}`, `{{contact.first_name}}`,\n`{{campaign.vars.<column>}}` (a column of the uploaded contact list, its header in lower case with\n`_` for spaces), `{{lookup.<key>}}` (the AI agent's pre-call lookup) and `{{call.number}}`.\n",
      "properties": {
        "opening": {
          "type": "object",
          "description": "What the AI agent says when the contact answers, per language (`es`, `en`). Empty uses the AI agent's own opening.",
          "additionalProperties": {
            "type": "string"
          }
        },
        "first_speaker": {
          "type": "string",
          "enum": [
            "",
            "agent",
            "caller"
          ],
          "description": "Who opens the call. Empty follows the AI agent's own voice section; `agent` makes it speak first, as it does today; `caller` makes it wait for the contact to speak, greet nobody, ask whether it reached the person, and say the recording notice only after that answer."
        },
        "dispositions": {
          "type": "array",
          "maxItems": 20,
          "description": "The outcomes the AI agent may record. `outcome` decides what the dialer does next; `next_in_group` means the person who answered is not the one the call is for, so the next person of the contact's group is called instead.",
          "items": {
            "type": "object",
            "required": [
              "key",
              "label",
              "outcome"
            ],
            "properties": {
              "key": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{0,39}$"
              },
              "label": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "outcome": {
                "type": "string",
                "enum": [
                  "contacted",
                  "retry",
                  "closed",
                  "do_not_call",
                  "next_in_group"
                ]
              }
            }
          }
        },
        "extraction_fields": {
          "type": "array",
          "maxItems": 15,
          "description": "The facts the AI agent collects during the call.",
          "items": {
            "type": "object",
            "required": [
              "key",
              "label",
              "type"
            ],
            "properties": {
              "key": {
                "type": "string",
                "pattern": "^[a-z][a-z0-9_]{0,39}$"
              },
              "label": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "string",
                  "number",
                  "boolean",
                  "date",
                  "enum"
                ]
              },
              "options": {
                "type": "array",
                "description": "The allowed values of an `enum` field.",
                "items": {
                  "type": "string"
                }
              },
              "description": {
                "type": "string"
              }
            }
          }
        },
        "voicemail": {
          "type": "object",
          "properties": {
            "action": {
              "type": "string",
              "enum": [
                "hang_up",
                "leave_message"
              ]
            },
            "message": {
              "type": "object",
              "description": "The message left on a voicemail, per language.",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        },
        "hot_lead": {
          "type": "object",
          "properties": {
            "route_key": {
              "type": [
                "string",
                "null"
              ],
              "description": "The AI agent's handover route an interested contact is transferred to, live."
            }
          }
        },
        "ringing_seconds": {
          "type": "integer",
          "minimum": 15,
          "maximum": 60
        },
        "variable_map": {
          "type": "object",
          "maxProperties": 50,
          "description": "Which column of the contact list feeds each of the AI agent's declared variables, column header to variable key. Columns it does not name are matched by name.",
          "additionalProperties": {
            "type": "string"
          }
        },
        "whatsapp_follow_up": {
          "description": "Overrides any key of the AI agent's WhatsApp follow-up (its voice section's `whatsapp_follow_up`) for this campaign's calls; an empty value follows the AI agent. Check a configuration with `POST /voice_follow_ups/check`.",
          "allOf": [
            {
              "$ref": "#/$defs/voice_follow_up_config"
            }
          ]
        }
      }
    },
    "voice_follow_up_config": {
      "type": "object",
      "description": "The WhatsApp message an AI agent may send after the contact says yes on a phone call. Set on the AI agent's\n`voice` section as `whatsapp_follow_up`; a voice campaign may override any key in its `ai_settings.whatsapp_follow_up`\n(an empty value follows the AI agent). The AI agent never chooses the number, the text or the template: the message\ngoes to the call's number, as set here.\n",
      "properties": {
        "enabled": {
          "type": "boolean",
          "description": "Whether the AI agent offers it."
        },
        "inbox_id": {
          "type": [
            "integer",
            "null"
          ],
          "description": "The WhatsApp inbox that sends it."
        },
        "template_name": {
          "type": "string",
          "description": "An approved template of that inbox, with variables in the body only."
        },
        "template_language": {
          "type": "string"
        },
        "params": {
          "type": "object",
          "description": "Each body variable of the template and where its value comes from: `contact.first_name`, `contact.name`, `campaign.name`, `campaign.vars.<column>` (a column of the campaign's contact list), `call.direction`, `call.line`, `call.number`, or `literal:<text>`.",
          "additionalProperties": {
            "type": "string"
          }
        },
        "offer": {
          "type": "object",
          "description": "What the AI agent says to offer it, per language (`es`, `en`). Must name the business and WhatsApp and must not promise that the message arrives.",
          "additionalProperties": {
            "type": "string"
          }
        },
        "skip_dispositions": {
          "type": "array",
          "description": "Campaign outcomes that never get it. The wrong person (`next_in_group`) never does either.",
          "items": {
            "type": "string"
          }
        }
      }
    }
  }
}