AI Outbound Calling API
Ctrl+K
Docs Home
Docs Home / API Reference / AI Outbound Calling API

AI Outbound Calling API

The AI Outbound Calling API lets an external system — a CRM, scheduler, ERP, or any custom application — queue outbound calls that a Cloudspire AI agent places and conducts. You POST one request per call; the platform dials the number, the AI agent handles the conversation, and (optionally) the result is POSTed back to a callback URL of your choice when the call completes.

Calls route through the same PBX infrastructure as every other call on the platform, so they carry proper caller ID, STIR/SHAKEN attestation, and call recording.

How It Works

  1. Your system POSTs a call request to the webhook endpoint, authenticated with the campaign's API key.
  2. The request is validated and added to the campaign's outbound queue.
  3. The platform attempts to place the call immediately. If concurrency is exhausted or the request arrives outside the campaign's calling schedule, the call stays queued and a worker (running every minute) places it as soon as conditions allow.
  4. The AI agent converses with the called party using the per-call variables you supplied.
  5. When the call ends and is analyzed, the queue record is updated with the outcome. If you supplied a webhook_response_url, the result is POSTed there.
This endpoint is push-only. It receives call requests from your system; it does not poll or pull from your CRM/ERP. If your source system cannot POST out on its own, you will need a small connector that reads from your system and POSTs each call to this endpoint. See Integrating an External System below.

Prerequisites

Before your requests will result in placed calls, your Cloudspire administrator must have the following in place. Several of these are the reason a request can be accepted (queued) yet not dial.

RequirementWhy
A campaign in active statusThe API key belongs to a single campaign. A paused, completed, or archived campaign rejects requests (409).
A campaign API keyAuthenticates your requests. Issued per campaign; regenerating it invalidates the old key immediately.
An AI agent + From DID on the campaignThe agent conducts the call; the From DID is the caller ID. Both are configured on the campaign, not per request — you cannot choose the caller ID per call.
An AI Voice Minutes pool on the accountThe metered pool that outbound (and inbound) AI voice draws on. Required for any AI call to be placed.
The AI Outbound Calling add-on on the accountEnables the outbound system specifically (outbound needs both this AND the AI Voice Minutes pool). Without either, requests are still queued but never dialed (the inline result is skipped_no_plan).
Outbound AI calling enabled platform-wideA global master switch. When off, requests are rejected (503).

Base URL

https://api.cloudspirevoice.com/

Authentication

Authenticate every request with the campaign's API key as a Bearer token:

Authorization: Bearer {campaign_api_key}

The key maps the request to exactly one campaign — which determines the tenant, AI agent, From DID, schedule, concurrency limits, and retry behavior. There is no separate campaign ID to send; it is implied by the key.

Queue an Outbound Call

Queue a single outbound AI call.

Endpoint

POST https://api.cloudspirevoice.com/agent/campaign-intake.php

Headers

HeaderValue
AuthorizationBearer {campaign_api_key} (required)
Content-Typeapplication/json

Request Body

{
  "to_number": "+17705551234",
  "contact_name": "John Doe",
  "variables": {
    "appointment_date": "March 20th",
    "appointment_time": "2:30 PM",
    "provider": "Dr. Smith"
  },
  "external_id": "CRM-12345",
  "webhook_response_url": "https://your-system.example.com/cloudspire/callback"
}

Request Fields

FieldTypeRequiredDescription
to_numberstringYesThe number to call, in E.164 format (+17705551234). Numbers in a digits-only form with a country code are normalized, but E.164 is recommended. Rejected with 400 if missing or unparseable.
contact_namestringNoThe called party's name, for display in the queue and reports. Truncated to 200 characters.
variablesobjectNoPer-call dynamic variables passed to the AI agent (see Dynamic Variables). Flat key/value JSON. The serialized object must be under 64 KB, else 400.
external_idstringNoYour own reference for this call (e.g. a CRM record ID). Stored on the queue record and echoed back in the completion callback so you can reconcile results. Truncated to 255 characters.
webhook_response_urlstringNoAn https:// URL to receive the completion callback. Must be a valid https:// URL or the request is rejected with 400. Up to 500 characters.

Success Response

HTTP 202 Accepted

