API Reference

Audelo API

A REST API to manage AI voice agents, place and control outbound calls, read transcripts and recordings, query analytics, store per-call context, and subscribe to webhooks β€” programmatically.

REST / JSON API-key auth HTTPS only πŸ“„ llms.txt for AI tools

Authentication

Every request authenticates with an API key sent as a Bearer token in the Authorization header.

curl https://audelo.ai/api/v1/agents \
  -H "Authorization: Bearer cgk_your_api_key" \
  -H "Accept: application/json"
  • Generate keys in the dashboard under API Keys (account admins only).
  • The full key (cgk_ + 48 characters) is shown once at creation β€” store it securely. Afterward only a cgk_xxxxxxxx prefix is displayed.
  • API access requires a paid plan (Starter or higher). Free / Explorer accounts receive 403 plan_required.
  • Up to 20 active keys per account. Delete a key to revoke it, or set it inactive to disable.

Scopes

Each key is limited to the scopes you assign when creating it. A key created with no scopes has full access. A request missing the required scope returns 403 forbidden.

ScopeGrants
agents:readList and read agents
calls:readList/read calls, transcripts, recordings; read context
calls:writeInitiate/end calls; write/delete context Pro
calls:listenMint listen-only tokens for live in-progress calls (LiveKit pipeline) Pro
calls:takeoverSilence the AI agent and speak to the caller directly on a live outbound call Pro
analytics:readRead call and agent analytics
webhooks:manageCreate, update, delete, and test webhook endpoints
integrations:readRead customer_lookup config (per-agent and business-wide)
integrations:writeSet the customer_lookup URL, toggle it, rotate signing secrets
sms:writeSend a transactional one-off SMS from a number you own
numbers:readList, read, and search your phone numbers
numbers:writeAssign/unassign a number to an agent for inbound routing
numbers:buyPurchase a brand-new number β€” spends wallet credit (requires numbers:read too)
campaigns:readCampaign status, progress counts and per-target outcomes Pro
campaigns:writeCreate, add targets to, start, pause, resume and cancel bulk outbound campaigns Pro

Plan requirements

CapabilityMinimum plan
Any API access; read agents / calls / analytics; manage webhooksStarter
Initiate / end calls; all context endpointsPro

Base URL

https://audelo.ai/api/v1

All endpoints are relative to this base URL. All responses are application/json; timestamps are ISO-8601.

Official SDK

Building in Node or TypeScript? Use the official client instead of hand-rolling HTTP β€” it ships full type definitions, handles auth and errors, and includes webhook signature verification.

npm install @audelo/sdk
import { AudeloClient } from '@audelo/sdk';

const cg = new AudeloClient({ apiKey: process.env.AUDELO_KEY! });

const call = await cg.calls.initiate({
  agent_id: 42,
  phone_number: '+61400000000',
  dial_pipeline: 'livekit',
});

Source, changelog and more examples: github.com/audelo-ai/audelo-sdk — MIT licensed, Node 18+. Every endpoint below is available on the client; no SDK is required to use this API.

Errors

Errors use a consistent envelope. request_id is unique per request β€” quote it when contacting support. errors appears only on validation failures.

{
  "error": "validation_error",
  "message": "The given data was invalid.",
  "errors": { "phone_number": ["The phone number format is invalid."] },
  "request_id": "f1c2a4e8-..."
}
CodeerrorMeaning
401unauthorizedMissing, malformed, inactive, or expired key
403forbidden / plan_requiredKey lacks the scope, or plan too low
404not_foundResource missing or belongs to another account
422validation_errorInvalid body or parameters
429too_many_requestsRate limit exceeded

Rate limits

300 requests per minute, per API key. Exceeding the limit returns 429 too_many_requests.

Pagination

List endpoints (/agents, /calls) return a length-aware paginator. Page size is fixed at 50; navigate with ?page=N. Single-resource, transcript, recording, analytics, and context endpoints are not paginated.

{
  "data": [ ... ],
  "current_page": 1, "last_page": 3, "per_page": 50, "total": 142,
  "next_page_url": "https://audelo.ai/api/v1/calls?page=2",
  "prev_page_url": null
}

