# API

One outbound call from your system — a CRM event, a form, a missed-call callback. A list on a schedule is [Campaigns](https://docs.voicelogica.ai/outbound-calls/campaigns/), not this endpoint.

The agent’s [Outgoing](https://docs.voicelogica.ai/outbound-calls/outgoing/) settings still apply. This request picks the number, the agent, and optional extra context for *this* call.

## Before you call

1. Plan includes **Allow Outbound Calls**.
2. [API key](https://docs.voicelogica.ai/getting-started/api-keys/) with the **Calls** group.
3. A trunk (or VoIP phone) to dial from — [Your own carrier](https://docs.voicelogica.ai/telephony/your-own-carrier/) / [Voice Logica numbers](https://docs.voicelogica.ai/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

```
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

**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)

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

The body is `{ "error": "<code>", "reason": "<message>" }`.

| 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

```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

- 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](https://docs.voicelogica.ai/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

**fromNumber used to work**

The public body is `sipTrunkId` or `voipPhoneId`. Send the trunk id from **Telephony → Trunks**.

**I posted now and got scheduled: true**

Time or day restriction. Wait for the window, or set `overrideTimeRestriction` / `overrideDayRestriction` only if you are allowed to.

## Building something?

Write [developers@voicelogica.ai](mailto:developers@voicelogica.ai) — extra test credits and help on the integration.

- API REFERENCE [api.voicelogica.ai/api-docs](https://api.voicelogica.ai/api-docs)
- DEVELOPERS [developers@voicelogica.ai](mailto:developers@voicelogica.ai)
- SUPPORT [support@voicelogica.ai](mailto:support@voicelogica.ai)
