Outbound calls: campaigns, leads and scheduled calls # Get outbound working [Get a number](/telephony/) is inbound - a customer dials you. This section is the other direction: the agent places the call. **Try this agent** never proves outbound. A campaign or API call that looks “dead” is usually a missing plan flag, no trunk to dial out on, or a **Draft** campaign. ## Why setup matters [Section titled “Why setup matters”](#why-setup-matters) Three things have to be true or nothing rings out: 1. The plan includes **Allow Outbound Calls** - **Subscription & Billing**. 2. A **trunk with numbers** exists - [Get a number](/telephony/). Outbound spends **AI minutes and VoIP credits**. 3. **This agent** can place calls - **Call handling - Outgoing** is available (not plan-locked). Then pick a path. Do not start three at once. ## How the pieces connect [Section titled “How the pieces connect”](#how-the-pieces-connect) | Step | Where | What happens | | ---- | ---------------------------------------- | -------------------------------------------------- | | 1 | **Subscription & Billing** | **Allow Outbound Calls** is on. | | 2 | **Telephony - Trunks** | A number to dial *from*. | | 3 | Agent - **Call handling - Outgoing** | Who speaks first, how long it rings, dropout line. | | 4 | **Campaigns**, or `POST ./initiate-call` | Who gets called, and when. | The **Outbound** sub-tab on [Routing](/telephony/routing/) is only when a second trunk or prefix must win. With one trunk, skip it. ## Open Outbound [Section titled “Open Outbound”](#open-outbound) The company list is sidebar - **Campaigns**. The agent’s behaviour is **Call handling - Outgoing** - not a Campaigns tab. ![Campaigns empty list with New Campaign](/_derived/thumb/outbound-calls/campaigns.webp) ob-campaignsExpand Empty state: **No campaigns yet** - campaigns dial leads with an AI agent on a schedule you set, then capture structured results. Status chips at the top track **Active**, **Paused**, **Completed**, and **Draft**. [Outgoing](/outbound-calls/outgoing/) who speaksringdropout [Campaigns](/outbound-calls/campaigns/)OPENS HERE listsscheduleTry it on me [API](/outbound-calls/api/) one call from your system ## Which path [Section titled “Which path”](#which-path) A list on a schedule, no code TURN ONNew CampaignNEEDSSidebar - **Campaigns**. [Campaigns](/outbound-calls/campaigns/). CRM event, form, missed-call callback TURN ONinitiate-callNEEDS[API](/outbound-calls/api/) - API key with **Calls**, plus a trunk id. The agent should wait for hello before talking TURN ONWho speaks firstNEEDS[Outgoing](/outbound-calls/outgoing/) - set **Who speaks first?** to **User (Wait for user to speak)**. Demo / unpaid workspaces may lock **Outgoing**. [Activate your subscription](/getting-started/activate-subscription/) first. ## First setup - one test call that is not Try this agent [Section titled “First setup - one test call that is not Try this agent”](#first-setup---one-test-call-that-is-not-try-this-agent) 1. **Confirm inbound already works** [Your First Agent](/getting-started/your-first-agent/) sounds right, and a [production number](/telephony/) rings this agent. Outbound on a broken greeting just scales the same greeting. 2. **Confirm Allow Outbound Calls and a trunk with numbers** **Subscription & Billing**, then **Telephony - Trunks**. No number on the trunk = nothing to show as caller ID. 3. **Open Call handling - Outgoing** Set **Who speaks first?** for your use case. [Outgoing](/outbound-calls/outgoing/). 4. **Place one call** Fastest no-code path: [Campaigns](/outbound-calls/campaigns/) - **+ New Campaign** - pick a purpose (start with **Blank**), one lead, **Try it on me**. Or one [API](/outbound-calls/api/) `initiate-call`. ![Create a new campaign with Blank selected](/_derived/thumb/outbound-calls/index-form.webp) index-formExpand 5. **Open Calls** You should see an outbound row. If the list is empty, the campaign is still **Draft**, the schedule window is closed, or the plan / trunk is missing. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) ## Next Steps [Section titled “Next Steps”](#next-steps) [Outgoing - who speaks first, ring timeout, dropout.→](/outbound-calls/outgoing/)[Campaigns - list, schedule, **Try it on me**.→](/outbound-calls/campaigns/)[API - one call from your system.→](/outbound-calls/api/) # API One outbound call from your system — a CRM event, a form, a missed-call callback. A list on a schedule is [Campaigns](/outbound-calls/campaigns/), not this endpoint. The agent’s [Outgoing](/outbound-calls/outgoing/) settings still apply. This request picks the number, the agent, and optional extra context for *this* call. ## Before you call [Section titled “Before you call”](#before-you-call) 1. Plan includes **Allow Outbound Calls**. 2. [API key](/getting-started/api-keys/) with the **Calls** group. 3. A trunk (or VoIP phone) to dial from — [Your own carrier](/telephony/your-own-carrier/) / [Voice Logica numbers](/telephony/voice-logica-numbers/). 4. The **agent id** — from the agent URL on **My Agents**, or **Copy** on a campaign row. ## First setup — one call, then Calls [Section titled “First setup — one call, then Calls”](#first-setup--one-call-then-calls) ```plaintext POST https://api.voicelogica.ai/api/v1/phones/calls/initiate-call ``` ```http x-api-key: YOUR_API_KEY Content-Type: application/json ``` Resellers: optional `x-company-id` to act on a subsidiary. Without it, the key’s own company is used. Send **`destinationNumber`** and **`agentId`**. Send **`sipTrunkId`** or **`voipPhoneId`** unless the company already has an outbound route / default trunk that can carry the call — if neither is sent and no trunk can be resolved, the API returns that no active SIP trunk or VoIP phone was specified. **Trunk (usual):** ```bash curl -X POST https://api.voicelogica.ai/api/v1/phones/calls/initiate-call \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sipTrunkId": "your_sip_trunk_id", "destinationNumber": "+14155552671", "agentId": "your_agent_id" }' ``` **VoIP phone id** still works — the server resolves it to a trunk. Success is `{ "callId": "..." }`. If the destination is outside 09:00–21:00 local time, you may get `"scheduled": true` plus `scheduleTime` and `reason`. Open **Calls** and listen. `destinationNumber` in E.164 (`+14155552671`). ## What you can send [Section titled “What you can send”](#what-you-can-send) **Required for an AI outbound call** | Field | What it does | | ------------------- | ------------------------------------------------------------------------------- | | `destinationNumber` | Number to call. | | `agentId` | Agent that talks. Required for AI outbound; optional only for `isInternalCall`. | **Call source — send one, or rely on the company outbound default** | Field | What it does | | ------------- | ------------------------------------------------------------------------------------------- | | `sipTrunkId` | Trunk from **Telephony → Trunks**. Prefer this. | | `voipPhoneId` | Legacy line id. Resolved to a trunk. | | `callerId` | E.164 or SIP URI shown to the recipient. Must be a number that trunk is allowed to present. | **This call only** | Field | What it does | | ----------------------------- | ----------------------------------------------------------------------------------------------- | | `contextForAi` | Appended to the system prompt for this call. | | `dynamicVariables` | String key/value pairs the prompt can use (`{{customer_name}}`). | | `superVariables` | Typed values: `{ placeholder, value, type }` — `text` / `number` / `boolean` / `date` / `time`. | | `metadata` | Your tracking. Stored on the call. Keys starting `scenario_` are stripped. | | `emailAddress` | Where to send the summary / recording. | | `welcomeMessageOverride` | Spoken instead of the agent greeting. | | `isWelcomeMessageAIGenerated` | AI writes the greeting for this call. | | `scheduledAt` | ISO date. Call later instead of now. | | `maxRetries` | 1–3 if busy / no answer / voicemail. Default 3. | | `retryDelayMinutes` | 30–1440. Default 30. | | `additionalPhoneNumbers` | Extra numbers on the same request. | Do not send `fromNumber`. The public contract is `sipTrunkId` / `voipPhoneId`. ## Protections (outbound only) [Section titled “Protections (outbound only)”](#protections-outbound-only) Inbound is never restricted. * **Time** (on) — 09:00–21:00 in the *destination* country’s local time. Outside that, the call is blocked or scheduled. Bypass: `overrideTimeRestriction: true`. * **Day** (on) — weekends / holidays may be blocked by destination country. Bypass: `overrideDayRestriction: true`. * **Anti-harassment** (off unless you send `useAntiHarassmentProtection: true`) — max 3 successful and 3 failed calls per number per day, 1 hour between successful calls, max 5 successful per week, no duplicate active/scheduled call. Campaign **Allowed test numbers** bypass this. Bypassing time or day rules can break local law. Turn anti-harassment on for any repeated-contact job. ## If it fails [Section titled “If it fails”](#if-it-fails) The body is `{ "error": "", "reason": "" }`. | Code | Meaning | | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | `call_already_in_progress` | A live or scheduled call to this number already exists. | | `agent_not_found` / `voip_phone_not_found` | Bad id. | | `daily_call_limit_reached` | 3 successful calls to this number today (anti-harassment). | | `failed_call_limit_reached` | 3 failed attempts today. | | `cooldown_period_active` | Wait 1 hour after a successful call to this number. | | `weekly_call_limit_reached` | 5 successful calls this week. | | `call_already_scheduled` / `failed_to_schedule_call` | Schedule conflict. | | `callee_opted_out` | This number asked not to be called again. | | `insufficient_credits` / `no_seconds_available` / `no_channels_available` / `no_concurrent_calls_available` | Plan / credits. | | `maximum_authentication_attempts_exceeded` | Phone deactivated after repeated auth failures. | 402 is billing. 400 is a bad request or a protection. 500 is the calls server — retry later. ## Example [Section titled “Example”](#example) ```typescript const response = await fetch('https://api.voicelogica.ai/api/v1/phones/calls/initiate-call', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ destinationNumber: '+14155552671', agentId: 'your_agent_id', sipTrunkId: 'your_sip_trunk_id', contextForAi: 'Customer asked about order ORD123', dynamicVariables: { customer_name: 'Ada', order_id: 'ORD123' }, useAntiHarassmentProtection: true, }), }); const result = await response.json(); if (result.callId) console.log(result.callId); else console.error(result.error, result.reason); ``` Schedule + retries: ```json { "destinationNumber": "+14155552671", "agentId": "your_agent_id", "sipTrunkId": "your_sip_trunk_id", "scheduledAt": "2026-09-16T14:30:00Z", "maxRetries": 2, "retryDelayMinutes": 120 } ``` `scheduledAt` is converted to the destination country’s zone. Protections still apply unless you override them. ## What “broken” looks like [Section titled “What “broken” looks like”](#what-broken-looks-like) * 400 “no active SIP trunk” — send `sipTrunkId`, or add a trunk with numbers and an outbound default. * `scheduled: true` when you expected now — destination local time is outside 09:00–21:00. * Agent talks over “hello” — [Outgoing](/outbound-calls/outgoing/) **Who speaks first**, not this payload. * Call missing on **Calls** — wrong company (`x-company-id`), or the request never returned a `callId`. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) ## Building something? [Section titled “Building something?”](#building-something) Write — extra test credits and help on the integration. API REFERENCE [api.voicelogica.ai/api-docs](https://api.voicelogica.ai/api-docs) DEVELOPERS SUPPORT # Campaigns Sidebar - **Campaigns** is the list dialer. You pick a goal, an agent, a number to call *from*, a schedule, then the leads. The agent’s voice and [Outgoing](/outbound-calls/outgoing/) settings apply to every row. Page line: *Outbound calling campaigns — your AI agents dial leads on the schedule you set and capture structured results on every call.* A campaign starts as **Draft**. Nothing dials until status is **Active** *and* you are inside the calling days and hours. ![Campaigns empty list No campaigns yet](/_derived/thumb/outbound-calls/campaigns.webp) ob-campaignsExpand ## Why this page exists [Section titled “Why this page exists”](#why-this-page-exists) The [API](/outbound-calls/api/) is one number at a time from your system. A campaign is a list you can pause, retry, and watch. Use it for reminders, qualification, invites, collections - not for a single missed-call callback (that is the API). ## What you configure here [Section titled “What you configure here”](#what-you-configure-here) **+ New Campaign** opens **Create a new campaign** (*Name the campaign and pick an agent.*). **What is this campaign for?** sets the goal at create and stays. I will write the script and fields myself TURN ONBlankNEEDSStart empty and configure everything yourself. First campaign should be this. Book a slot on the call TURN ONBook appointmentsNEEDSThe agent proposes available slots and books appointments on the call. Needs calendar + Schedule Appointment on that agent when those are required. Who is interested TURN ONQualify leadsNEEDSFind out who is interested in your product or offer. RSVP to an event TURN ONInvite to an eventNEEDSInvite contacts and collect clear RSVPs. Agree amount and date TURN ONCollect paymentsNEEDSAgree on payment amount and date for outstanding balances. Also fill **Campaign name**, optional **Description**, and **AI Agent**, then **Create campaign**. You can edit everything after creating. ![Create a new campaign purpose tiles with Blank selected](/_derived/thumb/outbound-calls/campaigns-form.webp) campaigns-formExpand After save, the campaign editor has tabs. Day one usually uses: Campaign Name, agent. The agent may lock after leads exist. Dialing **Call from** - trunk number / pool / verified caller ID. Concurrent calls. Retries. **Allowed test numbers** bypass anti-harassment for those DIDs. Schedule Time zone, calling days, calling hours. Lead information What columns a lead row has. **phone** is required. Extra columns become prompt variables. Leave for later: stages, human agents, campaign call results, notifications, and goal-specific instructions when shown. ## First setup - one Blank campaign, one lead, Try it on me [Section titled “First setup - one Blank campaign, one lead, Try it on me”](#first-setup---one-blank-campaign-one-lead-try-it-on-me) Do this after [Get outbound working](/outbound-calls/). 1. **+ New Campaign** Goal **Blank**. Name it after the job, not “Campaign 1”. Pick the agent (for example the onboarding agent). **Create campaign**. 2. **Dialing** **Call from** a trunk that has numbers, or a verified caller ID. Concurrent calls = 1 for the first test. 3. **Schedule** Company time zone. Weekdays. Hours that include *now*. 4. **Save as Draft** if needed, then open the campaign leads table. 5. **Add one lead** Your own mobile. **phone** is required. 6. **Try it on me** Enter that same mobile. This uses the campaign’s agent and goal - it does not wait for **Active**. Answer. Open **Calls**. 7. **Only then set Active** Status **Active**. Stay inside the window. **Pause** stops new dials. Draft never dials the list **Try it on me** can succeed while the campaign is still **Draft**. The list runs only when status is **Active** and the clock is inside Schedule. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) ## Next Steps [Section titled “Next Steps”](#next-steps) [Outgoing - who speaks first on every lead.→](/outbound-calls/outgoing/)[API - one-off calls from your system.→](/outbound-calls/api/)[Call results - what each answered call extracts.→](/agent-configuration/call-results/) # Outgoing **Call handling - Outgoing** is how *this* agent behaves the moment someone picks up an outbound call. It does not pick the list, the schedule, or the caller ID. Those live on [Campaigns](/outbound-calls/campaigns/) and the [API](/outbound-calls/api/). The page title is **How the agent makes outgoing calls.** Configure who speaks first, how long to let the line ring, and what to say if the connection drops mid-call. If the plan does not include **Allow Outbound Calls**, this tab is locked. [Activate your subscription](/getting-started/activate-subscription/). ![Call handling Outgoing Who speaks first and ringing timeout](/_derived/thumb/outbound-calls/outgoing.webp) ob-outgoingExpand ## What you configure here [Section titled “What you configure here”](#what-you-configure-here) Under **Call Behavior**: Who speaks first? **Agent (AI speaks first)** - the greeting plays as soon as the line answers. Right for reminders and surveys. **User (Wait for user to speak)** - the agent waits for the person. If they stay quiet for 4 seconds, the AI starts talking. Ringing timeout (seconds) How long the phone rings before the call is marked unanswered (for example 60). Call dropout prompt Spoken if the connection drops mid-call. Example placeholder: apologize and say you will call back shortly. Leave empty until you have heard a real drop. ## On the campaign / API [Section titled “On the campaign / API”](#on-the-campaign--api) | Place | What this tab supplied | | ----------------------------------------- | -------------------------------------------------------------------------------------------------- | | [Campaigns](/outbound-calls/campaigns/) | The campaign picks the agent. These settings travel with that agent on every lead. | | [API](/outbound-calls/api/) | Same. `welcomeMessageOverride` replaces the greeting for *that* call; it does not change this tab. | | [Routing - Outbound](/telephony/routing/) | Which trunk / caller ID leaves. Not who speaks. | ## First setup - pick who speaks, then prove it [Section titled “First setup - pick who speaks, then prove it”](#first-setup---pick-who-speaks-then-prove-it) 1. **Allow Outbound Calls is on** - otherwise this tab stays locked. 2. **Set Who speaks first?** - Agent for a scripted reminder; User when a receptionist should not talk over “hello.” 3. **Set ringing timeout** - start around 30-60s depending on how fast you want retries vs pickups. 4. **Place one call** - [Campaigns](/outbound-calls/campaigns/) **Try it on me**, or one [API](/outbound-calls/api/) call. Open **Calls**. Appointment reminder, survey, calling about your order TURN ONAgentNEEDSThe script starts. The person can interrupt. A receptionist who should not talk over hello TURN ONUserNEEDSWait for the person. 4 seconds of silence and the AI starts. ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) ## Next Steps [Section titled “Next Steps”](#next-steps) [Campaigns - list and schedule.→](/outbound-calls/campaigns/)[API - one call from your system.→](/outbound-calls/api/)[Tools - **Detect Voice Mail** on outbound.→](/agent-configuration/built-in-tools/)