Agents

GET /api/v1/agents agents:read List agents

Returns your agents, paginated (50/page), ordered by name. Each row: id, name, status, voice, language, voice_engine, deepgram_voice, created_at, updated_at. voice_engine is "xai" or "deepgram" β€” which TTS engine actually renders this agent's calls (computed from its assigned phone number). voice is the legacy xAI voice id and is not consulted when voice_engine is "deepgram" β€” check deepgram_voice instead.

curl https://audelo.ai/api/v1/agents \
  -H "Authorization: Bearer cgk_..."
GET /api/v1/agents/{id} agents:read Get agent

Returns one agent, or 404 if it isn't in your account.

{
  "id": 12, "name": "Reception Bot", "status": "active",
  "voice": "aria", "language": "en-AU",
  "voice_engine": "deepgram", "deepgram_voice": "aura-2-hyperion-en",
  "humour_level": 10, "empathy_level": 80,
  "created_at": "2026-06-25T10:30:00.000000Z",
  "updated_at": "2026-06-25T10:30:00.000000Z"
}

Calls

GET /api/v1/calls calls:read List calls

Paginated call records, newest first. Row fields: id, agent_id, from_e164, to_e164, direction, status, duration_sec, created_at. Filters: agent_id, status, from (date), to (date).

curl "https://audelo.ai/api/v1/calls?status=completed&agent_id=12" \
  -H "Authorization: Bearer cgk_..."
GET /api/v1/calls/{id} calls:read Get call

One call, including the AI summary and recording link.

{
  "id": 456, "agent": { "id": 12, "name": "Reception Bot" },
  "from_e164": "+61412345678", "to_e164": "+61253009003",
  "direction": "inbound", "status": "completed", "duration_sec": 92,
  "summary": "Caller booked a consultation for Tuesday...",
  "recording_url": "https://...", "created_at": "2026-06-25T10:30:00.000000Z"
}
GET /api/v1/calls/{id}/transcript calls:read Transcript

Ordered turns. timestamp is the offset in milliseconds from call start; speaker is caller or agent.

{
  "call_id": 456,
  "transcript": [
    { "timestamp": 0,    "speaker": "agent",  "text": "Hi, thanks for calling..." },
    { "timestamp": 3200, "speaker": "caller", "text": "I'd like to book an appointment" }
  ]
}
GET /api/v1/calls/{id}/recording calls:read Recording

Returns the recording link, or 404 if the call has no recording.

{ "call_id": 456, "recording_url": "https://...", "duration_sec": 92 }
POST /api/v1/calls/initiate calls:write Pro Initiate an outbound call

Places an outbound call. The call runs through your DNC list, calling-hours, and max-concurrent caps. Returns 202 Accepted.

Two ways to get real-world data into the call: push it now via custom_data below (needs dial_pipeline: "livekit"), or let the agent pull it live mid-call via Customer lookup β€” works on any call, inbound or outbound.

FieldTypeRequiredDescription
agent_idintyesAn agent in your account with a number assigned
phone_numberstringyesE.164, e.g. +61412345678
caller_namestringnoThe person's name β€” never a business name; use business_name for that
business_namestringnoThe business being called, for B2B outreach. custom_data.contact_name/custom_data.business_name are preferred over these top-level fields when both are present
callback_urlurlnoOptional per-call callback
dial_pipelinestringno"twilio" (default) or "livekit". Must be "livekit" for custom_data to actually reach the agent
custom_dataobjectnoPer-call facts the agent speaks from during the call β€” amount owing, order id, appointment time, anything relevant. Only used when dial_pipeline is "livekit"
idempotency_keystringno≀64 chars; de-dupes retries for 24h
curl -X POST https://audelo.ai/api/v1/calls/initiate \
  -H "Authorization: Bearer cgk_..." \
  -H "Content-Type: application/json" \
  -d '{"agent_id":12,"phone_number":"+61412345678","caller_name":"Jane","dial_pipeline":"livekit","custom_data":{"amount_owing":"$412.50","invoice_number":"INV-2231"}}'
{ "call_id": "cgt_789", "agent_id": 12, "status": "queued",
  "created_at": "2026-06-25T10:30:00+00:00" }

