POST /api/v1/lessons/{lessonId}/video-upload
Prepares a hosted video for one lesson, in one call: the video is created on
Bunny Stream, it enters the media library as processing, and its player
address is set as the lesson's video right away. You then send the file
yourself, straight to the video host, with the signed credentials of the
response — the file never goes through Cohorte.
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed |
ADMIN | Allowed |
MEMBER | Refused — 403 |
| Requirement | Rule for this endpoint |
|---|---|
| Business role | None — the permission is that of the lesson |
| Resource scope | The lesson belongs to the key's organization |
| Participation | None |
| Resource state | The lesson's program and course are not archived |
| Parameter | Type | Required | Description |
|---|---|---|---|
lessonId | UUID | yes | Lesson of the key's organization |
| Header | Required | Description |
|---|---|---|
x-api-key | yes | API key created for this organization |
Idempotency-Key | no | Accepted, but there is no replay: every call opens a new video |
X-Correlation-Id | no | Caller correlation id, returned or generated |
| Field | Type | Required | Nullable | Description |
|---|---|---|---|---|
title | string | no | no | Title in the media library. Defaults to the lesson title |
mimeType | string | no | no | For example video/mp4, recorded in the library |
sizeBytes | integer | no | no | File size, recorded in the library |
# 1. Open the upload — the lesson now points at the new video.
curl -X POST "$COHORTE_API_BASE_URL/api/v1/lessons/$LESSON_ID/video-upload" \
-H "x-api-key: $COHORTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title": "Welcome", "mimeType": "video/mp4"}'
# 2. Send the file with any TUS client, using the four signed headers.
# Metadata values are base64-encoded, as the TUS protocol requires.
curl -X POST "$UPLOAD_ENDPOINT" \
-H "Tus-Resumable: 1.0.0" \
-H "Upload-Length: $(wc -c < welcome.mp4 | tr -d ' ')" \
-H "Upload-Metadata: filetype $(printf video/mp4 | base64),title $(printf Welcome | base64)" \
-H "AuthorizationSignature: $SIGNATURE" \
-H "AuthorizationExpire: $EXPIRES" \
-H "VideoId: $VIDEO_ID" \
-H "LibraryId: $LIBRARY_ID" \
-i
# then PATCH the returned Location with the file bytes (Upload-Offset: 0).201 Created — the upload is open and the lesson points at the video.400 Bad Request — invalid JSON, or video hosting is not configured for this platform.401 Unauthorized — missing, unknown or expired key.403 Forbidden — the bearer is not OWNER or ADMIN, or the lesson's program or course is archived.404 Not Found — the lesson is unknown or belongs to another school. No video is created.| Field | Type | Description |
|---|---|---|
data.mediaId | UUID | The video in the media library |
data.lessonId | UUID | The lesson it is attached to |
data.embedUrl | string | Player address, now the lesson's video |
data.upload.endpoint | string | TUS endpoint to send the file to |
data.upload.libraryId | string | Value of the LibraryId header |
data.upload.videoId | string | Value of the VideoId header |
data.upload.expires | integer | Value of the AuthorizationExpire header — Unix seconds, one hour ahead |
data.upload.signature | string | Value of the AuthorizationSignature header |
{
"success": true,
"data": {
"mediaId": "8c9d0e1f-2a3b-4c4d-8e5f-6a7b8c9d0e1f",
"lessonId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"upload": {
"endpoint": "https://video.bunnycdn.com/tusupload",
"libraryId": "123456",
"videoId": "0f1e2d3c-4b5a-6978-8a9b-0c1d2e3f4a5b",
"expires": 1759312800,
"signature": "5e3b…"
},
"embedUrl": "https://iframe.mediadelivery.net/embed/123456/0f1e2d3c-4b5a-6978-8a9b-0c1d2e3f4a5b"
},
"correlationId": "c0ffee00-1111-4222-8333-444455556666"
}POST /api/v1/lessons/{lessonId}/video-upload — keep upload and mediaId.upload.endpoint with a TUS client and the four signed headers, before upload.expires.?kind=video until the media with mediaId is ready.