Transcription
Recorded calls can be transcribed with each speaker labelled. After the call you get the transcript, a short summary and outcome fields. During the call the transcript can stream to your own WebSocket server.
Turn on transcription
Set the account default in recording settings. Dial sessions and single calls take the same transcription field to override it.
PATCH/v1/recording_settings
| Parameters | Type | Description |
|---|---|---|
transcription | off | post_call | live | post_call transcribes after hang-up. live also streams the transcript during the call. Default off. |
tonality | boolean | Tonality analysis on transcribed calls. Default false. |
curl -X PATCH https://api.liveleg.dev/v1/recording_settings \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transcription": "post_call",
"tonality": true
}'const res = await fetch('https://api.liveleg.dev/v1/recording_settings', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
transcription: "post_call",
tonality: true
}),
});
const data = await res.json();import os, requests
data = requests.patch(
"https://api.liveleg.dev/v1/recording_settings",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"transcription": "post_call",
"tonality": True,
},
).json() Transcripts cover the recorded audio only. On an all-party consent call, only the agent's side is transcribed until the lead gives consent. How each call is recorded is in Recording.
Transcripts after the call
Each turn has a speaker, a start and end time in milliseconds from the bridge, and the text. Speakers are agent, prospect and gatekeeper. transcript.ready arrives after hang-up. With redact=payment_card, spoken card numbers are removed from the text.
GET/v1/calls/{id}/transcript
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transcript \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transcript', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/transcript",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
).json() {
"object": "transcript",
"call_id": "cal_8Jq2mT4x",
"language": "en-US",
"duration_seconds": 262,
"turns": [
{
"speaker": "prospect",
"start_ms": 0,
"end_ms": 1180,
"text": "Hello, this is Dana."
},
{
"speaker": "agent",
"start_ms": 1420,
"end_ms": 6050,
"text": "Hi Dana, this is Jordan with Ridgeline Roofing. You asked for a quote on a roof inspection."
},
{
"speaker": "prospect",
"start_ms": 6300,
"end_ms": 9920,
"text": "I'm on a job right now. Can you call me Thursday after 2?"
}
],
"redactions": 0,
"created_at": "2026-11-03T15:46:58.402Z"
} Live transcript stream
With transcription=live, open a transcript stream on the call when call.bridged arrives. It uses the same endpoint as media streams with content=transcript. Turns spoken before the stream opened are sent first.
POST/v1/calls/{id}/streams
| Parameters | Type | Description |
|---|---|---|
urlrequired | string | Your WebSocket server, wss:// only. |
content | audio | transcript | transcript sends transcript messages instead of audio. Default audio. |
tracks | agent | prospect | both | Which speakers to send. Default both. |
curl https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/streams \
-H "Authorization: Bearer $LIVELEG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "wss://coach.example.com/transcript",
"content": "transcript",
"tracks": "both"
}'const res = await fetch('https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/streams', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: "wss://coach.example.com/transcript",
content: "transcript",
tracks: "both"
}),
});
const data = await res.json();import os, requests
data = requests.post(
"https://api.liveleg.dev/v1/calls/cal_8Jq2mT4x/streams",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
json={
"url": "wss://coach.example.com/transcript",
"content": "transcript",
"tracks": "both",
},
).json() Each message holds the current text of one turn. Messages with is_final false are updated as the speaker goes on. The last message for a turn has is_final true. The stream closes when the call ends.
{
"event": "transcript",
"call_id": "cal_8Jq2mT4x",
"sequence": 37,
"speaker": "prospect",
"turn_id": 4,
"start_ms": 6300,
"end_ms": 9920,
"text": "I'm on a job right now. Can you call me Thursday after 2?",
"is_final": true
} Summary and outcome fields
After-call transcripts come with summary.ready. It holds a two or three sentence summary and fields you can write straight into your CRM.
{
"id": "evt_3Qw88Tp0",
"type": "summary.ready",
"created_at": "2026-11-03T15:47:02.118Z",
"livemode": true,
"data": {
"call_id": "cal_8Jq2mT4x",
"summary": "Owner answered. Busy on a job, asked for a call Thursday after 2 pm.",
"outcome": "callback_requested",
"callback_at": "2026-11-06T14:00:00-06:00",
"opt_out_requested": false
}
} | outcome | Meaning |
|---|---|
interested | The lead wants a next step: a quote, a meeting or a demo. |
callback_requested | The lead asked to be called at another time. callback_at holds the time when one was said. |
not_interested | The lead declined. |
do_not_call | The lead asked not to be called again. opt_out_requested is true. |
wrong_number | The person is not the lead or the business. |
gatekeeper | The agent spoke only with a receptionist or assistant. |
no_conversation | The call ended before a conversation. |
When a lead asks not to be called again, the number goes on your do-not-call list and dnc.added arrives with source call_phrase. callback_at does not schedule a call. Send it as a disposition to schedule one.
Tonality analysis
Tonality analysis scores each speaker on a transcribed call. Turn it on with tonality=true. tonality.ready arrives after the transcript.
| Field | Type | Description |
|---|---|---|
speaker | string | agent, prospect or gatekeeper. |
tone | string | friendly, neutral, tense or flat, for most of the call. |
energy | number | 0 to 1, from voice volume and pace. |
sentiment | number | -1 (negative) to 1 (positive), from words and voice. |
talk_ratio | number | Share of the call's talk time spoken by this speaker, 0 to 1. |
{
"id": "evt_3Qw91Mx2",
"type": "tonality.ready",
"created_at": "2026-11-03T15:47:20.640Z",
"livemode": true,
"data": {
"call_id": "cal_8Jq2mT4x",
"speakers": [
{
"speaker": "agent",
"tone": "friendly",
"energy": 0.71,
"sentiment": 0.42,
"talk_ratio": 0.58
},
{
"speaker": "prospect",
"tone": "neutral",
"energy": 0.48,
"sentiment": 0.15,
"talk_ratio": 0.42
}
]
}
} Search transcripts
Search finds calls whose transcripts contain the words or phrase you send. Each result lists the matching turns with their times. Results are newest first and use the usual pagination.
GET/v1/transcripts
| Query | Type | Description |
|---|---|---|
queryrequired | string | Words or a phrase in quotes. |
speaker | string | Only turns by agent, prospect or gatekeeper. |
agent_id | string | Only calls taken by this agent. |
outcome | string | Only calls with this outcome. |
created_after, created_before | string | Date range, ISO 8601. |
curl "https://api.liveleg.dev/v1/transcripts?query=%22roof+inspection%22&speaker=prospect&outcome=interested" \
-H "Authorization: Bearer $LIVELEG_API_KEY"const res = await fetch('https://api.liveleg.dev/v1/transcripts?query=%22roof+inspection%22&speaker=prospect&outcome=interested', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.LIVELEG_API_KEY}`,
},
});
const data = await res.json();import os, requests
data = requests.get(
"https://api.liveleg.dev/v1/transcripts",
headers={"Authorization": f"Bearer {os.environ['LIVELEG_API_KEY']}"},
params={
"query": "\"roof inspection\"",
"speaker": "prospect",
"outcome": "interested",
},
).json() {
"object": "list",
"data": [
{
"call_id": "cal_7Hd1aP2c",
"agent_id": "agt_114",
"lead_id": "led_9Pq1",
"outcome": "interested",
"matches": [
{
"speaker": "prospect",
"start_ms": 48210,
"text": "We could do the roof inspection next week."
}
]
}
],
"has_more": false
} Retention and use
A transcript is kept as long as its recording: 1 year by default, configurable from 30 days to 10 years. Deleting the recording deletes the transcript, summary and tonality results. Deleted recordings and transcripts are removed from our systems, except where the law requires us to keep a copy. We use transcripts only to provide the service to the account that made the call. We do not use them to train models.
Prices
Transcripts, live transcripts and tonality analysis are billed per minute of transcribed call. Recording is part of the per-minute call rate.
| Transcript, after the callSpeaker-labelled, with summary and outcome fields. | $0.02/ min |
| Live transcript, during the callStreamed in real time. | $0.04/ min |
| Tonality analysisTone, energy, sentiment and talk/listen ratio per speaker. | $0.03/ min analyzed |