Note: call_id is a cgt_... placeholder for the queued call β€” it hasn't been dialed yet. Use it with /end, or poll GET /calls for the materialized record once it connects.

Note: the agent treats custom_data as data to relay, not instructions to follow β€” it will ignore anything in there that reads like a command, and won't invent values that aren't present.

Note: once the call actually connects and its webhooks start firing, they carry the internal numeric call_id, not this cgt_... token. Match a webhook back to this call via its data.outbound_target_id field (equal to this cgt_... value) instead of polling GET /calls/{id} in a race with delivery.

POST /api/v1/calls/{id}/end calls:write Pro End a call

Hangs up an in-progress call. id may be a numeric call id or a cgt_... placeholder. Idempotent for already-ended calls. The final status is set by the carrier callback β€” poll GET /calls/{id} to confirm.

{ "call_id": "cgt_789", "status": "in-progress",
  "hangup_requested_at": "2026-06-25T10:31:00+00:00" }
POST /api/v1/calls/{id}/listen calls:listen Pro Listen to a live call

Mints a short-lived, room-scoped, listen-only LiveKit token for an in-progress call, so your own UI can play the live audio in-page (connect with livekit-client, subscribe to the audio tracks). The token cannot publish audio and expires after expires_in seconds β€” request a fresh one on the same endpoint when it does.

{ "call_id": 1850, "url": "wss://…livekit.cloud", "token": "eyJ…",
  "room": "outbound-…", "identity": "api-7-1850", "expires_in": 600 }

Errors: 409 call_not_active (call already ended) Β· 409 unsupported_pipeline (classic-pipeline call β€” live listening is available for LiveKit-pipeline calls only) Β· 404 (not found / not yours).

Note: anyone your app hands this token to can hear the live call β€” your application's own authentication is the real gate on who gets to press Listen. Treat the token like a secret and never log it.

POST /api/v1/calls/{id}/takeover calls:takeover Pro Take over a live call

Silences the AI agent on an in-progress outbound call β€” it stops speaking and stops responding to the caller for the remainder of the call β€” and mints a short-lived, room-scoped LiveKit token that can publish microphone audio, so a human supervisor speaks to the caller directly. Optional body {"handover_text": "…"} (≀200 chars): one sentence the agent speaks verbatim before going silent, e.g. a handover line β€” it also covers the connection gap while your microphone comes up.

{ "call_id": 1850, "url": "wss://…livekit.cloud", "token": "eyJ…",
  "room": "outbound-…", "identity": "api-takeover-7-1850", "expires_in": 600 }

The token also subscribes, so you keep hearing the call on this connection β€” disconnect any listen connection once this one is up, or every track plays twice. If the supervisor disconnects, the agent stays silent and the call continues until it is hung up or ended via End call; re-POST for a fresh token to rejoin (the handover line is never replayed). There is no agent-resume in this build.

Errors: 409 call_not_active (call already ended) Β· 409 unsupported_pipeline (classic-pipeline call) Β· 409 takeover_unavailable (inbound call β€” take-over is outbound-only in this build) Β· 502 takeover_unavailable (could not start β€” the agent was not silenced) Β· 404 (not found / not yours).

Note: anyone holding this token can speak to your caller as your business. Gate the button behind your own admin authentication, treat the token like a secret, and use headphones β€” echo cancellation is on by default in browsers, but speakers + open mic risk feeding the caller's own voice back to them.

Context Store

A per-account key/value store for data your integration uses across calls. Keys match [A-Za-z0-9._:-]+ (≀191 chars); values are JSON. Pro required for all context endpoints.

Values stored here are not currently read by the live agent during a call. This is bookkeeping storage for your own integration β€” use it to stash state between webhook events, not as a way to get data spoken on a call. For that, use custom_data on Initiate call or Customer lookup below.

POST /api/v1/context/{key} calls:write Pro Set context

