Comments and annotations
Comments are threaded notes on an entry, optionally pinned to one exact version. Annotations are marks drawn over one exact preview or thumbnail file. Reading needs read scope; posting, editing, reacting and uploading need write.
| Method and path | Purpose |
|---|---|
GET /entries/{entry_id}/comments |
List comments |
POST /entries/{entry_id}/comments |
Post a comment or reply |
PATCH /comments/{comment_id} |
Edit (author or admin) |
DELETE /comments/{comment_id} |
Delete (author or admin) |
POST /comments/{comment_id}/resolve, /unresolve |
Resolve or reopen a thread |
PUT /comments/{comment_id}/reactions |
Add a reaction |
DELETE /comments/{comment_id}/reactions/{emoji} |
Remove your reaction |
POST /attachments |
Upload a PNG to attach |
PUT /attachments/{attachment_id}/strokes |
Upload the attachment’s editable markup |
GET /attachments/{attachment_id}, /strokes |
Read an attachment |
POST /entries/{entry_id}/annotations |
Draw an annotation |
GET /entries/{entry_id}/annotations |
List annotations |
PATCH /annotations/{annotation_id} |
Link a discussion to an annotation |
DELETE /annotations/{annotation_id} |
Delete (author or admin) |
Comment payload
Section titled “Comment payload”{ "id": 91, "entry_id": 412, "object_id": 1880, "parent_id": null, "body": "Pivot looks off on this version, @Lee Lead", "author": { "id": 7, "name": "<user>", "display_name": "Sam Rigger" }, "mentions": [{ "id": 12, "name": "<user>", "display_name": "Lee Lead" }], "attachments": [{ "id": 301, "width": 1920, "height": 1080, "size": 402113, "has_strokes": true }], "reactions": [{ "emoji": "👀", "count": 2, "me": true, "names": ["Sam Rigger", "Lee Lead"] }], "created_at": "2026-07-25T12:00:00Z", "updated_at": null, "updated_by": 7, "resolved_at": null, "resolved_by": null}| Field | Description |
|---|---|
object_id |
The version the comment is about, or null for the entry as a whole. |
parent_id |
Set on replies. Threads are one level deep. |
updated_at, updated_by |
updated_at is set after an edit. updated_by is the user id of whoever last wrote the comment (the author until someone edits it). |
resolved_at, resolved_by |
Set when the thread is resolved; resolved_by is a user stub. |
reactions |
One chip per emoji; me is true if you reacted with it. |
GET /entries/{entry_id}/comments
Section titled “GET /entries/{entry_id}/comments”Returns {"comments": [comment]}, oldest first, replies included. No pagination. The same list is embedded in GET /entries/{entry_id}.
POST /entries/{entry_id}/comments
Section titled “POST /entries/{entry_id}/comments”{ "body": "Pivot looks off on this version, @Lee Lead", "object_id": 1880, "attachment_ids": [301] }| Field | Type | Description |
|---|---|---|
body |
string, required | 1 to 4,000 characters of plain text. |
object_id |
integer | Pin to a version of this entry’s lineage. 404 OBJECT_NOT_FOUND if unknown, 409 LINEAGE_CONFLICT if from another lineage. |
parent_id |
integer | Reply to a top-level comment on the same entry. A reply to a reply, or to another entry’s comment, is 422; unknown is 404 COMMENT_NOT_FOUND. |
attachment_ids |
array | Up to 10 of your own uploads from POST /attachments. |
Returns 201 with the comment. People watching the entry, the thread’s author and anyone mentioned get notifications.
Mentions
Section titled “Mentions”Write @ followed by a teammate’s display name (@Lee Lead) or login name. Mentions are resolved when the comment is saved; text that matches nobody, or matches more than one person’s display name, stays plain text. Resolved people appear in mentions. Editing a comment notifies only people newly mentioned by the edit.
PATCH /comments/{comment_id}
Section titled “PATCH /comments/{comment_id}”{ "body": "Pivot fixed in the next version", "attachment_ids": [] }body is required. attachment_ids is optional: omit it to keep the attachments, send a list to replace them. Only the author or an admin can edit (403). Returns the comment.
DELETE /comments/{comment_id}
Section titled “DELETE /comments/{comment_id}”Author or admin. Returns 204. Deleting a top-level comment deletes its replies.
Resolve and reopen
Section titled “Resolve and reopen”POST /comments/{comment_id}/resolve and POST /comments/{comment_id}/unresolve, no body. Anyone in the workspace can resolve or reopen. Only top-level comments can be resolved (422 for replies). Repeating a call changes nothing. Returns the comment.
Reactions
Section titled “Reactions”PUT /comments/{comment_id}/reactions with {"emoji": "🎉"} returns 204 and is idempotent. DELETE /comments/{comment_id}/reactions/{emoji} (URL-encode the emoji) removes your reaction. Allowed emoji: 👍 👎 ❤️ 😄 😮 😢 🎉 🚀 👀 🔥 ✅ ❌. Others are 422.
Attachments
Section titled “Attachments”Attachments are PNG images (typically marked-up screenshots) bound to a comment or a review note.
POST /attachmentswith the raw PNG bytes as the body (Content-Type: image/png, up to 8 MiB). Returns201{"id", "width", "height", "size"}. Until it is attached, only you (and admins) can see it.- Optionally
PUT /attachments/{attachment_id}/strokeswith a JSON document (up to 1 MiB) holding the editable markup your tool drew. Only the uploader can do this. Returns{id, width, height, size, has_strokes}. - Pass the id in
attachment_idson a comment, a review request or a review response. Each attachment can belong to one of these only; an attachment that is someone else’s is403, already attached is409.
GET /attachments/{attachment_id} and GET /attachments/{attachment_id}/strokes redirect (302) to the PNG or JSON. Attached files are visible to the whole workspace; unattached ones only to their uploader and admins (404 for others).
Attach an upload within a day; unattached uploads are eligible for cleanup after that.
Annotations
Section titled “Annotations”An annotation is drawn over one exact preview or thumbnail file (a preview artifact), so it never drifts when the preview is regenerated. Get the ids from preview_artifact_id and thumbnail_artifact_id on entry detail, and render against GET /previews/artifacts/{artifact_id}.
POST /entries/{entry_id}/annotations
Section titled “POST /entries/{entry_id}/annotations”{ "object_id": 1880, "preview_artifact_id": 5521, "shape": "rect", "geometry": { "x": 0.25, "y": 0.1, "w": 0.2, "h": 0.15 }, "comment_id": 91}| Field | Type | Description |
|---|---|---|
object_id |
integer, required | A version of this entry’s lineage. |
preview_artifact_id |
integer, required | A preview or thumbnail file of that version (422 otherwise). |
shape |
string, required | rect, ellipse, freehand or point3d. |
geometry |
object, required | See below. At most 16 KiB. |
comment_id |
integer | A top-level comment on the same entry to hold the discussion. |
| Shape | Geometry |
|---|---|
rect, ellipse |
Exactly {x, y, w, h}, each 0 to 1, relative to the image. |
freehand |
Exactly {points: [[x, y], ...]}, 2 to 2,000 points, each coordinate 0 to 1. |
point3d |
Exactly {point: [x, y, z], camera: {...}}: a world-space point on a 3D preview plus your viewer’s camera object, stored as sent. |
Returns 201:
{ "id": 44, "entry_id": 412, "object_id": 1880, "preview_artifact_id": 5521, "comment_id": 91, "shape": "rect", "geometry": { "x": 0.25, "y": 0.1, "w": 0.2, "h": 0.15 }, "author": { "id": 7, "name": "<user>", "display_name": "Sam Rigger" }, "created_at": "2026-07-25T12:05:00Z"}Other annotation calls
Section titled “Other annotation calls”| Endpoint | Detail |
|---|---|
GET /entries/{entry_id}/annotations?object_id= |
{"annotations": [annotation]}, oldest first. object_id limits to one version. |
PATCH /annotations/{annotation_id} |
{"comment_id"}: attach a discussion to an annotation that has none. Anyone can do this once; an annotation that already has one is 409 CONFLICT. The comment must be top-level on the same entry (422). Returns the annotation. |
DELETE /annotations/{annotation_id} |
Author or admin. 204. |
Unknown ids: 404 ANNOTATION_NOT_FOUND.
Comments, resolutions and annotations appear in the activity feed as comment.create, comment.resolve and annotation.create.
