videos:write, and the status poll needs videos:read.
Send it as Authorization: Bearer tplt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (or the
X-API-Key header). See Authentication.
The whole flow
1
Create the video
curl
201 Created
folder_id is optional and must be a folder in your workspace (see
Folders). plain_mode is optional: true makes the video a silent looping
clip with nothing on top of it (see Plain mode).2
Send the file
Under 100 MB, one multipart request is enough:Anything larger, or anything sent over a connection you do not trust, goes through the
resumable protocol below. Both answer
curl
202 Accepted with the
status object.3
Poll the status
curl
ready within one to three
minutes; an hour-long source can take 20 minutes or more.4
Paste the embed
When
status is ready, the response carries embed.html (the standard player) and
embed.plain_html (the plain mode snippet). Paste either into your page.The status object
Every upload call andGET /v1/videos/{video}/status return the same shape.
200 OK
string
One of four values.
waiting_for_upload: the video exists and no file has arrived.processing: a file is on its way to the encoder or being encoded.ready: every orientation you uploaded has finished encoding.embedis filled in.error: an orientation failed. Itserrorsays why, in a sentence you can show a person.
object
landscape and portrait, each null until a file is sent for it, then
{ status, progress, error }. progress is a percentage and moves in steps, not
smoothly.object | null
null until status is ready, because the player script does not exist before then
and an embed would render an empty box.The player publishes itself within about a minute of
ready. If you load the embed the
very second the status flips and see an empty box, reload once. Nothing is broken.Resumable upload
For large files. Send the file in chunks, so a dropped connection costs one chunk instead of the whole file.1
Init
integer
required
The video’s numeric id, from
POST /v1/videos.string
required
Max 255 characters.
integer
required
The file size in bytes, from 1 up to 5,368,709,120 (5 GB).
string
required
One of
video/mp4, video/quicktime, video/x-m4v, video/x-msvideo,
video/webm, video/ogg, video/3gpp, video/x-matroska.string
default:"landscape"
landscape, or portrait for the mobile version of the same video.curl
201 Created
/complete before expires_at, or the chunks expire
and you start again from init.2
Send chunks
PATCH the raw bytes of each chunk to upload_url, with Content-Type: application/offset+octet-stream and an Upload-Offset header holding the number of
bytes already sent (start at 0). Send chunks of chunk_size (8 MB); the last one is
whatever is left.curl
200 OK
offset as the next chunk’s Upload-Offset.Lost track of where you were (your process crashed)? Ask:curl
200 OK
3
Complete
curl
202 Accepted
mime_type you declared), then hands it to the encoder and fires
the video.uploaded webhook. From here, poll the status.If bytes are missing you get 409 INCOMPLETE with the real offset, and the session
stays open: send the rest and call complete again.Abort
curl
200 OK
{"deleted": true} for a session that already
expired too.
Single-shot upload
integer
required
The video’s numeric id.
file
required
Multipart form field. Same types as the resumable protocol. One request can carry at most
3,000 MB: a larger body is refused with
413 before it reaches TrackPlay.string
default:"landscape"
landscape or portrait.curl
202 Accepted
Replacing the file
Upload to a video that already has a file in that orientation and the new file replaces the old one. The video keeps its id, embed code, settings and analytics. If the running time changed, the transcript is read again from the new file.Folders
File uploads into a folder so you can find them in the dashboard. A folder changes where a video is listed and nothing about how it plays.curl
201 Created
GET /v1/folders (scope videos:read) lists them with a videos_count. Pass parent_id
to nest one folder in another. Move an existing video with
PATCH /v1/videos/{video} and { "folder_id": 42 }.
Limits
Errors
Every error has the shape{"error": {"code": "...", "message": "..."}}. Chunk and
complete errors also carry the server’s real offset, so you always know where to resume.
401: INVALID_TOKEN or AUTH_REQUIRED
401: INVALID_TOKEN or AUTH_REQUIRED
The token is missing, wrong, expired or revoked.
403: INSUFFICIENT_SCOPE
403: INSUFFICIENT_SCOPE
Uploads need
videos:write.404: VIDEO_NOT_FOUND
404: VIDEO_NOT_FOUND
No video with that id in the token’s workspace.
404: UPLOAD_NOT_FOUND
404: UPLOAD_NOT_FOUND
The session does not exist, expired, or belongs to another workspace.
400: OFFSET_REQUIRED, EMPTY_CHUNK, SIZE_EXCEEDED
400: OFFSET_REQUIRED, EMPTY_CHUNK, SIZE_EXCEEDED
A chunk had no
Upload-Offset header, carried zero bytes, or went past the size you
declared at init.409: OFFSET_MISMATCH
409: OFFSET_MISMATCH
Your
Upload-Offset is ahead of what the server has, so a chunk was lost. Resend from
the offset in the response.409: INCOMPLETE
409: INCOMPLETE
Complete was called before every byte arrived. Send the rest from
offset.422: UNSUPPORTED_MEDIA_TYPE
422: UNSUPPORTED_MEDIA_TYPE
The bytes are not a video, whatever the declared type said. The session is closed.
422: validation
422: validation
Laravel’s shape:
{"message": "...", "errors": {"size": ["..."]}}. Usually a file over
5 GB or a type that is not on the list.422: FOLDER_NOT_FOUND
422: FOLDER_NOT_FOUND
The
folder_id is not a folder in your workspace.507: INSUFFICIENT_STORAGE
507: INSUFFICIENT_STORAGE
The server cannot take a file that size right now. Retry later. Nothing was saved.
Status error: the encode failed
Status error: the encode failed
status is error and the orientation’s error explains it, for example a corrupt or
truncated file. Upload a fresh copy to the same video.429
429
Rate limited. Wait for the time in the
Retry-After header.