Body: value (any JSON, required), ttl_seconds (optional). TTL defaults to 86400 (24h), max 2592000 (30 days). Returns 201.

{ "key": "customer:42", "expires_at": "2026-06-26T10:30:00+00:00", "ttl_seconds": 86400 }
GET /api/v1/context/{key} calls:read Pro Get context

Returns key, value, expires_at. Expired or missing keys return 404.

DELETE /api/v1/context/{key} calls:write Pro Delete context

Idempotent β€” returns 204 No Content whether or not the key existed.

Customer lookup (pull)

The other way to get real-world data into a call β€” instead of pushing it up front, let the agent pull it live, mid-call, from your own API. Works on any call direction, inbound or outbound β€” unlike custom_data, which only applies to calls your integration places.

Configure it from the dashboard (Agent β†’ AI Tools β†’ Customer lookup) or with the Integrations endpoints below β€” turn it on, give it a URL on your own server, and Audelo generates an HMAC secret. From then on, the agent can call the customer_lookup tool whenever it needs to identify who it's talking to.

POST your-endpoint-here What Audelo sends you, mid-call

Audelo POSTs to your configured URL with an 8-second timeout, no redirects followed, and signs the body the same way as webhook deliveries:

POST https://yourapp.com/audelo/customer-lookup
User-Agent: Audelo-CustomerLookup/1.0
X-Audelo-Signature: sha256=<hmac>
Content-Type: application/json

{ "call_id": 8842, "tenant_id": 12, "from_e164": "+61412345678",
  "to_e164": "+61478889900", "query": "invoice INV-2231" }

query is whatever the caller gave the agent to search on β€” a name, phone number, order or account reference. X-Audelo-Signature is HMAC-SHA256 over the raw JSON body using your agent's lookup secret β€” verify it exactly like a webhook delivery.

Respond with a JSON object of whatever facts the agent should know β€” it's relayed to the model the same way custom_data is, treated as data to speak from, never as instructions. Return an empty {} (or any non-2xx) if nothing matches; the agent falls back to offering to take a message.

Integrations

Configure customer_lookup through the API instead of the dashboard β€” the endpoints above describe what your server receives; these set it up. Resolution is agent-first: an agent's own URL always wins over the business default. Whichever level's URL wins, that level's secret signs the request. Neither GET below ever returns a secret value β€” secrets come back exactly once, from the two POST .../secret endpoints.

GET /api/v1/agents/{id}/integrations integrations:read Get agent config

resolved_url / resolved_source ("agent", "tenant", or null) reflect what will actually be called on the next customer_lookup invocation β€” accounting for the tenant-level fallback, not just this agent's own raw columns.

{
  "agent_id": 12, "customer_lookup_enabled": true,
  "customer_lookup_url": null, "has_secret": false,
  "resolved_url": "https://api.yourcompany.com/lookup", "resolved_source": "tenant"
}
PUT /api/v1/agents/{id}/integrations integrations:write Set agent config

Partial update β€” body: { customer_lookup_url?, customer_lookup_enabled? }. Omit a field to leave it unchanged; setting the URL alone does not touch enabled. The URL must resolve to a public address β€” loopback, private-network, link-local, and cloud-metadata hosts are rejected with 422, re-checked on every call, not just here.

curl -X PUT https://audelo.ai/api/v1/agents/12/integrations \
  -H "Authorization: Bearer cgk_..." -H "Content-Type: application/json" \
  -d '{"customer_lookup_url": "https://api.yourcompany.com/sales-lookup"}'
POST /api/v1/agents/{id}/integrations/secret integrations:write Rotate agent secret

Rotates this agent's own signing secret. Returns { agent_id, secret, message } β€” secret is shown exactly once; store it immediately.

GET /api/v1/integrations integrations:read Get business default

The business-wide default. { tenant_id, customer_lookup_url, has_secret } β€” any agent with customer_lookup_enabled and no URL of its own uses this URL/secret.

PUT /api/v1/integrations integrations:write Set business default

Body: { customer_lookup_url } (required key; null clears it). Configure once and every agent with customer_lookup_enabled inherits this URL unless it has its own β€” the recommended setup for a single lookup endpoint shared across many agents.

