The Speak AI API exposes 11 labels endpoints under the base URL https://api.speakai.co/v1. Every request needs the x-speakai-key and x-access-token headers described in Authentication.
Labels mark passages of a transcript, such as an objection on a sales call or a quote for a report. Your company keeps one list of labels, with one level of groups, and anyone with the Labels: Assign permission can put them on any transcript they can open.
Permissions
Admins set these per person in Team management. Owners pass every check. Reading labels needs no permission.
| Permission | Allows | Admin default | Member default |
|---|---|---|---|
| Labels: Create | Create labels and groups, add Speak label sets | Yes | Yes |
| Labels: Update | Rename, recolour, describe, regroup, reorder; merge with Delete | Yes | Yes |
| Labels: Delete | Archive and restore; merge with Update | Yes | No |
| Labels: Assign | Apply, change and remove labels on transcripts | Yes | Yes |
A missing permission returns 403 with {"status": "failed", "message": "You do not have permission to perform this action"}.
Transcript revisions
Every saved change to a transcript increases the media’s transcriptRevision. Passages are addressed by word position on a given revision, so labelling and commenting send expectedTranscriptRevision and get 409 when it is stale. When a transcript is edited, labelled passages move with their words; a passage whose words changed too much is marked needs_review with a suggested new position, and nothing is deleted.
Notifications
Every label change, in the library or on a transcript, is listed for the person who made it under the label category of GET /v1/notification.
What can you do with the labels endpoints?
Speak AI groups these 11 endpoints under the labels resource. Each entry below links to the full reference for that endpoint further down this page.
Label list
Labels on a transcript
Label list
List labels
/labelsReturns your company’s labels as a tree: groups and ungrouped labels at the top level, each group carrying the labels inside it. Items are ordered by sortOrder, then by name. Groups go one level deep.
No labels permission is needed: anyone on the team can read the list.
Filtering
statuspicks active labels (the default), archived ones, or both.searchmatches names, ignoring case. A group is listed when its own name matches or when any of its labels match; it then carries only the matching labels.
Usage counts
usageCount counts labelled passages only on media you can open, so it never reveals anything about files in folders you cannot access. Owners and members with Access all on folders see company-wide counts.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
status |
query | string | No | Which labels to return. Leave it out for active. One of: active, archived, all. |
search |
query | string | No | Text to match against label and group names, ignoring case. Trimmed, up to 80 characters. An empty value returns everything. |
curl -X GET 'https://api.speakai.co/v1/labels?status=active&search=action' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | The result. |
data.labels |
object[] | Top-level groups and ungrouped labels. |
data.labels – 15 more fields
| Field | Type | Description |
|---|---|---|
labelId |
string | Id of the label or group. |
isGroup |
boolean | true for a group. A group holds labels and cannot be applied to a transcript. |
name |
string | Name. |
description |
string | Description. Left out when none was set. |
color |
string | #rrggbb in lower case. Groups have none. |
parentId |
string | labelId of the group this label sits in, or null at the top level. |
source |
string | speak for labels added from a Speak label set, user for everything else. One of: user, speak . |
isActive |
boolean | false once archived. |
mergedInto |
string | labelId this label was merged into. Present only on merged labels. |
sortOrder |
integer | Position among its siblings. |
userId |
string | Id of the person who created it. |
createdAt |
string (date-time) | When it was created. |
updatedAt |
string (date-time) | When it last changed. |
usageCount |
integer | How many labelled passages use it, counted only on media you can open. For a group, the sum over its listed labels. |
labels |
object[] | Groups only: the labels in the group that match the filter, with the same fields. |
labels – 14 more fields
| Field | Type | Description |
|---|---|---|
labelId |
string | Id of the label or group. |
isGroup |
boolean | true for a group. A group holds labels and cannot be applied to a transcript. |
name |
string | Name. |
description |
string | Description. Left out when none was set. |
color |
string | #rrggbb in lower case. Groups have none. |
parentId |
string | labelId of the group this label sits in, or null at the top level. |
source |
string | speak for labels added from a Speak label set, user for everything else. One of: user, speak . |
isActive |
boolean | false once archived. |
mergedInto |
string | labelId this label was merged into. Present only on merged labels. |
sortOrder |
integer | Position among its siblings. |
userId |
string | Id of the person who created it. |
createdAt |
string (date-time) | When it was created. |
updatedAt |
string (date-time) | When it last changed. |
usageCount |
integer | How many labelled passages use it, counted only on media you can open. For a group, the sum over its listed labels. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"labels": [
{
"labelId": "Gx7QkP2mVt9LwRz",
"isGroup": true,
"name": "Meetings",
"parentId": null,
"source": "speak",
"isActive": true,
"sortOrder": 0,
"userId": "65f0c1a2b3c4d5e6f7a8b9c0",
"createdAt": "2026-10-06T14:05:12.000Z",
"updatedAt": "2026-10-06T14:05:12.000Z",
"usageCount": 12,
"labels": [
{
"labelId": "Lb3NcY8sDf4HqPe",
"isGroup": false,
"name": "Action item",
"description": "Something a person agreed to do.",
"color": "#6366f1",
"parentId": "Gx7QkP2mVt9LwRz",
"source": "speak",
"isActive": true,
"sortOrder": 0,
"userId": "65f0c1a2b3c4d5e6f7a8b9c0",
"createdAt": "2026-10-06T14:05:12.000Z",
"updatedAt": "2026-10-06T14:05:12.000Z",
"usageCount": 12
}
]
}
]
}
}400 A query value failed validation
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (A query value failed validation), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 400,
"message": "\"status\" must be one of [active, archived, all]",
"hints": [
"The API request is invalid or improperly formed."
]
}Create a label or group
/labelsRequires the Labels: Create permission (on by default for admins and members).
Request rules
- Send
isGroup: trueto create a group. A group cannot havecolororparentId; sending either returns 400. parentIdmust be thelabelIdof an active group in your company. A missing group returns 404 “Parent group not found.”; a label that is not a group returns 400 “A label can only be placed inside a group.”colordefaults to#6366f1when you leave it out.sortOrderdefaults to 0.
Errors
400when a field fails validation or the parent is not a group.403without the permission.404when the parent group does not exist.409when an active label with the same name already exists under the same parent. Names are compared ignoring case, after trimming and collapsing spaces.
Request body
Fields marked required are the ones the server rejects the request without. Anything conditional, where a field becomes required only alongside another, is described under Request rules above rather than marked here.
| Field | Type | Description |
|---|---|---|
name |
string, required | Name of the label or group. Trimmed, then 1 to 80 characters. Repeated spaces are collapsed to one before saving. Names are unique among active labels with the same parent, compared without regard to case, so a duplicate returns 409. |
isGroup |
boolean | true to create a group instead of a label. Defaults to false. |
description |
string | What the label means, shown to people choosing it. Up to 500 characters after trimming. An empty string is accepted. |
color |
string | Colour as six-digit hex, #rrggbb (pattern ^#[0-9a-fA-F]{6}$). Stored in lower case. Not allowed on a group. |
parentId |
string | labelId of the group to put the label in. null or left out for the top level. Not allowed on a group. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
sortOrder |
integer | Position among its siblings, lowest first. Integer from 0 to 1,000,000. Labels with the same value are ordered by name. |
curl -X POST 'https://api.speakai.co/v1/labels' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '{
"name": "Action item",
"description": "Something a person agreed to do.",
"color": "#6366f1",
"parentId": "Gx7QkP2mVt9LwRz"
}'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | A label or group. |
data – 13 more fields
| Field | Type | Description |
|---|---|---|
labelId |
string | Id of the label or group. |
isGroup |
boolean | true for a group. A group holds labels and cannot be applied to a transcript. |
name |
string | Name. |
description |
string | Description. Left out when none was set. |
color |
string | #rrggbb in lower case. Groups have none. |
parentId |
string | labelId of the group this label sits in, or null at the top level. |
source |
string | speak for labels added from a Speak label set, user for everything else. One of: user, speak . |
isActive |
boolean | false once archived. |
mergedInto |
string | labelId this label was merged into. Present only on merged labels. |
sortOrder |
integer | Position among its siblings. |
userId |
string | Id of the person who created it. |
createdAt |
string (date-time) | When it was created. |
updatedAt |
string (date-time) | When it last changed. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"labelId": "Lb3NcY8sDf4HqPe",
"isGroup": false,
"name": "Action item",
"description": "Something a person agreed to do.",
"color": "#6366f1",
"parentId": "Gx7QkP2mVt9LwRz",
"source": "user",
"isActive": true,
"sortOrder": 0,
"userId": "65f0c1a2b3c4d5e6f7a8b9c0",
"createdAt": "2026-10-06T14:05:12.000Z",
"updatedAt": "2026-10-06T14:05:12.000Z"
}
}400 The request failed validation
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The request failed validation), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 400,
"message": "\"name\" length must be less than or equal to 80 characters long",
"hints": [
"The API request is invalid or improperly formed."
]
}403 You lack the Labels: Create permission
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
message |
string | Why the request was refused. |
Example response (You lack the Labels: Create permission), application/json.
{
"status": "failed",
"message": "You do not have permission to perform this action"
}404 The parent group does not exist
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The parent group does not exist), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 404,
"message": "Parent group not found.",
"hints": [
"The requested operation failed because a resource associated with the request could not be found."
]
}409 An active label with this name already exists under the same parent
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (An active label with this name already exists under the same parent), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 409,
"message": "A label with this name already exists here.",
"hints": [
"Please use PUT for update instead of POST",
"The requested operation failed because it tried to create a resource that already exists."
]
}Update a label or group
/labels/{labelId}Requires the Labels: Update permission (on by default for admins and members). Send at least one field; an empty body returns 400. Only the fields you send change.
Request rules
- An archived label cannot be edited: restore it first (400 “Restore this label before editing it.”).
- A group has no colour and cannot be placed inside another group (400).
parentIdmoves a label into a group;nullmoves it to the top level. A label cannot be its own group (400).
Errors
400for the rules above or a field that fails validation.403without the permission.404when the label or the new parent group does not exist.409when the new name is already used by an active label under the same parent.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
labelId |
path | string | Yes | Id of the label or group. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
Request body
Fields marked required are the ones the server rejects the request without. Anything conditional, where a field becomes required only alongside another, is described under Request rules above rather than marked here.
| Field | Type | Description |
|---|---|---|
name |
string | Name of the label or group. Trimmed, then 1 to 80 characters. Repeated spaces are collapsed to one before saving. Names are unique among active labels with the same parent, compared without regard to case, so a duplicate returns 409. |
description |
string | What the label means, shown to people choosing it. Up to 500 characters after trimming. An empty string is accepted. |
color |
string | Colour as six-digit hex, #rrggbb (pattern ^#[0-9a-fA-F]{6}$). Stored in lower case. Not allowed on a group. |
parentId |
string | labelId of a group to move the label into, or null to move it to the top level. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
sortOrder |
integer | Position among its siblings, lowest first. Integer from 0 to 1,000,000. Labels with the same value are ordered by name. |
curl -X PUT 'https://api.speakai.co/v1/labels/Lb3NcY8sDf4HqPe' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '{
"name": "Next step"
}'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | A label or group. |
data – 13 more fields
| Field | Type | Description |
|---|---|---|
labelId |
string | Id of the label or group. |
isGroup |
boolean | true for a group. A group holds labels and cannot be applied to a transcript. |
name |
string | Name. |
description |
string | Description. Left out when none was set. |
color |
string | #rrggbb in lower case. Groups have none. |
parentId |
string | labelId of the group this label sits in, or null at the top level. |
source |
string | speak for labels added from a Speak label set, user for everything else. One of: user, speak . |
isActive |
boolean | false once archived. |
mergedInto |
string | labelId this label was merged into. Present only on merged labels. |
sortOrder |
integer | Position among its siblings. |
userId |
string | Id of the person who created it. |
createdAt |
string (date-time) | When it was created. |
updatedAt |
string (date-time) | When it last changed. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"labelId": "Lb3NcY8sDf4HqPe",
"isGroup": false,
"name": "Next step",
"description": "Something a person agreed to do.",
"color": "#6366f1",
"parentId": "Gx7QkP2mVt9LwRz",
"source": "user",
"isActive": true,
"sortOrder": 0,
"userId": "65f0c1a2b3c4d5e6f7a8b9c0",
"createdAt": "2026-10-06T14:05:12.000Z",
"updatedAt": "2026-10-06T16:00:00.000Z"
}
}400 A rule above was broken or a field failed validation
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (A rule above was broken or a field failed validation), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 400,
"message": "Restore this label before editing it.",
"hints": [
"The API request is invalid or improperly formed."
]
}403 You lack the Labels: Update permission
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
message |
string | Why the request was refused. |
Example response (You lack the Labels: Update permission), application/json.
{
"status": "failed",
"message": "You do not have permission to perform this action"
}404 No label with that id in your company
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (No label with that id in your company), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 404,
"message": "Label not found.",
"hints": [
"The requested operation failed because a resource associated with the request could not be found."
]
}409 An active label with this name already exists under the same parent
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (An active label with this name already exists under the same parent), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 409,
"message": "A label with this name already exists here.",
"hints": [
"Please use PUT for update instead of POST",
"The requested operation failed because it tried to create a resource that already exists."
]
}Archive a label or group
/labels/{labelId}/archiveRequires the Labels: Delete permission (on by default for admins). Archived labels drop out of the default list and cannot be applied, but passages already labelled keep them. Archiving a group also archives every label in it. Safe to repeat: archiving something already archived returns archivedCount: 0. Nothing is deleted; restore it at any time.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
labelId |
path | string | Yes | Id of the label or group. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
curl -X POST 'https://api.speakai.co/v1/labels/Lb3NcY8sDf4HqPe/archive' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | The result. |
data.labelId |
string | Id of the label you archived. |
data.archivedCount |
integer | How many labels and groups changed from active to archived. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"labelId": "Gx7QkP2mVt9LwRz",
"archivedCount": 5
}
}403 You lack the Labels: Delete permission
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
message |
string | Why the request was refused. |
Example response (You lack the Labels: Delete permission), application/json.
{
"status": "failed",
"message": "You do not have permission to perform this action"
}404 No label with that id in your company
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (No label with that id in your company), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 404,
"message": "Label not found.",
"hints": [
"The requested operation failed because a resource associated with the request could not be found."
]
}Merge a label into another
/labels/{labelId}/mergeRequires both Labels: Update and Labels: Delete. Moves every use of the label in the path onto targetLabelId, then archives it with mergedInto set. A passage that already had both labels keeps one. A merged label cannot be restored.
movedCount is the number of labelled passages changed across the whole company.
Errors
400when the two ids are the same, when either is a group, or when either is archived.403without both permissions.404when either label does not exist.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
labelId |
path | string | Yes | Id of the label to merge away. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
Request body
Fields marked required are the ones the server rejects the request without. Anything conditional, where a field becomes required only alongside another, is described under Request rules above rather than marked here.
| Field | Type | Description |
|---|---|---|
targetLabelId |
string, required | Id of the label to keep. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
curl -X POST 'https://api.speakai.co/v1/labels/Lb9TmK2vXa6JrWo/merge' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '{
"targetLabelId": "Lb3NcY8sDf4HqPe"
}'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | The result. |
data.labelId |
string | Id of the label that was merged and archived. |
data.mergedInto |
string | Id of the label that now carries its uses. |
data.movedCount |
integer | Labelled passages changed, company-wide. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"labelId": "Lb9TmK2vXa6JrWo",
"mergedInto": "Lb3NcY8sDf4HqPe",
"movedCount": 7
}
}400 Same label, a group, or an archived label
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (Same label, a group, or an archived label), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 400,
"message": "Choose a different label to merge into.",
"hints": [
"The API request is invalid or improperly formed."
]
}403 You lack Labels: Update or Labels: Delete
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
message |
string | Why the request was refused. |
Example response (You lack Labels: Update or Labels: Delete), application/json.
{
"status": "failed",
"message": "You do not have permission to perform this action"
}404 Either label does not exist
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (Either label does not exist), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 404,
"message": "Target label not found.",
"hints": [
"The requested operation failed because a resource associated with the request could not be found."
]
}Restore an archived label or group
/labels/{labelId}/restoreRequires the Labels: Delete permission. Restoring a group also restores its archived labels, except ones that were merged. A label in that group whose name is now used by an active label stays archived and is counted in skippedCount.
Errors
400“This label was merged into another label and cannot be restored.”400“Restore its group first.” when the label sits in a group that is still archived.403without the permission.404when the label does not exist.409when the label’s own name is now used by an active label under the same parent. Rename one of them first.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
labelId |
path | string | Yes | Id of the label or group. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
curl -X POST 'https://api.speakai.co/v1/labels/Lb3NcY8sDf4HqPe/restore' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | The result. |
data.labelId |
string | Id of the label you restored. |
data.restoredCount |
integer | How many labels and groups changed from archived to active. |
data.skippedCount |
integer | Labels in the group left archived because their name is taken. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"labelId": "Gx7QkP2mVt9LwRz",
"restoredCount": 5,
"skippedCount": 0
}
}400 The label was merged, or its group is archived
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The label was merged, or its group is archived), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 400,
"message": "Restore its group first.",
"hints": [
"The API request is invalid or improperly formed."
]
}403 You lack the Labels: Delete permission
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
message |
string | Why the request was refused. |
Example response (You lack the Labels: Delete permission), application/json.
{
"status": "failed",
"message": "You do not have permission to perform this action"
}404 No label with that id in your company
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (No label with that id in your company), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 404,
"message": "Label not found.",
"hints": [
"The requested operation failed because a resource associated with the request could not be found."
]
}409 Its name is used by an active label
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (Its name is used by an active label), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 409,
"message": "A label with this name already exists here. Rename one of them first.",
"hints": [
"Please use PUT for update instead of POST",
"The requested operation failed because it tried to create a resource that already exists."
]
}Add Speak label sets
/labels/speak-setsAdds ready-made sets of labels, each as a group with source: "speak". Requires the Labels: Create permission.
| Set | Group | Labels |
|---|---|---|
sales_qa |
Sales QA | Unprofessional, Slang, Objection, Great moment, Compliance risk |
research |
Research | Pain point, Motivation, Quote for report, Surprise, Follow-up |
meetings |
Meetings | Decision, Action item, Risk, Open question |
transcript_feedback |
Transcript feedback | Wrong split, Misheard word, Wrong speaker, Bad translation |
Safe to repeat: an active group or label with the same name is reused rather than duplicated, so createdCount counts only what is new. A set whose group name is already used by a plain top-level label is skipped and listed in skippedSets.
Request body
Fields marked required are the ones the server rejects the request without. Anything conditional, where a field becomes required only alongside another, is described under Request rules above rather than marked here.
| Field | Type | Description |
|---|---|---|
sets |
string[], required | The sets to add. At least one, no repeats. Each item is one of: sales_qa, research, meetings, transcript_feedback . |
curl -X POST 'https://api.speakai.co/v1/labels/speak-sets' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '{
"sets": [
"meetings"
]
}'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | The result. |
data.groups |
object[] | The group for each set that was added or already there. |
data.createdCount |
integer | How many groups and labels were created by this request. |
data.skippedSets |
string[] | Sets skipped because a plain label already uses the group name. |
data.groups – 13 more fields
| Field | Type | Description |
|---|---|---|
labelId |
string | Id of the label or group. |
isGroup |
boolean | true for a group. A group holds labels and cannot be applied to a transcript. |
name |
string | Name. |
description |
string | Description. Left out when none was set. |
color |
string | #rrggbb in lower case. Groups have none. |
parentId |
string | labelId of the group this label sits in, or null at the top level. |
source |
string | speak for labels added from a Speak label set, user for everything else. One of: user, speak . |
isActive |
boolean | false once archived. |
mergedInto |
string | labelId this label was merged into. Present only on merged labels. |
sortOrder |
integer | Position among its siblings. |
userId |
string | Id of the person who created it. |
createdAt |
string (date-time) | When it was created. |
updatedAt |
string (date-time) | When it last changed. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"groups": [
{
"labelId": "Gx7QkP2mVt9LwRz",
"isGroup": true,
"name": "Meetings",
"parentId": null,
"source": "speak",
"isActive": true,
"sortOrder": 0,
"userId": "65f0c1a2b3c4d5e6f7a8b9c0",
"createdAt": "2026-10-06T14:05:12.000Z",
"updatedAt": "2026-10-06T14:05:12.000Z"
}
],
"createdCount": 5,
"skippedSets": []
}
}400 The request failed validation
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The request failed validation), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 400,
"message": "\"sets\" must contain at least 1 items",
"hints": [
"The API request is invalid or improperly formed."
]
}403 You lack the Labels: Create permission
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
message |
string | Why the request was refused. |
Example response (You lack the Labels: Create permission), application/json.
{
"status": "failed",
"message": "You do not have permission to perform this action"
}Labels on a transcript
List labels on a transcript
/media/{mediaId}/labelsReturns every labelled passage on the media, ordered by position in the transcript, with the media’s current transcriptRevision. Anyone who can open the file can read its labels; no labels permission is needed. The media must belong to your company and be one you can open. Owners and members with the folder Access all permission can open any file; everyone else can open files in folders assigned to them or their groups, and files they own. Any other media returns 404, the same as a media id that does not exist.
When a transcript is saved, labels and comments on passages are moved to the new text in the background, after the save has answered. Until that finishes, which is normally a moment, a list can return anchors whose anchor.transcriptRevision is lower than the top-level transcriptRevision. Fetch the list again shortly after a save.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes | Id of the media file. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
status |
query | string | No | Return only passages with this status. Leave it out for all of them. One of: active, shifted, needs_review. |
curl -X GET 'https://api.speakai.co/v1/media/b4994aa1267c/labels?status=needs_review' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | The result. |
data.transcriptRevision |
integer | The media’s current transcript revision. Send it as expectedTranscriptRevision when you label or comment. |
data.mediaLabels |
object[] | Labelled passages, ordered by anchor.startWord. |
data.mediaLabels – 9 more fields
| Field | Type | Description |
|---|---|---|
mediaLabelId |
string | Id of the labelled passage. |
mediaId |
string | Id of the media. |
labelIds |
string[] | Labels on the passage. |
anchor |
object | Where the passage sits in the transcript. Built by the server from the word range you send. |
status |
string | active: the same words, possibly at new positions after an edit. shifted: most of the words survived an edit near the same time, but the text changed. needs_review: the words changed too much to follow; anchor then holds a suggested passage on the new transcript and lastResolved the last position a person confirmed. One of: active, shifted, needs_review . |
lastResolved |
object | Present only while status is needs_review: the last position a person confirmed. |
userId |
string | Id of the person who first labelled the passage. |
createdAt |
string (date-time) | When it was created. |
updatedAt |
string (date-time) | When it last changed. |
anchor – 9 more fields
| Field | Type | Description |
|---|---|---|
startWord |
integer | Position of the first word. |
endWord |
integer | Position of the last word, inclusive. |
exact |
string | The quoted words, joined by single spaces. |
prefix |
string | Up to 32 characters of transcript text before the quote. |
suffix |
string | Up to 32 characters of transcript text after the quote. |
startInSec |
number | Start time of the first word, in seconds. |
endInSec |
number | End time of the last word, in seconds. |
speakerIds |
string[] | Speakers of the quoted words. |
transcriptRevision |
integer | The transcript revision these positions refer to. |
lastResolved – 9 more fields
| Field | Type | Description |
|---|---|---|
startWord |
integer | Position of the first word. |
endWord |
integer | Position of the last word, inclusive. |
exact |
string | The quoted words, joined by single spaces. |
prefix |
string | Up to 32 characters of transcript text before the quote. |
suffix |
string | Up to 32 characters of transcript text after the quote. |
startInSec |
number | Start time of the first word, in seconds. |
endInSec |
number | End time of the last word, in seconds. |
speakerIds |
string[] | Speakers of the quoted words. |
transcriptRevision |
integer | The transcript revision these positions refer to. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"transcriptRevision": 3,
"mediaLabels": [
{
"mediaLabelId": "Ml5RtB7nZc2GyQs",
"mediaId": "b4994aa1267c",
"labelIds": [
"Lb3NcY8sDf4HqPe"
],
"anchor": {
"startWord": 42,
"endWord": 49,
"exact": "we need the pricing sheet by Friday",
"prefix": "Before we close the quarter, ",
"suffix": ". Can you send it over?",
"startInSec": 61.4,
"endInSec": 64.2,
"speakerIds": [
"1"
],
"transcriptRevision": 3
},
"status": "active",
"userId": "65f0c1a2b3c4d5e6f7a8b9c0",
"createdAt": "2026-10-06T14:05:12.000Z",
"updatedAt": "2026-10-06T14:05:12.000Z"
}
]
}
}400 A path or query value failed validation
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (A path or query value failed validation), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 400,
"message": "\"status\" must be one of [active, shifted, needs_review]",
"hints": [
"The API request is invalid or improperly formed."
]
}404 The media does not exist or you cannot open it
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The media does not exist or you cannot open it), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 404,
"message": "Media not found.",
"hints": [
"The requested operation failed because a resource associated with the request could not be found."
]
}Label a passage
/media/{mediaId}/labelsRequires the Labels: Assign permission (on by default for admins and members). The media must belong to your company and be one you can open. Owners and members with the folder Access all permission can open any file; everyone else can open files in folders assigned to them or their groups, and files they own. Any other media returns 404, the same as a media id that does not exist.
How to address words
Send the word range and the revision you read it from. Word positions count every word of the transcript in order, starting at 0. Within each sentence, words come from entities in order (an entity that holds several words, such as “New York”, counts as several), or from the sentence text split on whitespace when the sentence has no entities. A token that is only punctuation is not counted. expectedTranscriptRevision is the transcriptRevision you read the transcript at, from GET /media/transcript/{mediaId}, GET /media/insight/{mediaId}, or the list endpoints on this page. The server builds the anchor from its own copy of the transcript. If the transcript has been saved since, the request returns 409 “This transcript changed since you opened it. Reload to keep editing.” and nothing is written: read the transcript again and recompute the word range.
Request rules
- When the same words already carry labels at this revision, the labels you send are added to that passage and the existing passage is returned.
- A transcript holds at most 5,000 labelled passages; past that the request returns 400.
Errors
400when a field fails validation, the range runs past the transcript, a label is archived, a group or not in your company, or the passage limit is reached.403without the permission.404when the media does not exist or you cannot open it.409whenexpectedTranscriptRevisionis stale.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes | Id of the media file. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
Request body
Fields marked required are the ones the server rejects the request without. Anything conditional, where a field becomes required only alongside another, is described under Request rules above rather than marked here.
| Field | Type | Description |
|---|---|---|
range |
object, required | Inclusive range of word positions. Word positions count every word of the transcript in order, starting at 0. Within each sentence, words come from entities in order (an entity that holds several words, such as “New York”, counts as several), or from the sentence text split on whitespace when the sentence has no entities. A token that is only punctuation is not counted. |
range.start |
integer, required | Position of the first word. Integer, 0 or more. |
range.end |
integer, required | Position of the last word, inclusive. Integer, at least start. A range that runs past the last word returns 400 “The selected words are outside this transcript.” |
labelIds |
string[], required | Labels to put on the passage. 1 to 20 ids, no repeats. Each must be an active label (not a group) in your company, or the request returns 400 “Choose active labels from this workspace (groups cannot be applied).” |
expectedTranscriptRevision |
integer, required | The transcriptRevision you read the transcript at. Integer, 0 or more. A stale value returns 409. |
curl -X POST 'https://api.speakai.co/v1/media/b4994aa1267c/labels' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '{
"range": {
"start": 42,
"end": 49
},
"labelIds": [
"Lb3NcY8sDf4HqPe"
],
"expectedTranscriptRevision": 3
}'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | Labels on one passage of a transcript. |
data – 9 more fields
| Field | Type | Description |
|---|---|---|
mediaLabelId |
string | Id of the labelled passage. |
mediaId |
string | Id of the media. |
labelIds |
string[] | Labels on the passage. |
anchor |
object | Where the passage sits in the transcript. Built by the server from the word range you send. |
status |
string | active: the same words, possibly at new positions after an edit. shifted: most of the words survived an edit near the same time, but the text changed. needs_review: the words changed too much to follow; anchor then holds a suggested passage on the new transcript and lastResolved the last position a person confirmed. One of: active, shifted, needs_review . |
lastResolved |
object | Present only while status is needs_review: the last position a person confirmed. |
userId |
string | Id of the person who first labelled the passage. |
createdAt |
string (date-time) | When it was created. |
updatedAt |
string (date-time) | When it last changed. |
anchor – 9 more fields
| Field | Type | Description |
|---|---|---|
startWord |
integer | Position of the first word. |
endWord |
integer | Position of the last word, inclusive. |
exact |
string | The quoted words, joined by single spaces. |
prefix |
string | Up to 32 characters of transcript text before the quote. |
suffix |
string | Up to 32 characters of transcript text after the quote. |
startInSec |
number | Start time of the first word, in seconds. |
endInSec |
number | End time of the last word, in seconds. |
speakerIds |
string[] | Speakers of the quoted words. |
transcriptRevision |
integer | The transcript revision these positions refer to. |
lastResolved – 9 more fields
| Field | Type | Description |
|---|---|---|
startWord |
integer | Position of the first word. |
endWord |
integer | Position of the last word, inclusive. |
exact |
string | The quoted words, joined by single spaces. |
prefix |
string | Up to 32 characters of transcript text before the quote. |
suffix |
string | Up to 32 characters of transcript text after the quote. |
startInSec |
number | Start time of the first word, in seconds. |
endInSec |
number | End time of the last word, in seconds. |
speakerIds |
string[] | Speakers of the quoted words. |
transcriptRevision |
integer | The transcript revision these positions refer to. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"mediaLabelId": "Ml5RtB7nZc2GyQs",
"mediaId": "b4994aa1267c",
"labelIds": [
"Lb3NcY8sDf4HqPe"
],
"anchor": {
"startWord": 42,
"endWord": 49,
"exact": "we need the pricing sheet by Friday",
"prefix": "Before we close the quarter, ",
"suffix": ". Can you send it over?",
"startInSec": 61.4,
"endInSec": 64.2,
"speakerIds": [
"1"
],
"transcriptRevision": 3
},
"status": "active",
"userId": "65f0c1a2b3c4d5e6f7a8b9c0",
"createdAt": "2026-10-06T14:05:12.000Z",
"updatedAt": "2026-10-06T14:05:12.000Z"
}
}400 Validation, range, label or limit problem
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (Validation, range, label or limit problem), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 400,
"message": "The selected words are outside this transcript.",
"hints": [
"The API request is invalid or improperly formed."
]
}403 You lack the Labels: Assign permission
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
message |
string | Why the request was refused. |
Example response (You lack the Labels: Assign permission), application/json.
{
"status": "failed",
"message": "You do not have permission to perform this action"
}404 The media does not exist or you cannot open it
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The media does not exist or you cannot open it), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 404,
"message": "Media not found.",
"hints": [
"The requested operation failed because a resource associated with the request could not be found."
]
}409 The transcript changed since you read it
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The transcript changed since you read it), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 409,
"message": "This transcript changed since you opened it. Reload to keep editing.",
"hints": [
"Please use PUT for update instead of POST",
"The requested operation failed because it tried to create a resource that already exists."
]
}Change, confirm or move a labelled passage
/media/{mediaId}/labels/{mediaLabelId}Requires the Labels: Assign permission. Send exactly one of labelIds or action; both or neither returns 400.
labelIdsreplaces the passage’s labels, with the same rules as when you label a passage.action: "keep"confirms the passage where it is now:statusbecomesactiveandlastResolvedis cleared. Use it on ashiftedpassage, or to accept the suggestion on aneeds_reviewone. It returns 409 if the passage is still on an older revision, which happens for a moment after a save.action: "replace"moves the passage to new words. It needsrangeandexpectedTranscriptRevision, which are not allowed with any other choice.expectedTranscriptRevisionis thetranscriptRevisionyou read the transcript at, fromGET /media/transcript/{mediaId},GET /media/insight/{mediaId}, or the list endpoints on this page. The server builds the anchor from its own copy of the transcript. If the transcript has been saved since, the request returns 409 “This transcript changed since you opened it. Reload to keep editing.” and nothing is written: read the transcript again and recompute the word range.
Errors
400when the body breaks the rules above, the range runs past the transcript, or a label cannot be applied.403without the permission.404when the media or the labelled passage does not exist.409when the revision is stale.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes | Id of the media file. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
mediaLabelId |
path | string | Yes | Id of the labelled passage, from mediaLabelId. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
Request body
Fields marked required are the ones the server rejects the request without. Anything conditional, where a field becomes required only alongside another, is described under Request rules above rather than marked here.
| Field | Type | Description |
|---|---|---|
labelIds |
string[] | Labels to put on the passage. 1 to 20 ids, no repeats. Each must be an active label (not a group) in your company, or the request returns 400 “Choose active labels from this workspace (groups cannot be applied).” |
action |
string | keep to confirm the current position, replace to move the passage. One of: keep, replace . |
range |
object | Required with action: "replace", not allowed otherwise. Inclusive range of word positions. Word positions count every word of the transcript in order, starting at 0. Within each sentence, words come from entities in order (an entity that holds several words, such as “New York”, counts as several), or from the sentence text split on whitespace when the sentence has no entities. A token that is only punctuation is not counted. |
range.start |
integer, required | Position of the first word. Integer, 0 or more. |
range.end |
integer, required | Position of the last word, inclusive. Integer, at least start. A range that runs past the last word returns 400 “The selected words are outside this transcript.” |
expectedTranscriptRevision |
integer | Required with action: "replace", not allowed otherwise. The transcriptRevision you read the transcript at. Integer, 0 or more. A stale value returns 409. |
curl -X PATCH 'https://api.speakai.co/v1/media/b4994aa1267c/labels/Ml5RtB7nZc2GyQs' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '{
"labelIds": [
"Lb3NcY8sDf4HqPe",
"Lb9TmK2vXa6JrWo"
]
}'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | Labels on one passage of a transcript. |
data – 9 more fields
| Field | Type | Description |
|---|---|---|
mediaLabelId |
string | Id of the labelled passage. |
mediaId |
string | Id of the media. |
labelIds |
string[] | Labels on the passage. |
anchor |
object | Where the passage sits in the transcript. Built by the server from the word range you send. |
status |
string | active: the same words, possibly at new positions after an edit. shifted: most of the words survived an edit near the same time, but the text changed. needs_review: the words changed too much to follow; anchor then holds a suggested passage on the new transcript and lastResolved the last position a person confirmed. One of: active, shifted, needs_review . |
lastResolved |
object | Present only while status is needs_review: the last position a person confirmed. |
userId |
string | Id of the person who first labelled the passage. |
createdAt |
string (date-time) | When it was created. |
updatedAt |
string (date-time) | When it last changed. |
anchor – 9 more fields
| Field | Type | Description |
|---|---|---|
startWord |
integer | Position of the first word. |
endWord |
integer | Position of the last word, inclusive. |
exact |
string | The quoted words, joined by single spaces. |
prefix |
string | Up to 32 characters of transcript text before the quote. |
suffix |
string | Up to 32 characters of transcript text after the quote. |
startInSec |
number | Start time of the first word, in seconds. |
endInSec |
number | End time of the last word, in seconds. |
speakerIds |
string[] | Speakers of the quoted words. |
transcriptRevision |
integer | The transcript revision these positions refer to. |
lastResolved – 9 more fields
| Field | Type | Description |
|---|---|---|
startWord |
integer | Position of the first word. |
endWord |
integer | Position of the last word, inclusive. |
exact |
string | The quoted words, joined by single spaces. |
prefix |
string | Up to 32 characters of transcript text before the quote. |
suffix |
string | Up to 32 characters of transcript text after the quote. |
startInSec |
number | Start time of the first word, in seconds. |
endInSec |
number | End time of the last word, in seconds. |
speakerIds |
string[] | Speakers of the quoted words. |
transcriptRevision |
integer | The transcript revision these positions refer to. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"mediaLabelId": "Ml5RtB7nZc2GyQs",
"mediaId": "b4994aa1267c",
"labelIds": [
"Lb3NcY8sDf4HqPe",
"Lb9TmK2vXa6JrWo"
],
"anchor": {
"startWord": 42,
"endWord": 49,
"exact": "we need the pricing sheet by Friday",
"prefix": "Before we close the quarter, ",
"suffix": ". Can you send it over?",
"startInSec": 61.4,
"endInSec": 64.2,
"speakerIds": [
"1"
],
"transcriptRevision": 3
},
"status": "active",
"userId": "65f0c1a2b3c4d5e6f7a8b9c0",
"createdAt": "2026-10-06T14:05:12.000Z",
"updatedAt": "2026-10-06T14:05:12.000Z"
}
}400 The body broke a rule or failed validation
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The body broke a rule or failed validation), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 400,
"message": "\"value\" must contain at least one of [labelIds, action]",
"hints": [
"The API request is invalid or improperly formed."
]
}403 You lack the Labels: Assign permission
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
message |
string | Why the request was refused. |
Example response (You lack the Labels: Assign permission), application/json.
{
"status": "failed",
"message": "You do not have permission to perform this action"
}404 The media or the labelled passage does not exist
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The media or the labelled passage does not exist), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 404,
"message": "Label on this transcript not found.",
"hints": [
"The requested operation failed because a resource associated with the request could not be found."
]
}409 The transcript changed since you read it
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The transcript changed since you read it), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 409,
"message": "This transcript changed since you opened it. Reload to keep editing.",
"hints": [
"Please use PUT for update instead of POST",
"The requested operation failed because it tried to create a resource that already exists."
]
}Remove a labelled passage
/media/{mediaId}/labels/{mediaLabelId}Requires the Labels: Assign permission. Removes the passage and all its labels from the transcript. The labels themselves stay in your list. Comments linked to the passage stay, without the link.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes | Id of the media file. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
mediaLabelId |
path | string | Yes | Id of the labelled passage, from mediaLabelId. Trimmed, then 1 to 64 characters from A-Z, a-z, 0-9, _ and - (pattern ^[A-Za-z0-9_-]{1,64}$). Anything else returns 400. |
curl -X DELETE 'https://api.speakai.co/v1/media/b4994aa1267c/labels/Ml5RtB7nZc2GyQs' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | The result. |
data.mediaLabelId |
string | Id of the passage you removed. |
Example response (Success), application/json.
{
"status": "success",
"data": {
"mediaLabelId": "Ml5RtB7nZc2GyQs"
}
}403 You lack the Labels: Assign permission
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
message |
string | Why the request was refused. |
Example response (You lack the Labels: Assign permission), application/json.
{
"status": "failed",
"message": "You do not have permission to perform this action"
}404 The media or the labelled passage does not exist
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
requestId |
string | Id of this request, for support. |
code |
integer | The HTTP status code again. |
message |
string | What went wrong, in words you can show to a person. |
hints |
string[] | Generic hints for this kind of error. |
Example response (The media or the labelled passage does not exist), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 404,
"message": "Label on this transcript not found.",
"hints": [
"The requested operation failed because a resource associated with the request could not be found."
]
}Related pages
- API reference for the base URL, authentication, and the error format.
- Authenticate with the Speak AI API using access tokens
- 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.