{
  "status": "queued",
  "queue_id": 84213,
  "campaign_id": 5,
  "immediate": "dialing"
}
FieldTypeDescription
statusstringAlways "queued" on acceptance. The call has been recorded in the queue.
queue_idintegerThe unique ID of the queue record. Reference it in support inquiries; it also appears in the completion callback.
campaign_idintegerThe campaign the call was queued under (derived from your API key).
immediatestringThe outcome of the one inline placement attempt made during the request (see below). This is a hint, not a final status — queued calls are retried by the per-minute worker.
202 means accepted, not connected. A 202 confirms the call was queued. Whether it dials immediately depends on concurrency, schedule, and account state, reported in immediate. The authoritative outcome arrives later via the completion callback (or in the campaign's queue view).

The immediate Field

Common values:

ValueMeaning
dialingThe call was placed immediately and is ringing.
skipped_concurrencyAt the campaign, tenant, or workspace concurrency ceiling. Stays queued; the worker places it when a slot frees.
skipped_scheduleOutside the campaign's calling window. Stays queued; placed when the window reopens.
skipped_no_planThe account has no active AI outbound capability. Stays queued but will not dial until enabled.
skipped_inactiveThe campaign is not active. (You would normally get a 409 first; this can occur on a race.)
retry_scheduledThe dial attempt hit a transient provider error; a retry was scheduled.
failedThe inline attempt failed terminally (e.g. an invalid From DID configured on the campaign).

Error Responses

HTTPBody errorCause / Resolution
400Invalid JSON request bodyThe body was not valid JSON. Send a well-formed JSON object with Content-Type: application/json.
400Invalid or missing to_number (must be E.164 format)to_number is absent or not a dialable number. Use E.164 with a country code.
400webhook_response_url must be a valid https:// URLThe callback URL was present but not a valid https:// URL. Omit it or fix it.
400variables payload too large (max 64KB)The serialized variables object exceeds 64 KB. Send only what the agent prompt needs.
401Missing or invalid Authorization headerNo Authorization: Bearer ... header. Add it.
401Invalid API keyThe key is not recognized. Confirm it was not rotated.
409Campaign is not activeThe campaign is paused/completed/archived. The body includes campaign_status. Resume the campaign.
409Number is blacklistedto_number is on the tenant's blacklist phonebook. Remove it there to allow calls.
503Outbound AI calling is currently disabledThe platform-wide master switch is off. Retry later or contact your administrator.
500Internal server errorA server-side error. Do not blind-retry: the queue row may already exist and the call may already be placed. Reconcile against the queue view first, then resubmit only if your reference is absent.

All error bodies are JSON of the form {"error": "..."}. The 409 Campaign is not active response additionally includes campaign_status.

Call Lifecycle & Queue Statuses

Each queued call moves through a series of statuses. You observe the final outcome via the completion callback; the intermediate states are visible in the campaign's queue view in the admin portal.

StatusMeaningTerminal?
queuedAccepted and awaiting placement (concurrency/schedule/plan permitting). Retried entries also return here until their next attempt time.No
dialingThe call has been placed and is ringing / in progress.No
completedThe call connected and ended normally (including a voicemail message left, when the campaign is set to do so).Yes
voicemailVoicemail was reached and the campaign's voicemail action was to skip (or no retries remained).Yes
failedThe call could not be completed after exhausting retries, or hit a terminal condition (e.g. scam-block, no valid payment), or stalled without a result.Yes
blacklistedThe number was found on the tenant blacklist at placement time.Yes

Retry behavior is set on the campaign (max attempts and retry delay). Transient outcomes — no answer, busy, dial failed, provider error, and (when configured) voicemail — re-queue the call with a future attempt time until attempts are exhausted.

Completion Callback

If you include a webhook_response_url on the request, Cloudspire POSTs the call result to that URL once the call reaches a terminal status (completed, voicemail, or failed) and post-call analysis has finished. This is how you get the transcript summary and sentiment back into your system.

Delivery Details

PropertyValue
MethodPOST
Content-Typeapplication/json
Schemehttps:// only
Timeout5 seconds (3-second connect timeout)
RetriesNone. The callback is fire-and-forget — deliver-once. Treat the queue view / reports as the source of truth, and have your endpoint respond quickly with a 2xx.

Callback Payload

{
  "queue_id": 84213,
  "campaign_id": 5,
  "external_id": "CRM-12345",
  "to_number": "+17705551234",
  "contact_name": "John Doe",
  "status": "completed",
  "duration_sec": 92,
  "disconnection_reason": "agent_hangup",
  "ai_summary": "Confirmed the appointment for March 20th at 2:30 PM with Dr. Smith.",
  "ai_sentiment": "Positive",
  "ai_successful": 1
}

Callback Fields

FieldTypeDescription
queue_idintegerThe queue record ID returned when you queued the call.
campaign_idintegerThe campaign that placed the call.
external_idstring or nullThe reference you supplied on the request. Use it to match the result to your record.
to_numberstringThe number that was called, in E.164.
contact_namestring or nullThe contact name you supplied.
statusstringTerminal queue status: completed, voicemail, or failed.
duration_secinteger or nullCall duration in seconds (null if the call never connected).
disconnection_reasonstringWhy the call ended (e.g. agent_hangup, user_hangup, no_answer, voicemail_reached, busy, dial_failed).
ai_summarystring or nullAn AI-generated summary of the conversation.
call_idstring or nullThe per-call identifier of the attempt that produced this result. The same value arrives as call.call_id in any mid-call custom integration request made during the call, so you can join the two. Null when the call never connected.
ai_sentimentstring or nullCaller sentiment: Positive, Neutral, Negative, or Unknown.
ai_successfulinteger or nullWhether the agent judged the call's goal met: 1 (yes), 0 (no), or null (not determined).
Because the callback fires only after analysis completes, expect it seconds to a minute or two after the call ends — not the instant the line drops. If you supplied no webhook_response_url, no callback is sent; retrieve results from the campaign's queue view and reports instead. Reconcile, don't rely: for calls carried by an externally-run AI provider (the dormant provider-hook surface), event ordering can skip the callback entirely — treat the queue view and reports as the authoritative outcome and use the callback as a convenience signal.

Dynamic Variables

The variables object carries per-call values into the AI agent's prompt. The campaign can define default variables; your per-call variables are merged over those defaults (per-call wins on a key clash). Reference them in the agent prompt with double-brace syntax, e.g. {{appointment_date}}.

Idempotency & Reconciliation

Each accepted POST creates exactly one queue entry and one call attempt sequence. The endpoint does not de-duplicate on external_id or to_number — if you POST the same record twice, the number is called twice. To stay idempotent:

Concurrency & Scheduling

You can POST calls faster than they are dialed; the queue absorbs the burst and the worker drains it within the configured limits. Placement is bounded by three ceilings, all configured by your administrator:

Calls submitted outside the campaign's scheduled calling window (days of week and a daily start/end time in the campaign's timezone) remain queued and are placed automatically when the next window opens. There is no need to time your POSTs to the window — load the queue whenever it suits your system.

