Developers · API
v1Create and terminate agents
One business is one agent. This page covers what an agent is, then creating, reading and terminating one.
Accounts and agents
An account is the owner of the API key — your company. One account holds many agents.
An agent is the AI that answers for one business. One phone number, one set of settings and one call history belong to it. When you take on a new business, create an agent.
An agent exists before it can take calls: the create response carries no number, and number stays null until we connect one (step 5 of the Quickstart).
Account (your company, one API key)
├── Agent A — number · settings · call history
├── Agent B
└── Agent C
The key works only on agents delegated to it. Agents you create are delegated automatically; any other agent answers 403 without revealing whether it exists.
Your own identifier
Attach your own identifier to an agent as externalId (1–128 printable ASCII characters, no whitespace). Set it once when you create the agent and it comes back in every list response, so you can match agents to your own records without a separate mapping table.
Two agents cannot share an externalId (409 EXTERNAL_ID_EXISTS), so it identifies the agent, not the business. A business that needs two numbers needs two agents — give each its own value (dental_00123_main, dental_00123_branch).
Region
Choose region when you create an agent. It is the service region, and it does not change unless you create the agent again.
| Value | Region |
|---|---|
kr | Korea |
ca | Canada |
Agent status
status | Meaning |
|---|---|
active | Normal — the agent answers calls and its settings can be changed |
terminated | Terminated — settings are locked and the agent leaves the list |
Once termination completes, the agent's externalId is released for reuse and the agent no longer counts against the agent quota. Accessing a terminated agent answers 403.
Create an agent
POST /v1/agents — send a display name, the region (kr or ca) and your own identifier (externalId). To set the agent up from the start, include settings (Agent settings). Add an Idempotency-Key header so retries are safe (API reference).
curl -s -X POST "$LS_BASE_URL/v1/agents" \
-H "Authorization: Bearer $LS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-dental-00123-1" \
-d '{
"name": "Riverside Dental",
"region": "kr",
"externalId": "dental_00123",
"settings": {
"greeting": "Hello, this is Riverside Dental."
}
}'const BASE_URL = process.env.LS_BASE_URL;
const API_KEY = process.env.LS_API_KEY;
const res = await fetch(`${BASE_URL}/v1/agents`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'create-dental-00123-1',
},
body: JSON.stringify({
name: 'Riverside Dental',
region: 'kr',
externalId: 'dental_00123',
settings: { greeting: 'Hello, this is Riverside Dental.' },
}),
});
if (res.status === 409) {
const body = await res.json();
if (body.code === 'EXTERNAL_ID_EXISTS') {
// Already created — find it in the list and use that agent.
}
}import os, requests
BASE_URL = os.environ["LS_BASE_URL"]
API_KEY = os.environ["LS_API_KEY"]
res = requests.post(
f"{BASE_URL}/v1/agents",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": "create-dental-00123-1",
},
json={
"name": "Riverside Dental",
"region": "kr",
"externalId": "dental_00123",
"settings": {"greeting": "Hello, this is Riverside Dental."},
},
)
if res.status_code == 409 and res.json().get("code") == "EXTERNAL_ID_EXISTS":
pass # Already created — find it in the list and use that agent.201
{
"agentId": "8f2c1d40-5b7a-4c31-9e08-1a2b3c4d5e6f",
"name": "Riverside Dental",
"region": "kr",
"status": "active",
"externalId": "dental_00123",
"createdAt": "2026-09-09T04:15:22.140Z"
}
The create response has no phone number. Email support@livespeech.ai with the agentId and region and we connect one; once connected, the number shows in the read response's number.
Every request and response field, including length and format limits, is in the API reference; failure responses are in Errors & limits.
List agents
GET /v1/agents — limit sets the page size (1–200, default 50); pass the previous response's nextCursor as cursor to read the next page.
curl -s "$LS_BASE_URL/v1/agents?limit=200" \
-H "Authorization: Bearer $LS_API_KEY"
# Next page: pass the previous response's nextCursor as cursor.
curl -s "$LS_BASE_URL/v1/agents?limit=200&cursor=<nextCursor from the previous response>" \
-H "Authorization: Bearer $LS_API_KEY"const BASE_URL = process.env.LS_BASE_URL;
const API_KEY = process.env.LS_API_KEY;
async function listAllAgents() {
const all = [];
let cursor = null;
do {
const url = new URL(`${BASE_URL}/v1/agents`);
url.searchParams.set('limit', '200');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const page = await res.json();
all.push(...page.agents);
cursor = page.nextCursor;
} while (cursor);
return all;
}import os, requests
BASE_URL = os.environ["LS_BASE_URL"]
API_KEY = os.environ["LS_API_KEY"]
def list_all_agents():
all_agents, cursor = [], None
while True:
params = {"limit": 200}
if cursor:
params["cursor"] = cursor
page = requests.get(
f"{BASE_URL}/v1/agents",
headers={"Authorization": f"Bearer {API_KEY}"},
params=params,
).json()
all_agents += page["agents"]
cursor = page.get("nextCursor")
if not cursor:
return all_agents{
"agents": [
{
"agentId": "8f2c1d40-5b7a-4c31-9e08-1a2b3c4d5e6f",
"name": "Riverside Dental",
"region": "kr",
"status": "active",
"externalId": "dental_00123",
"createdAt": "2026-09-09T04:15:22.140Z"
}
],
"nextCursor": null
}
nextCursor of null means the last page. Pass cursor values through unchanged — an edited cursor answers 400 INVALID_CURSOR. Every field is in the API reference.
Read an agent
GET /v1/agents/{agentId} — on top of the list fields, the response carries the number the agent is currently serving (number, null before one is connected), commerce integration status (integrations) and a plan summary (plan).
AGENT_ID="<agentId from the create response>"
curl -s "$LS_BASE_URL/v1/agents/$AGENT_ID" \
-H "Authorization: Bearer $LS_API_KEY"const BASE_URL = process.env.LS_BASE_URL;
const API_KEY = process.env.LS_API_KEY;
const agentId = '<agentId from the create response>';
const res = await fetch(`${BASE_URL}/v1/agents/${agentId}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const agent = await res.json();
console.log(agent.number ?? 'no number connected yet');import os, requests
BASE_URL = os.environ["LS_BASE_URL"]
API_KEY = os.environ["LS_API_KEY"]
agent_id = "<agentId from the create response>"
agent = requests.get(
f"{BASE_URL}/v1/agents/{agent_id}",
headers={"Authorization": f"Bearer {API_KEY}"},
).json()
print(agent["number"] or "no number connected yet"){
"agentId": "8f2c1d40-5b7a-4c31-9e08-1a2b3c4d5e6f",
"name": "Riverside Dental",
"region": "kr",
"status": "active",
"externalId": "dental_00123",
"createdAt": "2026-09-09T04:15:22.140Z",
"number": "07012345678",
"integrations": {
"cafe24": { "connected": true },
"nextengine": { "connected": false }
},
"plan": { "tier": "basic", "status": "active" }
}
Every field is in the API reference.
Terminate an agent
DELETE /v1/agents/{agentId} — both body fields are optional. reason records why; purge: true also removes what the agent stored, such as the persona and summary recipients (default false).
AGENT_ID="<agentId from the create response>"
curl -s -X DELETE "$LS_BASE_URL/v1/agents/$AGENT_ID" \
-H "Authorization: Bearer $LS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "The business closed its account", "purge": true }'const BASE_URL = process.env.LS_BASE_URL;
const API_KEY = process.env.LS_API_KEY;
const agentId = '<agentId from the create response>';
const res = await fetch(`${BASE_URL}/v1/agents/${agentId}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ reason: 'The business closed its account', purge: true }),
});
if (res.status === 502) {
// The number is still attached — email support@livespeech.ai with the agentId to release the number, then call again.
}import os, requests
BASE_URL = os.environ["LS_BASE_URL"]
API_KEY = os.environ["LS_API_KEY"]
agent_id = "<agentId from the create response>"
res = requests.delete(
f"{BASE_URL}/v1/agents/{agent_id}",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={"reason": "The business closed its account", "purge": True},
)
if res.status_code == 502:
pass # The number is still attached — email support@livespeech.ai with the agentId to release the number, then call again.{
"status": "terminated",
"numberReclaimed": false,
"terminatedAt": "2026-09-09T05:02:11.885Z"
}
When termination completes:
externalIdis released, so an agent can be created again with the same value.- The agent no longer counts against the agent quota.
- It disappears from list and read; later access answers
403.
If a number is still attached, termination is held and you get 502 CARRIER_RELEASE_FAILED — the agent stays active, so email support@livespeech.ai with the agentId to release the number, then call again. Sending DELETE again gets one of two answers. If the earlier termination recorded the status but had not finished releasing the delegation, you get 409 ALREADY_TERMINATED and the release runs again — nothing more to do. Once termination is complete the agent is no longer delegated to you, so a repeat DELETE (like any other request on that agent) answers 403 — treat it as already done.
Every field is in the API reference; failure responses are in Errors & limits.