开发者 API

AI Calls API 参考

使用平台 API key 创建即时或定时通话任务,轮询同一个版本化任务契约,查看可用费率,在调度前取消,并通过鉴权路由下载已归档录音。

Bearer API key

在设置页创建密钥,然后从服务端代码以 Authorization: Bearer <API_KEY> 发送。完整密钥只展示一次。

幂等创建

重试创建电话时传入 idempotency-key header 或 idempotencyKey body 字段。该键按当前鉴权用户隔离。

私有产物

录音通过平台下载路由提供。不要在客户端保存或暴露上游 URL 或私有存储 key。

鉴权 Header
Authorization: Bearer <API_KEY>
幂等 Header
idempotency-key: <CLIENT_GENERATED_KEY>
JSON 错误 envelope
{ "code": -1, "message": "Unauthorized" }
示例

从服务端代码创建电话

把 API key 放在服务端环境变量里;所有创建重试路径都发送幂等键;定时时间使用绝对 UTC ISO 时间戳。

curl

用 curl 创建即时电话

从 shell 环境读取 base URL 和 API key,并把客户端生成的幂等键放在请求 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

用 Node.js 创建定时电话

使用 Node 环境变量,生成唯一幂等键,并把 earliestAt 作为 UTC ISO 时间戳发送。

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

用 Python 创建定时电话

API key 不写入源码;从服务端进程使用 requests;HTTP 状态之后继续检查平台 response envelope。

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

验证 Webhook 签名

解析 JSON 前先对请求原始 body 验证 HMAC,拒绝过期时间戳,并使用常量时间比较签名。每次重试保持事件 ID 和 body 不变,但会使用新的时间戳和签名。

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 });
接口

当前公开通话 API

所有 JSON 接口返回平台 response envelope。录音下载在归档产物存在时返回二进制响应体。

创建即时电话

基于版本化 profile 创建一条 AI 外呼任务,预留积分,保存国家费率快照,并以 HTTP 202 返回平台任务契约。

POST/api/v1/call-tasks
请求体
{
  "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"
}
响应
{
  "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
    }
  }
}

创建定时电话

创建一条未来外呼任务。earliestAt 传入绝对 UTC ISO 时间,也可以附带 timezone 标识。

POST/api/v1/call-tasks
请求体
{
  "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"
}
响应
{
  "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
    }
  }
}

列出通话任务

按 cursor 分页返回当前鉴权用户拥有的任务。可以按已公开的任务状态筛选,并用 nextCursor 继续查询。

GET/api/v1/call-tasks?limit=50&status=completed
响应
{
  "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/api/v1/call-tasks/{id}
响应
{
  "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"
    }
  }
}

取消通话任务

在调度开始前取消定时或排队中的任务。重复请求会返回同一个已取消任务,不会重复扣费或重复产生完成事件。

POST/api/v1/call-tasks/{id}/cancel
响应
{
  "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"
    }
  }
}

列出可用费率

返回当前用户可用的 active 国家费率快照。历史通话创建后保留自己的价格快照。

GET/api/call-pricing-rules?limit=200
响应
{
  "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"
    }
  ]
}

下载已归档录音

在产物归档后,为用户自己的任务下载 mono 或 stereo 录音二进制。该路由不会返回上游 URL 或存储 object key。

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

<binary audio body>

创建 Webhook 端点

注册一个用户自有的 HTTPS 接收地址。签名密钥由平台生成且只在本次响应返回;离开页面或丢弃响应前必须保存在服务端。

POST/api/v1/webhook-endpoints
请求体
{
  "url": "https://example.com/webhooks/ai-calls",
  "description": "Production receiver",
  "eventTypes": ["call.completed", "call.failed", "call.canceled"]
}
响应
{
  "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>"
  }
}

验证 Webhook 端点

发送一条签名的 webhook.test 事件。只有接收端返回 2xx 后,端点才会变为 active;平台不会跟随重定向。

POST/api/v1/webhook-endpoints/{id}/test
响应
{
  "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"
  }
}

轮询 Webhook 投递状态

列出当前鉴权用户拥有的投递状态,可按 callTaskId、eventId、endpointId 或 status 过滤。接口不会返回密钥、事件原始载荷、上游数据或内部存储字段。

GET/api/v1/webhook-deliveries?callTaskId={id}
响应
{
  "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"
    }]
  }
}

创建电话号码申请

创建或查看当前用户的美国本地企业号码申请。创建后直接进入平台人工审核,不会调用号码供应商。

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

查看或更新认证资料

查看当前申请,或在需要补充资料后重新提交企业资料。不会返回供应商标识。

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