Integrating an External System (CRM / ERP)

Because the endpoint is push-only, integrating a system that exposes its own API (for example, an ERP you query for the day's appointments) means writing a small connector that:

  1. Reads the records to call from your source system (authenticate to it, query the relevant entity).
  2. Maps each record to a request body: to_number in E.164, a contact_name, the variables the agent prompt needs, and external_id set to the source record's unique ID.
  3. POSTs each call to this endpoint with the campaign's Bearer key.
  4. Consumes the completion callback (or polls your own results store) and writes the outcome back to the source record, matched on external_id.

The connector holds the credentials for both sides and owns the business logic of which records to call and when. Cloudspire's responsibility begins at the POST and ends at the callback.

Example: queue a call with curl

curl -X POST https://api.cloudspirevoice.com/agent/campaign-intake.php \
  -H "Authorization: Bearer {campaign_api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "to_number": "+17705551234",
    "contact_name": "John Doe",
    "variables": { "appointment_date": "March 20th", "appointment_time": "2:30 PM" },
    "external_id": "CRM-12345",
    "webhook_response_url": "https://your-system.example.com/cloudspire/callback"
  }'
HTTP/1.1 202 Accepted
{ "status": "queued", "queue_id": 84213, "campaign_id": 5, "immediate": "dialing" }

Billing & Limits