Reviews
Review state belongs to an exact version, not to the entry. Each version is draft until someone reviews it; the entry’s review_status shows the state of its current version, so pushing a new version resets the badge to draft and restoring an approved version brings approved back.
There are two ways to set it:
- A direct decision with
PUT /entries/{entry_id}/review. - A review request: one round in which up to 20 reviewers each respond. The version’s state is derived from their responses.
| Method and path | Scope | Purpose |
|---|---|---|
PUT /entries/{entry_id}/review |
write | Set the state directly |
POST /entries/{entry_id}/review-requests |
write | Open a review round |
GET /entries/{entry_id}/review-requests |
read | An entry’s rounds |
GET /review-requests |
read | The review queue across entries |
GET /review-requests/waiting-count |
read | How many rounds wait on you |
GET /review-requests/{request_id} |
read | One round |
POST /review-requests/{request_id}/respond |
write | Respond as a reviewer |
PATCH /review-requests/{request_id} |
write | Add or remove reviewers |
POST /review-requests/{request_id}/cancel |
write | Cancel a round |
Every review write names the version as object_id, which must be the entry’s current version. Otherwise it fails with 409 CONFLICT and details.current_object_id, so a review can never land on the wrong version. To review an older version, restore it first.
PUT /entries/{entry_id}/review
Section titled “PUT /entries/{entry_id}/review”{ "state": "approved", "object_id": 1880, "assignee_id": 12, "note": "LODs look right" }| Field | Type | Description |
|---|---|---|
state |
string, required | draft, in_review, changes_requested or approved. |
object_id |
integer, required | The entry’s current version. |
assignee_id |
integer or null |
Omit to keep the current assignee; null clears it. Not allowed with draft. |
note |
string | Up to 1,000 characters. Replaces the previous note (omitting it clears it). Not allowed with draft. |
Response:
{ "entry_id": 412, "object_id": 1880, "review_status": "approved", "review": { "object_id": 1880, "state": "approved", "assignee": { "id": 12, "name": "<user>", "display_name": "Lee Lead" }, "decided_by": { "id": 7, "name": "<user>", "display_name": "Sam Rigger" }, "decided_at": "2026-07-25T12:00:00Z", "note": "LODs look right", "request_id": null }}draft removes the review record (review: null). request_id is set when the record came from a review request.
Rules:
- While a review has an assignee, only the assignee or an admin can change it (
403 FORBIDDEN). Unassigned reviews are open to anyone. - While a review request is open on the version, only its requester or an admin can set the state directly, which closes the round as
superseded. Anyone else gets409 CONFLICTwithdetails.open_request_idand should respond through the request. - Unknown assignee:
404 USER_NOT_FOUND. Disabled assignee:422.
Review requests
Section titled “Review requests”POST /entries/{entry_id}/review-requests
Section titled “POST /entries/{entry_id}/review-requests”{ "object_id": 1880, "reviewer_ids": [12, 15], "note": "First pass please, @lee", "attachment_ids": [301] }| Field | Type | Description |
|---|---|---|
object_id |
integer, required | The entry’s current version. |
reviewer_ids |
array, required | 1 to 20 distinct library user ids (see Members). Must be active people, not API-only identities. |
note |
string | Up to 1,000 characters. @name mentions notify those people. |
attachment_ids |
array | Up to 10 of your own uploaded attachments. |
Returns 201 with {"request": <request>}. The version goes to in_review immediately. Only one round can be open per version (409 CONFLICT with details.open_request_id); opening a round on the current version closes rounds still open on older versions. Each reviewer gets a notification.
The request payload
Section titled “The request payload”Write endpoints return it wrapped as {"request": {...}}.
{ "id": 3, "entry_id": 412, "object_id": 1880, "lineage_id": "guid:6F9619FF8B86D011B42D00C04FC964FF", "status": "open", "requested_by": { "id": 7, "name": "<user>", "display_name": "Sam Rigger" }, "note": "First pass please, @lee", "attachments": [{ "id": 301, "width": 1920, "height": 1080, "size": 402113, "has_strokes": true }], "mentions": [{ "id": 12, "name": "<user>", "display_name": "Lee Lead" }], "created_at": "2026-07-25T12:00:00Z", "closed_at": null, "closed_by": null, "reviewers": [ { "user": { "id": 12, "name": "<user>", "display_name": "Lee Lead" }, "response": "pending", "note": null, "attachments": [], "mentions": [], "assign_to": null, "responded_at": null } ]}status is open, completed (everyone approved), cancelled or superseded (a newer round, or a direct decision). The version’s state follows the responses: any changes_requested wins, all approved completes the round, anything else is in_review.
Reading rounds
Section titled “Reading rounds”| Endpoint | Parameters | Response |
|---|---|---|
GET /entries/{entry_id}/review-requests |
status (optional) |
{"requests": [request]}, newest first |
GET /review-requests/{request_id} |
{"request": request, "entry": entry} |
|
GET /review-requests |
view, status, cursor, limit |
{"requests": [request with an extra entry field], "next_cursor"} |
GET /review-requests/waiting-count |
{"waiting": 2} |
view for the queue is waiting_on_me (open rounds where your response is pending), requested_by_me, or all (default). The queue uses an id cursor: pass next_cursor back as cursor; null means the end.
Queue rows and the single-request read include entry: {id, name, entry_type, asset_class, status, has_thumbnail, preview_status, preview_kind, review_status, face_object_id, date_updated}. When face_object_id differs from the round’s object_id, the round reviews an older version.
POST /review-requests/{request_id}/respond
Section titled “POST /review-requests/{request_id}/respond”{ "response": "changes_requested", "note": "Pivot is off; see markup", "assign_to": 7, "attachment_ids": [302] }| Field | Type | Description |
|---|---|---|
response |
string, required | approved or changes_requested. |
note |
string | Up to 1,000 characters, with @name mentions. |
assign_to |
integer | Only with changes_requested: who should make the fix (defaults to the requester). 422 with approved. |
attachment_ids |
array | Up to 10. Replaces the attachments on your previous response. |
Only listed reviewers can respond (403 FORBIDDEN, including admins who are not listed). You can respond again while the round is open; the new response replaces the old one. Closed rounds return 409. Returns {"request": <request>}.
PATCH /review-requests/{request_id}
Section titled “PATCH /review-requests/{request_id}”{ "add_reviewer_ids": [18], "remove_reviewer_ids": [15] }Requester or admin, open rounds only. Up to 20 ids per list. The reviewer list can never become empty (422). Returns {"request": <request>}.
POST /review-requests/{request_id}/cancel
Section titled “POST /review-requests/{request_id}/cancel”No body. Requester or admin, open rounds only. If nobody has responded yet, the version goes back to draft; otherwise its derived state stays as it is. Returns {"request": <request>}.
Unknown request ids return 404 REVIEW_REQUEST_NOT_FOUND.
Review changes appear in the activity feed as review.change, review.request, review.response and review.cancel, in the catalog change feed as entity review, and can be sent to webhooks.
