> ## Documentation Index
> Fetch the complete documentation index at: https://docs.noresponse.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Reviews

> Open a review for an account, assign it, discuss it, resolve it against an exact decision, and save review filters.

A review is your team's case for one account. It records who works on the account, how urgent it is, what the team said and how the team concluded. Use a review when a decision of `review`, or another signal, needs a person to look at an account.

A review does not change the account's risk. It does not report an outcome or apply an enforcement. To record what your team learned, [report an outcome](https://docs.noresponse.org/reference/investigation.md#report-an-outcome) as well.

All review operations need an API key with the `management` scope. A workspace member needs review access in the environment. A key acts only in its own workspace and environment. The other environment answers 404 [`not_found`](https://docs.noresponse.org/problems/not-found).

## Find accounts that need a review

`POST /v1/accounts/search` with `needs_action: true` returns the retained accounts that wait for your team. Each match gives `waiting_for` reasons. This filter also needs review access. [Limits, permissions and page changes](https://docs.noresponse.org/reference/investigation.md#limits-permissions-and-page-changes) describes it.

## Open a review

`POST /v1/workspaces/{workspace_id}/reviews/{environment}` opens a review for one account:

```http Open a review theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
POST /v1/workspaces/tnt_01JA2Z8K6Q4V7W9XBCDEFGHJKM/reviews/live
Content-Type: application/json

{"account_id":"acct_7f3a9c","priority":"high","assignee":"usr_customer_admin"}
```

* Send `account_id`. The account must have a retained assessment in this environment. Otherwise the answer is 404.
* `priority` is `low`, `normal`, `high` or `urgent`. The default is `normal`.
* `assignee` is optional. It must be the external user id of an active member who can read reviews in this environment.

The answer is 201 with the review. Its id starts with `rev_`. A new review has `status: "open"` and `version: 1`. `opened_risk_version` is the account's risk version when the review opened.

An account has one review in each environment. A second review for the same account answers 409 [`review_exists`](https://docs.noresponse.org/problems/review-exists). Get the existing review and update it. A resolved review stays the account's review. Reopen it to continue.

A review expires with the environment's retention. `expires_at` gives the time. After expiry, you can open a new review for the account.

## Read the review

| Field | Meaning |
| - | - |
| `status` | `open`, `in_progress` or `resolved`. |
| `priority` | `low`, `normal`, `high` or `urgent`. |
| `assignee` | The external user id of the member at assignment. Absent without an assignee. |
| `assignee_principal_id` | The member's permanent id. It starts with `mem_`. Absent without an assignee. |
| `version` | The review version. Every change increments it. |
| `opened_risk_version` | The account risk version when the review opened. |
| `current_risk_version` | The account risk version now. |
| `resolution` | The current resolution. Absent while the review is open or in progress. |

Get one review with `GET /v1/workspaces/{workspace_id}/reviews/{environment}/{review_id}`.

## List reviews

`GET /v1/workspaces/{workspace_id}/reviews/{environment}` lists the retained reviews, newest first. All filters are optional and combine with AND:

| Query parameter | Matches |
| - | - |
| `status` | `open`, `in_progress` or `resolved`. |
| `priority` | `low`, `normal`, `high` or `urgent`. |
| `assignee` | The member's external user id. |
| `assignee_principal_id` | The member's immutable `mem_` id. |
| `unassigned` | `true` for reviews with no assignee. |

Use only one of `assignee`, `assignee_principal_id` and `unassigned=true`. A combination answers 400 [`invalid_request`](https://docs.noresponse.org/problems/invalid-request).

`POST /v1/workspaces/{workspace_id}/reviews/{environment}/query` takes the same filters, `limit` and `cursor` in a JSON body.
It also accepts `account_id` for this exact account, before pagination. Keep that identifier out of the GET URL. Pages have 1 to 100 items, 50 by default. Pass `next_cursor` back as `cursor` with the same filters. The cursor is bound to its filters.

```ts TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { NoResponse } from "@noresponse/node";
import { reviewsList } from "@noresponse/node/funcs/reviews-list.js";

const nr = new NoResponse({ apiKey: process.env.NORESPONSE_API_KEY! });
const pages = await reviewsList(nr.core, {
  workspaceId: "tnt_01JA2Z8K6Q4V7W9XBCDEFGHJKM",
  environment: "live",
  status: "open",
  unassigned: true,
});
for await (const page of pages) {
  if (!page.ok) throw page.error;
  for (const review of page.value.result.items) {
    console.log(review.id, review.accountId, review.priority);
  }
}
```

```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import os
from noresponse import NoResponse

with NoResponse(api_key=os.environ["NORESPONSE_API_KEY"]) as nr:
    page = nr.reviews.list(
        workspace_id="tnt_01JA2Z8K6Q4V7W9XBCDEFGHJKM",
        environment="live",
        status="open",
        unassigned=True,
    )
    while page is not None:
        for review in page.result.items:
            print(review.id, review.account_id, review.priority)
        page = page.next()
```

## Change a review

Every change sends `expected_version`, the review version that you read. NoResponse applies the change only when the review still has that version. Otherwise the answer is 409 [`review_version_conflict`](https://docs.noresponse.org/problems/review-version-conflict). Get the review again and check what changed before you send the new version.

`PATCH /v1/workspaces/{workspace_id}/reviews/{environment}/{review_id}` changes `status`, `priority` or `assignee`. Send at least one of them:

```json Assign and start a review theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "expected_version": 1,
  "status": "in_progress",
  "priority": "urgent",
  "assignee": "usr_customer_admin"
}
```

* `status` accepts `open` or `in_progress`. Use the resolve operation to resolve a review.
* `assignee: null` unassigns the review.
* A status change on a resolved review reopens it. The review then has no current resolution. Its resolution history stays.

## Add comments

`POST /v1/workspaces/{workspace_id}/reviews/{environment}/{review_id}/comments` adds a comment:

```json Add a comment theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "expected_version": 2,
  "body": "Same card fingerprint as two blocked accounts."
}
```

`body` has 1 to 2,000 characters. Each comment records the review version that it created. A comment also increments the review version.

`GET .../comments` pages through the comments, 1 to 100 per page. A review has at most 1,000 comments. The next comment answers 409 [`review_limit_reached`](https://docs.noresponse.org/problems/review-limit-reached).

## Link evidence

A subject links evidence to a review. A link records investigation context only. It does not change account risk, report an outcome or apply an enforcement.

`POST /v1/workspaces/{workspace_id}/reviews/{environment}/{review_id}/subjects` adds a link. `kind` is one of these:

* `account`: a retained account.
* `assessment`: an exact assessment revision. Send `assessment_id` and `assessment_version`.
* `issued_credential`: a key association from [Associate keys issued by your platform](https://docs.noresponse.org/reference/investigation.md#associate-keys-issued-by-your-platform). Send `credential_id`.

```json Link an assessment revision theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "expected_version": 3,
  "kind": "assessment",
  "account_id": "acct_7f3a9c",
  "assessment_id": "asm_01JA2Z8K6Q4V7W9XBCDEFGHJKM",
  "assessment_version": 2,
  "reason": "First login from the shared device."
}
```

`reason` has 1 to 500 characters. Every reference must belong to its `account_id` in this environment. Each link keeps the account `risk_version` from when you added it. A review holds at most 100 links. The next link answers 409 [`review_limit_reached`](https://docs.noresponse.org/problems/review-limit-reached).

`GET .../subjects` lists the links. `DELETE .../subjects/{subject_id}` with `expected_version` deletes one link and answers 200 with the review. The source account, assessment or key association does not change. Deleting the source data also deletes its links.

## Resolve a review

`POST /v1/workspaces/{workspace_id}/reviews/{environment}/{review_id}/resolve` resolves a review against the exact account decision that your team looked at:

```json Resolve a review theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "expected_version": 4,
  "expected_risk_version": 7,
  "conclusion": "abuse",
  "note": "Confirmed token resale with two linked accounts."
}
```

* `expected_risk_version` is the account's `current_risk_version` that your team reviewed.
* `conclusion` is `benign`, `abuse` or `inconclusive`.
* Send a `note` of 1 to 2,000 characters.

NoResponse answers 409 [`review_version_conflict`](https://docs.noresponse.org/problems/review-version-conflict) in these cases:

* The review version or the account risk version changed.
* The review is already resolved.
* The account inputs changed and the decision is not current.
* NoResponse did not capture the decision at that risk version.

The resolution records the review version, risk version, assessment id and assessment version. Use them to get the exact decision that your team resolved with [Search and get exact decisions](https://docs.noresponse.org/reference/investigation.md#search-and-get-exact-decisions).

A resolution is your team's conclusion, not a fact about the user. It does not report an outcome or apply an enforcement. Report the outcome separately when your team is certain.

### Read resolution history

Resolutions do not change. Reopening and resolving again adds a new one. `GET .../resolutions` pages through them, 1 to 100 per page. Pass `next_before_version` back as `before_version` to get older resolutions.

## Save review views

A view is a saved review filter. It uses the same filters as the review list: `account_id`, `status`, `priority`, `assignee`, `assignee_principal_id` and `unassigned`.

`POST /v1/workspaces/{workspace_id}/views/{environment}` saves a view:

```ts TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { reviewsCreateView } from "@noresponse/node/funcs/reviews-create-view.js";

const view = await reviewsCreateView(nr.core, {
  workspaceId: "tnt_01JA2Z8K6Q4V7W9XBCDEFGHJKM",
  environment: "live",
  body: {
    name: "Urgent and unassigned",
    shared: true,
    filter: { status: "open", priority: "urgent", unassigned: true },
  },
});
if (!view.ok) throw view.error;
console.log(view.value.result.id);
```

```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
view = nr.reviews.create_view(
    workspace_id="tnt_01JA2Z8K6Q4V7W9XBCDEFGHJKM",
    environment="live",
    name="Urgent and unassigned",
    shared=True,
    filter_={"status": "open", "priority": "urgent", "unassigned": True},
)
print(view.result.id)
```

* A private view (`shared: false`) is visible only to its owner.
* A shared view is visible to everyone with review access in the environment.
* Only the owner can change or delete a view. A change or delete of a view that you do not own answers 404 [`not_found`](https://docs.noresponse.org/problems/not-found). `owner_type` is `user` for a member or `api_key` for an API key. `owner_principal_id` is the permanent id of that member or key.
* NoResponse saves an `assignee` filter as the member's `assignee_principal_id`.
* `name` has 1 to 100 characters.

`GET .../views/{environment}` lists your private views and all shared views. `PATCH .../{view_id}` replaces the name, sharing and filter. `DELETE .../{view_id}` deletes the view and answers 204. Both send `expected_version`, the view version that you read. A new view sends no `expected_version`, or 0. A stale version answers 409 [`review_version_conflict`](https://docs.noresponse.org/problems/review-version-conflict).

An owner has at most 100 views. An environment has at most 100 shared views. Another view answers 409 [`review_limit_reached`](https://docs.noresponse.org/problems/review-limit-reached).

## Export a review

`GET /v1/workspaces/{workspace_id}/reviews/{environment}/{review_id}/export` returns the review with its comments, resolutions and evidence links. An API key needs `management` and one of `exports`, `history` or `audit_manage`. A member needs both review access and export access. NoResponse records the download in the audit log before it returns data. If it cannot record the download, the export fails.

The export holds at most 10,000 records and 8 MiB. A larger review answers 413 [`review_export_too_large`](https://docs.noresponse.org/problems/review-export-too-large), never a partial export. Use the paged comment and resolution operations instead.

[Account exports](https://docs.noresponse.org/reference/investigation.md#export-an-account) do not include reviews.

## Related pages

> [card] [Feedback and investigations](https://docs.noresponse.org/reference/investigation.md)
>
> Report outcomes, search accounts and export account history.

> [card] [Connect an agent](https://docs.noresponse.org/guides/agents.md)
>
> The MCP tools for reviews and saved views.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.