Skip to content

client.workflow

Bases: SyncResource

Sync labeling-workflow namespace (client.workflow).

list_asset_states

list_asset_states(project_id: UUID | str, *, status: WorkflowStatus | str | None = None, status_not: list[WorkflowStatus | str] | None = None, assigned_to: UUID | str | None = None, assigned: bool | None = None, reviewer_id: UUID | str | None = None, rework_count_min: int | None = None, tags: list[str] | None = None, tags_none: list[str] | None = None, tagged: bool | None = None, classes: list[UUID | str] | None = None, classes_none: list[UUID | str] | None = None, annotated: bool | None = None, q: str | None = None, uploaded_by: UUID | str | None = None, created_at_from: datetime | str | None = None, created_at_to: datetime | str | None = None, include: str | None = None, include_total: bool = False, limit: int | None = None, cursor: str | None = None) -> Page[AssetState]

Browse asset states across the project (manager dashboard), newest-updated first (cursor-paginated; iterate for all pages).

Parameters:

Name Type Description Default
status WorkflowStatus | str | None

single WorkflowStatus (to_label / in_progress / in_review / done); unknown → 422.

None
status_not list[WorkflowStatus | str] | None

exclude ALL listed statuses (negative twin of status).

None
assigned_to UUID | str | None

keep only tasks assigned to this specific labeler (user id).

None
assigned bool | None

True = assigned to anyone, False = the unassigned pool.

None
reviewer_id UUID | str | None

keep only tasks last reviewed by this specific reviewer (user id).

None
rework_count_min int | None

keep tasks with rework_count >= N (N >= 1).

None
tags list[str] | None

contains-ALL over the per-(project, asset) tag set (ADR-0050).

None
tags_none list[str] | None

carries NONE of the listed tags (untagged matches) — combine with tags for "all of these, except those".

None
tagged bool | None

True = has tags, False = untagged.

None
classes list[UUID | str] | None

ANY-of over the asset's live annotation classes (ADR-0100).

None
classes_none list[UUID | str] | None

has NO annotation of ANY listed class.

None
annotated bool | None

True = has annotations, False = none.

None
q str | None

case-insensitive substring on the asset filename.

None
uploaded_by UUID | str | None

keep only assets uploaded by this specific user (user id).

None
created_at_from datetime | str | None

inclusive lower bound (>=) on asset created_at (datetime or ISO-8601 string).

None
created_at_to datetime | str | None

exclusive upper bound (<) on asset created_at (datetime or ISO-8601 string); half-open with created_at_from.

None
include str | None

CSV of embeds; only thumbnail_url is allowed (else 400).

None
include_total bool

also populate page.total (a COUNT over the same filter).

False

Raises:

Type Description
UnprocessableError

invalid status enum value (VALIDATION_ERROR).

BadRequestError

a contradictory combination — classes with annotated=False, the same value in a positive/negative pair (tags/tags_none, classes/classes_none, status/status_not), assigned=False with assigned_to, tagged=False with tags — an unknown include token, or a malformed cursor (INVALID_FILTER / INVALID_CURSOR).

NotFoundError

unknown project (PROJECT_NOT_FOUND).

ForbiddenError

caller is not a project member (AUTH_FORBIDDEN).

queue

queue(project_id: UUID | str, *, status: Literal['to_label', 'in_progress'] | str | None = None, rework: bool | None = None, include: str | None = None, include_total: bool = False, limit: int | None = None, cursor: str | None = None) -> Page[AssetState]

The caller's own queue: tasks they should work on now — their assigned in_progress / to_label tasks, plus (in self_claim mode) the unassigned claimable pool. Cursor-paginated; iterate for all pages.

Parameters:

Name Type Description Default
status Literal['to_label', 'in_progress'] | str | None

