> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/administration-guide/requests/approval-flows.md).

# Approval flows

Change who approves a request, understand the shipped defaults that route every approval to sysadmin, and diagnose a request that has stopped.

## What this is, and when you need it

An OpenIAM approval is a workflow definition plus rows in the `APPROVER_ASSOC` table, and there is no approval designer. This page changes **who approves** — one approval step, on one object, effective on the next request — and then diagnoses a request that has stopped. Changing the *shape* of a flow is a different job and a deployment one; the shipped shapes, the association columns and the entity types are in [Approver associations and the shipped flow shape](/administration-guide/requests/approval-flows/approver-association-reference.md).

## At a glance

|                          |                                                                                                                                                            |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | An approval step whose task lands in the inbox you intended, proved with one disposable request                                                            |
| **Where**                | The owning object's **Approver Associations** tab — resource, role, group or organization. There is no approval designer and no central approvals screen   |
| **You need first**       | The object that owns the flow (usually the `<FLOW>_AR` resource for the request type); the user, role or group that should approve; a disposable requester |
| **Takes**                | 10 minutes per step, plus one test request end to end                                                                                                      |
| **Reversible**           | Yes for the association itself. Requests already in flight keep the approvers they resolved at submission, so a fix does not repair them                   |

## Before you begin

{% hint style="danger" %}
**Out of the box, every approval in OpenIAM routes to `sysadmin`.** All 45 shipped approver associations resolve to user ID `3000`, and so does the default approver property behind every fallback path. If nobody monitors that account's task list, requests are not "stuck in workflow" — they are sitting, correctly assigned, in an inbox no human reads. Read [The shipped default approver resources](#the-shipped-default-approver-resources) before you conclude anything is broken, and reassign those associations as part of installing OpenIAM.
{% endhint %}

