Uploading videos
Uploading needs a token granted the content:write scope, which a personal
access token does not currently hold, so this is an OAuth path today. The same
scope covers creating documents and adding YouTube videos;
announcements have a scope of their own.
The file never passes through us. You ask for a presigned storage URL, upload the bytes to it directly, then tell us the upload landed.
Published, or waiting for review
Section titled “Published, or waiting for review”Whether an uploaded video goes into the library or waits for an administrator
is decided for you, from what the person behind the token may do and the
audience you asked for. Every response says which happened, in mode:
mode |
What it means |
|---|---|
published |
The video goes into the library once it has processed. |
review |
The video waits for an administrator to approve it, and is not visible to learners until then. |
Someone who may publish to the audience they named gets published. Anyone else
who is allowed to submit content gets review, including a team admin asking
for a wider audience than their teams. The audience you send is not stored on a
video that goes to review: whoever approves it chooses one. A video waiting for
review is still fully processed, so the reviewer can watch it, and the person
who started the upload can finish it. Nobody else can.
Show the difference to whoever uploaded it. “Your video is live” and “your video is with a reviewer” are not the same message.
Not uploading the same video twice
Section titled “Not uploading the same video twice”Add a sourceKey to the start of an upload (single or multipart): any string
you choose that names what the video was generated from, up to 200 characters.
The first upload under a key records it on the video. Sending the same key again
does not replace that video. A video’s file cannot be swapped without
viewers seeing the new one before anyone has approved it, so a repeat is refused
with 409 and SOURCE_KEY_UPDATE_UNSUPPORTED, and nothing is created. To make
a separate video from the same source, use a new key. Guides work differently:
uploading a guide again under the same key does replace it.
The start response includes updatesExisting, which is always false for a
video, so a client written for guides keeps working.
Two things about a key are the same as for guides:
- It is unique per organization, so two uploads racing to create the same key produce one video and one refusal, never two videos.
- When you cannot publish, the upload is a proposal for an administrator.
Uploading again under the same key while your own proposal is still pending
replaces it with a fresh one, and the response says
replacedPending: true. The earlier proposal is withdrawn only once the new one exists, and an administrator’s decision made in the meantime stands. Send"stack": trueto file another beside it, which carries no key. Someone else’s pending proposal is never touched: you get409andSOURCE_KEY_AWAITING_REVIEW. If an administrator rejects a first proposal, the key is released.
| Code | Meaning |
|---|---|
SOURCE_KEY_UPDATE_UNSUPPORTED |
A video already exists under this key and videos cannot be replaced yet. |
SOURCE_KEY_BUSY |
The video is still being processed, or another upload for it is in progress. Try again in a few minutes. An upload that was started and abandoned holds the key for up to four hours. |
SOURCE_KEY_AWAITING_REVIEW |
A proposal for this source is waiting in the review queue. It has to be approved or rejected first. |
Choosing single or multipart
Section titled “Choosing single or multipart”Under roughly 100 MB, use the single-request flow: one presigned URL, one PUT.
Above that, use multipart. It costs an extra call but each part can be retried on its own, so a dropped connection near the end of a large file does not mean starting over.
Single-request upload
Section titled “Single-request upload”Start the upload
Section titled “Start the upload”POST /v1/videos/upload/init
Creates the video and returns a URL to PUT the file to.
{ "filename": "safety-walkthrough.mp4", "contentType": "video/mp4", "public": false, "teamIds": []}public and teamIds are required — the video’s audience is decided when
it is created, not afterwards, so there is no default to fall back on.
public: trueshares the video with everyone in the organization. Only a caller with organization-wide create permission may do this.public: falsewith one or moreteamIdsshares it with those teams. A caller whose permission is team-scoped must name at least one team they belong to, or the request is refused.
The response carries the video’s id, the URL, and when the URL stops working:
{ "videoId": "…", "mode": "review", "uploadUrl": "https://…", "method": "PUT", "expiresIn": 3600, "expiresAt": "2026-09-01T13:00:00.000Z", "requiredHeaders": { "Content-Type": "video/mp4" }}Send requiredHeaders verbatim on the PUT when it is present. Storage rejects
the upload if the content type does not match what the URL was signed for.
Finish the upload
Section titled “Finish the upload”POST /v1/videos/{id}/upload/complete
Call this once the PUT has succeeded. We check the object actually arrived and then begin processing — transcription, chaptering, and the rest.
{ "id": "…", "title": "safety-walkthrough", "status": "PROCESSING", "mode": "published", "url": "https://mereon.ai/videos/…/view"}The video is not watchable when this returns. status reflects that, and
the title is seeded from the filename until processing produces a better one.
url is the video’s page in the web app, so you can hand it to someone; it will
say the video is processing for a few minutes. It is present only when mode is
published: a video waiting for review is not visible to learners, so there is
nothing to link to. The multipart flow returns the same shape when it completes.
An empty or missing object answers 400: the upload genuinely did not land, so
retry the PUT. A storage failure on our side answers 503 — retry this call,
not the upload.
Multipart upload
Section titled “Multipart upload”Start the upload
Section titled “Start the upload”POST /v1/videos/upload/multipart/init
Same body as the single-request flow plus fileSize in bytes, which we use to
decide how the file is divided.
The response tells you the part size and gives a presigned URL per part. Part
numbers start at 1. Upload each part and keep the ETag storage returns for it
— completing the upload requires all of them.
Part size grows rather than part count when a file is large enough to exceed the storage part limit, so do not assume a fixed size.
Refresh expired part URLs
Section titled “Refresh expired part URLs”POST /v1/videos/{id}/upload/multipart/urls
Part URLs expire. Send the part numbers still outstanding and you get fresh URLs for those, rather than starting the upload again.
{ "partNumbers": [7, 8, 9] }Finish the upload
Section titled “Finish the upload”POST /v1/videos/{id}/upload/multipart/complete
Send every part with its ETag:
{ "parts": [{ "partNumber": 1, "eTag": "\"abc…\"" }] }The parts are assembled into one object, verified, and processing begins. The response matches the single-request flow.
Abandon the upload
Section titled “Abandon the upload”DELETE /v1/videos/{id}/upload/multipart
Cancels an upload that is still in progress and removes the placeholder video.
This only applies to an upload in flight. A video whose upload has already
completed is refused with 400 — deleting finished content is not part of this
scope, and is not something the upload endpoints can do.
What can go wrong
Section titled “What can go wrong”| Status | Meaning |
|---|---|
400 |
The body is invalid, the file type is not a video, the audience is not resolvable, or the upload has not landed. |
403 |
The token lacks content:write, or its user can neither add videos nor submit them for review in this organization. |
409 |
A sourceKey you sent is already in use. The error code says how: see above. |
404 |
The video does not exist, or belongs to somewhere the caller cannot see, or is someone else’s upload that is still waiting for review. The cases are deliberately indistinguishable. |
503 |
Storage could not be reached to verify the upload. Retry the call. |