The Speak AI API exposes 13 media 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.
This media collection of endpoints enables operation of list, insights, transcript and export.
Uploading audio or video
When uploading a file, a media file URL (accessible to our servers) must be provided.
This can be a publicly accessible URL hosted by yourselves or a third-party.
You can upload files directly to our storage (AWS S3 bucket) and upload the file using the returned URL.
Step 2, PUT the file to the presigned URL
This step targets the storage host returned in preSignedUrl, not api.speakai.co, so it is described here rather than as a Speak AI endpoint.
PUT Request to Upload Audio File
This endpoint allows you to upload an audio file (in this case, an MP3 file) to an Amazon S3 bucket. The request is made using the HTTP PUT method, which is commonly used for updating resources.
Request URL
https://xs3.amazonaws.com/video/1622234682034-medianame.mp3?AWSAccessKeyId=x&Content-Type=audio/mp3&Expires=1622245482&Signature=nQWU4yxBuUjQJD1yTr92qblQx6s=&x-amz-acl=public-readQuery Parameters
-
AWSAccessKeyId: Your AWS access key ID, used for authentication. -
Content-Type: Specifies the media type of the resource being uploaded (in this case,audio/mp3). -
Expires: A timestamp indicating when the URL will expire. -
Signature: A signature for authentication, ensuring the request is valid. -
x-amz-acl: Sets the access control list (ACL) for the uploaded file. Here, it is set topublic-read, allowing public access to the file.
Request Body
The request does not specify a body type, indicating that the audio file is uploaded directly to the specified URL.
Expected Response
The response from a successful upload typically includes:
-
A confirmation of the upload status (e.g., HTTP status code 200 OK).
-
Metadata about the uploaded file, such as its URL and size.
Ensure that the audio file being uploaded adheres to the specified content type and that all required parameters are included in the request.
What can you do with the media endpoints?
Speak AI groups these 13 endpoints under the media resource. Each entry below links to the full reference for that endpoint further down this page.
List
/mediaIf you are looking for a paginated, sorted and filtered listing of your media library.
- You can pass
userIdinparamsto get data for a user. (Only for enterprise use cases)
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaType |
query | string | No | audio, video, text |
page |
query | integer | No | Default 1st page |
pageSize |
query | integer | No | Default 50 |
sortBy |
query | string | No | Default Created At in Descending order Sort by: Created At, positive, neutral, negative asc or desc |
filterMedia |
query | integer | No | 0 – Uploaded 1 – Assigned Media to a User 2 – Uploaded OR Assigned Default: 0 |
filterName |
query | string | No | Search from media name, keywords, labels, faces, brands, people, locations and transcription. |
folderId |
query | string | No | Pass folderId to fetch only those records |
curl -X GET 'https://api.speakai.co/v1/media?mediaType=audio&page=1&pageSize=50&sortBy=createdAt%3Adesc&filterMedia=0&folderId=568sd56899' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 OK
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
data |
object |
data.totalCount |
integer |
data.pages |
integer |
data.mediaList |
object[] |
data.mediaList – 12 more fields
| Field | Type | Description |
|---|---|---|
duration |
object | |
duration.inSecond |
string | |
duration.start |
string | |
duration.end |
string | |
privacyMode |
string | |
processingProgress |
string | |
state |
string | |
remark |
string | |
_id |
string | |
sentiment |
any[] | |
mediaId |
string | |
name |
string | |
description |
string | |
createdAt |
string (date-time) | |
thumbnailUrl |
string | A signed link to the video’s thumbnail, custom or automatic. Present only when the file has one. It expires, so do not store it. |
Example response (success), application/json.
{
"status": "success",
"data": {
"totalCount": 71,
"pages": 71,
"mediaList": [
{
"duration": {
"inSecond": "60",
"start": "0:00:00",
"end": "0:01:00.182"
},
"privacyMode": "Public",
"processingProgress": "100",
"state": "processed",
"remark": "",
"_id": "5f46c87668ee9913005e5bef",
"sentiment": [],
"mediaId": "50ek03r4kebud105",
"name": "vatsal-30",
"description": "url-test",
"createdAt": "2020-08-26T20:39:18.870Z"
}
]
}
}Update meta-data
/media/{mediaId}If you want to rename, edit description or add a tag to your media, you can do it here.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes |
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 |
|---|---|---|
createdAt |
string | The creation date to store on the file. Optional. An empty string is allowed, but the controller only writes the value when it is truthy, so an empty string does not clear the existing date. |
description |
string | A short description for the file. Optional. Trimmed. An empty string is allowed by validation, but the controller only writes a non empty value, so you cannot clear the description this way. |
folderId |
string | The id of the folder you want to move the file into. Optional. An empty string is allowed but is treated as no change. |
mediaId |
string | Ignored. The server takes the media id from the path, not from the body. Optional. An empty string is allowed. Accepted by validation and never destructured by the controller. |
manageBy |
string | The id of the user you want the file assigned to. Optional. An empty string is allowed but is treated as no change. Stored as a reference to a user record (media model: Schema.Types.ObjectId, ref ‘User’). |
name |
string, required | The new title for the file. Required. Minimum length 1. This is the only property the controller always writes, so every update overwrites the title. |
remark |
string | A note stored with the file. Optional. An empty string is allowed but is treated as no change. |
status |
string | The workflow status of the file. Optional and unconstrained by the schema, so any string passes validation. The media model recognizes ‘’, pending, progress, completed and pendingPayment, and the update runs through findOneAndUpdate without runValidators, so the model enum is not enforced on this path. Only written when not empty. |
tags |
string[] | The full list of tags for the file. The list you send replaces the existing one. Optional. Items are strings and empty strings are allowed. There is no cap on the number of items. Only written when the property is present, and an empty array is truthy so it does clear the tags. |
curl -X PUT 'https://api.speakai.co/v1/media/MEDIA_ID' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '{
"name": "update-1",
"description": "description-1",
"tags": [
"work",
"personal"
]
}'200 OK
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
message |
string |
Example response (Update Media data - success), application/json.
{
"status": "success",
"message": "Updated successfully."
}404 Not Found
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
requestId |
string |
code |
integer |
message |
string |
hints |
string[] |
Example response (Update Media data - failure), application/json.
{
"status": "failed",
"requestId": "7ed88c1e-e1ee-44b0-afa5-50041d2b2309",
"code": 404,
"message": "Media does not exist!",
"hints": [
"The requested operation failed because a resource associated with the request could not be found."
]
}Delete File
/media/{mediaId}If you want to remove media from your library - you can do it here.
We can revert the changes if you send us a request.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes |
curl -X DELETE 'https://api.speakai.co/v1/media/MEDIA_ID' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 Success
The spec records this status code with no example body.
Set a custom thumbnail
/media/{mediaId}/thumbnailReplace a video’s thumbnail with your own image or with a frame from the video. Send exactly one of:
- an image file as multipart form data in the
imagefield, for example-F 'image=@cover.png' - JSON
{ "url": "https://..." }, a public https link to an image, which the server downloads - JSON
{ "seekSec": 12.5 }, to use the video frame at that many seconds from the start
The image must be JPEG, PNG or WebP and at most 10 MB. The server checks the file’s content, not its name or declared type, so a GIF is refused. A url must be https and point to a public address, every redirect included. The server follows up to 3 redirects and gives up after 8 seconds.
A frame request answers 202 straight away and the frame is saved in the background. A seekSec past the end of the video uses the frame one second before the end. To follow it, poll GET /media/status/{mediaId} and read data.thumbnail: pending is true while the frame is being made, error says why it failed, and once it is saved url points to the new frame and source is custom. While a frame request for the video is still running, another one returns 409. A request that has not finished after 5 minutes no longer blocks a new one.
Each change replaces the current thumbnail and deletes the previous custom image. There is no way back to the automatic thumbnail. Only a video can have a thumbnail, and a frame cannot be taken from an archived video. You need permission to edit media.
Errors
400when the request does not carry exactly one ofimage,urlandseekSec: “Send exactly one of an image file, an https url or seekSec.”400whenurlis not https orseekSecis below 0, with the validation message.400when the media is not a video: “Only a video can have a thumbnail.”400when the image is not a JPEG, PNG or WebP file: “Use one JPEG, PNG or WebP image of up to 10 MB.”400when the link is blocked or cannot be downloaded: “That image url could not be downloaded.”400when a frame is asked of an archived video, or of one whose file Speak does not hold: “A frame cannot be taken from this video.”400when the frame service refuses the request: “That frame could not be taken.”403when you do not have permission to edit media.404when the media does not exist.409when a frame is already being taken for this video.413when the image is larger than 10 MB: “Use one JPEG, PNG or WebP image of up to 10 MB.”503when the frame service is busy: “Thumbnails are busy right now. Try again in a minute.”
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes | Id of the video. |
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 |
|---|---|---|
url |
string (uri) | A public https link to a JPEG, PNG or WebP image of up to 10 MB. The server downloads it. Send this or seekSec, not both. |
seekSec |
number | Use the video frame at this many seconds from the start. A value past the end uses the frame one second before the end. Send this or url, not both. |
curl -X PUT 'https://api.speakai.co/v1/media/b4994aa1267c/thumbnail' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com/images/cover.png"
}'200 The image is saved as the thumbnail
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | |
data.thumbnail |
object |
data.thumbnail – 2 more fields
| Field | Type | Description |
|---|---|---|
url |
string | A signed link to the new thumbnail. It expires, so do not store it. |
source |
string | custom for a thumbnail you set. One of: custom . |
202 The frame request is accepted and runs in the background
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success on every 2xx response. One of: success . |
data |
object | |
data.thumbnail |
object |
data.thumbnail – 1 more field
| Field | Type | Description |
|---|---|---|
pending |
boolean | Always true. The frame is still being taken. One of: true . |
400 Not exactly one of image, url and seekSec, an unsupported image, a link that cannot be downloaded, or a frame that cannot be taken
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. |
403 You do not have permission to edit media
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | failed. One of: failed . |
message |
string | What went wrong. |
404 The media 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. |
409 A frame is already being taken for this video
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. |
413 The image is larger than 10 MB
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. |
503 The frame service is busy
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. |
Insights
/media/insight/{mediaId}Load the results of analysis (sentiment, transcription, named entities, etc) for your media object here.
- You can pass
userIdinparamsto get specifically for any user.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes |
curl -X GET 'https://api.speakai.co/v1/media/insight/5a4690abf255' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 OK
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
data |
object |
data – 20 more fields
| Field | Type | Description |
|---|---|---|
insight |
object | |
duration |
object | |
duration.inSecond |
string | |
duration.end |
string | |
duration.start |
string | |
summary |
object | |
summary.original |
object | |
tags |
any[] | |
translationSource |
object | Present only on a file Speak AI created by translating another one. It says whether the original source-language transcript can be read, so a client can offer the control without fetching the transcript. Fetch the transcript itself from GET /media/transcript/{mediaId}/source, which documents every reason value. |
translationSource.available |
boolean | |
translationSource.reason |
string | Why the original cannot be read. Present only when available is false. |
translationSource.sourceMediaId |
string | |
translationSource.sourceLanguage |
string | |
translationSource.mediaType |
string | |
isTranscriptionOnly |
boolean | |
processingProgress |
string | |
state |
string | |
remark |
string | |
sentiment |
object[] | |
sentiment[].document |
object | |
sentiment[].sentences |
object[] | |
createdAt |
string (date-time) | |
updatedAt |
string (date-time) | |
name |
string | |
description |
string | |
publishedUrl |
string | |
mediaId |
string | |
originalCreatedAt |
string (date-time) | |
faceSpriteImg |
string | |
transcriptRevision |
integer | Revision of the transcript. Starts at 0 and increases by one on every saved change to the transcript. Send it back as expectedTranscriptRevision when you save the transcript, label a passage or comment on one. |
thumbnail |
object | The video’s thumbnail. An empty object when the file has none, which includes every audio file and text note. Use it to follow a frame asked for with PUT /media/{mediaId}/thumbnail: pending is true while the frame is being made, and error is set when it failed, while url still shows the previous thumbnail. |
thumbnail.url |
string | A signed link to the current thumbnail. Present only when the file has one. It expires, so do not store it. |
thumbnail.source |
string | auto for the thumbnail Speak AI made, custom for one you set. Present only when the file has a thumbnail. One of: auto, custom . |
thumbnail.pending |
boolean | Present and true while a requested frame is still being made. |
thumbnail.error |
string | Why the last requested frame was not saved, for example “The frame took too long. Try again.” Present only when it failed. |
insight – 23 more fields
| Field | Type |
|---|---|
isAnalyze |
boolean |
transcript |
object[] |
faces |
any[] |
keywords |
object[] |
keywords[].isDeleted |
boolean |
keywords[].instances |
object[] |
keywords[].id |
integer |
keywords[].name |
string |
labels |
any[] |
brands |
any[] |
topics |
object[] |
topics[].isDeleted |
boolean |
topics[].instances |
object[] |
topics[].id |
integer |
topics[].name |
string |
people |
any[] |
locations |
any[] |
arts |
any[] |
dates |
any[] |
events |
any[] |
geopolitical |
any[] |
languages |
any[] |
laws |
any[] |
money |
any[] |
ordinals |
any[] |
percentages |
any[] |
quantities |
any[] |
times |
any[] |
cardinals |
any[] |
nationalities |
any[] |
products |
any[] |
transcript – 6 more fields
| Field | Type |
|---|---|
id |
integer |
text |
string |
confidence |
number |
speakerId |
integer |
language |
string |
instances |
object[] |
instances[].adjustedStart |
string |
instances[].adjustedEnd |
string |
instances[].start |
string |
instances[].end |
string |
keywords[].instances – 4 more fields
| Field | Type |
|---|---|
adjustedStart |
string |
adjustedEnd |
string |
start |
string |
end |
string |
topics[].instances – 4 more fields
| Field | Type |
|---|---|
adjustedStart |
string |
adjustedEnd |
string |
start |
string |
end |
string |
summary.original – 2 more fields
| Field | Type |
|---|---|
text |
string |
createdAt |
string (date-time) |
sentiment[].document – 3 more fields
| Field | Type |
|---|---|
Negative |
integer |
Neutral |
integer |
Positive |
integer |
sentiment[].sentences – 4 more fields
| Field | Type |
|---|---|
id |
integer |
instances |
any[] |
score |
object |
score.compound |
integer |
score.neg |
integer |
score.neu |
integer |
score.pos |
integer |
text |
string |
Example response (success), application/json. Arrays are shortened to one entry and long strings are cut.
{
"status": "success",
"data": {
"insight": {
"isAnalyze": true,
"transcript": [
{
"id": 1,
"text": "Both. Hello hello good how are you?",
"confidence": 0.8278,
"speakerId": 1,
"language": "en-US",
"instances": [
{
"adjustedStart": "0:00:00",
"adjustedEnd": "0:00:05.92",
"start": "0:00:00",
"end": "0:00:05.92"
}
]
}
],
"faces": [],
"keywords": [
{
"isDeleted": false,
"instances": [
{
"adjustedStart": "0:00:08.44",
"adjustedEnd": "0:00:13.84",
"start": "0:00:08.44",
"end": "0:00:13.84"
}
],
"id": 1,
"name": "company"
}
],
"labels": [],
"brands": [],
"topics": [
{
"isDeleted": false,
"instances": [
{
"adjustedStart": "0:00:00",
"adjustedEnd": "0:00:47.744",
"start": "0:00:00",
"end": "0:00:47.744"
}
],
"id": 1,
"name": "Promotions"
}
],
"people": [],
"locations": [],
"arts": [],
"dates": [],
"events": [],
"geopolitical": [],
"languages": [],
"laws": [],
"money": [],
"ordinals": [],
"percentages": [],
"quantities": [],
"times": [],
"cardinals": [],
"nationalities": [],
"products": []
},
"duration": {
"inSecond": "47",
"end": "0:00:47.744",
"start": "0:00:00"
},
"summary": {
"original": {
"text": "",
"createdAt": "2020-09-04T16:16:19.118Z"
}
},
"tags": [],
"isTranscriptionOnly": false,
"processingProgress": "100",
"state": "processed",
"remark": "",
"sentiment": [
{
"document": {
"Negative": 0,
"Neutral": 0,
"Positive": 0
},
"sentences": [
{
"id": 1,
"instances": [],
"score": {
"compound": 0,
"neg": 0,
"neu": 1,
"pos": 0
},
"text": "Sentence 1"
}
]
}
],
"createdAt": "2020-08-22T23:05:57.000Z",
"updatedAt": "2020-08-22T23:11:11.288Z",
"name": "REc557e3c8f2ac42a828973c39f1d6be37",
"description": "",
"publishedUrl": "media/5d03a9d5d4bca272e9c8cf89/audio/1598137553998-rec557e3c8f2ac42a828973c39f1d6be37.mp3",
"mediaId": "81c226f66705",
"originalCreatedAt": "2020-11-08T16:40:10.706Z",
"faceSpriteImg": "",
"transcriptRevision": 3
}
}Example response (Insights - notUploaded), application/json.
{
"status": 200,
"state": "notUploaded",
"message": {
"insight": {
"isAnalyze": false,
"faces": [],
"keywords": [],
"sentiments": [],
"emotions": [],
"labels": [],
"brands": [],
"topics": [],
"transcript": [],
"speakers": []
},
"duration": {
"inSecond": "0"
},
"createdAt": "2019-09-25T14:32:19.000Z",
"_id": "5d8b7a730838363d90a17e86",
"videoId": "QHw2hPhk9-R7rrlPKBZk-FBY9P8rDBV",
"name": "ForBiggerBlazes - Copy",
"description": "",
"privacyMode": "Public",
"size": "0",
"processingProgress": "0",
"state": "notUploaded",
"publishedUrl": "video/5cc37a170eb2173efcfc1f7d/1569421938203-ForBiggerBlazesCopymp4.mp4",
"videoFormat": 1,
"sourceLanguage": "en-US",
"order": 0,
"__v": 0
}
}Re-analyze a media file or text note
/media/reanalyze/{mediaId}Run the Speak AI analysis again over a record that has already been processed, and replace the insights it produced the first time.
One route covers both kinds of record. Pass the mediaId of an uploaded audio or video file, or the mediaId of a text note, and Speak AI re-analyzes whichever record the id belongs to.
What the call does
The record moves into the preparingAnalysis state and the analysis runs in the background, so the response comes back before the new insights exist. Poll GET /media/status/{mediaId} to follow it, or subscribe to the media.reanalyzed and text.reanalyzed webhook events.
The query parameters select which parts of the analysis run again. Omit all of them to re-run the default analysis.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes | Id of the media file or text note to re-analyze. |
isInsights |
query | boolean | No | Re-run insight extraction. |
isSentiment |
query | boolean | No | Re-run sentiment analysis. |
isFillerWords |
query | boolean | No | Re-run filler word detection. |
isEmbeddings |
query | boolean | No | Rebuild the embeddings used by AI chat. |
curl -X GET 'https://api.speakai.co/v1/media/reanalyze/3f8d1ec6ca39?isInsights=true&isSentiment=true&isFillerWords=true&isEmbeddings=true' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 OK
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
message |
string |
Example response (Re-analysis started), application/json.
{
"status": "success",
"message": "Leon Wednesday Meeting re-analyzed successfully!"
}Example response (Record already in the failed state), application/json.
{
"status": "success",
"message": "Media analysis failed. Please contact support if you need assistance."
}500 Internal Server Error
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
requestId |
string |
code |
integer |
message |
string |
hints |
string[] |
Example response (Internal server error), application/json.
{
"status": "failed",
"requestId": "ca665625-645e-438a-867d-366a6e3f133a",
"code": 500,
"message": "Something went wrong. Please try again later.",
"hints": [
"Something went wrong. Please try again later."
]
}Update Transcript Speakers
/media/speakers/{mediaId}Request Description
This API endpoint allows you to update speaker names in the transcription of a media object.
Endpoint
PUT /v1/media/speakers/{mediaId}
Method
PUT
Purpose
The purpose of this request is to update the speaker information associated with a specific media object. This includes modifying the names of the speakers in the transcription data.
Input Parameters
-
mediaId(path parameter): The unique identifier for the media object whose speaker information you wish to update. -
Request Body: The request body should be a JSON array containing objects with the following structure:
-
id (string): The unique identifier for the speaker (ORIGINAL).
-
name (string): The new name of the speaker.
Response Structure
The response will return a JSON object containing the following fields:
-
status: Indicates the status of the request.
-
message: String
Example Response
{
"status": "",
"message": ""
}Notes
-
Ensure that the
mediaIdprovided in the URL corresponds to an existing media object. -
The request body must be formatted as a JSON array, even if updating a single speaker.
-
The response will confirm the status of the update operation, along with any relevant data about the media analysis.
Request rules. The body is an array, one entry per speaker you are relabelling, and every entry needs id and name. Omit segmentIds to relabel every segment for that speaker, or pass them to limit the rename to one paragraph. Pass mergeIntoId to merge this speaker into an existing one.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes |
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 |
|---|---|---|
[].id |
string, required | The speaker you are renaming, given as either the speaker’s current label or its numeric speaker id. Required on every array entry. Trimmed. The server matches the current label first and the numeric id second (resolveTargetSpeaker at, so a label wins when both could match. |
[].name |
string, required | The new name for that speaker. Required on every array entry. Trimmed. Reusing another speaker’s name returns 400 unless you also send mergeIntoId on the same entry. |
[].segmentIds |
number[] | The transcript segment ids you want renamed. Send it to limit the change to one paragraph, and leave it out to rename every segment belonging to that speaker. Optional. Items must be numbers. No minimum or maximum item count. |
[].mergeIntoId |
string | The id of the speaker you are merging this one into. Sending it confirms you meant to combine two speakers, so the duplicate name check is skipped for this entry. Optional. An empty string is allowed, and an empty string counts as not set. |
curl -X PUT 'https://api.speakai.co/v1/media/speakers/b4994aa1267c' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '[
{
"id": "0",
"name": "Vatsal"
}
]'200 OK
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
data |
object |
data – 17 more fields
| Field | Type |
|---|---|
insight |
object |
duration |
object |
duration.inSecond |
string |
duration.end |
string |
duration.start |
string |
summary |
object |
summary.original |
object |
tags |
any[] |
isTranscriptionOnly |
boolean |
processingProgress |
string |
state |
string |
remark |
string |
sentiment |
object[] |
sentiment[].document |
object |
sentiment[].sentences |
object[] |
createdAt |
string (date-time) |
updatedAt |
string (date-time) |
name |
string |
description |
string |
publishedUrl |
string |
mediaId |
string |
originalCreatedAt |
string (date-time) |
faceSpriteImg |
string |
insight – 23 more fields
| Field | Type |
|---|---|
isAnalyze |
boolean |
transcript |
object[] |
faces |
any[] |
keywords |
object[] |
keywords[].isDeleted |
boolean |
keywords[].instances |
object[] |
keywords[].id |
integer |
keywords[].name |
string |
labels |
any[] |
brands |
any[] |
topics |
object[] |
topics[].isDeleted |
boolean |
topics[].instances |
object[] |
topics[].id |
integer |
topics[].name |
string |
people |
any[] |
locations |
any[] |
arts |
any[] |
dates |
any[] |
events |
any[] |
geopolitical |
any[] |
languages |
any[] |
laws |
any[] |
money |
any[] |
ordinals |
any[] |
percentages |
any[] |
quantities |
any[] |
times |
any[] |
cardinals |
any[] |
nationalities |
any[] |
products |
any[] |
transcript – 6 more fields
| Field | Type |
|---|---|
id |
integer |
text |
string |
confidence |
number |
speakerId |
integer |
language |
string |
instances |
object[] |
instances[].adjustedStart |
string |
instances[].adjustedEnd |
string |
instances[].start |
string |
instances[].end |
string |
keywords[].instances – 4 more fields
| Field | Type |
|---|---|
adjustedStart |
string |
adjustedEnd |
string |
start |
string |
end |
string |
topics[].instances – 4 more fields
| Field | Type |
|---|---|
adjustedStart |
string |
adjustedEnd |
string |
start |
string |
end |
string |
summary.original – 2 more fields
| Field | Type |
|---|---|
text |
string |
createdAt |
string (date-time) |
sentiment[].document – 3 more fields
| Field | Type |
|---|---|
Negative |
integer |
Neutral |
integer |
Positive |
integer |
sentiment[].sentences – 4 more fields
| Field | Type |
|---|---|
id |
integer |
instances |
any[] |
score |
object |
score.compound |
integer |
score.neg |
integer |
score.neu |
integer |
score.pos |
integer |
text |
string |
Example response (success), application/json. Arrays are shortened to one entry and long strings are cut.
{
"status": "success",
"data": {
"insight": {
"isAnalyze": true,
"transcript": [
{
"id": 1,
"text": "Both. Hello hello good how are you?",
"confidence": 0.8278,
"speakerId": 1,
"language": "en-US",
"instances": [
{
"adjustedStart": "0:00:00",
"adjustedEnd": "0:00:05.92",
"start": "0:00:00",
"end": "0:00:05.92"
}
]
}
],
"faces": [],
"keywords": [
{
"isDeleted": false,
"instances": [
{
"adjustedStart": "0:00:08.44",
"adjustedEnd": "0:00:13.84",
"start": "0:00:08.44",
"end": "0:00:13.84"
}
],
"id": 1,
"name": "company"
}
],
"labels": [],
"brands": [],
"topics": [
{
"isDeleted": false,
"instances": [
{
"adjustedStart": "0:00:00",
"adjustedEnd": "0:00:47.744",
"start": "0:00:00",
"end": "0:00:47.744"
}
],
"id": 1,
"name": "Promotions"
}
],
"people": [],
"locations": [],
"arts": [],
"dates": [],
"events": [],
"geopolitical": [],
"languages": [],
"laws": [],
"money": [],
"ordinals": [],
"percentages": [],
"quantities": [],
"times": [],
"cardinals": [],
"nationalities": [],
"products": []
},
"duration": {
"inSecond": "47",
"end": "0:00:47.744",
"start": "0:00:00"
},
"summary": {
"original": {
"text": "",
"createdAt": "2020-09-04T16:16:19.118Z"
}
},
"tags": [],
"isTranscriptionOnly": false,
"processingProgress": "100",
"state": "processed",
"remark": "",
"sentiment": [
{
"document": {
"Negative": 0,
"Neutral": 0,
"Positive": 0
},
"sentences": [
{
"id": 1,
"instances": [],
"score": {
"compound": 0,
"neg": 0,
"neu": 1,
"pos": 0
},
"text": "Sentence 1"
}
]
}
],
"createdAt": "2020-08-22T23:05:57.000Z",
"updatedAt": "2020-08-22T23:11:11.288Z",
"name": "REc557e3c8f2ac42a828973c39f1d6be37",
"description": "",
"publishedUrl": "media/5d03a9d5d4bca272e9c8cf89/audio/1598137553998-rec557e3c8f2ac42a828973c39f1d6be37.mp3",
"mediaId": "81c226f66705",
"originalCreatedAt": "2020-11-08T16:40:10.706Z",
"faceSpriteImg": ""
}
}Status
/media/status/{mediaId}If you need a list or a quick preview of a media you can request meta-data about media objects with this endpoint, this will include all information except for results of analysis, transcripts, etc.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes |
curl -X GET 'https://api.speakai.co/v1/media/status/MEDIA_ID' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 OK
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
data |
object |
data – 14 more fields
| Field | Type | Description |
|---|---|---|
duration |
object | |
duration.inSecond |
string | |
duration.end |
string | |
duration.start |
string | |
tags |
any[] | |
processingProgress |
string | |
state |
string | |
remark |
string | |
_id |
string | |
createdAt |
string (date-time) | |
updatedAt |
string (date-time) | |
name |
string | |
description |
string | |
publishedUrl |
string | |
mediaId |
string | |
originalCreatedAt |
string (date-time) | |
thumbnail |
object | The video’s thumbnail. An empty object when the file has none, which includes every audio file and text note. Use it to follow a frame asked for with PUT /media/{mediaId}/thumbnail: pending is true while the frame is being made, and error is set when it failed, while url still shows the previous thumbnail. |
thumbnail.url |
string | A signed link to the current thumbnail. Present only when the file has one. It expires, so do not store it. |
thumbnail.source |
string | auto for the thumbnail Speak AI made, custom for one you set. Present only when the file has a thumbnail. One of: auto, custom . |
thumbnail.pending |
boolean | Present and true while a requested frame is still being made. |
thumbnail.error |
string | Why the last requested frame was not saved, for example “The frame took too long. Try again.” Present only when it failed. |
Example response (success), application/json.
{
"status": "success",
"data": {
"duration": {
"inSecond": "47",
"end": "0:00:47.744",
"start": "0:00:00"
},
"tags": [],
"processingProgress": "100",
"state": "processed",
"remark": "",
"_id": "5f41a4d5c35d8d0fd556e075",
"createdAt": "2020-08-22T23:05:57.000Z",
"updatedAt": "2020-08-22T23:11:11.288Z",
"name": "REc557e3c8f2ac42a828973c39f1d6be37",
"description": "",
"publishedUrl": "media/5d03a9d5d4bca272e9c8cf89/audio/1598137553998-rec557e3c8f2ac42a828973c39f1d6be37.mp3",
"mediaId": "81c226f66705",
"originalCreatedAt": "2020-11-08T16:40:10.706Z"
}
}Example response (Insights - notUploaded), application/json.
{
"status": 200,
"state": "notUploaded",
"message": {
"insight": {
"isAnalyze": false,
"faces": [],
"keywords": [],
"sentiments": [],
"emotions": [],
"labels": [],
"brands": [],
"topics": [],
"transcript": [],
"speakers": []
},
"duration": {
"inSecond": "0"
},
"createdAt": "2019-09-25T14:32:19.000Z",
"_id": "5d8b7a730838363d90a17e86",
"videoId": "QHw2hPhk9-R7rrlPKBZk-FBY9P8rDBV",
"name": "ForBiggerBlazes - Copy",
"description": "",
"privacyMode": "Public",
"size": "0",
"processingProgress": "0",
"state": "notUploaded",
"publishedUrl": "video/5cc37a170eb2173efcfc1f7d/1569421938203-ForBiggerBlazesCopymp4.mp4",
"videoFormat": 1,
"sourceLanguage": "en-US",
"order": 0,
"__v": 0
}
}Transcript
/media/transcript/{mediaId}Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes |
curl -X GET 'https://api.speakai.co/v1/media/transcript/b4994aa1267c' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 OK
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
data |
object |
data – 10 more fields
| Field | Type | Description |
|---|---|---|
folderId |
string | |
mediaType |
string | |
name |
string | |
sourceLanguage |
string | |
insight |
object | |
insight.transcript |
object[] | |
insight.speakers |
object[] | |
duration |
object | |
duration.start |
string | |
duration.end |
string | |
duration.inSecond |
number | |
createdAt |
string (date-time) | |
mediaId |
string | |
mediaUrl |
string | |
transcriptRevision |
integer | Revision of the transcript. Starts at 0 and increases by one on every saved change to the transcript. Send it back as expectedTranscriptRevision when you save the transcript, label a passage or comment on one. |
insight.transcript – 7 more fields
| Field | Type |
|---|---|
id |
integer |
text |
string |
speakerId |
string |
confidence |
integer |
language |
string |
instances |
object[] |
instances[].start |
string |
instances[].end |
string |
instances[].startInSec |
number |
instances[].endInSec |
number |
entities |
object[] |
entities – 6 more fields
| Field | Type |
|---|---|
id |
string |
text |
string |
confidence |
number |
speakerId |
string |
language |
string |
instances |
object |
instances.startInSec |
number |
instances.endInSec |
number |
insight.speakers – 6 more fields
| Field | Type |
|---|---|
id |
integer |
name |
string |
instances |
object[] |
instances[].start |
string |
instances[].end |
string |
instances[].startInSec |
number |
instances[].endInSec |
number |
totalWords |
integer |
duration |
integer |
wpm |
integer |
Example response (Transcript), application/json. Arrays are shortened to one entry and long strings are cut.
{
"status": "success",
"data": {
"folderId": "0648e533f72c",
"mediaType": "audio",
"name": "vatsal",
"sourceLanguage": "en-US",
"insight": {
"transcript": [
{
"id": 0,
"text": "Okay. Let's do the testing for transcription.",
"speakerId": "0",
"confidence": 1,
"language": "en-US",
"instances": [
{
"start": "00:00:00.240",
"end": "00:00:02.994",
"startInSec": 0.24,
"endInSec": 2.9940499999999997
}
],
"entities": [
{
"id": "1",
"text": "Okay.",
"confidence": 0.99782515,
"speakerId": "0",
"language": "en-US",
"instances": {
"startInSec": 0.24,
"endInSec": 0.48
}
}
]
}
],
"speakers": [
{
"id": 0,
"name": "0",
"instances": [
{
"start": "00:00:00.240",
"end": "00:00:02.994",
"startInSec": 0.24,
"endInSec": 2.9940499999999997
}
],
"totalWords": 10,
"duration": 3,
"wpm": 200
}
]
},
"duration": {
"start": "00:00:00.240",
"end": "00:00:12.144",
"inSecond": 12.144
},
"createdAt": "2025-10-17T17:25:57.608Z",
"mediaId": "b4994aa1267c",
"mediaUrl": "MEDIA_URL",
"transcriptRevision": 3
}
}Update Transcript
/media/transcript/{mediaId}Replaces the transcript of a media file with the array you send. Read the transcript first with GET /media/transcript/{mediaId}, change what you need, and send the whole array back.
Avoid overwriting someone else’s save
Send expectedTranscriptRevision with the transcriptRevision you read. If anyone saved the transcript since, the request returns 409 “This transcript changed since you opened it. Reload to keep editing.” and changes nothing: read the transcript again, reapply your change, and save with the new revision. Leave it out and the save always goes through, replacing whatever is there.
A successful save increases the revision by one and returns the new value. Labels and comments on passages are then moved to follow their words in the background.
Request rules
transcriptneeds at least one sentence; an empty transcript cannot be saved.- Sentence
idvalues must be unique when you sendexpectedTranscriptRevision. - Keys that are not listed below return 400.
Errors
400when the body fails validation.404when your company has no media with that id.409whenexpectedTranscriptRevisionis stale.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes | Id of the media file. |
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 |
|---|---|---|
transcript |
object[], required | The full transcript, as an array of sentences. At least one. |
expectedTranscriptRevision |
integer | Optional. The transcriptRevision you read the transcript at. Integer, 0 or more. When it is stale the save returns 409 and nothing is written. |
transcript – 9 more fields
| Field | Type | Description |
|---|---|---|
id |
number, required | Sentence id. Must be unique across the array when you send expectedTranscriptRevision. |
speakerId |
string, required | Speaker id. Trimmed, required. |
speaker |
object | The team member matched to this speaker, when there is one. Every key may be empty or null. |
speaker.userId |
string | Matched user id. |
speaker.name |
string | Matched name. |
speaker.email |
string | Matched email. |
text |
string, required | Sentence text. Trimmed. An empty string is accepted. |
confidence |
number, required | Sentence confidence, as returned by the transcript endpoint. |
language |
string | Language code, as returned by the transcript endpoint. |
score |
object | Sentence sentiment, as returned by the transcript endpoint. Send it back so a save does not drop it. |
score.compound |
number, required | |
score.neg |
number, required | |
score.neu |
number, required | |
score.pos |
number, required | |
instances |
object[] | Timing of the sentence. |
entities |
object[] | Word-level entries, as returned by the transcript endpoint. Not validated further. |
instances – 6 more fields
| Field | Type | Description |
|---|---|---|
start |
string, required | Start time as HH:MM:SS.mmm. Required. |
end |
string, required | End time as HH:MM:SS.mmm. Required. |
startInSec |
number | |
endInSec |
number | |
adjustedStart |
string | Adjusted start time. |
adjustedEnd |
string | Adjusted end time. |
curl -X PUT 'https://api.speakai.co/v1/media/transcript/b4994aa1267c' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '{
"transcript": [
{
"id": 0,
"text": "Okay. Let'\''s do the testing for transcription.",
"speakerId": "0",
"confidence": 1,
"language": "en-US",
"instances": [
{
"start": "00:00:00.240",
"end": "00:00:02.994",
"startInSec": 0.24,
"endInSec": 2.994
}
],
"entities": []
}
],
"expectedTranscriptRevision": 3
}'200 Success
Response body, application/json.
| Field | Type | Description |
|---|---|---|
status |
string | success. One of: success . |
message |
string | Updated successfully. |
data |
object | |
data.transcriptRevision |
integer | The transcript revision after this save. |
Example response (Success), application/json.
{
"status": "success",
"message": "Updated successfully.",
"data": {
"transcriptRevision": 4
}
}400 The body 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 failed validation), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 400,
"message": "\"transcript\" must contain at least 1 items",
"hints": [
"The API request is invalid or improperly formed."
]
}404 No media 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 media with that id in your company), application/json.
{
"status": "failed",
"requestId": "req_0000000000000000",
"code": 404,
"message": "Media details 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."
]
}Source transcript of a translated file
/media/transcript/{mediaId}/sourceReturns the transcript of the original source-language file behind a translated one, so you can read a translation against the text it came from.
Pass the media id of the translated file. Speak AI resolves the original from the translation record, either as the separate media the two-step flow keeps or, when the translation replaced the text in place, by rebuilding the original from the words the transcription engine stored.
When the original cannot be shown, the request still answers 200. Read data.available first: false carries a reason and nothing else, and true carries the transcript. There is no 404 for an unavailable original.
The sentence shape matches GET /media/transcript/{mediaId}. The rebuilt in-place original omits the per-sentence entities array; the two-step original carries whatever the source media holds. confidence on every sentence is the confidence the transcription engine reported for the original, not a measure of the translation.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaId |
path | string | Yes | The id of the translated media. Sending the id of the original returns available false with reason no-link. |
curl -X GET 'https://api.speakai.co/v1/media/transcript/b4994aa1267c/source' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 OK
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
data |
object |
data – 9 more fields
| Field | Type | Description |
|---|---|---|
available |
boolean | Whether the original transcript can be shown. Everything below is present only when this is true. |
reason |
string | Why the original cannot be shown. Present only when available is false. no-link: this file is not a translation, or its translation has not finished. original-not-retained: a text note, whose original text the translation overwrote. source-deleted: the original media was deleted. source-missing: the original is not in your workspace, or your folder access does not reach it. source-not-ready: the original is still processing. source-has-no-transcript: the original holds no transcript to return. One of: no-link, original-not-retained, source-deleted, source-missing, source-not-ready, source-has-no-transcript . |
sourceMediaId |
string | The id of the original media. On an in-place translation this is the same id you asked for, because one media holds both versions. |
sourceLanguage |
string | The language the original was transcribed in. It can be auto on a file uploaded without a declared language. |
mediaType |
string | |
name |
string | |
duration |
object | |
duration.start |
string | |
duration.end |
string | |
duration.inSecond |
number | |
createdAt |
string (date-time) | |
insight |
object | |
insight.transcript |
object[] | |
insight.speakers |
object[] |
insight.transcript – 6 more fields
| Field | Type |
|---|---|
id |
integer |
text |
string |
speakerId |
string |
confidence |
number |
language |
string |
instances |
object[] |
instances[].start |
string |
instances[].end |
string |
instances[].startInSec |
number |
instances[].endInSec |
number |
insight.speakers – 2 more fields
| Field | Type |
|---|---|
id |
integer |
name |
string |
Example response (The original is available), application/json.
{
"status": "success",
"data": {
"available": true,
"sourceMediaId": "0648e533f72c",
"sourceLanguage": "en-US",
"mediaType": "audio",
"name": "Kickoff call",
"duration": {
"start": "00:00:00.000",
"end": "00:00:12.480",
"inSecond": 12.48
},
"createdAt": "2026-08-20T10:14:52.117Z",
"insight": {
"transcript": [
{
"id": 0,
"text": "Okay. Let's do the testing for transcription.",
"speakerId": "0",
"confidence": 0.98,
"language": "en-US",
"instances": [
{
"start": "00:00:00.240",
"end": "00:00:02.994",
"startInSec": 0.24,
"endInSec": 2.994
}
]
}
],
"speakers": [
{
"id": 0,
"name": "Speaker 1"
}
]
}
}
}Example response (The original cannot be shown), application/json.
{
"status": "success",
"data": {
"available": false,
"reason": "source-not-ready"
}
}Upload your file with the Signed Url or External URL
/media/uploadIf you want to add a hosted media (you must have a valid URL) to your media library, do it here.
Parameters
Please must specify mediaType as audio or video
name: String required
description: string;
url: string required; // public URL or AWS signed URL
mediaType: 'audio'; // 'video'
userId: ""; // for enterprise users only (optional)
medicalId: "" // custom media Id or medical Id for your reference (optional)
createdAt: "2021-08-09T00:13:11.717Z" // must match the date format (optional)
tags: "comma separated strings",
folderId: "folder Id" // to save in the folder
fields: { id: string, value: string } = []; (Optional)Webhook Callback URL:
-
If you created a webhook for media events and want to receive a callback to a specific URL for this text note, you can assign
callbackUrl-stringin areq.body. -
Speak will consider the
callbackUrland will skip the callbackUrl created via Webhook API.
Fields:
You can pass the id and value in the Fields array to include related to your media files.
Upload Considerations:
-
Maximum duration limit of 3 hours for a media URL.
-
The URL needs to be accessible publicly. For Example - Google Drive, Dropbox are not supported.
-
Valid YouTube URL examples are:
https://www.youtube.com/watch?v=pTJ-yvNWCUE. -
Supported File Formats are:
-
Audio -
mp3 (recommended), m4a, wav, ogg, webm, m4p -
Video -
mp4 (recommended), m4v, wmv, avi, mov, flv
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 | The title you want the uploaded file to have in Speak. Required. Trimmed. No maximum length and no pattern. |
description |
string | A short description stored alongside the file. Optional. Trimmed. An empty string is allowed. The schema declares a default of an empty string, but the validation middleware discards the schema defaults, so an omitted value simply stays unset. |
url |
string, required | The link Speak fetches the file from. YouTube, Google Drive, Amazon S3 and the social platforms the resolver supports (TikTok, Instagram, X, Facebook, Reddit, SoundCloud, Twitch, Dailymotion, Streamable, Snapchat, Pinterest, Tumblr, Bilibili, VK, OK.ru and Rutube) are all handled. Vimeo and Loom page links are not resolved here: import Vimeo through the connected Vimeo integration instead. Required. Plain string with no uri format check, so the server does not reject a malformed link at validation time. |
mediaType |
string | The kind of file you are uploading, and the field that decides how it is stored and what can be run on it later. Send it whenever you know which you want, including for a page link: asking for audio on a YouTube link imports the audio track only. Leave it out when you do not know, and the server works it out from better evidence than you have: it inspects the file for a direct URL, and for a page link the resolver returns the best track the platform offers, which can still be audio when no video rendition fits the size limit. Either way the stored type follows what was actually fetched. It has no default, so leaving it out is a real choice rather than a value invented on your behalf, and the only fallback is audio when nothing else can be determined. Do not send a value guessed from the file extension. A value you send is kept, which means it stops the server inspecting the file, and a video stored as audio can never be analysed as video afterwards. Lowercased before validation, so uppercase values such as VIDEO are accepted. ‘all’ is a filter value that leaked into this schema: it validates but is not a real upload type. One of: audio, video, text, media, csv, all . |
sourceLanguage |
string | The language spoken in the file, given as a transcription language code such as en-US. Optional. Any string passes, because the validator substitutes rather than rejects. An unrecognized code becomes auto. The schema declares a default of ‘en-US’, but that default is discarded by the middleware, so if you omit it the server uses the source language on your account onboarding and falls back to auto. Defined. |
callbackUrl |
string | A URL you want Speak to call when the file finishes processing. Optional. Trimmed. An empty string is allowed. Stored on the media as webhookMetaData.callbackUrl. No uri format check. |
tags |
string | Tags to attach to the file, as a single comma separated string. Optional. Declared as a union, the schema) and allows null and an empty string, so an array also passes validation. The controller only reads a string and splits it on commas, so an array is accepted and then ignored. |
createdAt |
string | The creation timestamp to store on the file instead of the time of upload. Optional. An empty string is allowed. Parsed with new Date, so send an ISO 8601 string. |
isTranscriptionOnly |
boolean | Not used by this endpoint. Nothing in the upload flow reads it. Optional. Declared with a default of false, but that default is discarded and the value is never destructured or read by the controller. |
folderId |
string | The folder you want the file to land in. If no folder with that id exists on your account, the server puts the file in your first folder. Optional. An empty string is allowed. |
fields |
object[] | Custom field values to attach to the file. Each entry pairs the id of a field you created in Speak with the value you want stored. Optional. An unconstrained array, so validation does not check the item shape and does not cap the length. The controller expects objects with id and value, checks each id against your account fields, and silently drops the ones that do not match (checkForValidFields ends with.filter(Boolean)). |
curl -X POST 'https://api.speakai.co/v1/media/upload' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000' \
-H 'Content-Type: application/json' \
-d '{
"name": "Vatsal Test 1",
"description": "Vatsal Description",
"url": "https://kns.im/msgs/files/32dc84df-9650-4c17-b633-e80a5097ba6f.ogg",
"mediaType": "video",
"sourceLanguage": "he-IL",
"tags": "your custom tags1, tag 2, tag 3",
"fields": [
{
"id": "FIELD_ID_CREATED_BY_SPEAK",
"value": "YOUR_VALUE"
}
]
}'200 OK
Response body, application/json.
| Field | Type |
|---|---|
status |
string |
data |
object |
data.mediaId |
string |
data.state |
string |
Example response (success), application/json.
{
"status": "success",
"data": {
"mediaId": "86a36b59b38e",
"state": "uploaded"
}
}Get a Signed Url
/media/upload/signedurlIf you have a locally saved file and you want to upload it to our system using API, you’ll have to follow through 3 simple steps:
Process
-
Run this endpoint to retrieve an upload link for a placeholder on our storage bucket. You’ll have
preSignedUrl. -
This
preSignedUrlis your ticket to which youPUTyour local file usingcurlor any other HTTP client. -
Use “Upload your file with the Signed Url or External URL” to upload your file on Speak.
How does this work?
You can find more information about uploading a file using signed URLs here. If you still find this unenlightening, shoot a message to us on success@speakai.co!
Expiry
The link will expire in 2 hours.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mediaType |
query | string | Yes | The kind of file you are uploading. It decides the storage prefix, so it has to match the real file: a video signed as audio is stored as audio and can never be analysed as video. Required. The prefix is chosen before any bytes exist, so the server cannot work it out for you: omit it and the request is rejected. Nothing is read from the extension on filename. |
filename |
query | string | Yes | Filename with the extension is required |
mimeType |
query | string | No | For example audio/mpeg, audio/mp4, audio/wav or video/mp4 |
curl -X GET 'https://api.speakai.co/v1/media/upload/signedurl?mediaType=audio&filename=media_name.mp3&mimeType=audio%2Fmpeg' \
-H 'x-speakai-key: sk_test_speak_0000000000000000' \
-H 'x-access-token: eyJhbGciOiJIUzI1NiJ9.test-access-token.0000000000'200 Success
The spec records this status code with no example body.
Related pages
- API reference for the base URL, authentication, and the error format.
- Authenticate with the Speak AI API using access tokens
- Create and update live transcription sessions
- Analyze text notes with the Speak AI text endpoints
- Export Speak AI transcripts and insights to a file
Get an API key on the Speak AI developer page.