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 is available on every plan, including Explorer — there is no plan gate on any endpoint. Your prepaid balance is what limits usage.
  • 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. Every key must be given at least one scope, and grants only what is listed on it β€” there is no implicit β€œall scopes” state. A request missing the required scope returns 403 forbidden.

ScopeGrants
agents:readList and read agents
agents:writeCreate and update agents β€” name, voice, personality, prompts
calls:readList/read calls, transcripts, recordings; read context
calls:writeInitiate/end calls; write/delete context
calls:listenMint listen-only tokens for live in-progress calls (LiveKit pipeline)
calls:takeoverSilence the AI agent and speak to the caller directly on a live outbound call
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
campaigns:writeCreate, add targets to, start, pause, resume and cancel bulk outbound campaigns
tenants:writeProvision client businesses under your account, each with its own API key (partners only)
email:readVoice-agent email: sender status, templates, variable catalogue, previews, emails on a call
email:writeConnect / verify / sign in a sender mailbox, edit templates and allow-lists, test-send, unsubscribe

Plan requirements

There are none. Every /api/v1 endpoint is available on every plan, Explorer included.

What differs between plans is the included-minutes allowance (the per-minute rate is the same on every plan) — not what the API can do. Calls draw on included minutes first, then prepaid wallet credit. An account with neither cannot place a call: it fails for lack of funds, not for being on the wrong plan.

Authorisation is by scope, not by plan — see Scopes.

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
403forbiddenAPI key lacks the scope required by the endpoint
404not_foundResource missing or belongs to another account
422validation_errorInvalid body or parameters
429too_many_requestsRate limit exceeded

Rate limits

1,000 requests per hour, per API key — configurable per 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": "Sally", "status": "active",
  "voice": "eve", "language": "en-AU",
  "voice_engine": "deepgram", "deepgram_voice": "aura-2-hyperion-en",
  "brand_name": "McGrath Parramatta",
  "greeting_owner_name": "Joe Wehbe",
  "welcome_greeting_mode": "agent_script",
  "outbound_ai_disclosure": true,
  "outbound_recording_notice": true,
  "humour_level": 10, "empathy_level": 80,
  "formality_level": 50, "energy_level": 60, "directness_level": 60,
  "system_prompt": "You answer calls for a real estate office.",
  "created_at": "2026-06-25T10:30:00.000000Z",
  "updated_at": "2026-06-25T10:30:00.000000Z"
}

brand_name is the business the agent names on a call — it overrides your account name, so one account can run agents presenting different businesses. greeting_owner_name is whose assistant it says it is: an outbound call opens “Hi, this is {name}, {owner}’s AI assistant, calling from {brand_name}.” Put the person’s name in greeting_owner_name, not in name.

POST /api/v1/agents agents:write Create agent

name and voice are required; everything else takes a default. Returns 201 with the same shape as the GET above.

curl -X POST https://audelo.ai/api/v1/agents \
  -H "Authorization: Bearer cgk_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Sally","voice":"eve","brand_name":"McGrath Parramatta",
       "greeting_owner_name":"Joe Wehbe","humour_level":10}'
PATCH /api/v1/agents/{id} agents:write Update agent

Partial — only the fields in your body change. Returns the full agent.

Writable: name, brand_name, greeting_owner_name, outbound_identity_question, inbound_recording_notice, outbound_recording_notice, welcome_greeting_mode, welcome_greeting_custom_text, the five personality sliders, system_prompt, business_context, outbound_instructions, website_url, voice, deepgram_voice, language, voice_runtime, gpt_live_voice.

call_scripts is read-only. The agent's named call scripts as {key, name, default}, empty for most agents.

humour_level is 0–30. The other four sliders are 0–100; a higher value is a 422.

inbound_recording_notice and outbound_recording_notice drop only the spoken sentence on that direction. Off does not stop recording. They default on. outbound_ai_disclosure is readable but not writable and stays on.

Every change snapshots a version you can roll back to from the dashboard. Consecutive edits from the same key within two minutes coalesce into one version, so an autosaving editor does not fill the history with a row per keystroke. That is version history, not request volume — your key’s hourly rate limit still counts every PATCH (1,000/hour by default), so debounce for that.

curl -X PATCH https://audelo.ai/api/v1/agents/12 \
  -H "Authorization: Bearer cgk_..." \
  -H "Content-Type: application/json" \
  -d '{"greeting_owner_name":"Joe Wehbe","empathy_level":85}'
GET /api/v1/voices agents:read Voices & languages

Every legal value for the voice-shaped fields above. Read this before creating an agent — voice is required and is validated against it. Returns voices, deepgram_voices, voice_runtimes, gpt_live_voices and languages.

languages lists only what we can actually serve — a voice AND speech-to-text. A language we can speak but not hear is worse than one we decline: the agent replies fluently and mishears every answer. The list is therefore narrower than the set of codes the API will accept, and it widens as speech models are added. Pick from here.

Legacy regional codes (en-AU, fr-FR…) still save, so agents created before 2026-05-19 keep working, but they are not listed — use the base code for anything new.

curl https://audelo.ai/api/v1/voices \
  -H "Authorization: Bearer cgk_..."

