Skip to content

client.comments

Bases: SyncResource

Sync comments namespace (client.comments).

create

create(project_id: UUID | str, asset_id: UUID | str, *, body: str, anchor_kind: AnchorKind | str = AnchorKind.ASSET, anchor_coords: AnchorCoordsParam | None = None, annotation_id: UUID | str | None = None, parent_id: UUID | str | None = None) -> Comment

Create a root comment or a reply (201).

Parameters:

Name Type Description Default
body str

comment text (1..COMMENT_BODY_MAX chars; @email mentions resolve against project members).

required
anchor_kind AnchorKind | str

asset (whole-asset, default) | point | box | annotation. Ignored for replies (they inherit the root's anchor).

ASSET
anchor_coords AnchorCoordsParam | None

{x, y} for point, {x, y, w, h} for box; omit otherwise.

None
annotation_id UUID | str | None

required when anchor_kind='annotation'.

None
parent_id UUID | str | None

set to reply under a thread root (inherits its anchor).

None

Raises:

Type Description
NotFoundError

unknown asset (ASSET_NOT_FOUND) or, for an annotation anchor, unknown annotation (ANNOTATION_NOT_FOUND).

BadRequestError

reply to a reply, or reply on a different asset than the root (BAD_REQUEST).

ConflictError

per-asset comment cap reached (RESOURCE_LIMIT_EXCEEDED).

UnprocessableError

anchor shape mismatch / reserved 3D kind (VALIDATION_ERROR).

ForbiddenError

caller is not a project member (AUTH_FORBIDDEN).

list_for_asset

list_for_asset(project_id: UUID | str, asset_id: UUID | str, *, status: CommentStatus | str | None = None, parent_id: UUID | str | None = None, include_total: bool = False, limit: int | None = None, cursor: str | None = None) -> Page[Comment]

The per-asset thread list, newest first (cursor-paginated). Root comments only by default; pass parent_id to load replies under one root.

Parameters:

Name Type Description Default
status CommentStatus | str | None

filter root threads by open / resolved.

None
parent_id UUID | str | None

when set, returns only the replies under this root.

None
include_total bool

also populate page.total (a COUNT for the filter).

False

Raises:

Type Description
BadRequestError

malformed cursor (INVALID_CURSOR).

ForbiddenError

caller is not a project member (AUTH_FORBIDDEN).

list

list(project_id: UUID | str, *, status: CommentStatus | str | None = None, author: UUID | str | None = None, asset_id: UUID | str | None = None, created_at_from: datetime | str | None = None, created_at_to: datetime | str | None = None, include_total: bool = False, limit: int | None = None, cursor: str | None = None) -> Page[Comment]

The project-wide comment feed (the "issues" view), newest first — roots only (threading is loaded per-asset via :meth:list_for_asset).

Parameters:

Name Type Description Default
author UUID | str | None

filter to threads opened by this user (the author_id).

None
asset_id UUID | str | None

filter to threads anchored on this asset.

None
include_total bool

also populate page.total (a COUNT for the filter).

False

Raises:

Type Description
BadRequestError

malformed cursor (INVALID_CURSOR).

ForbiddenError

caller is not a project member (AUTH_FORBIDDEN).

update

update(project_id: UUID | str, comment_id: UUID | str, *, body: str | None = None, anchor_kind: AnchorKind | str | None = None, anchor_coords: AnchorCoordsParam | None = None, annotation_id: UUID | str | None = None, frame_number: int | None = None) -> Comment

Edit a comment's body and/or its anchor (drag-to-move). Pass at least one of body / anchor fields. Editing any anchor field replaces the whole anchor, so anchor_kind must be given then. Author or project manager only; anchor edits apply to root comments only.

Parameters:

Name Type Description Default
frame_number int | None

video-only anchor frame. It is rejected on image assets — the only asset kind today — so any value currently 422s (UnprocessableError: 'frame_number is not valid on image assets').

None

Raises:

Type Description
NotFoundError

unknown comment, or (annotation anchor) unknown annotation (ANNOTATION_NOT_FOUND).

ForbiddenError

caller is neither the author nor a project manager.

UnprocessableError

nothing to update, anchor shape mismatch, missing anchor_kind on an anchor edit, or moving a reply's anchor (VALIDATION_ERROR).

delete

delete(project_id: UUID | str, comment_id: UUID | str) -> None

Soft-delete a comment (204). Replies are preserved. Author or project manager only.

Raises:

Type Description
NotFoundError

unknown comment.

ForbiddenError

caller is neither the author nor a project manager.

resolve

resolve(project_id: UUID | str, comment_id: UUID | str) -> Comment

Mark a thread ROOT resolved (any project member). Idempotent.

Raises:

Type Description
NotFoundError

unknown comment.

BadRequestError

comment_id is a reply, not a root (BAD_REQUEST).

reopen

reopen(project_id: UUID | str, comment_id: UUID | str) -> Comment

Reopen a resolved thread ROOT (any project member). Idempotent.

Raises:

Type Description
NotFoundError

unknown comment.

BadRequestError

comment_id is a reply, not a root (BAD_REQUEST).

Response models

Models returned by client.comments methods (fields, types, and what each means).

Comment / issue domain models (ADR-0043).

A comment is project-scoped review feedback anchored to an asset. Its anchor is one-of four kinds, discriminated by the SIBLING anchor_kind field (NOT a tag inside the coords blob):

  • asset — whole-asset note, no coords
  • pointanchor_coords is a :class:PointAnchor (x, y)
  • boxanchor_coords is a :class:BoxAnchor (x, y, w, h)
  • annotation — pinned to annotation_id, no coords

Because a point dict {x, y} is a field-subset of a box dict, a bare PointAnchor | BoxAnchor union can't self-discriminate; anchor_coords is coerced against anchor_kind in a before-validator, mirroring the server.

Depends only on common (enums) — a leaf module in the type dependency graph.

PointAnchor

Bases: BaseModel

A point anchor: normalized coords (one image-dimension of margin, so the server accepts [-1, 2] for drag-to-edge / zoomed-out canvases).

BoxAnchor

Bases: BaseModel

A box anchor: top-left (x, y) + width/height, in the same normalized space.

Comment

Bases: BaseModel

One comment or reply. Roots carry an anchor + a stable per-asset seq (the "#N" issue number, unique per asset, never reused — gaps after deletes are expected); replies inherit the root's anchor, carry parent_id, and have seq=None. reply_count is populated only on roots in list views.