Developer API

Callany 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 calls as a local wall-clock time plus an IANA timezone.

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 '{
    "assistantId": "<ASSIGNED_ASSISTANT_ID>",
    "phoneNumberId": "<ASSIGNED_PHONE_NUMBER_ID>",
    "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 a local wall-clock time with an IANA timezone.

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 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({
    assistantId: '<ASSIGNED_ASSISTANT_ID>',
    phoneNumberId: '<ASSIGNED_PHONE_NUMBER_ID>',
    toNumber: '<E164_PHONE_NUMBER>',
    schedule: {
      mode: 'scheduled',
      localDateTime: '2026-07-01T07:30',
      timeZone: 'America/Los_Angeles',
    },
  }),
});

const envelope = await response.json();
if (!response.ok || envelope.code !== 0) {
  throw new Error(envelope.message || 'Callany 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 = {
    "assistantId": "<ASSIGNED_ASSISTANT_ID>",
    "phoneNumberId": "<ASSIGNED_PHONE_NUMBER_ID>",
    "toNumber": "<E164_PHONE_NUMBER>",
    "schedule": {
        "mode": "scheduled",
        "localDateTime": "2026-07-01T07:30",
        "timeZone": "America/Los_Angeles",
    },
}

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", "Callany 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, resolves the E.164 destination, reserves credits, snapshots its pricing route, and returns HTTP 202.

POST/api/v1/call-tasks
Request body
{
  "assistantId": "<ASSIGNED_ASSISTANT_ID>",
  "phoneNumberId": "<ASSIGNED_PHONE_NUMBER_ID>",
  "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",
    "cancelable": true,
    "destination": { "number": "<E164_PHONE_NUMBER>", "region": "US" },
    "schedule": { "dispatchAt": "2026-07-01T14:30:00.000Z", "timeZone": null },
    "charge": { "status": "reserved", "currency": "USD", "estimatedCents": 500, "heldCents": 500, "finalCents": 0 },
    "result": {
      "durationSeconds": null,
      "billableMinutes": null,
      "transcript": null,
      "summary": null,
      "recording": { "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 localDateTime with an explicit IANA timeZone; Callany converts it once to the UTC dispatch instant.

POST/api/v1/call-tasks
Request body
{
  "assistantId": "<ASSIGNED_ASSISTANT_ID>",
  "phoneNumberId": "<ASSIGNED_PHONE_NUMBER_ID>",
  "toNumber": "<E164_PHONE_NUMBER>",
  "schedule": {
    "mode": "scheduled",
    "localDateTime": "2026-07-01T07:30",
    "timeZone": "America/Los_Angeles"
  },
  "idempotencyKey": "scheduled_call_2026_07_01_001"
}
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "<CALL_TASK_ID>",
    "reference": "<CALL_REFERENCE>",
    "mode": "scheduled",
    "status": "scheduled",
    "cancelable": true,
    "destination": { "number": "<E164_PHONE_NUMBER>", "region": "US" },
    "schedule": { "dispatchAt": "2026-07-01T14:30:00.000Z", "timeZone": "America/Los_Angeles" },
    "charge": { "status": "reserved", "currency": "USD", "estimatedCents": 500, "heldCents": 500, "finalCents": 0 },
    "result": {
      "durationSeconds": null,
      "billableMinutes": null,
      "transcript": null,
      "summary": null,
      "recording": { "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",
      "cancelable": false,
      "destination": { "number": "<E164_PHONE_NUMBER>", "region": "US" },
      "schedule": { "dispatchAt": "2026-07-01T14:30:00.000Z", "timeZone": null },
      "charge": { "status": "settled_provider_cost", "currency": "USD", "estimatedCents": 500, "heldCents": 0, "finalCents": 180 },
      "result": {
        "durationSeconds": 74,
        "billableMinutes": 2,
        "transcript": "...",
        "summary": "...",
        "recording": {
          "status": "archived",
          "available": true,
          "downloadUrl": "/api/v1/call-tasks/<CALL_TASK_ID>/recording"
        }
      },
      "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",
    "cancelable": false,
    "destination": { "number": "<E164_PHONE_NUMBER>", "region": "US" },
    "schedule": { "dispatchAt": "2026-07-01T14:30:00.000Z", "timeZone": null },
    "charge": { "status": "settled_provider_cost", "currency": "USD", "estimatedCents": 500, "heldCents": 0, "finalCents": 180 },
    "result": {
      "durationSeconds": 74,
      "billableMinutes": 2,
      "transcript": "...",
      "summary": "...",
      "recording": {
        "status": "archived",
        "available": true,
        "downloadUrl": "/api/v1/call-tasks/<CALL_TASK_ID>/recording"
      }
    },
    "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",
    "cancelable": false,
    "destination": { "number": "<E164_PHONE_NUMBER>", "region": "US" },
    "schedule": { "dispatchAt": "2026-07-01T14:30:00.000Z", "timeZone": "America/Los_Angeles" },
    "charge": { "status": "settled_canceled", "currency": "USD", "estimatedCents": 500, "heldCents": 0, "finalCents": 0 },
    "result": {
      "durationSeconds": null,
      "billableMinutes": null,
      "transcript": null,
      "summary": null,
      "recording": { "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"
    }
  }
}

Resolve destination and quote

Combines the selected phone number's confirmed origin with the destination E.164 prefix and returns the authoritative pricing route and maximum credit reservation.

POST/api/v1/call-quotes
Request body
{
  "assistantId": "<ASSIGNED_ASSISTANT_ID>",
  "phoneNumberId": "<ASSIGNED_PHONE_NUMBER_ID>",
  "toNumber": "<E164_PHONE_NUMBER>",
  "destinationRegion": "US"
}
Response
{
  "code": 0,
  "message": "ok",
  "data": {
    "origin": { "kind": "region", "region": "US" },
    "destination": {
      "schemaVersion": 2,
      "validity": "valid",
      "callingCode": "1",
      "resolution": { "kind": "exact", "region": "US" }
    },
    "pricing": {
      "routeCode": "NANP",
      "routeName": "North American Numbering Plan",
      "isFallback": false,
      "maximumReservationCents": 500
    }
  }
}

Download archived recording

Downloads the recording 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}/recording
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/callany",
  "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/callany",
    "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/callany",
    "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"
    }]
  }
}