Identity & Sign-InHow Access Reviews & Access Requests Work
On this page0

Reference

Included in: All plans. Which Plan Includes What

How Access Reviews & Access Requests Work

What a review records and who it goes to, what a decision does, and the two approvals a request passes. The steps on screen are in Run an access review and Handle access requests.

Access Reviews

A review is run as a campaign. Launching one takes a list, once, of what each person holds at that moment. The list does not change afterwards: access given after the launch is not added to it, and is picked up by the next campaign.

  1. Who is covered. Every active person, or the active people in one department.
  2. What is listed. Each Zonitix group and role the person holds becomes one line, called an item. A campaign can also list their apps: the ones their persona gives them, the ones a connected system reports them using, or both. An app that is in use but not in the persona is marked as drift.
  3. Who reviews. Each person’s items go to that person’s manager. A person with no manager on record has their items sent to whoever created the campaign.
  4. What a reviewer decides. Keep or revoke, one item at a time or many together, with an optional note. The campaign completes by itself when the last item is decided.

When The Reviewer Cannot Act

A manager who has left, or whose account is disabled, cannot open their items. Zonitix does not leave those items waiting. It hands them to the first of these people whose account is active: the reviewer’s own manager, then whoever created the campaign, then a person who administers access reviews. It never picks the person whose access is under review. This is done when the campaign is launched, again when a reviewer is offboarded, and once a day for anything missed. The new reviewer is told by email, where email is set up, and the item records who it came from.

If none of those people can take the item, Zonitix leaves it where it is, does not guess, and writes a warning to the audit log. An administrator of access reviews then reassigns it by hand.

Rules On A Decision

  • Nobody reviews their own access. This holds for everyone, administrators included, and it is the separation of duties an auditor looks for.
  • A reviewer decides only their own items. A person who administers access reviews may decide for a reviewer, and may hand an item to someone else.
  • Two things cannot be revoked in a review: the group every active person belongs to, and the roles and groups of the primary administrator.
  • A closed or archived campaign is a record. Its items can no longer be decided or reassigned.

What Revoke Does

Revoking a Zonitix group or role removes it from the person straight away. Revoking an app is different, because the account lives in another system: Zonitix starts a removal run that waits for IT to approve it. After that approval the run gains the step that removes the account: an account step for a connected app, a task for IT to do by hand for anything else. Until then the access is still there, and the campaign’s download says so.

Access Requests

A request names one app or software licence and the person it is for. It passes two approvals before anything is granted, and the two must come from two different people unless an administrator is acting.

  1. The request. A person asks for themselves and may say why. An approver, or someone who manages users, may also file a request for another person.
  2. First approval: should this person have it? The approvers are notified. One of them approves or rejects. An approver cannot approve a request they filed, or one that is for themselves; holders of the administrator role are exempt from that rule. Anyone who may approve may still reject.
  3. A check on the person. If the person the request is for is no longer an active employee when the approver says yes, Zonitix rejects the request instead. Nobody is exempt from this check.
  4. Second approval: IT sets it up. Approving starts a run that waits for IT. The person who gave the first approval is counted as the one who asked for this run, and whoever asks for a run may not approve it. Holders of the administrator role are exempt from that rule too: their approval is allowed, and the run records it as a self-approval exception. The separate-approver rule is a workflow policy that is on by default. When IT approves, the run gains the step that grants the app: an account step for a connected app, a task for IT to do by hand for anything else.
  5. The outcome. The requester is told when the access has been provisioned, or that the request was rejected or declined.

If something here is not working as you expect, start with When something goes wrong.

The plain explanation is above. This part is for IT and developers: the exact settings, permission names, API addresses and data fields behind it. Closed: select the heading above to open it. While it is closed, your browser’s find-on-page (Ctrl+F or Cmd+F) does not search this part.

Review Endpoints

Session routes on your own instance’s API address, https://<your-instance>-api.zonitix.com. Both features are served by the user-management service; the two runs are created in the workflow service, described in How Workflows Run (Orchestration).

