Skip to content
Start free

Developers · API

v1

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

ValueRegion
krKorea
caCanada

Agent status

statusMeaning
activeNormal — the agent answers calls and its settings can be changed
terminatedTerminated — 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/agentslimit 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:

  • externalId is 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.