curl -X PUT https://audelo.ai/api/v1/integrations \
  -H "Authorization: Bearer cgk_..." -H "Content-Type: application/json" \
  -d '{"customer_lookup_url": "https://api.yourcompany.com/lookup"}'
POST /api/v1/integrations/secret integrations:write Rotate business secret

Rotates the tenant-wide secret. Returns { tenant_id, secret, message, affected_agents } β€” affected_agents ([{id, name}]) lists every agent currently inheriting this tenant-level URL (enabled, no URL of its own) β€” exactly the agents whose requests you need to re-verify against the new secret. An agent with its own URL/secret is unaffected and not listed.

Analytics

GET /api/v1/analytics/calls analytics:read Call analytics

Aggregate call stats for a window (from, to dates; default last 30 days).

{
  "period": { "from": "2026-05-26", "to": "2026-06-25" },
  "calls": { "total_calls": 412, "total_duration_sec": 38400,
             "avg_duration_sec": 93, "completed": 388 }
}
GET /api/v1/analytics/agents analytics:read Agent analytics

Per-agent breakdown for the window (from, to; default last 30 days).

{
  "period": { "from": "2026-05-26", "to": "2026-06-25" },
  "agents": [ { "id": 12, "name": "Reception Bot",
               "total_calls": 250, "avg_duration_sec": 88 } ]
}

Phone Numbers

Full white-label provisioning: search available numbers, buy one, and assign it to an agent for inbound routing β€” or reassign/unassign a number you already own β€” all via the API. Buying is real wallet spend and needs numbers:buy in addition to numbers:read. None of the endpoints below require a specific plan.

GET /api/v1/numbers/countries numbers:read Purchasable countries

Countries currently purchasable (Phase 1 markets). Cheap, no auth-sensitive data.

GET /api/v1/numbers/available numbers:read Search available numbers

Search Twilio's inventory for numbers to buy. Query params: country_code (2-letter, default AU), type (local/mobile/toll_free), contains, locality, limit (max 50).

GET /api/v1/numbers/available?country_code=AU&type=local&locality=Sydney

If no inventory exists for that country/type, returns 200 with { numbers: [], warning } rather than an error.

POST /api/v1/numbers numbers:read numbers:buy Buy a brand-new number

