Skip to content

Images

Images are file-based widgets that display raster or vector graphics on a Canvus canvas. Use images to place photos, diagrams, screenshots, or any visual content alongside other canvas widgets.

Image files are uploaded via multipart form data and can be downloaded at any time through a dedicated download endpoint. The server generates a content hash for each uploaded file, which is used internally for deduplication and caching.

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 image 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 "Image" for this endpoint.
  • location -- object (patchable). Position relative to the parent widget, with x and y float fields.
  • size -- object (patchable). Dimensions in canvas units, with width and height float 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, size is 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). When true, 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 that location coordinates are always relative to the parent -- changing parent_id without updating location will reinterpret the same x/y values in the new parent's coordinate space.
  • auto_raise -- boolean (patchable, PATCH only). When true, 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.

Image-specific properties

  • title -- string (patchable). Display title for the image widget. Default: "".
  • original_filename -- string (patchable). The original filename of the uploaded image file. Default: "".
  • hash -- string (read-only). Server-generated content hash of the image file. Empty string immediately after creation while the file is being processed. Do not include this in POST or PATCH requests.

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 images

Retrieves all image widgets on a canvas.

GET /api/v1/canvases/:canvas_id/images

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=false also streams.

Example request:

curl -H "Private-Token: YOUR_TOKEN" \
  https://canvus.example.com/api/v1/canvases/c3d319c2-5eed-456d-acbb-8da46e788157/images

Example response:

[
  {
    "depth": 2.0,
    "hash": "cbcb439a796b",
    "id": "40a30897-57ea-4c35-9bf8-64db7fc710e0",
    "location": {
      "x": 5130.68,
      "y": 2164.98
    },
    "original_filename": "team-photo.jpg",
    "parent_id": "aad4f204-2324-401e-9acf-257d2dad4d99",
    "pinned": false,
    "scale": 1,
    "size": {
      "height": 720,
      "width": 1280
    },
    "state": "normal",
    "title": "Team Photo",
    "widget_type": "Image"
  }
]

Get single image

Retrieves a single image widget by ID.

GET /api/v1/canvases/:canvas_id/images/:image_id

Path parameters:

  • canvas_id (uuid, required) -- ID of the canvas
  • image_id (uuid, required) -- ID of the image

Query parameters:

  • subscribe (flag, optional) -- Enable streaming updates. The parameter takes no value; its presence alone turns streaming on, so ?subscribe=false also streams.

Example request:

curl -H "Private-Token: YOUR_TOKEN" \
  https://canvus.example.com/api/v1/canvases/c3d319c2-5eed-456d-acbb-8da46e788157/images/40a30897-57ea-4c35-9bf8-64db7fc710e0

Example response:

{
  "depth": 2.0,
  "hash": "cbcb439a796b",
  "id": "40a30897-57ea-4c35-9bf8-64db7fc710e0",
  "location": {
    "x": 5130.68,
    "y": 2164.98
  },
  "original_filename": "team-photo.jpg",
  "parent_id": "aad4f204-2324-401e-9acf-257d2dad4d99",
  "pinned": false,
  "scale": 1,
  "size": {
    "height": 720,
    "width": 1280
  },
  "state": "normal",
  "title": "Team Photo",
  "widget_type": "Image"
}

Create image

Creates a new image widget by uploading a file. The request must be multipart/form-data with a mandatory data part (the image file) and an optional json part (widget metadata).

POST /api/v1/canvases/:canvas_id/images

Path parameters:

  • canvas_id (uuid, required) -- ID of the canvas

Multipart form parts:

  • data (file, required) -- The image file to upload (JPEG, PNG, SVG, etc.)
  • json (string, optional) -- JSON string with widget properties

JSON properties (all optional):

  • title -- string
  • original_filename -- string
  • location -- object with x and y floats
  • size -- object with width and height floats
  • depth -- float (must be >= 1.0)
  • scale -- float
  • pinned -- boolean
  • parent_id -- string (uuid)

Example request -- upload with metadata:

curl -X POST \
  -H "Private-Token: YOUR_TOKEN" \
  -F 'json={"title": "Architecture Diagram", "depth": 3.0}' \
  -F 'data=@architecture.png' \
  https://canvus.example.com/api/v1/canvases/c3d319c2-5eed-456d-acbb-8da46e788157/images

Example request -- upload with no metadata:

curl -X POST \
  -H "Private-Token: YOUR_TOKEN" \
  -F 'data=@photo.jpg' \
  https://canvus.example.com/api/v1/canvases/c3d319c2-5eed-456d-acbb-8da46e788157/images