One more thing to settle first. **Reminders and escalation ship disabled**, so everything you configure in `DAYS_BEFORE_REMINDER`, `NUMBER_OF_REMINDERS` and the `ESCALATION` rows is inert until you enable two batch tasks — see [Debugging a stuck request](#debugging-a-stuck-request). Configure the step first, then enable the tasks, or the step will look broken when it is only silent.

## Change who approves

{% stepper %}
{% step %}

### Find the object that owns the approver association

Approver associations hang off a resource, role, group or organization. Open the object's **Approver Associations** tab. The Webconsole routes are:

| Object       | Route                                                               |
| ------------ | ------------------------------------------------------------------- |
| Resource     | `/webconsole/resourceApproverAssociations?id=<RESOURCE_ID>`         |
| Role         | `/webconsole/roleApproverAssociations?id=<ROLE_ID>`                 |
| Group        | `/webconsole/groupApproverAssociations?id=<GROUP_ID>`               |
| Organization | `/webconsole/organizationApproverAssociations?id=<ORGANIZATION_ID>` |

All four render the same screen; only the kind of owning object differs.
{% endstep %}

{% step %}

### Add or edit a step

Each row is one approval step. **Approver object type** offers `USER`, `SUPERVISOR`, `ROLE`, `GROUP`, `OWNER` and `ADMIN` — the six of the fifteen entity types that are offered as approvers, listed with the rest in [Approver associations and the shipped flow shape](/administration-guide/requests/approval-flows/approver-association-reference.md#the-approver-association-model). **Approver level** is the step order starting at `0`, and **Required?** is the `IS_MANDATORY` column, which defaults to yes.

**Required?** also decides what happens when the step's approver resolves to nobody — an empty approver group, or a target user with no supervisor. A required step falls back to the default approver instead, and the task shows it: the approver name reads `<display name> (Default approver)`. A step that is not required is dropped from the request silently, and nothing on screen reports that a step was removed.

**Confirm the save landed** by reloading the tab rather than trusting the confirmation toast — the list you see after a reload is the list the next request will use.
{% endstep %}

{% step %}

### Set the SLA on the resource, not the step

Days-to-complete for the *request* is `RES.DAYS_TO_COMPLETE_REQUEST`, shown on the approver association screen with the label *"Days to complete request. Requests not completed within this period will expire."* Blank means the request never expires. Days-to-complete on an individual escalation row is a different column and only drives escalation.
{% endstep %}

{% step %}

### Test with one disposable request

Raise a single request that uses the flow and confirm the task appears for the expected approver. Do not test by editing a production approver association — add a step, test, remove it.
{% endstep %}
{% endstepper %}

## Verification

After any change to an approval flow, one disposable request should produce all of:

* a decision task in the expected approver's **My Tasks**, with the task name you expect;
* `ApproverCardindality` containing exactly the approvers you intended, at the levels you intended;
* an audit event for the request type's init and notify actions — for the generic entitlement flows these are `CREATE_GENERIC_REQUEST_INIT` and `CREATE_GENERIC_REQUEST_NOTIFY`, and on decision `CREATE_GENERIC_REQUEST_ACCEPT` or `CREATE_GENERIC_REQUEST_REJECT` followed by `CREATE_GENERIC_REQUEST_COMPLETE`. Each request type has its own action names; take the ones for your flow from a known-good request rather than assuming;
* a `TASK_LISTENER` event carrying the `CANDIDATE_USER_IDS` attribute;
* on approval, the target object actually changed — the role membership exists. **The request showing Approved is not evidence of the grant**; check the user.

## When it goes wrong

| Symptom                                                                     | Cause to check                                                                                                                                               | Fix                                                                                                                                                                                                                |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Every request goes to `sysadmin`                                            | The 45 shipped `<FLOW>_AA` rows all have `APPROVER_ENTITY_ID = '3000'`                                                                                       | Reassign the associations for the flows you use; also set `org.openiam.idm.activiti.default.approver.user`                                                                                                         |
| Requests sit forever, no reminder email                                     | `REQUEST_REMINDERS` batch task ships disabled                                                                                                                | Enable it; then confirm `DAYS_BEFORE_REMINDER` and `NUMBER_OF_REMINDERS` are set on the association                                                                                                                |
| Step SLA passes, nothing escalates                                          | `REQUEST_ESCALATION` ships disabled, and/or the `ESCALATION` rows have no `DAYS_TO_COMPLETE`                                                                 | Enable the task; populate `ESCALATION.DAYS_TO_COMPLETE`                                                                                                                                                            |
| A step vanished from the flow                                               | The requester was in the candidate list and a further level existed, so the step was auto-approved                                                           | Look for `AUTO_COMPLETED` in `StepsOfApprove` and an `AUTOAPPROVAL` comment in `TASK_HISTORY`; remove the requester's population from the association                                                              |
| A change to a workflow definition had no effect                             | Definitions load only when the workflow service starts, and in-flight requests keep their original version                                                   | The service must be restarted — [Installation Guide](/installation/installation.md). Then handle in-flight requests explicitly                                                                                     |
| A flow exists that nobody configured                                        | Every XML file in the workflow directory is deployed, including leftovers                                                                                    | A deployment-side cleanup; see the [Installation Guide](/installation/installation.md)                                                                                                                             |
| Approver association edits ignored for disentitlement                       | `RES.REFERENCE_ID` is `DISENTITLE_USR_FROM_RESOURCE`, not `DISENTITLE_USER_FROM_RESOURCE`, so the lookup falls back                                          | Edit `org.openiam.idm.activiti.default.approver.association.resource.id`'s resource, or add a resource with the exact `REFERENCE_ID`                                                                               |
| Custom script's approvers appear alongside, not instead of, configured ones | `org.openiam.idm.activiti.merge.custom.approver.with.approver.associations` is `true`                                                                        | Set it to `false`                                                                                                                                                                                                  |
| A new-hire approver level is missing                                        | A value in `org.openiam.idm.activiti.new.user.approver.association.order` is not a recognised approver-type name; the log says `Cannot add association type` | Use only the names from [the approver type table](/administration-guide/requests/approval-flows/approver-association-reference.md#the-approver-association-model)                                                  |
| Approved request did not grant access                                       | The grant runs as a separate step after the decision, and it can fail on its own                                                                             | Search the workflow service log for `EntityMembershipDelegate` on the entitlement flows; the audit log will still show the approval                                                                                |
| Task appears for several people and nobody owns it                          | More than one candidate resolved, so each was added as a candidate rather than one being assigned                                                            | Expected; either claim it or reduce the association to one approver                                                                                                                                                |
| Role-level owners are not asked                                             | `org.openiam.workflow.create.request.rbac.mode` is `false`, so approvers come from the resource                                                              | Set it to `true`                                                                                                                                                                                                   |
| Requests expire unexpectedly                                                | `RES.DAYS_TO_COMPLETE_REQUEST` is set on the resource                                                                                                        | Clear it, or raise it; blank means never expire                                                                                                                                                                    |
| The screen says `Could not initiate workflow`                               | The workflow service failed or returned nothing when asked to start the request; the message carries no reason of its own                                    | Read the workflow service log for that minute; then confirm the request type's approver association resolves at all — for an edit-user request that is the `EDIT_USER_AR` resource's **Approver Associations** tab |
| An approval step you configured is missing, and no approver was involved    | The step's approver resolved to nobody and **Required?** was cleared, so the step was dropped                                                                | Set **Required?** on the step, or fix the approver so it resolves; a required step falls back to the default approver instead of vanishing                                                                         |
| Removed every approver and the request still needs approving                | There is no "no approval" configuration — the flow falls back to the default approver resource, then to `org.openiam.idm.activiti.default.approver.user`     | Expected. An approver named `<display name> (Default approver)` on the task confirms which fallback fired                                                                                                          |
| A revoke request completed and the role is still on the user                | Completion end-dates the membership rather than removing it                                                                                                  | `org.openiam.workflow.termination.access.remove` ships `false` — see [Remove versus end-date](/administration-guide/governance/certification/operate-a-live-campaign.md#remove-versus-end-date)                    |

## Debugging a stuck request

### Start with the reminders, not the engine

**`REQUEST_REMINDERS` and `REQUEST_ESCALATION` both ship disabled.** Their cron is `0 0 0 1/1 * ?` and their beans are `requestRemindersInitializer.initializeSendReminders` and `requestEscalationInitializer.initializeEscalation`, as shown on their **Batch Tasks** rows. Say plainly what this means: on a default installation, approval reminders are never sent and expired steps are never escalated. Everything you configured in `DAYS_BEFORE_REMINDER`, `NUMBER_OF_REMINDERS` and the `ESCALATION` rows is inert until you enable the tasks in **Administration → Batch Tasks**, where their `ENABLED` column reads `N` until you do.

Combined with the shipped `sysadmin` default approver, this is very likely the cause of a real "approvals just sit there" ticket. Check both before you touch a BPMN file. See [Shipped batch tasks](/administration-guide/reference/shipped-batch-tasks.md).

When the tasks do run, they audit under `SEND_REMINDERS_TO_APPROVERS_PROCEDURE` and `ESCALATE_REQUESTS_PROCEDURE`, with child events `INITIATE_ESCALATION_OF_EXPIRED_STEP`, `DELEGATE_TO_DEFAULT_APPROVER` and `INITIATE_CANCELING_OF_EXPIRED_REQUEST`. An enabled task that has run but done nothing still writes its parent action, so the presence of the procedure action with no children means "ran, found nothing due" rather than "did not run".

### Then read the process variables

Open the request in **SelfService → Request Management → Request Administration** (`/selfservice/request-administration`) or the task itself at `/selfservice/task`, and collect these variables. Names and REST field names are listed in [Workflow variables](/administration-guide/reference/enums.md#workflow-variables).

| Variable               | What to conclude                                                                                                                                                                              |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ApproverCardindality` | The resolved approver list as `<userId>:<level>`. **Empty means resolution failed** — the association or the resolver script is the problem, not the engine                                   |
| `ApproverLevel`        | Which step the request is on. Not advancing means the current step's task is unclaimed or unanswered                                                                                          |
| `CountOfApproverSteps` | Total steps; compare with `ApproverLevel`                                                                                                                                                     |
| `IsTaskApproved`       | `false` on a live request is normal — it is only set `true` on a decision or auto-approval                                                                                                    |
| `StepsOfApprove`       | One entry per step with its status. `AUTO_COMPLETED` = auto-approved; `PENDING` / `UNANSWERED` = waiting; `ESCALATED` / `DELEGATED` = moved                                                   |
| `TASK_HISTORY`         | The comment list. Comment types `AUTOAPPROVAL`, `DELEGATE`, `ESCALATE` and `CANCEL` tell you what happened without reading the engine tables                                                  |
| `EscalationLevel`      | Reset to `"0"` each time the request advances a level                                                                                                                                         |
| `candidateUsersIds`    | Task-**local**, so it is scoped to one task rather than the request. Compare with `ApproverCardindality` — a single ID means the task was assigned outright; several means it must be claimed |
| `AssigneeUserId`       | Set only when there was exactly one candidate                                                                                                                                                 |
| `RequestId`            | The correlation ID to search the audit log with                                                                                                                                               |
| `AuditLogId`           | Parent audit event; every delegate sets its own event's parent to this                                                                                                                        |

{% hint style="warning" %}
**Break-glass: reading a variable the request screens do not show.** `candidateUsersIds` is task-local, and the request administration screen shows process variables rather than task variables — so when a task is sitting unclaimed and you need to know exactly whom it was offered to, the screen will not tell you. The audit entry for `TASK_LISTENER` carries `CANDIDATE_USER_IDS` and is the supported answer; where the audit has been pruned, Activiti's own history holds it:

{% code overflow="wrap" %}

```sql
SELECT v.TASK_ID_, v.NAME_, v.TEXT_
FROM ACT_HI_VARINST v
WHERE v.PROC_INST_ID_ = ? AND v.NAME_ IN ('candidateUsersIds', 'ApproverCardindality');
```

{% endcode %}

This is outside the supported change path. It is read-only, so it risks nothing, but it bypasses OpenIAM's access control and audit — the query itself is not recorded — and it reads the engine's tables directly, so column meanings can change between releases without notice. Prefer the `TASK_LISTENER` audit entry when it is still there.
{% endhint %}

### Then check whether the definition was even reloaded

If a change to a workflow definition appears not to have taken effect, the definition was almost certainly never reloaded: that happens only when the workflow service restarts. Confirm the restart happened *after* the change, and remember that in-flight requests stay on the definition version they started with, so even a correct redeploy leaves them behaving the old way.

## The shipped default approver resources

Since 4.0.0.0 OpenIAM has shipped a `RESOURCE_TYPE` called `ACTIVITI_REQUEST` with **45** resources of that type — one per flow, each named `<FLOW>_AR` — plus **45** matching `APPROVER_ASSOC` rows named `<FLOW>_AA`.

**Every one of the 45 associations resolves to the same approver: user ID `3000`, the built-in `sysadmin` account**, a member of `SUPER_SEC_ADMIN_GRP`. Each row is `APPROVER_LEVEL = 0`, `ASSOCIATION_TYPE = 'RESOURCE'`, `APPROVER_ENTITY_TYPE = 'USER'`, `APPROVER_ENTITY_ID = '3000'`, `APPLY_DELEGATION_FILTER = 'N'`.

**Confirm it on your install** by opening any `<FLOW>_AR` resource's **Approver Associations** tab and reading the approver. If it says `sysadmin`, nothing has been reassigned yet.

Reassigning these 45 associations to real approvers or groups is part of installing OpenIAM, not an optimisation. Reassign the flows you actually use first, and set `org.openiam.idm.activiti.default.approver.user` at the same time — otherwise the fallback paths below keep landing on the same account.

**Removing every approver association does not turn approval off.** With none on the target resource the request takes the fallback chain below, which ends at `org.openiam.idm.activiti.default.approver.user` — so a request you expected to complete unattended lands in `sysadmin`'s inbox instead. `REVOKE_ACCESS` and `TERMINATE_USER` reach it most often, because neither has a seeded `_AR` resource. Approver associations on the role or group being requested are not read on this path at all; they are read only when `org.openiam.workflow.create.request.rbac.mode` is `true`.

Resources are matched to flows by `RES.REFERENCE_ID`, compared **exactly** against the request type's name. The 45 seeded `REFERENCE_ID` values are:

`NEW_HIRE_WITH_APPROVAL`, `NEW_HIRE_NO_APPROVAL`, `SELF_REGISTRATION`, `ADD_USER_TO_ROLE`, `REMOVE_USER_FROM_ROLE`, `ADD_USER_TO_GROUP`, `REMOVE_USER_FROM_GROUP`, `ENTITLE_USER_TO_RESOURCE`, `DISENTITLE_USR_FROM_RESOURCE`, `EDIT_USER`, `ADD_USER_TO_ORG`, `REMOVE_USER_FROM_ORG`, `ATTESTATION`, `GROUP_ATTESTATION`, `RESOURCE_CERTIFICATION`, `ADD_SUPERIOR`, `REMOVE_SUPERIOR`, `SAVE_LOGIN`, `DELETE_LOGIN`, `NEW_RESOURCE`, `EDIT_RESOURCE`, `DELETE_RESOURCE`, `NEW_GROUP`, `EDIT_GROUP`, `DELETE_GROUP`, `NEW_ROLE`, `EDIT_ROLE`, `DELETE_ROLE`, `NEW_ORGANIZATION`, `EDIT_ORGANIZATION`, `DELETE_ORGANIZATION`, `ADD_GROUP_TO_GROUP`, `REMOVE_GROUP_FROM_GROUP`, `ADD_ROLE_TO_GROUP`, `REMOVE_ROLE_FROM_GROUP`, `ADD_ROLE_TO_ROLE`, `REMOVE_ROLE_FROM_ROLE`, `ENTITLE_RESOURCE_TO_GROUP`, `DISENTITLE_RESOURCE_FROM_GROUP`, `ENTITLE_RESOURCE_TO_ROLE`, `DISENTITLE_RESOURCE_FROM_ROLE`, `ADD_RESOURCE_TO_RESOURCE`, `REMOVE_RESOURCE_FROM_RESOURCE`, `CREATE_USER_REQUEST`, `CREATE_USER_REQUEST_APPROVERS`.

If no `ACTIVITI_REQUEST` resource matches the request type's name, OpenIAM falls back to the resource named by `org.openiam.idm.activiti.default.approver.association.resource.id`. Nine request types take that fallback path because they have no seeded `_AR` resource with a matching `REFERENCE_ID`:

| Request type                       | Why                                                                                                                        |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `DISENTITLE_USER_FROM_RESOURCE`    | Seeded `REFERENCE_ID` is `DISENTITLE_USR_FROM_RESOURCE` — `USR`, not `USER`, so the names do not match                     |
| `REVIEW_ASSESS_ON_POSITION_CHANGE` | No `_AR` resource seeded                                                                                                   |
| `REVOKE_ACCESS`                    | No `_AR` resource seeded                                                                                                   |
| `ACCESS_CERTIFICATION`             | Reviewers come from the campaign, not `APPROVER_ASSOC`                                                                     |
| `MANUAL_TASK`                      | Approvers are the target resource's administrators — see [Manual fulfilment](/administration-guide/requests/fulfilment.md) |
| `USER_ACCEPTED_INVITE`             | No `_AR` resource seeded                                                                                                   |
| `TERMINATE_USER`                   | No `_AR` resource seeded                                                                                                   |
| `ADD_USER_AND_INVITE`              | No `_AR` resource seeded                                                                                                   |

The `DISENTITLE_USR_FROM_RESOURCE` mismatch is worth knowing: editing the `DISENTITLE_USR_FROM_RESOURCE_AA` row has no effect on disentitlement requests, because the lookup by `REFERENCE_ID` never finds that resource. Change `org.openiam.idm.activiti.default.approver.association.resource.id`'s target instead, or add an `ACTIVITI_REQUEST` resource whose `REFERENCE_ID` is exactly `DISENTITLE_USER_FROM_RESOURCE`.

## The properties that route approvals

All of these live on **Administration → System Configuration**, category `Workflow` (32 keys). Full list and types in [System properties](/administration-guide/reference/system-properties.md); these are the ones that change routing.

| Property                                                                    | Webconsole label                                                               | Type                     | Shipped default                      |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------ | ------------------------------------ |
| `org.openiam.idm.activiti.default.approver.user`                            | Default Workflow Approver                                                      | User                     | `3000`                               |
| `org.openiam.idm.activiti.default.approver.association.resource.id`         | Default Approver Resource                                                      | Resource                 | `CREATE_USER_REQUEST_APPROVERS_AR`   |
| `org.openiam.idm.activiti.merge.custom.approver.with.approver.associations` | Merge Custom Approver with Approver Associations                               | Boolean                  | `true`                               |
| `org.openiam.idm.activiti.new.user.approver.association.order`              | New Hire Approver Association Order                                            | OrderableValue           | `ORGANIZATION,ROLE,GROUP,SUPERVISOR` |
| `org.openiam.workflow.create.request.rbac.mode`                             | Use approver association or role/group instead of resource                     | Boolean                  | `false`                              |
| `org.openiam.workflow.request.access.admin.role`                            | Roles whose members are permitted to cancel and delegate requests for any user | Role                     | `9`                                  |
| `org.openiam.workflow.revoke.access.admin.role`                             | Roles whose members are permitted to revoke access for any user                | Role                     | `9`                                  |
| `org.openiam.workflow.termination.with.approver.association`                | Terminate user via the approver association flow                               | Boolean                  | `false`                              |
| `org.openiam.workflow.initiate.access.requests.for.entitlements`            | Initiate access requests for selected roles/groups (New Hire / Edit User)      | Boolean                  | `false`                              |
| `org.openiam.workflow.task.decision.details`                                | Additional information fields on the task view page                            | MultiSelect              | *(empty)*                            |
| `org.openiam.workflow.master.resource`                                      | Workflow 'Master' Resource                                                     | Resource (**read-only**) | `WORKFLOW_MASTER`                    |

What each of the first five actually does:

**`org.openiam.idm.activiti.default.approver.user`** is the safety net, and it catches three different failures. It is appended to the candidate list when no approver could be resolved at all; it is the reroute target when the requester turns out to be their own reviewer and has no supervisor; and an escalation with nowhere else to go is delegated to it, audited as `DELEGATE_TO_DEFAULT_APPROVER`. Setting it to a real person or a monitored service account is the highest-value single change on this page — because at its shipped value all three of those failures land silently in `sysadmin`'s inbox. Search the audit log for `DELEGATE_TO_DEFAULT_APPROVER` to see how often it is firing today.

**`org.openiam.idm.activiti.default.approver.association.resource.id`** is the fallback resource described above. Default `CREATE_USER_REQUEST_APPROVERS_AR`, whose one association is also user `3000`.

**`org.openiam.idm.activiti.merge.custom.approver.with.approver.associations`** ships `true`, which means the approvers returned by the approver-resolver Groovy script — which it returns as `<userId>:0` entries — are **unioned** with the approvers from the approver associations, and the requester is then removed from the merged set. At `false`, the approver associations win outright when there are any, and the script's list is used only when there are none. Flip it to `false` when a script is meant to *replace* rather than *supplement* the configured approvers. Either way, `ApproverCardindality` on a test request tells you which behaviour you have.

**`org.openiam.idm.activiti.new.user.approver.association.order`** is a comma-separated list of approver-type names, and levels are assigned in list order — so the shipped `ORGANIZATION,ROLE,GROUP,SUPERVISOR` makes the new hire's organization approvers level 0 and the supervisor last. A value that is not a recognised name is logged as `Cannot add association type: <value>` and **silently skipped**, which on a new-hire request looks exactly like an approver level going missing. Nothing on screen flags a bad value: search the workflow service log for that line after you change this property.

**`org.openiam.workflow.create.request.rbac.mode`** ships `false`. At `true`, OpenIAM reads approver associations for **each requested role and group individually**, in addition to the target resource's, and builds one sub-task per distinct approver grouping. At `false`, approvers come from the target resource — and when that resource is `CREATE_USER_REQUEST_APPROVERS_AR`, the request is split into one workflow for entitlements that have their own approvers and one for those that do not. Turn it on when you want "the owner of *this role* approves requests for *this role*"; leave it off when approvals belong to the application. The difference is visible on a single test request: at `true` you get a sub-task per approver grouping, at `false` you do not. One consequence is easy to meet by accident: at `true` the fallback above does not apply to those sub-requests. A role or group with no approver association of its own inherits the target resource's associations, and where that resource has none either, the sub-request has no approval step and completes on submission. There is no per-step default and no automatic fall back to the requester's supervisor.

### The pluggable approver resolvers

Approver resolution is a Groovy script per request family. Each property below holds a script path; the shipped script is in Groovy Manager under that path with a `_1.0` version suffix, so `bpm/CreateUserRequestHelper.groovy` appears as `bpm/CreateUserRequestHelper_1.0.groovy`. Open the shipped script and edit a copy — see [Shipped Groovy scripts](/administration-guide/reference/shipped-groovy-scripts.md).

| Property                                                                                   | Webconsole label                                       | Shipped default                                                                |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------------------------------ |
| `org.openiam.activiti.generic.approver.association.groovy.script`                          | Basic Workflow Approver Calculation Script             | `bpm/CustomDefaultGenericWorkflowRequestApproverAssociationIdentifier.groovy`  |
| `org.openiam.activiti.rbac.membership.approver.association.groovy.script`                  | RBAC Workflow Approver Calculation Script              | `bpm/CustomRBACMembershipWorkflowRequestApproverAssociationIdentifier.groovy`  |
| `org.openiam.activiti.new.user.approver.association.groovy.script`                         | New User Workflow Approver Calculation Script          | `bpm/CustomDefaultNewHireRequestApproverAssociationIdentifier.groovy`          |
| `org.openiam.activiti.edit.user.approver.association.groovy.script`                        | Edit User Workflow Approver Calculation Script         | `bpm/CustomDefaultEditUserApproverAssociationIdentifier.groovy`                |
| `org.openiam.activiti.user.accepted.invite.approver.association.groovy.script`             | Fill Profile Info Workflow Approver Calculation Script | `bpm/CustomDefaultUserAcceptInviteApproverAssociationIdentifier.groovy`        |
| `org.openiam.activiti.manage.access.approver.association.groovy.script`                    | Access Management Approver Script                      | `/bpm/CustomManageAccessApproverAssociationIdentifier.groovy`                  |
| `org.openiam.activiti.termination.access.approver.association.groovy.script`               | Termination Approver Association Script                | `/bpm/CustomTerminationAccessApproverAssociationIdentifier.groovy`             |
| `org.openiam.activiti.create.user.request.entitlements.approver.association.groovy.script` | Create Access Request Entitlements Approver Script     | `/bpm/CustomCreateUserRequestEntitlementsApproverAssociationIdentifier.groovy` |
| `org.openiam.activiti.access.certification.helper.groovy.script`                           | Access Certification Helper Script                     | `/bpm/certification/CustomCertificationHelper.groovy`                          |
| `org.openiam.activiti.create.request.helper.script`                                        | Workflow Request Creation Helper Script                | `/bpm/CreateUserRequestHelper.groovy`                                          |
| `org.openiam.activiti.custom.notification.groovy.script`                                   | Workflow Custom Notification Script                    | `bpm/CustomNotificationDelegate.groovy`                                        |

Which of the first two runs is decided by the request type, not by configuration. Requests that create, edit or delete an RBAC object use the RBAC script; everything else uses the generic one. There is no setting that overrides that choice, so editing the wrong one of the two is a common and entirely silent mistake — put a log line in the script you edited and raise one test request to prove which one ran.

{% hint style="info" %}
Note the inconsistent leading slash across shipped values — some paths start with `/bpm/`, some with `bpm/`. Both resolve. Copy the existing value's form when you edit one rather than normalising it.
{% endhint %}

## Auto-approval when the requester is an approver

This is a real, shipped behaviour that is easy to mistake for a bug, and it is worth stating precisely because it silently skips an approval step.

A step is auto-approved when **all three** of these hold:

1. the candidate list for the next level is not empty,
2. a further approver level exists after the current one, and
3. that candidate list **contains the requester's own user ID**.

OpenIAM then writes exactly two pieces of evidence, and these are the strings to search for:

* the step in the `StepsOfApprove` process variable gets the status **`AUTO_COMPLETED`**, one of the five statuses that count as step-complete;
* a comment of type **`AUTOAPPROVAL`** is appended to the `TASK_HISTORY` variable, reading `This step was automatically approved. User <name> is the requester.` for `USER`, `SUPERVISOR`, `OWNER` and `ADMIN` approver types, or `... is a member of <approverName>.` for `GROUP` and `ROLE`.

The audit log also carries `Current step has been auto approved because requester is an approver or part of approver group` — search for that line as written.

On the create-user-request-approvers flow the auto-approve branch bypasses the decision task entirely, so **no user task is ever created for an auto-approved step**. Do not look for a completed task as evidence: there is none, and its absence is not a fault. `StepsOfApprove` and `TASK_HISTORY` are the record.

{% hint style="warning" %}
Auto-approval requires a *next* level to exist. A single-level flow whose only approver is the requester is not auto-approved — the requester gets their own task. If self-approval is unacceptable in your controls, remove the requester's population from the approver association rather than relying on this behaviour.
{% endhint %}

### One person approving more than one level

OpenIAM does not prevent the same person deciding two levels of the same request, and there is no setting that changes it: each level resolves its approvers independently, and a delegation can hand a later step to somebody who already decided an earlier one. Where your controls require distinct approvers per level, build that into the approver associations — different roles or groups at each level, with no overlapping membership — and audit the overlap yourself, because the product will not.

## What a completed approval can trigger

An approval is not always the end of the work. Two Groovy hooks hang off the request path, and both are configured as part of the flow rather than as anything you would set up on its own.

| Hook                          | Selected by                                                                                         | Runs                                | Audited as                                                               |
| ----------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------ |
| **Request post-action**       | The system property `org.openiam.activiti.create.request.helper.script`, for the whole installation | **Before** the entitlement is saved | `CREATE_REQUEST_POST_ACTION`                                             |
| **Ticket-system integration** | The **Groovy Script** field on the *target resource of the request*, at `/webconsole/editResource`  | **After** the entitlement is saved  | `TICKET_SYSTEM_INTEGRATION`, as a child of the request's own audit event |

Use the post-action hook when you need to change the request before it is applied, and the ticket-system hook when you need to tell somebody else that it was applied. The shipped post-action script has an empty method body, so nothing happens on that path until you write one.

The ticket-system hook is the one most people want, and the worked task is [Raise a ServiceNow incident from an approval](/administration-guide/requests/approval-flows/raise-a-servicenow-incident.md). Two things about it change the design before anything else does. **The entitlement is recorded before any outbound work happens**, so OpenIAM's own record says the user holds the access from the moment the request completes and the ticket is only a request for somebody to make that true elsewhere. And **the child audit event is marked successful as soon as your script returns**, whatever the script did — so a ServiceNow outage produces a `TICKET_SYSTEM_INTEGRATION` event marked succeeded and no ticket. The extension point's four invocation points and the full audit contract are on [Integration patterns for external systems](/administration-guide/operations/enterprise-integrations.md#the-ticket-system-extension-point).

## Related

* [Raise a ServiceNow incident from an approval](/administration-guide/requests/approval-flows/raise-a-servicenow-incident.md) — the outbound integration an approved request can trigger
* [Approver associations and the shipped flow shape](/administration-guide/requests/approval-flows/approver-association-reference.md) — every `APPROVER_ASSOC` column, the 15 entity types and where each is offered, and what the shipped entitlement flow does step by step
* [Shipped approval workflows](/administration-guide/reference/shipped-workflows.md) — all 49 definitions and their process IDs
* [The request catalog and questionnaires](/administration-guide/requests/request-catalog.md) — what people can request, and the questions they answer
* [Manual fulfilment](/administration-guide/requests/fulfilment.md) — the `manualTask` flow for targets OpenIAM cannot provision
* [Shipped batch tasks](/administration-guide/reference/shipped-batch-tasks.md) — reminders and escalation, and their enabled state
* [Enumerated values](/administration-guide/reference/enums.md#workflow-variables) — workflow variables and approver association types
* [System properties](/administration-guide/reference/system-properties.md) — the full `Workflow` category
* [Change configuration safely](/administration-guide/start/change-safely.md) — the change, test and rollback standard

*Applies to OpenIAM 2026.8.1. Last reviewed 7 August 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/administration-guide/requests/approval-flows.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
