Developer API

AI Calls API reference

Use platform API keys to create immediate or scheduled call tasks, poll one versioned task contract, check pricing options, cancel before dispatch, and download archived recordings through authenticated routes.

Bearer API keys

Create keys in Settings, then send them from server-side code as Authorization: Bearer <API_KEY>. Full keys are only visible once.

Idempotent creation

Send idempotency-key or idempotencyKey when retrying call creation. The key is scoped to the authenticated user.

Private artifacts

Recordings are served through platform download routes. Do not store or expose upstream URLs or private object keys in client code.

Authentication header
Authorization: Bearer <API_KEY>
Idempotency header
idempotency-key: <CLIENT_GENERATED_KEY>
JSON error envelope
{ "code": -1, "message": "Unauthorized" }
Examples

Create calls from server-side code

Keep API keys in server environment variables, send an idempotency key on every create retry path, and submit scheduled times as absolute UTC ISO timestamps.

curl

Immediate call with curl

Reads the base URL and API key from the shell environment and keeps the client-generated idempotency key in the request header.

curl
: "${AI_CALLS_API_KEY:?set AI_CALLS_API_KEY}"
: "${AI_CALLS_BASE_URL:?set AI_CALLS_BASE_URL}"

curl -sS -X POST "${AI_CALLS_BASE_URL%/}/api/v1/call-tasks" \
  -H "authorization: Bearer ${AI_CALLS_API_KEY}" \
  -H "content-type: application/json" \
  -H "idempotency-key: call-$(date -u +%Y%m%dT%H%M%SZ)-001" \
  --data '{
    "profileId": "<CALL_PROFILE_ID>",
    "countryCode": "US",
    "toNumber": "<E164_PHONE_NUMBER>",
    "toName": "Example Contact",
    "schedule": { "mode": "now" },
    "variables": {
      "customerName": "Example Contact",
      "reason": "appointment reminder"
    }
  }'
Node.js

Scheduled call with Node.js

Uses Node environment variables, generates a unique idempotency key, and sends earliestAt as a UTC ISO timestamp.

Node.js
import { randomUUID } from 'node:crypto';

const baseUrl = process.env.AI_CALLS_BASE_URL?.replace(/\/$/, '');
const apiKey = process.env.AI_CALLS_API_KEY;
if (!baseUrl || !apiKey) {
  throw new Error('Set AI_CALLS_BASE_URL and AI_CALLS_API_KEY');
}

const earliestAt = new Date('2026-07-01T14:30:00.000Z').toISOString();

const response = await fetch(baseUrl + '/api/v1/call-tasks', {
  method: 'POST',
  headers: {
    authorization: 'Bearer ' + apiKey,
    'content-type': 'application/json',
    'idempotency-key': 'scheduled-' + randomUUID(),
  },
  body: JSON.stringify({
    profileId: '<CALL_PROFILE_ID>',
    countryCode: 'US',
    toNumber: '<E164_PHONE_NUMBER>',
    schedule: {
      mode: 'scheduled',
      timezone: 'America/Los_Angeles',
      earliestAt,
    },
  }),
});

const envelope = await response.json();
if (!response.ok || envelope.code !== 0) {
  throw new Error(envelope.message || 'AI Calls API error');
}

console.log(envelope.data.id);
Python

Scheduled call with Python

Keeps the API key out of source code, uses requests from a server process, and checks the platform response envelope after HTTP status.

Python
import os
import uuid
import requests

base_url = os.environ["AI_CALLS_BASE_URL"].rstrip("/")
api_key = os.environ["AI_CALLS_API_KEY"]

payload = {
    "profileId": "<CALL_PROFILE_ID>",
    "countryCode": "US",
    "toNumber": "<E164_PHONE_NUMBER>",
    "schedule": {
        "mode": "scheduled",
        "timezone": "America/Los_Angeles",
        "earliestAt": "2026-07-01T14:30:00.000Z",
    },
}

response = requests.post(
    base_url + "/api/v1/call-tasks",
    headers={
        "authorization": "Bearer " + api_key,
        "idempotency-key": "scheduled-" + str(uuid.uuid4()),
    },
    json=payload,
    timeout=30,
)
response.raise_for_status()