Body {"e164": "+61491570156", "country_code": "AU", "number_type": "local", "agent_id": 42} β€” e164 is the exact number from an available-number search; agent_id is optional and assigns the number immediately on purchase. Same wallet-credit gate as the dashboard (must have enough credit to cover at least one month's wholesale cost before Twilio is charged) and the same regulatory-bundle/address handling β€” never reimplemented here, only wrapped.

{ "id": 17, "e164": "+61491570156", "country_code": "AU",
  "capabilities": { "voice": true, "sms": true },
  "agent_id": 42, "agent_name": "Rebecca",
  "created_at": "2026-05-07T18:42:11Z" }

Errors: 402 insufficient_credit (wallet balance can't cover it β€” required_cents/available_cents included) Β· 422 agent_tenant_mismatch Β· 422 purchase_failed (a required regulatory bundle isn't set up β€” the message explains) Β· 502 carrier_error (transient β€” retry).

GET /api/v1/numbers numbers:read List phone numbers

Paginated (50/page), this business's numbers only. Never includes the internal Twilio SID β€” that's an implementation detail, not something you need.

{ "data": [ { "id": 17, "e164": "+61491570156", "country_code": "AU",
    "capabilities": { "voice": true, "sms": true },
    "agent_id": 42, "agent_name": "Rebecca",
    "created_at": "2026-05-07T18:42:11Z" } ] }
GET /api/v1/numbers/{id} numbers:read Get a single number

Same shape as one row of the list above. 404 if the id isn't in your account.

POST /api/v1/numbers/{id}/assign numbers:write Assign to an agent

Body {"agent_id": 42}. Inbound calls to this number now route to that agent. Re-assigning to the same agent it's already on is a harmless no-op.

{ "id": 17, "e164": "+61491570156", "country_code": "AU",
  "capabilities": { "voice": true, "sms": true },
  "agent_id": 42, "agent_name": "Rebecca",
  "created_at": "2026-05-07T18:42:11Z" }

Errors: 404 (number not yours) Β· 422 agent_tenant_mismatch (agent_id isn't an agent in your account) Β· 409 number_already_assigned (already on a different agent β€” unassign it first) Β· 502 twilio_number_missing (the carrier record is gone β€” remove the number and add a new one) Β· 502 carrier_error (transient β€” retry).

POST /api/v1/numbers/{id}/unassign numbers:write Unassign from its agent

Clears inbound routing for this number β€” it stops routing to any agent until reassigned. No body required. A no-op if it wasn't assigned to begin with.

Webhooks

POST /api/v1/webhooks webhooks:manage Manage endpoints

Full CRUD for webhook subscriptions (max 20 per account):

GET/api/v1/webhooksList endpoints
POST/api/v1/webhooksCreate β€” returns the signing secret once
PUT/api/v1/webhooks/{id}Update url / events / active
DELETE/api/v1/webhooks/{id}Delete endpoint
POST/api/v1/webhooks/{id}/testFire a synchronous test delivery

Create β€” body: url (required, public HTTPS), description (optional), events (required array). Returns 201 including the signing secret (whsec_...) β€” shown once.

{ "id": 5, "url": "https://example.com/hooks/audelo",
  "events": ["call.ended"], "secret": "whsec_...",
  "is_active": true, "created_at": "..." }
EVENTS Delivery & verification Payload, signature, retries

When a subscribed event fires, Audelo sends an HTTP POST to your URL. Available events:

EventFires whendata
call.startedA call connectscall_id, agent_id, from, to, direction, started_at, outbound_target_id
call.endedA call completes+ status, duration_sec, ended_at, outbound_target_id
call.transcriptA transcript segment is finalizedcall_id, speaker, text, outbound_target_id
booking.createdAn agent books an appointmentbooking_id, call_id, agent_id, customer_name, customer_phone, customer_email, starts_at, ends_at, status, calendar_event_id
lead.capturedAn agent captures a demo/callback requestcall_id, agent_id, caller_name, caller_phone, caller_email, preferred_callback_time, notes, source
call.summary.readyThe post-call summary finishes generatingcall_id, agent_id, summary, sentiment, key_insights, action_items, duration_sec, started_at, ended_at
sms.receivedAn inbound SMS or MMS arrivessms_log_id, from, to, body, type, agent_id, received_at

Payload

{
  "id": "wh_a1b2c3d4e5f6a7b8",
  "event": "call.ended",
  "timestamp": "2026-06-25T10:31:32.000000Z",
  "data": { "call_id": 456, "agent_id": 12, "status": "completed",
            "duration_sec": 92, "ended_at": "2026-06-25T10:31:32.000000Z",
            "outbound_target_id": "cgt_789" }
}

outbound_target_id is the same cgt_... token Initiate call returned when you queued this call β€” match it against your own records instead of polling GET /calls/{id} to correlate a webhook back to the call you placed. It's null for inbound calls and any call not placed via /calls/initiate.

Headers

X-Audelo-Signaturesha256=<hex> β€” HMAC-SHA256 of the raw body
X-Audelo-EventThe event name
X-Audelo-DeliveryUnique delivery id β€” dedupe on this

Verify the signature β€” recompute HMAC-SHA256 over the raw request body with your whsec_ secret and compare (constant-time):

import hmac, hashlib

def verify(raw_body, header, secret):
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)

Delivery: a 2xx marks success. Failures retry up to 5 times (backoff 5s, 30s, 5m, 30m, 2h). An endpoint auto-disables after 10 consecutive failures. The same event may arrive more than once β€” dedupe on X-Audelo-Delivery.

Ready to build?

Create a free account, upgrade to a paid plan, and generate an API key from the dashboard.

Get Started Free

Building with an AI tool? Point it at audelo.ai/llms.txt for a machine-readable version of this reference.