Videos
Videos are file-based widgets that display and play back video content on a Canvus canvas. Use videos for presentations, recorded demos, tutorials, or any motion content that needs to live alongside other canvas widgets.
Video files are uploaded via multipart form data and can be downloaded through a dedicated endpoint. The API provides full playback control including play, pause, stop, and seek, as well as mute/unmute. The server also exposes the video duration once a Canvus desktop client has decoded the file.
Authentication
All endpoints require authentication via the Private-Token header or a CanvusSession cookie, unless the canvas has link sharing enabled (View or Edit permission). Write operations (POST, PATCH, DELETE) require edit access. View-only users receive 403 Forbidden on any write attempt.
Common widget properties
Every video shares these properties with all other canvas widget types.
id-- string (uuid, read-only). Unique identifier assigned by the server on creation.widget_type-- string (read-only). Always"Video"for this endpoint.location-- object (patchable). Position relative to the parent widget, withxandyfloat fields.size-- object (patchable). Dimensions in canvas units, withwidthandheightfloat fields. Aspect ratio is enforced on PATCH -- the requested size is treated as a bounding box and the widget is fit within it, keeping the widget's current width-to-height ratio. The response contains the actual applied dimensions. On create,sizeis stored as given.depth-- float (patchable). Z-order among sibling widgets. Higher values render on top. Must be >= 1.0 -- the server returns 400 Bad Request if you set depth below 1.0.scale-- float (patchable). Uniform scale factor applied to the widget. Default:1.pinned-- boolean (patchable). Whentrue, the widget cannot be moved or resized through touch interaction. API changes still apply. Default:false.state-- string (read-only). Widget state, typically"normal".parent_id-- string (uuid, patchable). ID of the parent widget. Changing this reparents the widget. Note thatlocationcoordinates are always relative to the parent -- changingparent_idwithout updatinglocationwill reinterpret the same x/y values in the new parent's coordinate space.auto_raise-- boolean (patchable, PATCH only). Whentrue, the server sets the widget's depth to the highest sibling depth + 1.0, bringing it to the front. Not persisted -- this is a one-shot parameter. Default:false.
Video-specific properties
title-- string (patchable). Display title for the video widget. Default:"".original_filename-- string (patchable). The original filename of the uploaded video file. Default:"".hash-- string (read-only). Server-generated content hash of the video file. Empty string immediately after creation while the file is being processed.playback_state-- string (patchable). Current playback state. One of:"PLAYING","PAUSED","STOPPED". Default:"STOPPED". Values are case-sensitive: send them in uppercase, as they are returned. Any other value is ignored.playback_position-- float (patchable). Current playback position in seconds. Default:0. When you setplayback_stateto"PAUSED"without providingplayback_position, the server automatically computes and preserves the current position based on elapsed time since playback started.muted-- boolean (patchable). Whentrue, video audio is silenced. Default:false.duration-- float (read-only). Total duration of the video in seconds. This value is0.0until a Canvus desktop client decodes the video file and reports its duration. Do not set this via the API.
Streaming
All GET endpoints support real-time updates via the ?subscribe query parameter. When present, the server holds the connection open and sends newline-delimited JSON whenever the resource changes. The server sends periodic keepalive pings to maintain the connection.
List videos
Retrieves all video widgets on a canvas.
GET /api/v1/canvases/:canvas_id/videos
Path parameters:
canvas_id(uuid, required) -- ID of the canvas
Query parameters:
subscribe(flag, optional) -- Enable streaming updates. The parameter takes no value; its presence alone turns streaming on, so?subscribe=falsealso streams.
Example request:
curl -H "Private-Token: YOUR_TOKEN" \
https://canvus.example.com/api/v1/canvases/a8b5ed9e-6e57-41c8-96cb-be4aec4bfd03/videos
Example response:
[
{
"depth": 4.0,
"duration": 127.5,
"hash": "863b0937ee95",
"id": "105a8486-ac84-49f9-a487-763279a6f658",
"location": {
"x": 3737.21,
"y": 2311.91
},
"muted": false,
"original_filename": "product-demo.mp4",
"parent_id": "34f8909a-6736-49f9-9027-7f7cab5c1acf",
"pinned": false,
"playback_position": 0,
"playback_state": "STOPPED",
"scale": 1,
"size": {
"height": 720,
"width": 1280
},
"state": "normal",
"title": "Product Demo",
"widget_type": "Video"
}
]
Get single video
Retrieves a single video widget by ID.
GET /api/v1/canvases/:canvas_id/videos/:video_id
Path parameters:
canvas_id(uuid, required) -- ID of the canvasvideo_id(uuid, required) -- ID of the video
Query parameters:
subscribe(flag, optional) -- Enable streaming updates. The parameter takes no value; its presence alone turns streaming on, so?subscribe=falsealso streams.
Example request:
curl -H "Private-Token: YOUR_TOKEN" \
https://canvus.example.com/api/v1/canvases/a8b5ed9e-6e57-41c8-96cb-be4aec4bfd03/videos/105a8486-ac84-49f9-a487-763279a6f658
Example response:
{
"depth": 4.0,
"duration": 127.5,
"hash": "863b0937ee95",
"id": "105a8486-ac84-49f9-a487-763279a6f658",
"location": {
"x": 3737.21,
"y": 2311.91
},
"muted": false,
"original_filename": "product-demo.mp4",
"parent_id": "34f8909a-6736-49f9-9027-7f7cab5c1acf",
"pinned": false,
"playback_position": 0,
"playback_state": "STOPPED",
"scale": 1,
"size": {
"height": 720,
"width": 1280
},
"state": "normal",
"title": "Product Demo",
"widget_type": "Video"
}
Create video
Creates a new video widget by uploading a file. The request must be multipart/form-data with a mandatory data part (the video file) and an optional json part (widget metadata).
POST /api/v1/canvases/:canvas_id/videos
Path parameters:
canvas_id(uuid, required) -- ID of the canvas
Multipart form parts:
data(file, required) -- The video file to upload (MP4, WebM, MOV, etc.)json(string, optional) -- JSON string with widget properties
JSON properties (all optional):
title-- stringoriginal_filename-- stringlocation-- object withxandyfloatssize-- object withwidthandheightfloatsdepth-- float (must be >= 1.0)scale-- floatpinned-- booleanparent_id-- string (uuid)playback_state-- string ("STOPPED","PLAYING", or"PAUSED", uppercase)playback_position-- float (seconds)muted-- boolean
Current behavior
- Create does not yet read
muted; a new video is always unmuted. Set it with an update after creating. - Create does not yet read
playback_position; it readsseek_positioninstead.
These are being corrected.
Example request:
curl -X POST \
-H "Private-Token: YOUR_TOKEN" \
-F 'json={"title": "Product Demo", "muted": true}' \
-F 'data=@product-demo.mp4' \
https://canvus.example.com/api/v1/canvases/a8b5ed9e-6e57-41c8-96cb-be4aec4bfd03/videos
Example response:
{
"depth": 1.0,
"duration": 0.0,
"hash": "",
"id": "370b112b-c6e9-440f-bdad-f6d6cf6a6d9f",
"location": {
"x": 5148.75,
"y": 2807.72
},
"muted": true,
"original_filename": "product-demo.mp4",
"parent_id": "34f8909a-6736-49f9-9027-7f7cab5c1acf",
"pinned": false,
"playback_position": 0,
"playback_state": "STOPPED",
"scale": 1,
"size": {
"height": 720,
"width": 1280
},
"state": "normal",
"title": "Product Demo",
"widget_type": "Video"
}
The hash field is empty immediately after creation and the duration is 0.0. Both are populated once the file is processed (hash) and decoded by a desktop client (duration). Subscribe to the widget or poll with GET to see updated values.
Update video
Updates one or more properties of an existing video widget. Only the fields included in the request body are changed -- all other properties remain untouched.
PATCH /api/v1/canvases/:canvas_id/videos/:video_id
Path parameters:
canvas_id(uuid, required) -- ID of the canvasvideo_id(uuid, required) -- ID of the video to update
Request body (JSON):
Any combination of patchable properties: title, original_filename, location, size, depth, scale, pinned, parent_id, auto_raise, playback_state, playback_position, muted.
Example request -- start playback:
curl -X PATCH \
-H "Private-Token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"playback_state": "PLAYING"}' \
https://canvus.example.com/api/v1/canvases/a8b5ed9e-6e57-41c8-96cb-be4aec4bfd03/videos/105a8486-ac84-49f9-a487-763279a6f658
Example request -- pause (position preserved automatically):
curl -X PATCH \
-H "Private-Token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"playback_state": "PAUSED"}' \
https://canvus.example.com/api/v1/canvases/a8b5ed9e-6e57-41c8-96cb-be4aec4bfd03/videos/105a8486-ac84-49f9-a487-763279a6f658
When pausing without providing playback_position, the server computes the current position from the seek position plus elapsed time since playback started. There is no need to calculate and send the position yourself.
Current behavior
The computed position is currently only reliable if playback was last started with an explicit playback_position. If you started playback without one, send playback_position when pausing. This is being corrected.
Example request -- seek to specific position and play:
curl -X PATCH \
-H "Private-Token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"playback_state": "PLAYING", "playback_position": 45.0}' \
https://canvus.example.com/api/v1/canvases/a8b5ed9e-6e57-41c8-96cb-be4aec4bfd03/videos/105a8486-ac84-49f9-a487-763279a6f658
Example request -- mute:
curl -X PATCH \
-H "Private-Token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"muted": true}' \
https://canvus.example.com/api/v1/canvases/a8b5ed9e-6e57-41c8-96cb-be4aec4bfd03/videos/105a8486-ac84-49f9-a487-763279a6f658
Example request -- resize (aspect ratio enforced):
curl -X PATCH \
-H "Private-Token: YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"size": {"width": 640, "height": 640}}' \
https://canvus.example.com/api/v1/canvases/a8b5ed9e-6e57-41c8-96cb-be4aec4bfd03/videos/105a8486-ac84-49f9-a487-763279a6f658
The server keeps the widget's current aspect ratio. The requested size is treated as a bounding box. If you request 640x640 for a widget currently sized at 16:9, the response will contain {"width": 640, "height": 360}. Always read the response body to get the actual applied size.
Example response:
{
"depth": 4.0,
"duration": 127.5,
"hash": "863b0937ee95",
"id": "105a8486-ac84-49f9-a487-763279a6f658",
"location": {
"x": 3737.21,
"y": 2311.91
},
"muted": true,
"original_filename": "product-demo.mp4",
"parent_id": "34f8909a-6736-49f9-9027-7f7cab5c1acf",
"pinned": false,
"playback_position": 45.0,
"playback_state": "PLAYING",
"scale": 1,
"size": {
"height": 360,
"width": 640
},
"state": "normal",
"title": "Product Demo",
"widget_type": "Video"
}
Validation errors:
- Setting
depthbelow 1.0 returns 400 Bad Request:Depth must be >= 1, got <value> - Setting
sizewith zero or negative dimensions is rejected
Download video
Downloads the original video file as a binary attachment.
GET /api/v1/canvases/:canvas_id/videos/:video_id/download
Path parameters:
canvas_id(uuid, required) -- ID of the canvasvideo_id(uuid, required) -- ID of the video to download
Example request:
curl -H "Private-Token: YOUR_TOKEN" \
-o downloaded-video.mp4 \
https://canvus.example.com/api/v1/canvases/a8b5ed9e-6e57-41c8-96cb-be4aec4bfd03/videos/105a8486-ac84-49f9-a487-763279a6f658/download
The response includes a content-disposition: attachment header with the original filename.
Current behavior
The filename comes from the widget's original_filename. The server does not yet take it from the uploaded file's name, so send original_filename when you create the widget; otherwise the download uses an internal name. This is being corrected.
Delete video
Permanently removes a video widget from the canvas. The underlying file is deleted from server storage later, once no canvas references it.
DELETE /api/v1/canvases/:canvas_id/videos/:video_id
Path parameters:
canvas_id(uuid, required) -- ID of the canvasvideo_id(uuid, required) -- ID of the video to delete
Example request:
curl -X DELETE \
-H "Private-Token: YOUR_TOKEN" \
https://canvus.example.com/api/v1/canvases/a8b5ed9e-6e57-41c8-96cb-be4aec4bfd03/videos/105a8486-ac84-49f9-a487-763279a6f658
A successful deletion returns an empty response with status 200 OK.
Error responses
All endpoints return errors as JSON with a msg field.
- 400 Bad Request -- Invalid input (e.g. depth below 1.0, missing file in upload, zero-size dimensions)
- 401 Unauthorized -- Missing or invalid authentication token
- 403 Forbidden -- View-only user attempted a write operation
- 404 Not Found -- Canvas or video does not exist
Current behavior
- An invalid or expired token returns
403 Forbidden, not401. See Current behavior. - Create does not yet enforce the
depthlimit. A widget created withoutdepthgets0, and a value below 1.0 is accepted. Only update rejects it. - A zero or negative
sizeis not rejected. Create stores it; update ignores it and still returns200 OK. - An update rejected with
400 Bad Requestcan still apply other fields sent in the same request.
These are being corrected.
Example error response:
{"msg": "Depth must be >= 1, got 0"}