Skip to content

Save a whiteboard sketch on a post

POST
/posts/{id}/whiteboards

Persists a brush-sketch as a top-level comment with annotation_type='whiteboard'. The content payload carries the strokes (vector source of truth — perfect-freehand input points + per-layer arrangement) plus the source canvas dimensions so the viewer can re-render proportionally regardless of viewport size.

Requires posts.comment capability (Base default) — same gate as posting a regular comment, since whiteboards ARE comments at the storage layer. Future phases can split the capability if finer governance is needed.

title is optional. If omitted the sidebar list shows “Untitled sketch” with the author + timestamp.

id
required
string format: uuid
object
title

Optional title for the sidebar list.

string
<= 200 characters
content
required

Vector source of truth for a whiteboard / annotation sketch. The frontend renders from this; OCR / AI ingestion later can consume it directly without going through the raster snapshot.

object
source_w
required

Width of the source canvas in CSS pixels at save time. Used to scale the drawing proportionally when re-rendered in a different viewport size — strokes are stored in source-canvas coordinates, not viewport pixels.

integer
>= 1
source_h
required
integer
>= 1
image_hash

Optional content-addressed hash of a rasterized PNG snapshot in object storage. Convenience output for OCR / AI / PDF export later. Null on save means strokes are authoritative and the snapshot will be lazily rendered.

string
nullable
canvas_color

Canvas background color (C-1.18). Hex string painted behind every layer before items render. Defaults to ‘#ffffff’ when missing so saves from C-1.0..C-1.17 render identically.

string
layers
required

Ordered bottom-to-top. Each layer carries its own visible / opacity / lock / items payload — same shape Photoshop / Procreate use. Capped at 32 to keep payloads bounded.

Array<object>
>= 1 items <= 32 items
object
id
required

Stable per-layer identifier (UUIDv4 from the client).

string
name
string
visible
boolean
default: true
opacity
number
default: 1 <= 1
locked

When true the layer is read-only; visibility stays toggleable.

boolean
items
required

Polymorphic items on this layer. kind is the discriminator: stroke (free-form brush), shape (line / arrow / rect / ellipse), text (inline label), or image (pasted/dropped bitmap, stored inline as a base64 data: URL for now).

Array<object>
object
kind
required
string
Allowed values: stroke shape text image
tool
string
Allowed values: pen marker highlighter eraser line arrow rect rounded-rect ellipse triangle right-triangle diamond pentagon hexagon star heart callout-rect callout-oval
color

Legacy single color (still set on shape items for back-compat). Stroke + text items use this exclusively; shape items prefer strokeColor / fillColor when present.

string
strokeColor

Shape outline color (C-1.17). Falls back to color when missing.

string
fillColor

Shape fill color (C-1.17). Falls back to color when missing.

string
width
number
<= 200
opacity
number
default: 1 <= 1
points
Array<Array<number>>
x
number
y
number
w
number
h
number
fill

Shape fill opacity. 0 = outline only, 1 = solid fill.

number
<= 1
body
string
<= 10000 characters
fontSize
number
>= 8 <= 256
align
string
default: left
Allowed values: left center right
bold
boolean
italic
boolean
src

Base64 data: URL OR remote URL. Pasted images are embedded as data URLs (5 MB cap enforced client-side); content-addressed image_hash reference lands in a later phase.

string
rotation
number
0

Whiteboard saved

A single comment in a thread. Threading is via parent_id + root_id + depth (caller renders nested replies indented by depth). annotation_* fields are NULL for plain comments; the review-mode feature reuses the same row shape.

Federation: exactly one of {author_user_ref} XOR {peer_id + actor_uri} is set per row. Local-authored comments have author_user_ref set + peer_id NULL. Remote-authored comments (from a paired peer per ADR 0043 / phase 1.22.D) have author_user_ref NULL + peer_id + actor_uri + display_name populated. Clients render “<display_name> @ <peer’s host>” for remote rows.

object
id
required
string format: uuid
target_kind
required
string
Allowed values: post asset collection
target_id
required
string format: uuid
parent_id
string format: uuid
nullable
root_id
required
string format: uuid
depth
required
integer
author_user_ref

Local author user.ref. NULL for remote-authored comments (see peer_id / actor_uri).

integer format: int64
nullable
body
required
string
body_html
required

Server-rendered safe HTML; clients should render this rather than reformatting body.

string
annotation_type
string
nullable
Allowed values: point rect timestamp frame whiteboard text-range
annotation_data
object
key
additional properties
any
like_count
required
integer format: int64
edited_at
string format: date-time
nullable
created_at
required
string format: date-time
updated_at
required
string format: date-time
peer_id

The federation peer that originated this comment. NULL for local-authored rows. Set in tandem with actor_uri

  • display_name when the comment came in via the federation inbox.
string format: uuid
nullable
actor_uri

The remote actor’s URI on their home instance (e.g. https://studio-b.example/users/bob). NULL for local-authored rows.

string
nullable
display_name

Display name resolved from federation_remote_actors cache for remote-authored comments. May be empty if the remote instance hasn’t shipped display hints yet; clients should fall back to the actor_uri’s host + local-part when this is empty.

string
nullable

Malformed request

object
error
required

Human-readable error summary

string
Example
the request could not be completed

Authentication required, missing, or invalid

object
error
required

Human-readable error summary

string
Example
the request could not be completed
Example
{
"error": "authentication required: sign in and retry with a valid session or API token"
}

Authenticated but missing required capabilities

object
error
required

Human-readable error summary

string
Example
the request could not be completed

Resource not found

object
error
required

Human-readable error summary

string
Example
the request could not be completed