envelope = response.json()
if envelope.get("code") != 0:
    raise RuntimeError(envelope.get("message", "AI Calls API error"))

print(envelope["data"]["id"])
Node.js webhook

Verify webhook signatures

Verify the HMAC over the exact raw request body before parsing JSON, reject stale timestamps, and compare signatures in constant time. Each retry keeps the event ID and body stable but receives a fresh timestamp and signature.

Node.js webhook
import { createHmac, timingSafeEqual } from 'node:crypto';

const body = await request.text();
const timestamp = request.headers.get('x-ai-calls-timestamp') || '';
const received = (request.headers.get('x-ai-calls-signature') || '')
  .replace(/^v1=/, '');
const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(ageSeconds) || ageSeconds > 300) {
  return new Response('stale signature', { status: 401 });
}

const expected = createHmac('sha256', process.env.AI_CALLS_WEBHOOK_SECRET)
  .update(timestamp + '.' + body)
  .digest();
const actual = Buffer.from(received, 'hex');
if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) {
  return new Response('invalid signature', { status: 401 });
}

const event = JSON.parse(body);
return new Response(null, { status: 204 });
Endpoints

Current public call API

All JSON endpoints return the platform response envelope. Recording downloads return a binary body when the archived artifact exists.

Create an immediate call

Creates one outbound AI call task from a versioned profile, reserves credits, snapshots country pricing, and returns HTTP 202 with the platform task contract.

POST/api/v1/call-tasks
Request body
{
  "profileId": "<CALL_PROFILE_ID>",
  "countryCode": "US",
  "toNumber": "<E164_PHONE_NUMBER>",
  "toName": "Example Contact",
  "schedule": { "mode": "now" },
  "variables": {
    "customerName": "Example Contact",
    "reason": "appointment reminder"
  },
  "idempotencyKey": "call_2026_07_01_001"
}
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "<CALL_TASK_ID>",
    "reference": "<CALL_REFERENCE>",
    "mode": "immediate",
    "status": "queued",
    "destination": { "number": "<E164_PHONE_NUMBER>", "countryCode": "US" },
    "schedule": { "dispatchAt": "2026-07-01T14:30:00.000Z" },
    "charge": { "unit": "credits", "reserved": 500, "final": 0 },
    "result": {
      "durationSeconds": null,
      "billableMinutes": null,
      "transcript": null,
      "summary": null,
      "recordings": {
        "mono": { "status": null, "available": false, "downloadUrl": null },
        "stereo": { "status": null, "available": false, "downloadUrl": null }
      }
    },
    "error": null,
    "timestamps": {
      "createdAt": "2026-07-01T14:30:00.000Z",
      "startedAt": null,
      "endedAt": null,
      "completedAt": null
    }
  }
}

Create a scheduled call

Creates one future outbound call task. Send earliestAt as an absolute UTC ISO timestamp and optionally include a timezone identifier.

POST/api/v1/call-tasks
Request body
{
  "profileId": "<CALL_PROFILE_ID>",
  "countryCode": "US",
  "toNumber": "<E164_PHONE_NUMBER>",
  "schedule": {
    "mode": "scheduled",
    "timezone": "America/Los_Angeles",
    "earliestAt": "2026-07-01T14:30:00.000Z"
  },
  "idempotencyKey": "scheduled_call_2026_07_01_001"
}
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "<CALL_TASK_ID>",
    "reference": "<CALL_REFERENCE>",
    "mode": "scheduled",
    "status": "scheduled",
    "destination": { "number": "<E164_PHONE_NUMBER>", "countryCode": "US" },
    "schedule": { "dispatchAt": "2026-07-01T14:30:00.000Z" },
    "charge": { "unit": "credits", "reserved": 500, "final": 0 },
    "result": {
      "durationSeconds": null,
      "billableMinutes": null,
      "transcript": null,
      "summary": null,
      "recordings": {
        "mono": { "status": null, "available": false, "downloadUrl": null },
        "stereo": { "status": null, "available": false, "downloadUrl": null }
      }
    },
    "error": null,
    "timestamps": {
      "createdAt": "2026-07-01T13:00:00.000Z",
      "startedAt": null,
      "endedAt": null,
      "completedAt": null
    }
  }
}

