The Speak AI API exposes 4 analytics endpoints under the base URL https://api.speakai.co/v1. Every request needs your API key in the x-speakai-key header, as described in Authentication. No access token is needed.
What can you do with the analytics endpoints?
Speak AI groups these 4 endpoints under the analytics resource. Each entry below links to the full reference for that endpoint further down this page.
Agent Analytics
/voice/analytics/agentAgent Analytics
Everything the agent analytics view shows for one agent over a date range: headline numbers with an optional comparison to the previous period, a daily series, audience (locations, devices, traffic sources), leads, handoffs, action items, engagement, sentiment, topics, end reasons, satisfaction, call timing, languages and web versus phone. The response payload sits directly on data, not nested under a named key.
Leave out start for all time. A start more than 5 years before end is moved up to 5 years before end. A start after end returns 400. Rates are fractions from 0 to 1.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
agentId |
query | string | Yes | The agent to report on. Required. Must be an agent in your workspace, otherwise the request returns 404. |
start |
query | string (date-time) | No | Start of the window, as an ISO 8601 date. Optional. Leave it out for all time. |
end |
query | string (date-time) | No | End of the window, as an ISO 8601 date. Optional. Defaults to now. |
tz |
query | string | No | IANA time zone used to bucket days, hours and weekdays, such as America/Toronto. Optional. Defaults to UTC. At most 100 characters. An unknown zone returns 400. |
compare |
query | boolean | No | Also return the same numbers for the period of equal length just before start, in previousKpis. Optional. Defaults to false. Has no effect without start. |
includeTests |
query | boolean | No | Count test calls placed from the dashboard. Optional. Defaults to false. testCalls.count reports them either way. |
curl -X GET 'https://api.speakai.co/v1/voice/analytics/agent?agentId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&start=2026-09-01T00%3A00%3A00.000Z&end=2026-09-30T23%3A59%3A59.999Z&tz=America%2FToronto&compare=true&includeTests=false' \
-H 'x-speakai-key: sk_test_speak_0000000000000000'200 OK
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | One of: success . |
data |
object |
data – 9 more fields
| Field | Type | Description |
|---|---|---|
range |
object | The window the numbers cover. |
range.start |
string (date-time) | Start of the window. Null when you sent no start, which means all time. |
range.end |
string (date-time) | End of the window. Now when you sent no end. |
range.tz |
string | The time zone used to bucket days, hours and weekdays. |
range.previousStart |
string (date-time) | Start of the comparison window. Null unless compare is true and start was sent. |
range.previousEnd |
string (date-time) | End of the comparison window, which is the current start. Null unless compare is true and start was sent. |
testCalls |
object | |
testCalls.count |
integer | Test calls placed from the dashboard in the window, counted whether or not they are included. |
testCalls.included |
boolean | Whether test calls are counted in the other numbers, matching includeTests. |
overview |
object | |
overview.kpis |
object | |
overview.previousKpis |
object | The same numbers for the comparison window. Null unless compare is true and start was sent. |
overview.overTime |
object[] | One entry per day in the window, zero filled, in the requested time zone. Capped at the most recent 3,660 days. Empty when no start was sent and there are no conversations. |
audience |
object | |
audience.locations |
object[] | Up to 10 locations with the most conversations. |
audience.locationUnknown |
integer | Conversations not counted in locations. |
audience.devices |
object | Web calls only. Phone calls carry no device data. |
audience.sources |
object[] | Up to 10 traffic sources for web calls. |
attention |
object | |
attention.notJoined |
integer | Web calls that ended before the caller said anything. |
attention.unanswered |
integer | Knowledge gaps for this agent still waiting for an answer. Not limited to the window. |
outcomes |
object | |
outcomes.leads |
object | |
outcomes.handoffs |
object | |
outcomes.actionItems |
object | |
quality |
object | |
quality.engagement |
object[] | Always 4 buckets, in order: Bounce (0-1 turns), Brief (2-5), Engaged (6-15), Deep (16+). |
quality.sentiment |
object | |
quality.topTopics |
object[] | Up to 10 key topics, lowercased. |
quality.endReasons |
object[] | Up to 10 reasons calls ended. |
quality.satisfaction |
object | Average post-call rating. Null when no call in the window was rated. |
whenAndWho |
object | |
whenAndWho.byHour |
object[] | 24 entries, hour 0 to 23 in the requested time zone. |
whenAndWho.byWeekday |
object[] | 7 entries, weekday 0 (Sunday) to 6 (Saturday) in the requested time zone. |
whenAndWho.languages |
object[] | Up to 10 caller languages. |
channel |
object | |
channel.web |
object | |
channel.phone |
object |
overview.kpis – 4 more fields
| Field | Type | Description |
|---|---|---|
conversations |
integer | |
completionRate |
number | Completed conversations divided by all conversations, from 0 to 1. |
avgDurationSeconds |
number | |
returningCallers |
integer | Callers in the window who called more than once, or who also called before the window. |
overview.previousKpis – 4 more fields
| Field | Type |
|---|---|
conversations |
integer |
completionRate |
number |
avgDurationSeconds |
number |
returningCallers |
integer |
overview.overTime – 4 more fields
| Field | Type | Description |
|---|---|---|
date |
string | Day as YYYY-MM-DD. |
conversations |
integer | |
web |
integer | |
phone |
integer |
audience.locations – 4 more fields
| Field | Type | Description |
|---|---|---|
city |
string | |
region |
string | |
country |
string | Country name. |
count |
integer |
audience.devices – 4 more fields
| Field | Type | Description |
|---|---|---|
withDeviceData |
integer | |
deviceTypes |
object | |
deviceTypes.desktop |
integer | |
deviceTypes.mobile |
integer | |
deviceTypes.tablet |
integer | |
browsers |
object[] | Up to 5 browsers. |
browsers[].name |
string | |
browsers[].count |
integer | |
os |
object[] | Up to 5 operating systems. |
os[].name |
string | |
os[].count |
integer |
audience.sources – 3 more fields
| Field | Type | Description |
|---|---|---|
kind |
string | campaign when the call carried UTM tags, page when it came from a referring page, direct otherwise. One of: page, campaign, direct . |
label |
string | The UTM source and campaign, or the referring host and path. Empty for direct. |
count |
integer |
outcomes.leads – 4 more fields
| Field | Type | Description |
|---|---|---|
count |
integer | |
rate |
number | Lead conversations divided by all conversations, from 0 to 1. |
fields |
object[] | Up to 6 collected fields that were filled in most often. |
fields[].field |
string | |
fields[].count |
integer | |
latest |
object[] | Up to 10 most recent conversations that collected data. |
latest – 8 more fields
| Field | Type |
|---|---|
conversationId |
string |
createdAt |
string |
name |
string |
email |
string |
phone |
string |
summary |
string |
mediaId |
string |
folderId |
string |
outcomes.handoffs – 3 more fields
| Field | Type | Description |
|---|---|---|
count |
integer | |
rate |
number | Handed off conversations divided by all conversations, from 0 to 1. |
reasons |
object[] | Up to 5 most common handoff reasons. |
reasons[].reason |
string | |
reasons[].count |
integer |
outcomes.actionItems – 3 more fields
| Field | Type | Description |
|---|---|---|
conversations |
integer | |
rate |
number | Conversations with action items divided by all conversations, from 0 to 1. |
recent |
object[] | Up to 5 action items from the most recent conversations. |
recent[].conversationId |
string | |
recent[].createdAt |
string | |
recent[].item |
string |
quality.engagement – 7 more fields
| Field | Type |
|---|---|
label |
string |
minMessages |
integer |
maxMessages |
integer |
count |
integer |
percentage |
integer |
avgDurationSeconds |
number |
sentimentBreakdown |
object |
sentimentBreakdown.positive |
integer |
sentimentBreakdown.neutral |
integer |
sentimentBreakdown.negative |
integer |
sentimentBreakdown.unanalyzed |
integer |
quality.sentiment – 4 more fields
| Field | Type |
|---|---|
positive |
integer |
neutral |
integer |
negative |
integer |
unanalyzed |
integer |
quality.topTopics – 2 more fields
| Field | Type |
|---|---|
topic |
string |
count |
integer |
quality.endReasons – 2 more fields
| Field | Type |
|---|---|
reason |
string |
count |
integer |
quality.satisfaction – 2 more fields
| Field | Type |
|---|---|
average |
number |
count |
integer |
whenAndWho.byHour – 2 more fields
| Field | Type |
|---|---|
hour |
integer |
count |
integer |
whenAndWho.byWeekday – 2 more fields
| Field | Type |
|---|---|
weekday |
integer |
count |
integer |
whenAndWho.languages – 2 more fields
| Field | Type |
|---|---|
language |
string |
count |
integer |
channel.web – 2 more fields
| Field | Type |
|---|---|
count |
integer |
avgDurationSeconds |
number |
channel.phone – 2 more fields
| Field | Type |
|---|---|
count |
integer |
avgDurationSeconds |
number |
Example response, application/json. Arrays are shortened to one entry and long strings are cut.
{
"status": "success",
"data": {
"range": {
"start": "2026-09-01T00:00:00.000Z",
"end": "2026-09-30T23:59:59.999Z",
"tz": "America/Toronto",
"previousStart": "2026-08-03T00:00:00.001Z",
"previousEnd": "2026-09-01T00:00:00.000Z"
},
"testCalls": {
"count": 3,
"included": false
},
"overview": {
"kpis": {
"conversations": 120,
"completionRate": 0.85,
"avgDurationSeconds": 142.6,
"returningCallers": 14
},
"previousKpis": {
"conversations": 96,
"completionRate": 0.81,
"avgDurationSeconds": 131.2,
"returningCallers": 9
},
"overTime": [
{
"date": "2026-09-01",
"conversations": 5,
"web": 4,
"phone": 1
}
]
},
"audience": {
"locations": [
{
"city": "Toronto",
"region": "Ontario",
"country": "Canada",
"count": 22
}
],
"locationUnknown": 31,
"devices": {
"withDeviceData": 88,
"deviceTypes": {
"desktop": 52,
"mobile": 33,
"tablet": 3
},
"browsers": [
{
"name": "Chrome",
"count": 54
}
],
"os": [
{
"name": "Windows",
"count": 40
}
]
},
"sources": [
{
"kind": "page",
"label": "example.com/pricing",
"count": 27
}
]
},
"attention": {
"notJoined": 4,
"unanswered": 2
},
"outcomes": {
"leads": {
"count": 36,
"rate": 0.3,
"fields": [
{
"field": "email",
"count": 34
}
],
"latest": [
{
"conversationId": "c0ffee00-1234-4abc-9def-0123456789ab",
"createdAt": "2026-09-29T15:42:10.000Z",
"name": "Jordan Example",
"email": "jordan@example.com",
"phone": null,
"summary": "Asked about annual pricing and booked a demo.",
"mediaId": "3afc714552cd",
"folderId": null
}
]
},
"handoffs": {
"count": 8,
"rate": 0.0667,
"reasons": [
{
"reason": "Requested a human agent",
"count": 5
}
]
},
"actionItems": {
"conversations": 41,
"rate": 0.3417,
"recent": [
{
"conversationId": "c0ffee00-1234-4abc-9def-0123456789ab",
"createdAt": "2026-09-29T15:42:10.000Z",
"item": "Send the annual pricing sheet."
}
]
}
},
"quality": {
"engagement": [
{
"label": "Engaged (6-15)",
"minMessages": 6,
"maxMessages": 15,
"count": 48,
"percentage": 40,
"avgDurationSeconds": 168.3,
"sentimentBreakdown": {
"positive": 30,
"neutral": 14,
"negative": 3,
"unanalyzed": 1
}
}
],
"sentiment": {
"positive": 70,
"neutral": 38,
"negative": 9,
"unanalyzed": 3
},
"topTopics": [
{
"topic": "pricing",
"count": 25
}
],
"endReasons": [
{
"reason": "customer-ended-call",
"count": 77
}
],
"satisfaction": {
"average": 4.4,
"count": 52
}
},
"whenAndWho": {
"byHour": [
{
"hour": 14,
"count": 12
}
],
"byWeekday": [
{
"weekday": 2,
"count": 25
}
],
"languages": [
{
"language": "en",
"count": 104
}
]
},
"channel": {
"web": {
"count": 98,
"avgDurationSeconds": 139.1
},
"phone": {
"count": 22,
"avgDurationSeconds": 158.2
}
}
}
}400 A query parameter failed validation
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | One of: failed . |
message |
string |
404 Agent not found
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | One of: failed . |
requestId |
string | |
code |
integer | |
message |
string | |
hints |
string[] |
Example response, application/json.
{
"status": "failed",
"code": 404,
"message": "Agent not found",
"hints": []
}Engagement Funnel Summaries
/voice/analytics/funnelEngagement Funnel Summaries
The same bounce/brief/engaged/deep breakdown as Engagement Funnel For Agent, but as one summary row per agent across your whole workspace, sorted by deepRate descending. The response payload is a bare array directly on data – not an object, and not nested under a named key.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
since |
query | string | No | ISO date. Defaults to 30 days ago. |
curl -X GET 'https://api.speakai.co/v1/voice/analytics/funnel' \
-H 'x-speakai-key: sk_test_speak_0000000000000000'200 OK
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | One of: success . |
data |
object[] |
data – 7 more fields
| Field | Type |
|---|---|
agentId |
string |
agentName |
string |
totalConversations |
integer |
bounceRate |
integer |
briefRate |
integer |
engagedRate |
integer |
deepRate |
integer |
Example response, application/json.
{
"status": "success",
"data": [
{
"agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"agentName": "Support Agent",
"totalConversations": 80,
"bounceRate": 15,
"briefRate": 30,
"engagedRate": 40,
"deepRate": 15
}
]
}400 Invalid since date
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | One of: failed . |
message |
string |
Analytics Overview
/voice/analytics/overviewAnalytics Overview
Workspace-level call metrics: totals, week-over-week counts, completion rate, average duration, a 14-day daily time series, and average post-call feedback rating. Optionally scope to one agentId. Unlike most endpoints in this API, the response payload sits directly on data, not nested under a named key.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
agentId |
query | string | No | Scope to one agent. Omit for the whole workspace. |
curl -X GET 'https://api.speakai.co/v1/voice/analytics/overview' \
-H 'x-speakai-key: sk_test_speak_0000000000000000'200 OK
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | One of: success . |
data |
object |
data – 8 more fields
| Field | Type | Description |
|---|---|---|
totalConversations |
integer | |
thisWeekCount |
integer | |
lastWeekCount |
integer | |
completionRate |
number | 0-1. Share of calls that reached status completed. |
avgDurationSeconds |
number | |
dailyCounts |
object[] | Last 14 days, oldest first, zero-filled for days with no calls. |
dailyCounts[].date |
string | |
dailyCounts[].count |
integer | |
averageRating |
number | Average post-call feedback rating (1-5). 0 if no feedback submitted yet. |
ratingCount |
integer |
Example response, application/json.
{
"status": "success",
"data": {
"totalConversations": 214,
"thisWeekCount": 31,
"lastWeekCount": 27,
"completionRate": 0.92,
"avgDurationSeconds": 138.4,
"dailyCounts": [
{
"date": "2026-07-19",
"count": 4
},
{
"date": "2026-07-20",
"count": 6
}
],
"averageRating": 4.3,
"ratingCount": 58
}
}Engagement Funnel For Agent
/voice/analytics/funnel/agentEngagement Funnel For Agent
Buckets one agent’s conversations by transcript turn count (bounce/brief/engaged/deep), each with average duration and sentiment split. Defaults to the last 30 days if since is omitted; since older than 365 days is floored to 365 days ago. The response payload sits directly on data, not nested under a named key.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
agentId |
query | string | Yes | Required. |
since |
query | string | No | ISO date. Defaults to 30 days ago. |
curl -X GET 'https://api.speakai.co/v1/voice/analytics/funnel/agent' \
-H 'x-speakai-key: sk_test_speak_0000000000000000'200 OK
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | One of: success . |
data |
object | |
data.buckets |
object[] | Always 4 buckets, in order: Bounce (0-1 turns), Brief (2-5), Engaged (6-15), Deep (16+). |
data.totalConversations |
integer | |
data.since |
string (date-time) |
data.buckets – 7 more fields
| Field | Type |
|---|---|
label |
string |
minMessages |
integer |
maxMessages |
integer |
count |
integer |
percentage |
integer |
avgDurationSeconds |
number |
sentimentBreakdown |
object |
sentimentBreakdown.positive |
integer |
sentimentBreakdown.neutral |
integer |
sentimentBreakdown.negative |
integer |
sentimentBreakdown.unanalyzed |
integer |
Example response, application/json.
{
"status": "success",
"data": {
"buckets": [
{
"label": "Bounce (0-1)",
"minMessages": 0,
"maxMessages": 1,
"count": 12,
"percentage": 15,
"avgDurationSeconds": 8.2,
"sentimentBreakdown": {
"positive": 2,
"neutral": 8,
"negative": 2,
"unanalyzed": 0
}
}
],
"totalConversations": 80,
"since": "2026-07-01T00:00:00.000Z"
}
}400 Invalid since date
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | One of: failed . |
message |
string |
404 Agent not found
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | One of: failed . |
requestId |
string | |
code |
integer | |
message |
string | |
hints |
string[] |
Example response, application/json.
{
"status": "failed",
"code": 404,
"message": "Agent not found",
"hints": []
}Related pages
- API reference for the base URL, authentication, and the error format.
- Authenticate with the Speak AI API using an API key
- Upload audio and video to Speak AI and read insights
- Create and update live transcription sessions
- Analyze text notes with the Speak AI text endpoints
Get an API key on the Speak AI developer page.