---
title: Access reviews
description: Record periodic reviews of who has access, with a decision per
  item. Records only for now.
url: https://hexelstudio.com/docs/identity/access-reviews
---

<Warning>
Access reviews are records only. A `REVOKE` decision on an item does not remove the access it describes: detach the policy, remove the group member or delete the credential yourself. Hexel Studio does not fill a review with items; you add them. Review schedules are stored but never run, so no review is created from a schedule. The `reviewer_user_id` of a review is not enforced: anyone with `iam:review:manage` can decide its items. This API may change.
</Warning>

## What works today

- Reviews, items and schedules are stored in your organization.
- Each decision records who made it (`reviewed_by_user_id`) and when (`reviewed_at`).
- Nobody can decide an item about themselves.

Use it as a log of a review you run yourself. To find what to review, list [policy attachments](https://hexelstudio.com/docs/identity/write-policies), [group members](https://hexelstudio.com/docs/identity/groups) and [service users and their keys](https://hexelstudio.com/docs/identity/service-users).

## Permissions

| Action | What it allows |
| --- | --- |
| `iam:review:read` | Read reviews, items and schedules. |
| `iam:review:write` | Create reviews, items and schedules, change a review's status and delete schedules. |
| `iam:review:manage` | Decide items. |

## Reviews

| Field | Type | Description |
| --- | --- | --- |
| `review_type` | string | `USER_ACCESS`, `ROLE_MEMBERS`, `WORKSPACE` or `FULL_ORG`. |
| `scope_type`, `scope_id` | string | Optional. What the review covers, for example a workspace. Stored only. |
| `reviewer_user_id` | string | The person expected to do the review. Stored only. |
| `due_date` | string | Timestamp the review is due. |

The response adds `id` (`ar_...`), `organization_id`, `status` (starts as `PENDING`), `completed_at`, `created_at` and `updated_at`.

```bash
curl -X POST https://api.hexelstudio.com/v1/reviews \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "review_type": "USER_ACCESS",
    "reviewer_user_id": "<user-id>",
    "due_date": "2026-11-30T00:00:00Z"
  }'
```

```json
{
  "id": "ar_...",
  "organization_id": "<org-id>",
  "review_type": "USER_ACCESS",
  "status": "PENDING",
  "reviewer_user_id": "<user-id>",
  "due_date": "2026-11-30T00:00:00Z",
  "created_at": "2026-10-20T09:00:00Z",
  "updated_at": "2026-10-20T09:00:00Z"
}
```

| Call | Permission | Response |
| --- | --- | --- |
| `POST /v1/reviews` | `iam:review:write` | `201` with the review |
| `GET /v1/reviews` | `iam:review:read` | Array of your organization's reviews, earliest due first. Filter with `?status=`. |
| `GET /v1/reviews/{review_id}` | `iam:review:read` | The review, or `404` |
| `PATCH /v1/reviews/{review_id}/status` | `iam:review:write` | `{"status": "..."}` |

### Change a review's status

Send `{"status": "..."}` with `PENDING`, `IN_PROGRESS`, `COMPLETED` or `CANCELLED`. Any status can follow any other. `COMPLETED` also sets `completed_at`. Another value answers `400`.

```bash
curl -X PATCH https://api.hexelstudio.com/v1/reviews/<review-id>/status \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "COMPLETED"}'
```

## Items

An item is one piece of access to review, such as "this member has this policy".

| Field | Type | Description |
| --- | --- | --- |
| `subject_type` | string | Who has the access, for example `USER` or `SERVICE_USER`. |
| `subject_id` | string | ID of that member or service user. They cannot decide this item. |
| `access_type` | string | Kind of access, for example `POLICY` or `GROUP`. |
| `access_value` | string | The access itself, for example a policy or group ID. |

These fields are stored as given and not checked against your organization.

```bash
curl -X POST https://api.hexelstudio.com/v1/reviews/<review-id>/items \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subject_type": "USER",
    "subject_id": "<user-id>",
    "access_type": "POLICY",
    "access_value": "<policy-id>"
  }'
```

The response is `201 Created` with the item: `id` (`ari_...`), `review_id`, the four fields above, `current_status` (`PENDING`) and `created_at`.

| Call | Permission | Response |
| --- | --- | --- |
| `POST /v1/reviews/{review_id}/items` | `iam:review:write` | `201` with the item |
| `GET /v1/reviews/{review_id}/items` | `iam:review:read` | Array of the review's items |
| `PATCH /v1/reviews/{review_id}/items/{item_id}` | `iam:review:manage` | The decided item |

### Decide an item

| Field | Type | Description |
| --- | --- | --- |
| `decision` | string | **Required.** `APPROVE`, `REVOKE` or `FLAG`. |
| `comment` | string | Optional. |

```bash
curl -X PATCH https://api.hexelstudio.com/v1/reviews/<review-id>/items/<item-id> \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"decision": "REVOKE", "comment": "No longer on the team"}'
```

```json
{
  "id": "ari_...",
  "review_id": "<review-id>",
  "subject_type": "USER",
  "subject_id": "<user-id>",
  "access_type": "POLICY",
  "access_value": "<policy-id>",
  "current_status": "REVOKED",
  "reviewer_decision": "REVOKE",
  "reviewer_comment": "No longer on the team",
  "reviewed_by_user_id": "<your-user-id>",
  "reviewed_at": "2026-10-20T09:10:00Z",
  "created_at": "2026-10-20T09:00:00Z",
  "decision": "REVOKE"
}
```

`current_status` becomes `APPROVED`, `REVOKED` or `FLAGGED`. `decision` repeats `reviewer_decision`. A later decision replaces an earlier one.

<Note>
`REVOKE` only records the decision. The member in this example keeps the policy until you detach it.
</Note>

| Status | When |
| --- | --- |
| `400` | `decision` is not `APPROVE`, `REVOKE` or `FLAG`. |
| `403` | You are the item's subject (`the subject of a review item cannot decide it`), or you lack `iam:review:manage`. |
| `404` | No such review in your organization, or no such item in the review. |

## Schedules

A schedule describes a recurring review. Schedules are stored but nothing runs them.

| Field | Type | Description |
| --- | --- | --- |
| `review_type` | string | `USER_ACCESS`, `ROLE_MEMBERS`, `WORKSPACE` or `FULL_ORG`. |
| `frequency_days` | integer | How often the review should happen. |
| `scope_type`, `scope_id` | string | Optional. |
| `reviewer_user_id` | string | The person expected to do the review. |
| `next_review_date` | string | Timestamp of the next review. |
| `is_enabled` | boolean | Default `true`. |

The response adds `id` (`ars_...`), `organization_id`, `last_review_date`, `created_at` and `updated_at`.

```bash
curl -X POST https://api.hexelstudio.com/v1/reviews/schedules \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "review_type": "FULL_ORG",
    "frequency_days": 90,
    "reviewer_user_id": "<user-id>",
    "next_review_date": "2027-01-15T00:00:00Z"
  }'
```

| Call | Permission | Response |
| --- | --- | --- |
| `POST /v1/reviews/schedules` | `iam:review:write` | `201` with the schedule |
| `GET /v1/reviews/schedules` | `iam:review:read` | Array of your organization's schedules |
| `DELETE /v1/reviews/schedules/{schedule_id}` | `iam:review:write` | `204`, or `404` |

There is no endpoint to update a schedule. Delete it and create a new one.