List call tasks

Returns cursor-paginated tasks owned by the authenticated user. Filter by a documented task status and pass nextCursor to continue.

GET/api/v1/call-tasks?limit=50&status=completed
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "items": [{
      "id": "<CALL_TASK_ID>",
      "reference": "<CALL_REFERENCE>",
      "mode": "immediate",
      "status": "completed",
      "destination": { "number": "<E164_PHONE_NUMBER>", "countryCode": "US" },
      "schedule": { "dispatchAt": "2026-07-01T14:30:00.000Z" },
      "charge": { "unit": "credits", "reserved": 500, "final": 180 },
      "result": {
        "durationSeconds": 74,
        "billableMinutes": 2,
        "transcript": "...",
        "summary": "...",
        "recordings": {
          "mono": {
            "status": "archived",
            "available": true,
            "downloadUrl": "/api/v1/call-tasks/<CALL_TASK_ID>/recordings/mono"
          },
          "stereo": { "status": null, "available": false, "downloadUrl": null }
        }
      },
      "error": null,
      "timestamps": {
        "createdAt": "2026-07-01T14:30:00.000Z",
        "startedAt": "2026-07-01T14:30:05.000Z",
        "endedAt": "2026-07-01T14:31:19.000Z",
        "completedAt": "2026-07-01T14:31:21.000Z"
      }
    }],
    "nextCursor": null
  }
}

Get call task detail

Returns the same platform task contract used by create and list, including charge, result, artifact availability, transcript, and summary fields.

GET/api/v1/call-tasks/{id}
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "<CALL_TASK_ID>",
    "reference": "<CALL_REFERENCE>",
    "mode": "immediate",
    "status": "completed",
    "destination": { "number": "<E164_PHONE_NUMBER>", "countryCode": "US" },
    "schedule": { "dispatchAt": "2026-07-01T14:30:00.000Z" },
    "charge": { "unit": "credits", "reserved": 500, "final": 180 },
    "result": {
      "durationSeconds": 74,
      "billableMinutes": 2,
      "transcript": "...",
      "summary": "...",
      "recordings": {
        "mono": {
          "status": "archived",
          "available": true,
          "downloadUrl": "/api/v1/call-tasks/<CALL_TASK_ID>/recordings/mono"
        },
        "stereo": { "status": null, "available": false, "downloadUrl": null }
      }
    },
    "error": null,
    "timestamps": {
      "createdAt": "2026-07-01T14:30:00.000Z",
      "startedAt": "2026-07-01T14:30:05.000Z",
      "endedAt": "2026-07-01T14:31:19.000Z",
      "completedAt": "2026-07-01T14:31:21.000Z"
    }
  }
}

Cancel a call task

Cancels a scheduled or queued task before dispatch starts. Repeating the request returns the same canceled task without charging or emitting completion twice.

POST/api/v1/call-tasks/{id}/cancel
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "<CALL_TASK_ID>",
    "reference": "<CALL_REFERENCE>",
    "mode": "scheduled",
    "status": "canceled",
    "destination": { "number": "<E164_PHONE_NUMBER>", "countryCode": "US" },
    "schedule": { "dispatchAt": "2026-07-01T14:30:00.000Z" },
    "charge": { "unit": "credits", "reserved": 500, "final": 0 },
    "result": {
      "durationSeconds": null,
      "billableMinutes": null,
      "transcript": null,
      "summary": null,
      "recordings": {
        "mono": { "status": null, "available": false, "downloadUrl": null },
        "stereo": { "status": null, "available": false, "downloadUrl": null }
      }
    },
    "error": null,
    "timestamps": {
      "createdAt": "2026-07-01T13:00:00.000Z",
      "startedAt": null,
      "endedAt": null,
      "completedAt": "2026-07-01T13:05:00.000Z"
    }
  }
}

List active pricing options

Returns active country pricing snapshots available to the authenticated user. Historical calls keep their own snapshot after creation.