MethodPathPurposeAuth
GET/user-management/access-reviewsList access-review campaigns (one at /access-reviews/:id, CSV at /access-reviews/:id/export). Query: ?archived=1 returns the archived campaigns instead of the current ones. The list is not paged; newest first.
returns 200 AccessReviewCampaign[] (typed under Response Objects), every campaign of the kind asked for in one reply (no paging). GET /:id takes ?limit (default 200, 1–500) and ?offset and returns an AccessReviewCampaignDetail { campaign, items: AccessReviewItem[], total, limit, offset }, items ordered by person then access type | 404 unknown campaign. /:id/export returns text/csv
Session + view_access_reviews | manage_access_reviews
POST/user-management/access-reviewsStart an access-review campaign. Repeating the call starts another campaign; nothing deduplicates by name.
body An AccessReviewCampaignCreate: { name (required), description, scopeType: 'all' (default) or 'department', scopeValue (the department; required for a department scope), dueDate, externalSource: none | persona (default) | actual | both }
returns 201 the AccessReviewCampaign | 400 no name, or a department scope with no department
guard Repeat: starts a second review with its own items; names are not checked for duplicates
Session + manage_access_reviews
POST/user-management/access-reviews/:id/closeClose a campaign (also /archive and /restore; DELETE removes it). No body. Each is guarded by the campaign's state, so a repeat answers 409 instead of acting twice.
body No body, for close, archive, restore and delete alike.
returns 200 the AccessReviewCampaign | 404 | 409 when it is not active (close), already archived (archive) or not archived (restore). DELETE: 200 { deleted: true, id } | 409 when any item has been decided — archive it instead
guard Repeat: 409; a closed review is no longer active, an archived one is already archived, and a restored one is no longer archived
Session + manage_access_reviews
GET/user-management/access-reviews/my-queueThe review items waiting for your decision, from active campaigns only. Query: ?limit (default 50, 1–200), ?offset (default 0), ?filter = all (default), attention or expected.
returns 200 an AccessReviewQueue (typed under Response Objects) { items: AccessReviewItem[], total, limit, offset, filter, stats: { all, attention, expected, privileged, drift, people } }
Session (any signed-in user; only items assigned to you are returned)
GET/user-management/access-reviews/reviewersThe people an item can be reassigned to: every active holder of certify_access or manage_access_reviews. No query parameters; not paged, sorted by email.
returns 200 an array of { email:string }
Session + manage_access_reviews
GET/user-management/access-reviews/grant-detailThe permissions behind one role or group, for the expandable row of a review item. Query: ?accessType = role or group, ?accessId (the role or group id).
body Query: ?accessType and ?accessId; accessId is required for a role or a group.
returns 200 an AccessGrantDetail (typed under Response Objects): { accessType, accessId, accessLabel, external:false, permissions:string[] (sorted), permissionCount, privileged } for a role or group; for any other accessType { accessType, accessId, external:true, permissions:null, permissionCount:null, privileged:false } | 400 { error:'accessId is required.' } | 403 when you hold none of the three permissions and no item for that grant is assigned to you | 503 when your review assignments could not be checked; try again
guard read-only and safe to repeat
Session + certify_access | view_access_reviews | manage_access_reviews, or the reviewer an item for that grant is assigned to
POST/user-management/access-reviews/items/:id/decideKeep or revoke one reviewed grant (several at once at /access-reviews/items/bulk-decide). A second decision on the same item answers 409.
body An AccessReviewDecision: { decision: 'keep' | 'revoke' (required), note }. Bulk: { ids (1–500), decision, note }
returns 200 the AccessReviewItem | 400 | 403 for your own access, an item not assigned to you (without manage_access_reviews), or the primary administrator's access | 404 | 409 already decided, campaign no longer active, or a revoke of baseline access. Bulk: 200 { decision, ok, failed, results: [{ id, ok, error }] }; items that cannot be decided are skipped, not fatal
guard Repeat: 409; an item can be decided once
Session + certify_access | manage_access_reviews, or the reviewer the item is assigned to
POST/user-management/access-reviews/items/:id/reassignHand a review item to another reviewer (several at /access-reviews/items/bulk-reassign). Safe to repeat: reassigning to the current reviewer changes nothing.
body An AccessReviewReassign: { reviewerEmail }. Bulk: { ids (1–500), reviewerEmail }
returns 200 the AccessReviewItem | 400 no reviewerEmail, the person whose access it is, or an inactive user | 404 item or user not found | 409 already decided. Bulk: 200 { ok, failed, results }
guard Repeat: safe while the item is still pending; the reviewer is set again
Session + manage_access_reviews
GET/user-management/access-reviews/schedulesRecurring review schedules. Not paged; newest first.
returns 200 AccessReviewSchedule[] (typed under Response Objects), every schedule in one reply
Session + view_access_reviews | manage_access_reviews
POST/user-management/access-reviews/schedulesCreate a recurring review (PATCH or DELETE /schedules/:id; run now at /schedules/:id/run; pause or resume at /schedules/:id/status). Repeating the POST creates another schedule.
body An AccessReviewScheduleWrite: { name (required), cadence: monthly | quarterly (default) | semiannual | annual | custom, intervalMonths (custom only, at least 1), scopeType: all | department, scopeValue, externalSource: none | persona | actual | both (default), dueDays (1–365, default 14), remind (default true), autoClose (default true), skipIfOpen (default false), firstRunAt (a date; default now) }. PATCH takes the same fields, all optional. Status: { status: 'active' | 'paused' }
returns 201 the AccessReviewSchedule | 400 no name. PATCH: 200 | 400 a firstRunAt that is not a date | 404. Run: 201 the launched campaign | 409 when the previous run is still open and skipIfOpen is on. DELETE: 200 { deleted: true, id }
guard Repeat: creates a second schedule; names are not checked for duplicates. Run: 409 only when skipIfOpen is on and the previous run is still open, otherwise each call launches another review
Session + manage_access_reviews
GET/user-management/access-reviews/liveCurrent access as the reviews see it, computed on request. Query: ?scopeType (all or department), ?scopeValue, ?externalSource (none, persona, actual or both; default both), ?driftOnly=1. Not paged: at most 2000 items.
returns 200 a LiveAccessSnapshot (typed under Response Objects) { generatedAt, scopeType, scopeValue, externalSource, userCount, total, driftCount, privilegedCount, providersActive, externalScanned, capped, items } — capped is true when the list was cut at 2000
Session + view_access_reviews | manage_access_reviews
POST/user-management/access-reviews/live/revokeRevoke a grant outside a campaign.
body A LiveAccessRevoke: { userId, accessType, accessLabel (all required), accessId (required for a role or a group), providerType }
returns 200 { revoked: true, runId } | 400 | 403 for the primary administrator's access | 404 user not found | 409 for baseline access, which is restored automatically
Session + manage_access_reviews
  • Three permissions. manage_access_reviews launches, closes, archives, reassigns, schedules and revokes from the live view. view_access_reviews reads campaigns, schedules, the live view and the CSV. certify_access decides items.
  • Assignment also authorises. The person an item is assigned to may read their own queue and decide their own items without holding certify_access. The queue route returns only the caller’s items, and the decide routes check each item again. Other reviewers’ items, the campaign list and campaign administration stay behind the permissions.

