# Reviews API reference

> Canonical HTML: https://screenrig.ai/docs/api/reviews
> Source SHA-256: e0c997a2ab813502f0d99a41d2ed1d3d6b26dbaf1a3d7e514c1cce0278ff8422

Reviews of agent changes. This group has 7 operations, each with its authentication, parameters, request body fields, request template and responses. The [API overview](https://screenrig.ai/docs/api.md) covers services, authentication, requests and retries, problem codes and versioning.

Replace uppercase placeholders. Save a schema-valid JSON body in request.json when required. Cookie examples use a local cookie jar from an authenticated session.

## Reviews of agent changes

### GET /api/reviews

**List Reviews**

The project's reviews (docs/reviews.md): playlist and screen changes proposed under Propose access, the campaign, screen folder and move writes that reach a protected screen (kind protected_change, docs/screen-scope.md), and for an ad network the creatives buyers submitted (kind ad_creative).

Newest first, at most limit (default 50, at most 100) per page; pass next_cursor back as cursor with the same filters. state pending (the default), decided (every other state) or all; kind narrows to one kind.

A dashboard session reads every kind; a bearer reads the kinds its capabilities cover (playlists for playlist_change, screens for screen_change and protected_change, advertising for ad_creative), and a kind it cannot read is an empty page. pending_count counts the pending reviews of every kind the caller sees.

A caller limited to screens sees only the screen_change and protected_change reviews whose whole reach is inside its scope (a review that selects screens by tag never is) and the playlist_change reviews; ad_creative reviews are hidden from it, and pending_count counts what it sees.


Operation: listReviews.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| state | query | optional |  |
| kind | query | optional |  |
| limit | query | optional |  |
| cursor | query | optional |  |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/reviews'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One page of reviews. | ReviewList (application/json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/reviews/{id}

**Get Review**

One review. A playlist change adds preview: the live and the proposed playlist in the stored shape (pages with resolved_media); live is null for a create. A bearer that cannot read the review's kind (see listReviews) gets not_found, as does a caller limited to screens for a review whose reach is not inside its scope.


Operation: getReview.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/reviews/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The review. | Review (application/json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/reviews/{id}/locate

**Locate Review**

The project a review belongs to, for the email link: a signed-in person who is a member of that project gets its id and name and switches to it (PUT /api/session/project) before reading the review. Anyone else gets not_found. Works whatever project the session has selected.


Operation: locateReview.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/reviews/RESOURCE_ID/locate'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The review's project. | ReviewLocation (application/json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/reviews/{id}/approve

**Approve Review**

Approves a pending review as a person who can approve (the project owner or a member with can_approve; else approver_required). The flag is read again inside the decision's transaction, so a person whose approval was turned off meanwhile decides nothing (approver_required). Staff shadow sessions cannot decide.

A playlist update is applied with the base revision as If-Match; when the live playlist moved on the review becomes stale and the call answers review_stale.

A create or update whose tag, folder or all selectors would now play media they did not resolve when proposed, that the review does not name and that the live version does not already play, also becomes stale (review_stale).

A screen change replays its stored request (a tag resolves now): the call applies screens for a few seconds and the rest continue in the background while screen_change.result.applying is true.

A screen change waiting on a pending playlist review answers review_blocked naming it; one whose playlist has no approved version answers playlist_not_approved. include_dependents also approves, in order, the screen changes waiting on this playlist review. Events of the applied change carry the person as actor and the proposing agent as agent.

The answer carries warnings (PlaylistWarning): screen_fact_missing when the approved change puts pages whose iframe src or application params read a screen token on screens that lack it. A decided review is review_not_pending; repeating the same decision replays.

A review marked protected (a protected_change, or a screen_change that reached a protected screen) is decided by an approver other than the proposer and the person responsible for the proposing agent: second_approver_required (403) otherwise, and when the proposer cannot be traced to a person (docs/screen-scope.md).

An approver limited to screens decides only a review whose whole reach is inside the scope (outside_screen_scope, 403). A protected_change is applied by this request, as the approving person, at the revision it was proposed against; a campaign or folder that changed or went since, or screens that moved on, make the review stale (review_stale, 409).

A dependent that cannot be approved reports the same codes in dependents[].problem.


Operation: approveReview.

- Authentication: dashboardSession
- Request body: ReviewApproveRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| include_dependents | optional | boolean | Also approve, in order, the screen changes waiting on this playlist review. |

#### Request template

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/reviews/RESOURCE_ID/approve'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The approved review, and the dependents' outcomes. | ReviewDecisionResult (application/json) |
| 403 | approver_required, second_approver_required or outside_screen_scope. | Problem (application/problem+json) |
| 409 | review_not_pending, review_stale, review_blocked or playlist_not_approved. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/reviews/{id}/reject

**Reject Review**

Rejects a pending review with a reason the proposer reads. Approver only, as approveReview; an approver limited to screens decides only a review inside its scope (outside_screen_scope), and no person limited to screens decides an ad creative. Nothing changes on glass; a rejected playlist create stays unapproved.


Operation: rejectReview.

- Authentication: dashboardSession
- Request body: ReviewRejectRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| reason | required | string | minLength: 1 maxLength: 500 |

#### Request template

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/reviews/RESOURCE_ID/reject'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The rejected review. | ReviewDecisionResult (application/json) |
| 403 | outside_screen_scope - the caller is limited to screens and the screen, screen folder, selector or audience the request names or reaches is outside them (docs/screen-scope.md). An id outside the scope and an id that does not exist get the same answer; errors[0].screen_scope carries the caller's scope, and the write is refused whole, never narrowed. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/reviews/{id}/request-changes

**Request Review Changes**

Asks the proposer for changes with a note they read; the proposer proposes again. Approver only, as approveReview. For an ad creative the buyer reads the note on the creative. A scoped approver is limited as in rejectReview.


Operation: requestReviewChanges.

- Authentication: dashboardSession
- Request body: ReviewChangesRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| note | required | string | minLength: 1 maxLength: 500 |

#### Request template

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/reviews/RESOURCE_ID/request-changes'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The review, state changes_requested. | ReviewDecisionResult (application/json) |
| 403 | outside_screen_scope - the caller is limited to screens and the screen, screen folder, selector or audience the request names or reaches is outside them (docs/screen-scope.md). An id outside the scope and an id that does not exist get the same answer; errors[0].screen_scope carries the caller's scope, and the write is refused whole, never narrowed. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/reviews/{id}/withdraw

**Withdraw Review**

Withdraws a pending playlist, screen or protected_change review. A bearer withdraws only what its own agent or service client proposed (forbidden otherwise); a person withdraws as an approver or as the proposer's responsible person; a person limited to screens withdraws only a review inside its scope (outside_screen_scope).

A withdrawn playlist create stays unapproved.


Operation: withdrawReview.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/reviews/RESOURCE_ID/withdraw'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The withdrawn review. | ReviewDecisionResult (application/json) |
| 403 | outside_screen_scope - the caller is limited to screens and the screen, screen folder, selector or audience the request names or reaches is outside them (docs/screen-scope.md). An id outside the scope and an id that does not exist get the same answer; errors[0].screen_scope carries the caller's scope, and the write is refused whole, never narrowed. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |


## Sitemap

See the [public documentation sitemap](https://screenrig.ai/sitemap.md).
