Approvals
Group join requests, vault check-out requests and the workflow Approval step used to have three approval mechanisms with three pages. They now share one decision model and one Approvals page in the portal. Each kind keeps its own request record, its own audit trail and its own action after the decision: the membership is written, the check-out can be made, or the run continues. What is shared is who may decide, how many decisions are needed, escalation and timeout, the rule that the requester and the subject never decide, operator override, withdrawal, notifications, and one list to work from. Approvals need the Enterprise tier.
This runbook covers the shared page and the shared rules. Each kind also has its own runbook: the step’s fields are in Workflow approvals, the group policies are in Join policies, and the vault policies are in Check-out approvals.
The Approvals page
Approvals in the sidebar, its own entry directly below Mesh Objects, at /identity/approvals.
There is one sidebar entry for all three kinds. It is visible to anyone
who holds any of workflows.approve, groups.own or vault.approve.
The subtitle reads “Workflow steps, group joins and vault checkouts
waiting on a decision”. On a tier below Enterprise the page says
“Approvals require the Enterprise tier”.
The old pages redirect here with the kind filter preset, so old links in emails and inbox notices keep working:
| Old address | Opens with the kind |
|---|---|
/identity/workflows/approvals, /workflows/approvals | Workflow |
/groups/approvals, /groups/approvals/admin | Group join |
/vault/approvals | Vault checkout |
You can preset the filter yourself with ?kind=Workflow,
?kind=GroupJoin or ?kind=VaultCheckout on the address.
Tabs
| Tab | Shows |
|---|---|
| To decide | Open requests you may decide now. Empty: “Nothing is waiting for your decision.” |
| Decided by me | Requests you have decided. Empty: “You have not decided any approvals yet.” |
| All open | Every open request of the kinds you may override, plus decided requests whose follow-up action is still owed. Only for holders of workflows.execute, groups.admin or vault.admin, each for their own kind. Empty: “No approvals are open.” |
| Completion failed | Decided requests whose follow-up action failed or is still pending, for the same holders. Empty: “No completions have failed.” |
The kind filter offers All, Workflow, Group join and Vault checkout.
Columns
| Column | Shows |
|---|---|
| Kind | A badge: Workflow, Group join or Vault checkout. |
| Title | What is being asked. See the formats below. |
| Subject | The object the request is about. |
| Requested by | Who raised the request. |
| Justification | The requester’s reason, truncated, with more and less to expand it. |
| Stage | The stage, for two-stage requests. |
| Opened | When the request was opened. |
| Escalates / times out | The deadlines, or “none”. |
| Decisions so far | Approvals so far of the number needed. |
The title depends on the kind:
| Kind | Title |
|---|---|
| Workflow | The Approval step’s Title. |
| Group join | “Join group” for a request to join, or “Add person to group” when an owner adds someone. |
| Vault checkout | “Check out account for N min”. |
Deciding
Select Approve or Reject on the row. A dialog opens with a Reason (recorded in the audit trail) box. Confirm to record your decision.
On a vault row, the approve dialog also has Grant for (minutes). It is prefilled with the minutes the requester asked for. You can shorten it, from 1 up to the requested minutes, but you cannot lengthen it.
Your own request shows “Somebody else decides it (your request)” in place of the buttons.
A decision is refused when the request was already decided, when another decision landed first, or when the subject no longer exists. It is also refused when you are not permitted to decide it. Reload the page to see where the request stands.
Who can decide
The same rules apply to all three kinds.
- Approvers are mesh people. The portal links a signed-in person to
their mesh object by matching the sign-in against the object’s
userPrincipalName,mailorsAMAccountName. - A group named as an approver is expanded. Its direct members are asked, up to 100. Above that, a warning is logged.
- The requester and the subject never decide. This holds for every kind, even for an operator.
- Each person decides once. One rejection settles the request as rejected. It is approved when the approvals reach the number needed: 1 for group joins and vault check-outs, and Approvals needed for a workflow step.
- Eligibility is checked when you decide. It is not only checked when the request opened. An owner removed from the group, or a person removed from the vault approver role, can no longer decide the requests that named them.
- Two stages are decided by different people. A two-stage request (dual control) shows its Stage on the page, and the person who decided stage 1 cannot decide stage 2. Only group joins use two stages today, for high-impact groups.
Each kind decides who is asked in the first place:
| Kind | Who is asked |
|---|---|
| Workflow | The step’s Approvers, and Escalate to after escalation. See Who can approve. |
| Group join | The group’s owners at stage 1. For a high-impact group, stage 2 is decided by a groups.admin holder who is not the stage-1 decider. |
| Vault checkout | The members of the policy’s approver role. Deciding needs vault.approve and membership of that role. A policy with a blank approver role lets any vault.approve holder decide. |
Escalation and timeout
When nobody has decided by the escalation time, the escalation set is told as well and can decide beside the first set. When nobody has decided by the timeout, the request times out.
| Kind | Where the timing comes from | What a timeout means |
|---|---|---|
| Workflow | The step’s Escalate after and Time out after, unchanged. | The step’s When timed out decides: the run ends, or continues into the next step. |
| Group join | Configure → Groups: Join approval: escalate after (minutes) and Join approval: time out after (minutes). Both blank by default. | A denial. The requester is told. |
| Vault checkout | The vault policy: Escalate after (minutes), Escalation role and Time out after (minutes). All three blank by default. | A denial. The requester is told. |
Group and vault escalation and timeout are off until a policy sets them.
For group joins:
- escalation goes to the administrators, and only for groups that are not high impact. A high-impact group’s requests never escalate, because its second stage already belongs to the administrators,
- Join approval: escalate after must be shorter than Join approval: time out after. If it is not, the escalation is ignored,
- a group with no owner left escalates its open stage-1 requests to the administrators at once, when it is not high impact. A high-impact group keeps its requests open for the promoted owner, and administrators can reject them.
For vault check-outs, Escalation role names who an escalated request goes to. It is required when Escalate after is set.
Operator actions
Holders of a kind’s override permission can settle a request of that
kind without being an approver: workflows.execute for workflow
requests, groups.admin for group joins and vault.admin for vault
check-outs.
| Action | Where | Effect |
|---|---|---|
| Approve as operator / Reject as operator | Approvals page | Shown only when you are not an eligible approver on the row but hold the kind’s override permission. The decision settles the request alone. For a workflow request, the approvers are told it was decided by an administrator. For a group join or a vault check-out, the subject and the requester are told the outcome, as for any decision. |
| Withdraw | POST /api/approvals/{id}/withdraw | Withdraws any open request of your kind. A workflow request is withdrawn only by cancelling its run. |
| Retry completion | Completion failed tab | Runs the follow-up action of a decided request again. See After the decision. |
Every operator action is recorded in the administrator audit trail as an operator decision, with who and the reason. An operator who is the requester or the subject of a request still cannot decide it.
Withdrawn requests
A request is withdrawn when:
- the requester withdraws their own group join or vault request. A vault request can also be withdrawn after approval, while it is unused,
- a holder of the kind’s override permission withdraws an open request,
- a workflow run is cancelled, fails or is deleted with its workflow. See Withdrawn requests,
- the subject is erased or deleted. The sweep notices this within about a minute,
- a group is deleted. Its open requests are withdrawn,
- a vault account is archived, including by the source-missing sweep. Its open requests are withdrawn.
The people asked are told when the request was still pending. Withdrawing an approved vault request nobody has used tells nobody, since nothing is asked of anyone.
Requesters withdraw from their own lists: Withdraw on
/groups/requests (My Requests) for group joins, and Withdraw
on the My requests tab of the Vault self-service page for vault
requests.
After the decision
Each kind has its own follow-up action once a request is approved:
| Kind | Follow-up action |
|---|---|
| Workflow | The run continues. A workflow request has no follow-up action of its own, so it never appears on Completion failed. |
| Group join | The membership is written. |
| Vault checkout | The check-out can be made. The approved request shows the approval window left and a Check out button on the requester’s My requests tab. |
A vault approval is valid for the policy’s Approval valid for (minutes), 1440 (24 hours) by default. An approved request nobody checks out within that window becomes Expired, the requester is told, and they must request again.
A decided request whose follow-up action is still owed stays on All open. The approval sweep retries the action after a 30 second grace from the decision, up to five attempts. After that the request is marked failed and stays on Completion failed until someone selects Retry completion.
Notifications
Every person asked gets an inbox notice under the bell. When email is set up under Settings → Email and their person object has an email address, they get an email as well. Who hears the outcome depends on the kind: a workflow request tells its approvers, because the decision is theirs to know; a group join or a vault check-out tells the person it is about and, when someone else asked, the requester, because the decision is an answer to them.
The subject reads “prefix title (phase)”. The prefix is “Approval:” for a workflow request, “Group join:” for a group join and “Vault request:” for a vault check-out.
| Phase | Who is told | Example |
|---|---|---|
| needs your approval | the approvers | Group join: Join Finance (needs your approval) |
| escalated to you | the escalation set | Vault request: Check out svc-backup for 60 min (escalated to you) |
| approved / rejected | workflow: the approvers; group join and vault: the subject and the requester | Approval: Enable account for Ada Lovelace (approved) |
| timed out | the approvers (nobody decided), and for a group join or a vault check-out also the subject and the requester (it is a denial) | Group join: Join Finance (timed out) |
| withdrawn | the approvers | Vault request: Check out svc-backup for 60 min (withdrawn) |
| expired | the requester only, for a vault approval nobody used | Vault request: Check out svc-backup for 60 min (expired) |
| no approver can be reached / waiting on an operator | vault only: the vault administrators, and the requester | Vault request: Check out svc-backup for 60 min (no approver can be reached) |
The body names the workflow for a workflow request, or reads “Request: group join” or “Request: vault checkout”. It continues with About: the subject and its type, Requested by: when the requester is not the subject, the justification, the approvals so far of the number needed, and, for a new request, when it escalates and times out in UTC.
The last line links to the page when the Portal URL is set under Settings → Email, Links:
Open: https://identitymesh.example.com/#/identity/approvals
Without a Portal URL, the email ends with:
Open the IdentityMesh portal and go to Approvals.
Enter the Portal URL as the plain address with no #. See Email.
Nothing is sent for a request that was approved automatically, such as a join to an open group or a check-out under a vault policy that does not require approval.
On a test install, every approval email can go to one mailbox instead of the real approvers and requesters. See Redirect for testing.
The approval sweep
A background pass in the Admin API runs every minute while the licence tier is Enterprise. It:
- escalates and times out group and vault requests at their deadlines,
- retries follow-up actions that did not complete, after a 30 second grace from the decision, up to five attempts,
- expires vault approvals nobody used within their window,
- re-checks open vault requests against current role membership: when the approver role has emptied since the request was filed, the request escalates at once if the escalation role had members when the request was filed and still reaches someone. Otherwise the vault administrators and the requester are told once, and the request does not escalate,
- withdraws requests whose subject is gone,
- opens a decision record for any in-flight request that is missing one.
It keeps running when workflows are paused. It is not the workflow worker, and it has no setting to tune.
Operations card
The Approvals card on the Operations page shows:
- open requests per kind: “N workflow”, “N group join”, “N vault checkout”,
- a Completion failed count that links to that tab, red when above zero,
- “N vault policies with no reachable approver.”
The Workflows card still shows “N awaiting approval” for runs parked on an Approval step.
Permissions
| Permission | On the Approvals page |
|---|---|
workflows.approve | Open the page. Decide workflow requests that name you. |
groups.own | Open the page. Decide group join requests for groups you own. Ownership is still per group. |
vault.approve | Open the page. Decide vault check-out requests for policies whose approver role you hold. |
workflows.execute | All open and Completion failed for workflow requests. Decide any workflow request as an operator, withdraw, retry. |
groups.admin | The same for group joins, and stage 2 of high-impact joins. |
vault.admin | The same for vault check-outs. Override the approver-role rule, which is recorded. Check in anyone’s session. |
groups.request, vault.request | Withdraw your own request. |
No new permission was added. Every decision, override, withdrawal and retry is recorded in the administrator audit trail with who, whether as approver or as operator, and the reason. The group and vault hash-chained audit trails keep their own rows (Requested, Approved, Denied, Cancelled, Expired, and so on) exactly as before.
API
The /api/approvals routes need the Enterprise tier. Below it every
route is refused with “Enterprise tier required”.
| Method | Route | Does | Permission |
|---|---|---|---|
| GET | /api/approvals?tab=todo|mine|all|failed&kind=Workflow|GroupJoin|VaultCheckout | List. todo (the default) is the open requests the caller may decide now. mine is the requests the caller decided. all is every open request of the kinds the caller may override, plus decided ones whose action is still owed. failed is decided requests whose action is Pending or Failed. all and failed answer 403 when the caller may override none of the kinds. | any of workflows.approve, groups.own, vault.approve, workflows.execute, groups.admin, vault.admin |
| GET | /api/approvals/{id} | One request with its approvers, decisions and deadlines. Readable by the people asked, the requester, whoever decided it and operators of its kind. | same |
| POST | /api/approvals/{id}/approve | Body { reason, asOperator, payload: { grantedDurationMinutes } }. Needs the kind’s decide permission and eligibility, or asOperator: true with the kind’s override permission. A vault check-out may carry payload.grantedDurationMinutes, from 1 to the requested minutes (400 outside that). 409 when already decided, when another decision landed first, or when the subject no longer exists. 403 when not permitted. | same |
| POST | /api/approvals/{id}/reject | Same body and rules. Any eligible rejection settles the request. | same |
| POST | /api/approvals/{id}/withdraw | Body { reason }. The requester (a vault check-out also while approved and unused), or a holder of the kind’s override permission while the request is open. A workflow request closes with its run (409). | the above, plus groups.request, vault.request |
| POST | /api/approvals/{id}/retry-completion | Runs the follow-up action again for a request whose completion is Failed or Pending. | the kind’s override permission |
| GET | /api/vault/checkouts/mine | The caller’s own vault requests, newest first, with approvedExpiresUtc. | vault.request |
Each list row carries the id, kind, stage, title, subject, requester, justification, when it opened, the escalation and timeout times, the decisions so far and the number needed, the status, who decided and as what (Approver or Operator), the completion status, the requested duration in minutes for a vault row, and whether the caller can decide it.
GET /api/workflows/status and the operations posture carry
pendingApprovals { workflow, groupJoin, vaultCheckout },
completionFailedApprovals and policiesWithoutReachableApprover.
Deprecated routes
These routes are kept for one release as wrappers over the new ones.
Move integrations to /api/approvals.
POST /api/group-requests/{id}/approve,/denyand/cancelGET /api/groups/requests/pendingGET /api/groups/requests/pending-adminPOST /api/vault/checkouts/{id}/decideand/cancelGET /api/vault/checkouts/pending. Its list is empty for avault.adminholder outside the approver role.GET /api/workflows/approvalsand/api/workflows/approvals/{id}, with/approveand/reject
Retention and erasure
Workflow requests still age out with their runs, under run-history retention. See Retention. Group join and vault request records are removed when their request rows are purged, never on their own.
Erasing a person under DSAR and right to erasure blanks the title, subject name, message, justification and requester name on every request about them or raised by them, of every kind. Who decided, and how, stays on the record.
Upgrading
- Run the database step first. The installer’s database step adds what approvals need. A database updated outside the installer needs the database step run before the Admin API starts. Otherwise the Approvals page, group join requests and vault check-outs fail with an invalid column name.
- Read the reconciler’s lines. On its first start, the Admin API gives every in-flight group join request and every pending vault request its decision record, without sending the notices again. It logs the counts per kind, and each vault policy’s reachable approvers, to the IdentityMesh event log. Until it has run, the old routes answer 409 for an in-flight request.
- Check each vault policy’s approver role. Holders of
vault.approveoutside a policy’s approver role can no longer approve its requests. A policy with a blank approver role keeps the old behaviour, where anyvault.approveholder may decide. Set each policy’s approver role to a role people actually hold, under Settings → User Role Assignments. - Set the navigation theme again. One sidebar entry, Approvals, replaces the three old entries. An Appearance theme that renamed or hid one of the old entries loses that override; set it again on Approvals. See Appearance.
- Update notification filters. The notification kinds are now
ApprovalRequested,ApprovalEscalated,ApprovalDecided,ApprovalTimedOut,ApprovalWithdrawnandApprovalExpired. Old inbox rows keep working and their links redirect. A SIEM rule or report that filters on the old kind names needs updating.
See Upgrades for the release’s full list.
Troubleshooting
| What you see | Cause and fix |
|---|---|
| The page says “Approvals require the Enterprise tier” | The licence tier is below Enterprise. Approvals, and the approval sweep, need Enterprise. |
| A request you expect is missing from To decide | Eligibility is checked when you decide, so you may no longer be an owner or in the approver role. You may be the requester or the subject, who never decide. Or the kind filter is set to another kind; set it to All. |
| A vault row is refused as not permitted | Deciding a check-out needs vault.approve and membership of the policy’s approver role. Add yourself to the role under Settings → User Role Assignments, or have a vault.admin holder decide as operator. |
| An old link or bookmark lands on the Approvals page with a kind already selected | The old pages redirect to the shared page with the kind preset. Update the bookmark to /identity/approvals. |
| A decided request is still on All open | Its follow-up action is still owed. The sweep retries it after a 30 second grace. |
| A decided request is on Completion failed | The follow-up action failed five times, or is still pending. Fix the cause, then select Retry completion. |
| The Operations card or the event log says a vault policy’s role “reaches nobody in the mesh” | No member of the approver role resolves to a mesh person, so only a vault.admin operator can decide its requests. Set the policy’s approver role to a role people hold, and make sure those people have mesh objects that match their sign-in. |
| The email ends with “Open the IdentityMesh portal and go to Approvals.” | Portal URL is empty. Set it under Settings → Email, Links. See Email. |