GET/api/call-pricing-rules?limit=200
Response
{
  "code": 0,
  "message": "ok",
  "data": [
    {
      "pricingRuleId": "<PRICING_RULE_ID>",
      "countryCode": "US",
      "countryName": "United States",
      "dialPrefixes": ["+1"],
      "pricingMode": "fixed_credits_per_minute",
      "sellCreditsPerMinute": 120,
      "roundingMode": "ceil_minute"
    }
  ]
}

Download archived recording

Downloads mono or stereo recording bytes for a user-owned task after the artifact has been archived. The route never returns upstream URLs or storage object keys.

GET/api/v1/call-tasks/{id}/recordings/{kind}
Response
HTTP/1.1 200 OK
content-type: audio/mpeg
cache-control: private, no-store
x-recording-sha256: <SHA256>

<binary audio body>

Create a webhook endpoint

Registers a user-owned HTTPS receiver. The signing secret is generated by the platform and returned only in this response; store it server-side before leaving the page or discarding the response.

POST/api/v1/webhook-endpoints
Request body
{
  "url": "https://example.com/webhooks/ai-calls",
  "description": "Production receiver",
  "eventTypes": ["call.completed", "call.failed", "call.canceled"]
}
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "<WEBHOOK_ENDPOINT_ID>",
    "url": "https://example.com/webhooks/ai-calls",
    "eventTypes": ["call.completed", "call.failed", "call.canceled"],
    "status": "pending",
    "verifiedAt": null,
    "secret": "<SAVE_THIS_ONCE>"
  }
}

Verify a webhook endpoint

Sends a signed webhook.test event. The endpoint becomes active only after it returns a 2xx response. Redirects are not followed.

POST/api/v1/webhook-endpoints/{id}/test
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "<WEBHOOK_ENDPOINT_ID>",
    "url": "https://example.com/webhooks/ai-calls",
    "eventTypes": ["call.completed", "call.failed", "call.canceled"],
    "status": "active",
    "verifiedAt": "2026-07-18T10:00:00.000Z"
  }
}

Poll webhook delivery status

Lists delivery state owned by the authenticated user. Filter by callTaskId, eventId, endpointId, or status. Secrets, event payloads, upstream data, and internal storage fields are never returned.

GET/api/v1/webhook-deliveries?callTaskId={id}
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "items": [{
      "id": "<WEBHOOK_DELIVERY_ID>",
      "event": {
        "id": "<EVENT_ID>",
        "type": "call.completed",
        "occurredAt": "2026-07-18T10:00:00.000Z"
      },
      "endpointId": "<WEBHOOK_ENDPOINT_ID>",
      "callTaskId": "<CALL_TASK_ID>",
      "status": "delivered",
      "attempts": 1,
      "nextAttemptAt": null,
      "lastHttpStatus": 204,
      "lastLatencyMs": 183,
      "deliveredAt": "2026-07-18T10:00:01.000Z"
    }]
  }
}

Create a phone number application

Create or list an owned US local business-number application. Creation submits local intake for manual review and never contacts a phone provider.

GET / POST/api/v1/number-applications
Request body
{
  "businessName": "Example Manufacturing LLC",
  "website": "https://example.com",
  "contactName": "Ada Wong",
  "contactEmail": "ops@example.com",
  "callingUseCase": "Follow up with customers who requested quotations."
}
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "<NUMBER_APPLICATION_ID>",
    "countryCode": "US",
    "numberType": "local",
    "endUserType": "business",
    "status": "submitted",
    "selectedNumber": null,
    "actionRequired": []
  }
}

Read or update verification details

Read an owned application, or resubmit corrected business details after changes are required. Provider identifiers are never returned.

GET / PATCH/api/v1/number-applications/{id}
Request body
{
  "businessName": "Example Manufacturing LLC",
  "website": "https://example.com",
  "contactName": "Ada Wong",
  "contactEmail": "ops@example.com",
  "callingUseCase": "Call customers who submitted an explicit quote request."
}
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "<NUMBER_APPLICATION_ID>",
    "status": "submitted",
    "revision": 2,
    "actionRequired": []
  }
}