Uploading a video¶
Videos in PlusPlus are hosted via Mux. The API exposes the upload as a chained flow: create the content item, get a direct upload URL, upload the file, then poll until the asset is ready.
Flow at a glance¶
sequenceDiagram
participant Your app
participant PlusPlus API
participant Mux
Your app->>PlusPlus API: POST /videos
PlusPlus API-->>Your app: 201 { public_id }
Your app->>PlusPlus API: POST /videos/{public_id}/uploads
PlusPlus API-->>Your app: 201 { upload_url }
Your app->>Mux: PUT (binary upload)
Mux-->>Your app: 200
Your app->>PlusPlus API: GET /videos/{public_id}
PlusPlus API-->>Your app: 200 { playback: { ready: true } }
1. Create the video record¶
curl -X POST https://acme.plusplus.app/public_api/v2/videos \
-H "Authorization: Bearer pp_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Onboarding: Day One",
"duration_minutes": 12,
"is_hidden": true
}'
{
"data": {
"public_id": "01HXY8C5FZ7N4S0M2D3J6Q7K9P",
"name": "Onboarding: Day One",
"is_hidden": true,
"playback": null
}
}
Record the public_id — you'll use it for every subsequent step.
2. Request a direct upload URL¶
curl -X POST https://acme.plusplus.app/public_api/v2/videos/01HXY8C5FZ7N4S0M2D3J6Q7K9P/uploads \
-H "Authorization: Bearer pp_your_token_here"
{
"data": {
"upload_id": "upl_abcdef",
"upload_url": "https://storage.googleapis.com/mux-uploads/...",
"expires_at": "2026-04-29T13:00:00Z"
}
}
The upload_url is a single-use, time-limited URL issued by Mux. It does not require the bearer token.
3. Upload the file directly to Mux¶
Use a streaming upload for files larger than a few hundred MB.
4. Wait for the asset to be ready¶
Mux transcoding is asynchronous. Poll GET /videos/{public_id} until playback.ready is true:
curl https://acme.plusplus.app/public_api/v2/videos/01HXY8C5FZ7N4S0M2D3J6Q7K9P \
-H "Authorization: Bearer pp_your_token_here"
{
"data": {
"public_id": "01HXY8C5FZ7N4S0M2D3J6Q7K9P",
"playback": {
"ready": true,
"playback_id": "abc123",
"duration_seconds": 720
}
}
}
Or, subscribe to the video.updated webhook and react when the playback fields appear — no polling needed.
5. Publish¶
When you're satisfied, flip visibility:
curl -X PATCH https://acme.plusplus.app/public_api/v2/videos/01HXY8C5FZ7N4S0M2D3J6Q7K9P \
-H "Authorization: Bearer pp_your_token_here" \
-H "Content-Type: application/json" \
-d '{ "is_hidden": false }'
The video is now visible in the catalog and can be assigned to learners or added to tracks.
Common pitfalls¶
- The upload URL expired. They're valid for ~1 hour. Request a new one with
POST /videos/{public_id}/uploadsand try again. - Polling forever. Long videos take minutes to transcode. Switch to webhooks if your worker can't sit on a poll loop.
- Setting
playbackdirectly. Theplaybackobject is read-only — Mux owns it.