What A Campaign Holds

A campaign’s status is active, completed or archived. Its items are written in one transaction at launch, each with decision: "pending", the reviewer’s email and one of these kinds:

KindWhat it isWhat a revoke does
groupA Zonitix group the person is a member of. The baseline group “Everyone” is left out: every active person is put back in it automatically, so it cannot be revoked.Removed from the person at once.
roleA Zonitix role the person holds.Removed from the person at once.
app, licenseAn app or a software licence outside Zonitix: one the person’s persona gives them, one a connected system reports them using, or both, as the campaign was set up.A run is raised on the Master Access Revoke pipeline. It waits at the step “IT Deprovision Approval”.
externalSourceWhat is listed
noneNo apps. Only Zonitix groups and roles are reviewed.
personaThe apps and licences each person’s persona gives them. The default for a campaign started by hand.
actualThe apps a connected system reports the person using. One that is not in their persona is marked as drift.
bothBoth lists joined by app name. An app in the persona is never marked as drift; one seen only in use is. The default for a schedule.

An item is flagged privileged when it is the administrator role, when the role or group carries any permission whose name starts with manage_, or when it carries 15 permissions or more. The flag orders a reviewer’s queue (privileged first, then drift) and is a column of the CSV. It blocks nothing.

Reviewer Rerouting

A reviewer cannot act when no account has their email address, or when the account’s status is disabled, inactive, deleted, terminated, suspended, offboarded or archived. The replacement is the first of these that is an active account and is neither the person under review nor the reviewer being replaced:

OrderWhovia
1The manager of the reviewer who cannot actreviewer_manager
2The person who created the campaigncampaign_owner
3A person who holds manage_access_reviews, taking the first in alphabetical order of email addressreview_admin
ReasonWhen
reviewer_inactive_at_launchWhen the campaign is launched, for a person whose reviewer cannot act already.
reviewer_offboardedWhen a reviewer is offboarded: every undecided item assigned to them, in every active campaign.
sweepOnce a day, for any undecided item in an active campaign whose reviewer has since become unable to act, for example an account an administrator disabled.
  • What the item keeps. reroutedFrom, reroutedAt and rerouteReason. The move is audited as ACCESS_CERTIFICATION_REROUTED.
  • A decision always wins. An item is moved only if it is still undecided and still with the same reviewer at the moment of the write, so a decision or a reassignment made meanwhile is never overwritten.
  • No replacement. The item is left in place and ACCESS_CERTIFICATION_REROUTE_FAILED is written with the item ids. The daily pass tries again the next day.