restrict to to_label OR in_progress only (the queue's 2-value subset — any other value → 422).

None
rework bool | None

True keeps rework_count > 0, False keeps = 0 (pair with status=in_progress for the Rework / Continue split, ADR-0029).

None
include str | None

CSV of embeds; only thumbnail_url is allowed (else 400).

None
include_total bool

also populate page.total (queue_stats is cheaper for the unfiltered hero counts).

False

Raises:

Type Description
UnprocessableError

status outside {to_label, in_progress} (VALIDATION_ERROR).

BadRequestError

unknown include token or malformed cursor (INVALID_FILTER / INVALID_CURSOR).

NotFoundError

unknown project (PROJECT_NOT_FOUND).

ForbiddenError

caller is not a project member (AUTH_FORBIDDEN).

queue_stats

queue_stats(project_id: UUID | str) -> QueueStats

Bucket counts for the caller's 4-card hero (ADR-0029) — a complete snapshot, scoped exactly like :meth:queue.

Raises:

Type Description
NotFoundError

unknown project (PROJECT_NOT_FOUND).

ForbiddenError

caller is not a project member (AUTH_FORBIDDEN).

get_labeling_context

get_labeling_context(project_id: UUID | str, asset_id: UUID | str) -> LabelingContext

One-shot labeler-page bundle for an asset: workflow state + asset metadata + all its annotations in this project + a presigned download URL + the project's class vocabulary.

Raises:

Type Description
NotFoundError

the asset is not in this project's scope (ASSET_NOT_FOUND), or its bytes were hard-deleted (ASSET_DELETED).

ForbiddenError

caller is not a project member (AUTH_FORBIDDEN).

claim

claim(project_id: UUID | str, asset_id: UUID | str) -> AssetState

Self-claim a task (to_label/in_progressin_progress assigned to you). Requires assignment_mode=self_claim unless the task is already yours.

Raises:

Type Description
ConflictError

task is not claimable — wrong status or already assigned to another labeler (PROJECT_STATE_FORBIDDEN).

ForbiddenError

project requires manual assignment, or you're not a member (AUTH_FORBIDDEN).

NotFoundError

asset not in this project's scope (ASSET_NOT_FOUND).

assign

assign(project_id: UUID | str, asset_id: UUID | str, *, user_id: UUID | str) -> AssetState

Assign a task to a specific member (Manager+). The target must be a project member (labeler/reviewer/manager) or a workspace admin.

Sets the assignee but leaves status to_label — the assignee still :meth:claim\ s to start (that flips it to in_progress). Contrast workflow_batch.batch_assign, which assigns AND starts in one step.

Raises:

Type Description
NotFoundError

asset not in scope (ASSET_NOT_FOUND) or target user not in this tenant (USER_NOT_FOUND).

BadRequestError

target user is not a member of this project (PROJECT_MEMBER_NOT_FOUND).

ForbiddenError

caller is not a project manager (AUTH_FORBIDDEN).

unassign

unassign(project_id: UUID | str, asset_id: UUID | str) -> AssetState

Clear the current assignee (Manager+). An in_progress row reverts to to_label; already-unassigned is a no-op that returns the row unchanged.

Raises:

Type Description
NotFoundError

asset not in this project's scope (ASSET_NOT_FOUND).

ForbiddenError

caller is not a project manager (AUTH_FORBIDDEN).

submit

submit(project_id: UUID | str, asset_id: UUID | str) -> AssetState

Submit your work for review (in_progressin_review). Submission is final — the only way back is reviewer :meth:reject.

Raises:

Type Description
ConflictError

not in in_progress (PROJECT_STATE_FORBIDDEN).

ForbiddenError

you are not the assigned labeler (AUTH_FORBIDDEN).

BadRequestError

a required classification is unanswered — details.errors lists each (VALIDATION_REQUIRED_CLASSIFICATION_MISSING).

NotFoundError

asset not in this project's scope (ASSET_NOT_FOUND).

approve

approve(project_id: UUID | str, asset_id: UUID | str) -> AssetState

Approve reviewed work (in_reviewdone; stamps you as reviewer, Reviewer+).

Raises:

Type Description
ConflictError

not in in_review (PROJECT_STATE_FORBIDDEN).

ForbiddenError

caller is not a project reviewer (AUTH_FORBIDDEN).

NotFoundError

asset not in this project's scope (ASSET_NOT_FOUND).

reject

reject(project_id: UUID | str, asset_id: UUID | str, *, reason: str | None = None) -> AssetState

Reject reviewed work — loops back to the original labeler (in_reviewin_progress, bumps rework_count, Reviewer+). reason (optional) is stored on the state and dropped into the asset's comment thread the labeler sees.

Raises:

Type Description
ConflictError

not in in_review (PROJECT_STATE_FORBIDDEN).

ForbiddenError

caller is not a project reviewer (AUTH_FORBIDDEN).

NotFoundError

asset not in this project's scope (ASSET_NOT_FOUND).

skip

skip(project_id: UUID | str, asset_id: UUID | str) -> AssetState

Set the asset aside — 'nothing to label' or unusable data (in_progressskipped, ADR-0110). Terminal, distinct from a done label; no reason captured. Skipped assets are excluded from version freezes; a reviewer can :meth:reopen one back into the queue.

Raises:

Type Description
ConflictError

not in in_progress (PROJECT_STATE_FORBIDDEN).

ForbiddenError

you are not the assigned labeler (AUTH_FORBIDDEN).

NotFoundError

asset not in this project's scope (ASSET_NOT_FOUND).

reopen

reopen(project_id: UUID | str, asset_id: UUID | str) -> AssetState

Revive a skipped asset back into the queue (skippedto_label, clears the assignee; Reviewer+). For when set-aside data must be labeled after all (e.g. data is scarce).

Raises:

Type Description
ConflictError

not in skipped (PROJECT_STATE_FORBIDDEN).

ForbiddenError

caller is not a project reviewer (AUTH_FORBIDDEN).

NotFoundError

asset not in this project's scope (ASSET_NOT_FOUND).

create_annotation

create_annotation(project_id: UUID | str, asset_id: UUID | str, *, class_id: UUID | str, type: AnnotationType | str, geometry: Geometry | GeometryParam | None = None, data: dict[str, Any] | None = None, instance_id: UUID | str | None = None, parent_id: UUID | str | None = None, locked: bool | None = None, id: UUID | str | None = None) -> Annotation

Create one annotation on an asset (201). Creating more than a handful? Do NOT loop this — use annotations.asset_batch_create (one asset, ≤1000), annotations.project_batch_create (across assets, ≤1000), or annotations.import_ndjson (any size, async) instead.

geometry is required for drawable types (bbox/polygon/keypoint) and must be omitted for classification (whose answer goes in data). id is the client-minted UUID primary key — omit to let the server mint one; supplying an existing id upserts (last-write-wins, ADR-0092).

instance_id groups shapes that are the same real-world instance (e.g. one object across keyframes / parts) under a shared id. parent_id links this shape to a parent annotation for nesting (child rows cascade-delete with the parent). locked (default False) marks the shape edit-locked so later content edits are refused until it is unlocked (ADR-0084).

Raises:

Type Description
ConflictError

the asset is edit-locked for you — in_review / done without override (PROJECT_STATE_FORBIDDEN), or the per-asset annotation cap is reached (RESOURCE_LIMIT_EXCEEDED).

NotFoundError

asset not in scope (ASSET_NOT_FOUND) or class_id not in this project (PROJECT_CLASS_NOT_FOUND).

BadRequestError

archived class, type not in the class's allowed types, or bad geometry / topology / classification answer (VALIDATION_ERROR).

UnprocessableError

malformed body — e.g. geometry.type ≠ type (VALIDATION_ERROR).

ForbiddenError

caller is not a project member (AUTH_FORBIDDEN).

update_annotation

update_annotation(project_id: UUID | str, annotation_id: UUID | str, *, class_id: UUID | str | None = None, geometry: Geometry | GeometryParam | None = None, data: dict[str, Any] | None = None, instance_id: UUID | str | None = None, locked: bool | None = None) -> Annotation

Partially update an annotation (only the fields you pass change). type is immutable — changing geometry kind is a delete + create. Relabeling validates the new class_id against the annotation's stored type. instance_id re-groups the shape under a different instance id (the grouping shared by same-instance shapes; set to a new id to move it, ADR-0092).

Note: a locked=True shape rejects content edits unless this same call also sets locked=False; a lock-only toggle always passes (ADR-0084).

Raises:

Type Description
NotFoundError

no such annotation in this project (ANNOTATION_NOT_FOUND), or the new class_id is unknown (PROJECT_CLASS_NOT_FOUND).

ConflictError

the asset is edit-locked for you (PROJECT_STATE_FORBIDDEN), or the shape is locked and you're editing its content (CONCURRENT_MODIFICATION).

BadRequestError

the new class is archived or forbids the stored type (VALIDATION_ERROR).

ForbiddenError

caller is not a project member (AUTH_FORBIDDEN).

delete_annotation

delete_annotation(project_id: UUID | str, annotation_id: UUID | str) -> None

Hard-delete an annotation (204). Idempotent — an unknown id is a silent no-op. Children (rows whose parent_id is this shape) cascade-delete; comment anchors are set null (ADR-0092).

Raises:

Type Description
ConflictError

the asset is edit-locked for you (PROJECT_STATE_FORBIDDEN).

ForbiddenError

caller is not a project member (AUTH_FORBIDDEN).

Response models

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

Project workflow envelopes: sync batch assign/unassign/approve/reject (ADR-0041).

The four manager/reviewer workflow verbs each come in two flavors:

  • async bulk-* kickoffs return a Job (see types.jobs) — no model here; JobHandle.wait() polls it to terminal. Best-effort: the worker skips rows that fail the per-verb state precondition (e.g. an approve target that isn't in_review) rather than erroring.
  • sync batch-* ops return the canonical batch envelope modeled below: total == <verb_past> + skipped over the requested asset ids, one results entry per requested id. Each verb self-describes its count field (assigned / unassigned / approved / rejected) — no generic applied — so a caller reads the number without knowing which verb it called.

skipped counts requested ids the UPDATE's WHERE clause left untouched: the asset wasn't in the project, was already in the desired state (already assigned to that user / already unassigned), or wasn't in the required source state (only in_review rows approve/reject).

Response models only (extra="allow"); request bodies are built as dicts in the resource.

This module is shared by the workflow domain group (single-asset verbs + queue + asset-state list + per-asset annotation CRUD): its read shapes — :class:Annotation, :class:AssetState, :class:QueueStats, :class:LabelingContext — follow the batch envelopes below.

OWNERSHIP NOTE (STYLE §3): :class:Annotation is a shape shared with the annotations / annotation-list groups. It is declared here because this is the first workflow module to need it and the annotations type module had not landed; the highest-common-module home is types.annotations — if that module exists at assembly, promote :class:Annotation there and import it here (do NOT keep two copies).

BatchAssignResultItem

Bases: BaseModel

Per-asset outcome of a sync batch_assign. skipped = the id wasn't in the project or was already assigned to user_id (and not a to_label row).

BatchAssignResult

Bases: BaseModel

Sync batch-op envelope (ADR-0041): total == assigned + skipped and len(results) == total over the requested asset ids. To-label rows flip to in_progress on assign (claim-like semantics).

BatchUnassignResultItem

Bases: BaseModel

Per-asset outcome of a sync batch_unassign. skipped = the id wasn't in the project or already had no assignee.

BatchUnassignResult

Bases: BaseModel

Sync batch-op envelope (ADR-0041): total == unassigned + skipped and len(results) == total. in_progress rows revert to to_label on unassign.

BatchApproveResultItem

Bases: BaseModel

Per-asset outcome of a sync batch_approve. skipped = the id wasn't in the project or wasn't in in_review (only in_review rows approve to done).

BatchApproveResult

Bases: BaseModel

Sync batch-op envelope (ADR-0041): total == approved + skipped and len(results) == total. Approving flips in_review -> done and stamps the caller as reviewer.

BatchRejectResultItem

Bases: BaseModel

Per-asset outcome of a sync batch_reject. skipped = the id wasn't in the project or wasn't in in_review (only in_review rows reject).

BatchRejectResult

Bases: BaseModel

Sync batch-op envelope (ADR-0041): total == rejected + skipped and len(results) == total. Rejecting flips in_review -> in_progress, bumps rework_count, and (when reason is set) records it on the state row and a per-asset Comment (origin=workflow_reject) the labeler sees inline.

AssetState

Bases: BaseModel

One project_asset_state row — an asset's workflow state within a project.

status walks to_label -> in_progress -> in_review -> done (reject loops in_review back to in_progress and bumps rework_count). asset, assigned_to_user and reviewer_user are populated only when the route JOINed them (the list / queue / single-state reads do); tags are the per-(project, asset) tags (ADR-0050), independent of other projects on the same asset.

QueueStats

Bases: BaseModel

Bucket counts for the labeler's 4-card hero (ADR-0029), scoped to the caller's queue (their assigned tasks + the unassigned pool in self_claim mode). all == to_label + rework + continue_.

LabelingContext

Bases: BaseModel

One-shot labeler-page bundle (saves ~4 round trips): the asset's workflow asset_state + asset metadata + all its shapes (annotations) in this project + a presigned download_url for the full-res bytes + the project's class vocabulary (object classes with inline attributes + image-level image_classifications). annotations is a legacy alias of shapes.