Example response:

{
  "depth": 3.0,
  "hash": "",
  "id": "0b79a537-e9a6-4733-9610-a4066f49e01f",
  "location": {
    "x": 5226.29,
    "y": 2666.83
  },
  "original_filename": "architecture.png",
  "parent_id": "aad4f204-2324-401e-9acf-257d2dad4d99",
  "pinned": false,
  "scale": 1,
  "size": {
    "height": 900,
    "width": 1600
  },
  "state": "normal",
  "title": "Architecture Diagram",
  "widget_type": "Image"
}

The hash field is empty immediately after creation. It will be populated once the server finishes processing the file. Use a subsequent GET or subscribe for updates to see the final hash.


Update image

Updates one or more properties of an existing image widget. Only the fields included in the request body are changed -- all other properties remain untouched.

PATCH /api/v1/canvases/:canvas_id/images/:image_id

Path parameters:

  • canvas_id (uuid, required) -- ID of the canvas
  • image_id (uuid, required) -- ID of the image to update

Request body (JSON):

Any combination of patchable properties: title, original_filename, location, size, depth, scale, pinned, parent_id, auto_raise.

Example request -- move and pin:

curl -X PATCH \
  -H "Private-Token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"location": {"x": 2000, "y": 1500}, "pinned": true}' \
  https://canvus.example.com/api/v1/canvases/c3d319c2-5eed-456d-acbb-8da46e788157/images/40a30897-57ea-4c35-9bf8-64db7fc710e0

Example request -- resize (aspect ratio enforced):

curl -X PATCH \
  -H "Private-Token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"size": {"width": 800, "height": 800}}' \
  https://canvus.example.com/api/v1/canvases/c3d319c2-5eed-456d-acbb-8da46e788157/images/40a30897-57ea-4c35-9bf8-64db7fc710e0

In the resize example above, if the widget's current size has a 16:9 aspect ratio, the response will contain the corrected size that fits within the 800x800 bounding box:

{
  "size": {
    "height": 450,
    "width": 800
  }
}

Always read the response body to get the actual applied size after aspect ratio correction.

Example response:

{
  "depth": 2.0,
  "hash": "cbcb439a796b",
  "id": "40a30897-57ea-4c35-9bf8-64db7fc710e0",
  "location": {
    "x": 2000,
    "y": 1500
  },
  "original_filename": "team-photo.jpg",
  "parent_id": "aad4f204-2324-401e-9acf-257d2dad4d99",
  "pinned": true,
  "scale": 1,
  "size": {
    "height": 450,
    "width": 800
  },
  "state": "normal",
  "title": "Team Photo",
  "widget_type": "Image"
}

Validation errors:

  • Setting depth below 1.0 returns 400 Bad Request: Depth must be >= 1, got <value>
  • Setting size with zero or negative dimensions is rejected

Download image

Downloads the original image file as a binary attachment.

GET /api/v1/canvases/:canvas_id/images/:image_id/download

Path parameters:

  • canvas_id (uuid, required) -- ID of the canvas
  • image_id (uuid, required) -- ID of the image to download

Example request:

curl -H "Private-Token: YOUR_TOKEN" \
  -o downloaded-image.jpg \
  https://canvus.example.com/api/v1/canvases/c3d319c2-5eed-456d-acbb-8da46e788157/images/40a30897-57ea-4c35-9bf8-64db7fc710e0/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 image

Permanently removes an image widget from the canvas. The underlying file is deleted from server storage later, once no canvas references it.

DELETE /api/v1/canvases/:canvas_id/images/:image_id

Path parameters:

  • canvas_id (uuid, required) -- ID of the canvas
  • image_id (uuid, required) -- ID of the image to delete

Example request:

curl -X DELETE \
  -H "Private-Token: YOUR_TOKEN" \
  https://canvus.example.com/api/v1/canvases/c3d319c2-5eed-456d-acbb-8da46e788157/images/40a30897-57ea-4c35-9bf8-64db7fc710e0

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 image does not exist

Current behavior

  • An invalid or expired token returns 403 Forbidden, not 401. See Current behavior.
  • Create does not yet enforce the depth limit. A widget created without depth gets 0, and a value below 1.0 is accepted. Only update rejects it.
  • A zero or negative size is not rejected. Create stores it; update ignores it and still returns 200 OK.
  • An update rejected with 400 Bad Request can still apply other fields sent in the same request.

These are being corrected.

Example error response:

{"msg": "Depth must be >= 1, got 0.5"}