Partners

For partners who resell Audelo and create businesses on their customers’ behalf. If you manage a single account, you do not need this.

POST /api/v1/partner/tenants tenants:write Provision a client business

Creates a client business underneath your account and returns an API key scoped to it. Call this once when your customer signs up and store the key against them — they never log into Audelo. Only name is required.

The parent is taken from your API key and cannot be set in the body. There is no parent_tenant_id field; sending one is ignored.

A key cannot grant what it does not hold. Omit scopes and the new key inherits yours; name them and each must already be on your key. tenants:write is never passed on, so a business you provision cannot provision businesses of its own.

Their minutes are billed to you. The response carries parent_billed: true — the business can take calls from its first minute with no balance and no card, because its usage is invoiced to you rather than charged to it. Every minute is still metered against it, and that is what the invoice is built from.

By default the business gets no login. Pass owner_email for a real dashboard login; the address must not already exist on any account. api_key.key is returned once. Rate limit: 60 calls/hour, separate from your general key limit.

curl -X POST https://audelo.ai/api/v1/partner/tenants \
  -H "Authorization: Bearer cgk_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Barton Dental",
       "scopes":["agents:read","agents:write","calls:read","calls:write"]}'

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 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 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 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 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.

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 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 Get context

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

DELETE /api/v1/context/{key} calls:write 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 the first month's rental 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.

Voice-agent email

A voice agent emails the caller something they asked for, from the business's own mailbox. Transactional only β€” a promotional template never sends except as a test to its own sender. Blanks only β€” the voice agent fills catalogue blanks (one line, capped, no links) and never writes a subject or body. Each voice agent sends only the templates on its allow-list. Google / Microsoft 365 sign-in runs on your own OAuth app. No credential appears in any response.

GET /api/v1/agents/{id}/email/sender email:read The voice agent's sender: method, address, status (none | pending | verified)
POST /api/v1/agents/{id}/email/smtp email:write Connect by mail-server settings; a 6-digit code is emailed to the mailbox
POST /api/v1/agents/{id}/email/smtp/confirm email:write { "code" } β€” verifies the sender
POST /api/v1/agents/{id}/email/smtp/resend-code email:write Send a fresh code
POST /api/v1/agents/{id}/email/oauth/{google|microsoft} email:write Start a mailbox sign-in on your own OAuth app; returns auth_url
POST /api/v1/email/oauth/{google|microsoft}/complete email:write { "state", "code" } relayed from your redirect page
DELETE /api/v1/agents/{id}/email/sender email:write Disconnect the sender (turns email off)
PUT /api/v1/agents/{id}/email/enabled email:write { "enabled" } β€” refused unless the sender is verified
GET /api/v1/email-templates/variables email:read The fixed variable catalogue: who fills each blank, and caps
GET /api/v1/email-templates email:read Account templates
POST /api/v1/email-templates email:write name, category (transactional | promotional), trigger_description, subject, body_html
GET /api/v1/email-templates/{id} email:read One template, with the voice agents allowed to send it
PATCH /api/v1/email-templates/{id} email:write Partial update β€” reaches live calls immediately
DELETE /api/v1/email-templates/{id} email:write Delete; removed from every allow-list
POST /api/v1/email-templates/{id}/preview email:read Render subject + HTML + text with sample values; sends nothing
POST /api/v1/email-templates/{id}/test-send email:write { agent_id, recipient_email } β€” promotional only to the sender's own address
GET /api/v1/agents/{id}/email-templates email:read The voice agent's allow-list
PUT /api/v1/agents/{id}/email-templates email:write { "template_ids": [...] } replaces it
GET /api/v1/calls/{id}/emails email:read What was emailed on a call
POST /api/v1/email/unsubscribe/lookup email:read { "token" } β€” business name + masked address, for your unsubscribe page
POST /api/v1/email/unsubscribe email:write { "token" } β€” record the opt-out (idempotent)
GET /api/v1/email/branding email:read Logo, brand colour and footer applied around every email
PUT /api/v1/email/branding email:write Replaces all three; a field left out is cleared
GET /api/v1/agents/{id}/email/signature email:read The sender’s signature: name, email, title, mobile, photo
PUT /api/v1/agents/{id}/email/signature email:write { title, mobile, photo_url } β€” fills {{signature}}

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 appointment or takes a callback requestbooking_id, call_id, agent_id, customer_name, customer_phone, customer_email, starts_at, ends_at, status, kind (appointment | callback: a callback is status pending, ends_at null), calendar_event_id
lead.capturedA demo/callback request, sent once when the call ends. preferred_callback_time is the caller's words; preferred_callback_status is resolved, date_only, vague, contradictory, past, unresolvable or emptycall_id, agent_id, caller_name, caller_phone, caller_email, preferred_callback_time, preferred_callback_at, preferred_callback_date, preferred_callback_status, 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
email.sentA voice-agent email was accepted by the mail serveremail_id, call_id, agent_id, template_id, recipient, status, source, sent_at
email.failedThe mail server refused itemail_id, call_id, agent_id, template_id, recipient, status, source, failed_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 an account, choose Starter or higher, and generate an API key from the dashboard.

Get Started

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