Decisions And The CSV

A decision is claimed with one conditional write (undecided to decided) before any access is touched. The CSV at /access-reviews/:id/export carries every item of the campaign, with the columns Person, Access, Type, Source, Privileged, Decision, Applied, Decided by, Decided at, Reviewer and Note. Decision is what the reviewer chose; Applied is what happened to the access:

AppliedMeaning
n/aThe decision was to keep the access.
appliedA group or role was revoked and has been removed.
pending IT (run #…)An app or licence was revoked and the removal run has been raised. The run, not this column, records whether IT has finished it.
NOT appliedThe revoke was recorded but nothing was removed: the removal failed, the run could not be raised, or the person’s account no longer exists.
StatusWhen
400The decision is neither keep nor revoke; a batch names no items or more than 500; a reassignment names nobody, or the person under review, or an account that is not active.
403“You cannot certify your own access (separation of duties).” This applies to everyone, administrators included.
403“You can only decide review items assigned to you.” A holder of manage_access_reviews, or an administrator, may decide on a reviewer’s behalf.
403A revoke of a group or role held by the primary administrator.
404No campaign, item, schedule or active user answers to the id or email given.
409The item is already decided. Two decisions sent at the same moment cannot both win: the second gets this answer.
409The campaign is closed or archived, so its items can no longer be changed.
409A revoke of the baseline group; closing a campaign that is not active; deleting a campaign that holds decisions; “Run now” on a schedule whose previous campaign is still open while skip-if-open is on.
503Zonitix could not confirm which items are assigned to the caller. Access is refused; the call can be repeated.

Schedules

  • Cadence. monthly, quarterly, semiannual, annual, or custom with a number of months. Each campaign a schedule launches is due dueDays after launch (14 unless set, from 1 to 365) and is named after the schedule and the period.
  • The daily pass. Once a day Zonitix launches the schedules that are due, then looks at the open campaigns that schedules launched, then reroutes items whose reviewer cannot act.
  • Reminders. With remind on, each reviewer with undecided items is emailed when the due date is two days away or less, or has passed: at most once a day for each campaign.
  • Closing. With autoClose on, a scheduled campaign is closed on the day after its due date. Undecided items stay undecided and the close is audited as an early close.
  • One open run at a time. With skipIfOpen on, a schedule does not launch while its previous campaign is still active.

Reminders and automatic closing apply to campaigns a schedule launched. A campaign started by hand notifies its reviewers at launch and stays open until every item is decided or someone closes it.

Request Endpoints

MethodPathPurposeAuth
POST/user-management/access-requestsRequest access. Repeating the call files another pending request; nothing deduplicates.
body An AccessRequestCreate: { itemLabel (required), itemKind (default 'app'), providerType, justification, customFields (only keys in the custom-field list are kept, 500 characters each), userId (honoured only for an approver or manage_users; otherwise the request is for you) }
returns 201 the AccessRequest, status pending | 400 no itemLabel, or your account could not be resolved | 404 user not found
guard Repeat: files a second pending request; an earlier request for the same thing does not block it
Session + request_access | approve_access_request | manage_users
GET/user-management/access-requests/mineYour own requests. No query parameters; not paged, newest first.
returns 200 AccessRequest[] (typed under Response Objects), every request of yours in one reply
Session + request_access | approve_access_request | manage_users
GET/user-management/access-requestsAll requests, for approvers. Query: ?status to keep one status (for example pending). Not paged; newest first.
returns 200 AccessRequest[] (typed under Response Objects), every matching request in one reply
Session + approve_access_request | manage_users
POST/user-management/access-requests/:id/decideApprove or deny a request (several at /access-requests/bulk-decide). A request that is already decided answers 409; one whose provisioning run could not be created comes back with status failed and can be decided again.
body An AccessRequestDecision: { approve (true approves; anything else rejects), comment }. Bulk: { ids (1–500), approve, comment }
returns 200 the AccessRequest | 403 | 404 | 409 already decided. Bulk: 200 { approve, ok, failed, results: [{ id, ok, status, error }] }
guard a non-administrator cannot approve a request they submitted or one for their own access (403); rejecting it is allowed. Repeat: 409; a request that is already decided cannot be decided again
Session + approve_access_request | manage_users
GET/user-management/access-requests/custom-fieldsThe organisation's extra fields on the request form.
returns 200 { fields: [{ key, label, type: text | select | number | date, options }] }
guard the whole field list in one reply (no paging)
Session + request_access | approve_access_request | manage_users
POST/user-management/access-requests/custom-fieldsReplace the extra request fields. A replace-style write, safe to repeat. Audited as ACCESS_REQUEST_CUSTOM_FIELDS_SET.
body An AccessRequestFieldsWrite: { fields } — at most 20 fields; a label is cut to 60 characters and a select keeps up to 50 options; a malformed entry is skipped, not refused
returns 200 { fields } as stored
guard replaces the whole list. Repeat: safe; the same list is stored again
Session + manage_users
statusMeaning
pendingFiled and waiting for an approver (gate 1).
approvedAn approver said yes and a provisioning run exists. It is waiting for IT (gate 2). Nothing has been granted yet.
fulfilledIT completed the run. The access was provisioned.
declinedIT stopped the run at the provisioning step. Nothing was granted.
rejectedAn approver said no, or the person was no longer an active employee when the approver said yes. Final.
failedAn approver said yes but the provisioning run could not be created. Nothing was granted. Approving again retries it; rejecting closes it.
  • Gate 1. The approve route needs approve_access_request or manage_users. The move from pending (or failed) to approved is one conditional write, so two approvals cannot each raise a run.
  • Gate 2. The run is created on the Master Access Request pipeline and starts at the step IT Provisioning Approval, in the IT queue. It carries the first approver’s identity, and the workflow service counts that person among the run’s requesters. A requester may not sign the run’s approval unless they hold the administrator role: that approval is allowed and written to the run as a self-approval exception, and when the step is completed through the automation route it is audited as TASK_APPROVAL_SOD_OVERRIDE. The separate-approver rule is a workflow policy that is on by default; a refused attempt is audited as TASK_APPROVAL_DENIED_SOD.
  • How the status catches up. An approved request is compared with its run each time requests are listed: a completed run makes it fulfilled, a cancelled run declined.
StatusWhen
400No item was named; a batch names no requests or more than 500.
403“You cannot approve your own access request (separation of duties).” Returned when the approver filed the request or is the person it is for.
404No request or user answers to the id given.
409The request is already decided. Two approvals sent at the same moment cannot both raise a run: the second gets this answer.

What Is Audited

ActionWritten when
ACCESS_CERTIFICATION_STARTEDA campaign was launched, by hand or by a schedule.
ACCESS_CERTIFICATION_REROUTEDItems were moved off a reviewer who cannot act. The entry names both reviewers, the route taken and the reason.
ACCESS_CERTIFICATION_REROUTE_FAILEDItems sit with a reviewer who cannot act and no replacement was found. They were left in place.
ACCESS_CERTIFICATION_REASSIGNEDAn administrator of access reviews handed an item to another reviewer.
ACCESS_CERTIFIED_KEEPA reviewer kept an item.
ACCESS_REVOKED_BY_REVIEWA reviewer revoked an item: the group or role was removed, or the removal run was raised.
ACCESS_REVOKE_FAILEDA revoke was recorded but the removal did not happen.
ACCESS_REVOKE_NOOPA revoke was recorded for a person whose account no longer exists.
ACCESS_CERTIFICATION_DENIEDA decision was refused: a review of one’s own access, an item assigned to someone else, the baseline group or the primary administrator.
ACCESS_CERTIFICATION_BULKA batch of decisions or reassignments, with how many applied and how many were skipped.
ACCESS_CERTIFICATION_COMPLETEDEvery item was decided, or the campaign was closed early.
ACCESS_CERTIFICATION_ARCHIVEDA campaign was archived (ACCESS_CERTIFICATION_RESTORED when brought back, ACCESS_CERTIFICATION_DELETED when a campaign with no decisions was removed).
ACCESS_REVOKED_LIVEAccess was revoked from the live view, outside a campaign (ACCESS_REVOKE_DENIED when that was refused).
ACCESS_REVIEW_SCHEDULE_CREATEDA recurring review was created (also _UPDATED, _PAUSED, _RESUMED and _DELETED).
ActionWritten when
ACCESS_REQUESTEDA request was filed.
ACCESS_REQUEST_APPROVEDAn approver said yes and the provisioning run was created.
ACCESS_REQUEST_DENIEDAn approver rejected the request, an approval of one’s own request was blocked, or the person was no longer active.
ACCESS_REQUEST_FULFILMENT_FAILEDThe request was approved but the provisioning run could not be created.
ACCESS_REQUEST_FULFILLEDIT completed the run: the access was provisioned.
ACCESS_REQUEST_DECLINEDIT stopped the run: nothing was granted.
ACCESS_REQUEST_BULKA batch of approvals or rejections, with how many applied and how many were skipped.

How to read and filter these entries is in Audit Log & Log Forwarding.

Related reference