# Importing entitlements

After you have connected the application, the next logical step is importing users and entitlements from the application. In this section, the step of importing entitlements is covered.

To do this, OpenIAM provides a synchronization service, which allows an easy import of any entitlement type.

Before configuring synchronization, please ensure you've done the following:

* Identified the type of object you want to synchronize, e.g. User, Group, Role, Organization, etc.
* Ensured the connector is up and running.
* Groovy scripts for transforming/mapping the data to OpenIAM objects are ready.
* The attributes in the target system and OpenIAM match.
* The data the user wants to synchronize is identified.
* The script with all the attributes you want to make available in OpenIAM is defined.

After ensuring the above is in place, use the steps below to configure synchronization. As the AD PowerShell connector is the most used connector to integrate applications with OpenIAM, the synchronization process will be used for this connector type.

{% stepper %}
{% step %}

### Go to **Webconsole > Provisioning > Synchronization**

The synchronization page contains ready-made examples of synchronization for various objects. **If you are new to OpenIAM, then please leverage these examples instead of creating a new configuration.** However, in case you want to configure a custom synchronization process, use the steps below.
{% endstep %}

{% step %}

### Click **Create Synchronization** in the left-hand menu

You will see the screen below.

<figure><img src="/files/1bcad9401d349320377ea0507ab27fb687227b51" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Complete the form based on the table below.

<table data-full-width="true"><thead><tr><th width="211.33331298828125">Field name</th><th>Description</th></tr></thead><tbody><tr><td>Name</td><td>Descriptive value to identify this configuration.</td></tr><tr><td>Record count in one batch</td><td>This controls how many records will be created to process data coming from the connector or CSV file. The default value is 1000.</td></tr><tr><td>Is active?</td><td>Flag, which determines if the synchronization configuration can be executed. An <em>inactive</em> value disables the task.</td></tr><tr><td>Detect orphan</td><td>Orphan management is used to detect records in a target system which are not in the source. This notion is covered in detail in the Administration guide <a href="https://docs.openiam.com/docs-2026.2.1/admin/1-usradmin/9-orphanmanagement">Orphan management</a> section.</td></tr><tr><td>Provision to target systems</td><td>This flag enables downstream provisioning to the target system. Once you have configured your synchronization and managed systems, you MUST enable this checkbox to allow for downstream provisioning.</td></tr><tr><td>Synchronization source</td><td>Determines if you will be importing the data using connectors or from a CSV file.</td></tr><tr><td>Managed System</td><td>Indicates which managed system the user should automatically be added to.</td></tr><tr><td>Synchronization object</td><td>Defines the type of object that will be imported. Select <em>Group</em> in this case.</td></tr><tr><td>Synch type</td><td>Allows you to define if this should be an incremental or complete synchronization. For the initial synchronization, use the complete option.</td></tr><tr><td>Synch Frequency</td><td>Describes how often the synchronization process should run, if you want it to be running automatically. The frequency is expressed as a Cron expression. More details on how to set a [Cron expression](/spaces/0uHWHRgcad6P03ehqgmX/pages/52b84f6a70280cf0bf493da11d7e613db1e2c7fe#cronexpressions).</td></tr><tr><td>Pre-processor script</td><td>Pre-processor script runs before synchronization starts and allows a script developer to create any type of customization needed. There are no limits to what this script can do.</td></tr><tr><td>Post-processor script</td><td>Post-processor script runs after synchronization has been completed. It is called depending on the operation that is happening to the user at the very moment. Hence, within this method the user can add the desired logic. For example, it can be used to send out email notifications about a provisioning event being completed.</td></tr><tr><td>Validation Rule</td><td>Groovy script to validate the incoming data from the file.</td></tr><tr><td>Transformation rule</td><td>Select the Groovy script which will be responsible for mapping data from the source to objects which OpenIAM understands. The example of a script for importing groups for connected applications and CSV files is given below.</td></tr><tr><td>OpenIAM field name</td><td>Field which uniquely identifies a user in OpenIAM. Select from one of the following: User ID, PRINCIPAL NAME (by managed system in config), Principal, Email, Employee ID, CUSTOM ATTRIBUTE, Name. If these do not apply, then select CUSTOM ATTRIBUTE and enter the attribute name.</td></tr><tr><td>Source Attribute Name</td><td>Attribute name from your source (connector or CSV) which uniquely identifies a user.</td></tr><tr><td>Custom Rule for Matching</td><td>In cases where it's not possible to match on a single field, you can create a custom match rule, using Groovy script, which will allow more complex matching algorithms.</td></tr><tr><td>Source attribute names</td><td>Attribute names from your source which uniquely identify users.</td></tr></tbody></table>
{% endstep %}
{% endstepper %}

Upon completing the fields and clicking **Save**, the synchronization is configured. Now you can run it and import entitlements.

## Transformation scripts

Transformation scripts map attributes from the source system to objects in OpenIAM to create a user profile. This can include information such as first name, last name, job title, employee ID, etc. These objects enable synchronization operations on target systems.

As mentioned above, these scripts are vital for a successful mapping. Below, you can find some transformation scripts used in OpenIAM. You can take the ready-made script and, after amending it, apply it to meet your mapping needs.

### Sample transformation script for AD groups

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```groovy
// The class extends from AbstractGroupTransformScript to inherit its functionality 
// for performing transformations on groups of data
class ADGroupSampleTransformationScript extends AbstractGroupTransformScript {

    // Overrides the execute function in the parent class
    // The execute function is where the main logic of the transformation script is run
    @Override
    int execute(LineObject rowObj, Group group) {

        // Prints a debugging statement indicating that the transformation script has been called
        println "** - Group Transformation script called."
        try {
            // Sets the policy ID for the group to be "4000"
            group.setPolicyId("4000");

            // Sets the Metadata Type ID for the group to be "AD_GROUP"
            group.setMdTypeId("AD_GROUP");

            // Calls a helper function to populate group object with necessary details
            populateObject(rowObj, group)
        } catch (Exception ex) {
            // Prints the stack trace for any errors that occur
            ex.printStackTrace();
            // Prints a debugging statement indicating that there was an error in the transformation script
            println "** - Transformation script error."

            // Returns the SKIP constant, which indicates that this row should be skipped
            return SKIP
        }
        // Prints a debugging statement indicating that the transformation script has completed
        println "** - Transformation script completed."

        // Returns the NO_DELETE constant, which indicates that the row was transformed successfully
        return NO_DELETE
    }

    // A helper function to populate a Group object with details from a LineObject
    private void populateObject(LineObject rowObj, Group group) {
        // Gets a map of column names to values from the row object
        def columnMap = rowObj.columnMap
        
        // If the user is new, sets the group id to null
        if (isNewUser) {
            group.id = null
        }
        // Sets the name of the group to the "Name" column in the row object
        group.name = columnMap.get("Name")?.value
        
        // Sets the description of the group to the "description" column in the row object
        group.description = columnMap.get("description")?.value
        
        // If the description of the group is too long, trims it to 254 characters
        if (group.getDescription() && group.getDescription().length() > 254) {
            group.setDescription(group.getDescription().substring(0, 254));
        }
        
        // Sets the status of the group to "ACTIVE"
        group.status = "ACTIVE"
        
        // Adds attributes to the group
        addGroupAttribute(group, "sAMAccountName", columnMap.get("sAMAccountName")?.value);
        addGroupAttribute(group, "DistinguishedName", columnMap.get("DistinguishedName")?.value);
        
        // Sets the scope of the group based on the "groupScope" column in the row object
        def scope = columnMap.get("groupScope")?.value
        if (scope) {
            switch (scope) {
                case "0":
                    group.setAdGroupScopeId("Domain_Local")
                    break
                case "1":
                    group.setAdGroupScopeId("Global")
                    break
                case "2":
                    group.setAdGroupScopeId("Universal")
                    break
            }
        }
        
        // Sets the category of the group based on the "groupCategory" column in the row object
        def category = columnMap.get("groupCategory")?.value
        if (category) {
            switch (category) {
                case "0":
                    group.setAdGroupTypeId("DISTRIBUTION_GROUP")
                    break
                case "1":
                    group.setAdGroupTypeId("SECURITY_GROUP")
                    break
            }
        }

    }

    // The init function is required by Groovy but is not used in this script
    // It could be used for setup or initialization tasks if necessary
    @Override
    void init() {}

```

{% endcode %}

### Sample transformation script for a CSV file

#### CSV file structure example <a href="#csvfilestructureexample" id="csvfilestructureexample"></a>

For the script to work correctly, make sure the fields in the CSV file are the same as written in the transformation script. An example of the CSV file structure is given below.

{% code overflow="wrap" %}

```csv
MANAGED_SYSTEM_NAME,GROUP_TYPE,ROLE_TYPE,ENTITLEMENT_NAME,IS_ACTIVE,ENTITLEMENT_OWNER_TYPE,ENTITLEMENT_OWNER,ENTITLEMENT_ADMIN_TYPE,ENTITLEMENT_ADMIN,APPROVER1_TYPE,APPROVER1,APPROVER2_TYPE,APPROVER2,APPROVER3_TYPE,APPROVER3,AD_GROUPS,ATTRIBUTE_NAME1,ATTRIBUTE_VALUE1,ATTRIBUTE_NAME2,ATTRIBUTE_VALUE2,ATTRIBUTE_NAME3,ATTRIBUTE_VALUE3,MAX_MEMBERSHIP_DURATION01_Test Active Directory,,Access Role,Test AD Role,Y,GROUP,Security group,USER,snelson,SUPERVISOR,Supervisor,OWNER,,ADMIN,,,attrbiuteTest1,value1,attrbiuteTest2,value2,attrbiuteTest3,value3,9000
```

{% endcode %}

### Transformation script example <a href="#transformationscriptexample" id="transformationscriptexample"></a>

The text for transformation script (with comments) to get roles via CSV file for manual applications is provided below.

{% code lineNumbers="true" expandable="true" %}

```groovy
import org.openiam.base.request.ApproverAssociationsCrudRequest
import org.openiam.base.response.list.ApproverAssociationListResponse
import org.openiam.common.beans.mq.ApproverAssociationRabbitMQService
import org.openiam.common.beans.mq.MetadataTypeRabbitMQService
import org.openiam.common.beans.mq.RabbitMQSender
import org.openiam.idm.srvc.grp.dto.Group
import org.openiam.idm.srvc.membership.dto.ObjectAdmin
import org.openiam.idm.srvc.membership.dto.ObjectOwner
import org.openiam.idm.srvc.meta.domain.MetadataTypeGrouping
import org.openiam.idm.srvc.meta.dto.MetadataType
import org.openiam.idm.srvc.mngsys.bean.ApproverAssociationSearchBean
import org.openiam.idm.srvc.mngsys.domain.AssociationType
import org.openiam.idm.srvc.mngsys.dto.ApproverAssociation
import org.openiam.idm.srvc.role.dto.Role
import org.openiam.idm.srvc.synch.dto.LineObject
import org.openiam.idm.srvc.user.dto.User
import org.openiam.mq.constants.api.OpenIAMAPI
import org.openiam.mq.constants.api.am.ApproverAssociationAPI
import org.openiam.mq.constants.queue.am.ApproverAssociationQueue
import org.openiam.provision.type.Attribute
import org.openiam.sync.service.impl.service.AbstractRoleTransformScript
import org.springframework.util.CollectionUtils
import org.springframework.util.StringUtils

/**
 * This is OpenIAM common Role transformation script for default CSV file.
 * If you use your custom CSV or modify default CSV file please take care of the new fields.
 */
class CsvRoleCommonTransformationScript extends AbstractRoleTransformScript {

    private static final String DEFAULT_PASSWORD_POLICY_ID = "4000"

    private ApproverAssociationRabbitMQService approverAssociationRabbitMQService
    private ApproverAssociationQueue approverAssociationQueue
    private RabbitMQSender rabbitMQSender

    /**
     * We cannot use @Autowired annotation here so we need to get beans from the context.
     */
    @Override
    void init() {
        if (metadataTypeRabbitMQService == null) {
            metadataTypeRabbitMQService = context.getBean(MetadataTypeRabbitMQService.class)
        }
        if (approverAssociationRabbitMQService == null) {
            approverAssociationRabbitMQService = context.getBean(ApproverAssociationRabbitMQService.class)
        }
        if (approverAssociationQueue == null) {
            approverAssociationQueue = context.getBean(ApproverAssociationQueue.class)
        }
        if (rabbitMQSender == null) {
            rabbitMQSender = context.getBean(RabbitMQSender.class)
        }
    }


    @Override
    int execute(LineObject rowObj, Role role) {
        try {
            return populateObject(rowObj, role)
        } catch (Exception ex) {
            return SKIP
        }
    }

    /**
     * Populate Role object from CSV row.
     *
     * @param rowObj
     * @param role
     * @return
     */
    private int populateObject(LineObject rowObj, Role role) {
        Map<String, Attribute> map = rowObj.columnMap

        role.setPolicyId(DEFAULT_PASSWORD_POLICY_ID)

        Attribute attribute = map.get("ENTITLEMENT_NAME")
        if (attribute && StringUtils.hasText(attribute.getValue())) {
            role.setName(attribute.getValue())
        } else {
            log.error("ENTITLEMENT_NAME field cannot be empty!")
            return SKIP
        }

        attribute = map.get("ROLE_TYPE")
        if (attribute && StringUtils.hasText(attribute.getValue())) {
            role.setMdTypeId(getMetadataTypeByNameAndGrouping(attribute.getValue(), MetadataTypeGrouping.ROLE_TYPE)?.getId())
        }
        if (!StringUtils.hasText(role.getMdTypeId())) {
            log.error("ROLE_TYPE field empty or incorrect for ENTITLEMENT_NAME: ${role.getName()}.")
            return SKIP
        }

        attribute = map.get("MANAGED_SYSTEM_NAME")
        String managedSystemId = null
        if (attribute && StringUtils.hasText(attribute.getValue())) {
            managedSystemId = getManagedSystemByName(attribute.getValue())?.getId()
            if (!managedSystemId) {
                log.warn("Cannot find Managed System with name: ${attribute.getValue()}")
            }
        }
        role.setManagedSysId(managedSystemId)

        attribute = map.get("IS_ACTIVE")
        if (attribute && StringUtils.hasText(attribute.getValue()) && "Y" == attribute.getValue()) {
            role.setStatus("ACTIVE")
        } else {
            role.setStatus("INACTIVE")
        }

        attribute = map.get("DESCRIPTION")
        if (attribute && StringUtils.hasText(attribute.getValue())) {
            role.setDescription(attribute.getValue())
        }

        attribute = map.get("MAX_MEMBERSHIP_DURATION")
        if (attribute && StringUtils.hasText(attribute.getValue())) {
            role.setMembershipDuration(Integer.valueOf(attribute.getValue()))
        }


        def roleAttrName = map.get("ATTRIBUTE_NAME1")?.value
        def roleAttrValue = map.get("ATTRIBUTE_VALUE1")?.value
        if (role.getId() && StringUtils.hasText(roleAttrName) && StringUtils.hasText(roleAttrValue)) {
            addRoleAttribute(role, roleAttrName, roleAttrValue)
        }

        roleAttrName = map.get("ATTRIBUTE_NAME2")?.value
        roleAttrValue = map.get("ATTRIBUTE_VALUE2")?.value
        if (role.getId() && StringUtils.hasText(roleAttrName) && StringUtils.hasText(roleAttrValue)) {
            addRoleAttribute(role, roleAttrName, roleAttrValue)
        }

        roleAttrName = map.get("ATTRIBUTE_NAME3")?.value
        roleAttrValue = map.get("ATTRIBUTE_VALUE3")?.value
        if (role.getId() && StringUtils.hasText(roleAttrName) && StringUtils.hasText(roleAttrValue)) {
            addRoleAttribute(role, roleAttrName, roleAttrValue)
        }


        Attribute appOwnerType = map.get("ENTITLEMENT_OWNER_TYPE")
        Attribute appOwner = map.get("ENTITLEMENT_OWNER")
        if (appOwnerType && StringUtils.hasText(appOwnerType.getValue()) && appOwner && StringUtils.hasText(appOwner.getValue())) {
            role.setOwner(getOwnerObject(appOwnerType.getValue().trim(), appOwner.getValue().trim()))
        } else {
            role.setOwner(null)
        }

        Attribute appAdminType = map.get("ENTITLEMENT_ADMIN_TYPE")
        Attribute appAdmin = map.get("ENTITLEMENT_ADMIN")
        if (appAdminType && StringUtils.hasText(appAdminType.getValue()) && appAdmin && StringUtils.hasText(appAdmin.getValue())) {
            role.setAdmin(getAdminObject(appAdminType.getValue().trim(), appAdmin.getValue().trim()))
        } else {
            role.setAdmin(null)
        }
        // in case if we need to add some AD groups (1199 issue).
        attribute = map.get("AD_GROUPS")
        if (attribute && StringUtils.hasText(attribute.getValue())) {
            addRoleAttribute(role, attribute.getName(), attribute.getValue())
        }

        if (!isNewUser) {
            populateAndSaveApproverAssociations(role, map)
        }

        return NO_DELETE

    }


    /**
     * Get Owner object by type (user/group) and name.
     *
     * @param type
     * @param name
     * @return
     */

    private ObjectOwner getOwnerObject(String type, String name) {
        log.debug("==== execute getOwnerObject method start. =====")
        type = type.toUpperCase()
        ObjectOwner obj = new ObjectOwner()
        obj.setType(type.toLowerCase())
        switch (type) {
            case "USER":
                User user = getUserByLogin(name)
                if (user) {
                    obj.setId(user.getId())
                } else {
                    log.warn("Cannot find User for getOwnerObject method with login: ${name}")
                    obj = null
                }
                break
            case "GROUP":
                Group group = getGroupByName(name, config.getManagedSysId())
                if (group) {
                    obj.setId(group.getId())
                } else {
                    log.warn("Cannot find Group for getOwnerObject method with name: ${name}")
                    obj = null
                }
                break
            default:
                log.warn("Unknown type: ${type}")
                obj = null
        }
        log.debug("==== execute getOwnerObject method end. =====")
        return obj
    }

    /**
     * Get Admin object by type (user/group) and name.
     *
     * @param type
     * @param name
     * @return
     */
    private ObjectAdmin getAdminObject(String type, String name) {
        log.debug("==== execute getAdminObject method start. =====")
        type = type.toUpperCase()
        ObjectAdmin obj = new ObjectAdmin()
        obj.setType(type.toLowerCase())
        switch (type) {
            case "USER":
                User user = getUserByLogin(name)
                if (user) {
                    obj.setId(user.getId())
                } else {
                    log.warn("Cannot find User for getAdminObject method with login: ${name}")
                    obj = null
                }
                break
            case "GROUP":
                Group group = getGroupByName(name, config.getManagedSysId())
                if (group) {
                    obj.setId(group.getId())
                } else {
                    log.warn("Cannot find Group for getAdminObject method with name: ${name}")
                    obj = null
                }
                break
            default:
                log.warn("Unknown type: ${type}")
                obj = null
        }
        log.debug("==== execute getAdminObject method end. =====")
        return obj
    }

    /**
     * Populate and save new Approver Associations.
     *
     * @param role
     * @param map
     */
    private void populateAndSaveApproverAssociations(Role role, Map<String, Attribute> map) {
        log.debug("==== execute populateAndSaveApproverAssociations method start. =====")
        removeApproverAssociations(role.getId())

        Attribute approverType1 = map.get("APPROVER1_TYPE")
        Attribute approver1 = map.get("APPROVER1")

        if (approverType1 && approverType1.getValue()) {
            List<ApproverAssociation> approverAssociationList = new ArrayList<>()
            int level = 0
            ApproverAssociation firstApprover = getApproverAssociation(role.getId(),
                    approverType1.getValue().trim(), approver1.getValue().trim(), level++)
            if (firstApprover) {
                approverAssociationList.add(firstApprover)
                Attribute approverType2 = map.get("APPROVER2_TYPE")
                Attribute approver2 = map.get("APPROVER2")
                ApproverAssociation secondApprover = getApproverAssociation(role.getId(),
                        approverType2.getValue().trim(), approver2.getValue().trim(), level++)
                if (secondApprover) {
                    approverAssociationList.add(secondApprover)
                    Attribute approverType3 = map.get("APPROVER3_TYPE")
                    Attribute approver3 = map.get("APPROVER3")
                    ApproverAssociation thirdApprover = getApproverAssociation(role.getId(),
                            approverType3.getValue().trim(), approver3.getValue().trim(), level++)
                    if (thirdApprover) {
                        approverAssociationList.add(thirdApprover)
                    }
                }
            }


            if (!CollectionUtils.isEmpty(approverAssociationList)) {
                ApproverAssociationsCrudRequest request = new ApproverAssociationsCrudRequest()
                request.setApproverAssociationList(approverAssociationList)
                rabbitMQSender.send(approverAssociationQueue, (OpenIAMAPI) ApproverAssociationAPI.SaveApproverAssociations, request)
            }
        }
        log.debug("==== execute populateAndSaveApproverAssociations method end. =====")
    }

    /**
     * Remove existed Approver Associations.
     *
     * @param roleId
     */
    private void removeApproverAssociations(String roleId) {
        log.debug("==== execute removeApproverAssociations method start. =====")
        ApproverAssociationSearchBean bean = new ApproverAssociationSearchBean()
        bean.setAssociationEntityId(roleId)
        ApproverAssociationListResponse approvers = approverAssociationRabbitMQService.getApproverAssociations(bean)

        if (approvers && !CollectionUtils.isEmpty(approvers.getList())) {
            List<String> approverIdList = approvers.getList().collect { it.getApproverEntityId() }
            approverAssociationRabbitMQService.removeApproverAssociations(roleId, approverIdList)
        }
        log.debug("==== execute removeApproverAssociations method end. =====")
    }

    /**
     * Get ApproverAssociation object by type and name.
     * type can be: [USER, SUPERVISOR, ROLE, GROUP, OWNER, ADMIN].
     *
     * @param roleId
     * @param type
     * @param name
     * @param level
     * @return
     */
    private ApproverAssociation getApproverAssociation(String roleId, String type, String name, int level) {
        log.debug("==== execute getApproverAssociation method start. =====")
        type = type.toUpperCase()
        if (("USER" == type || "ROLE" == type || "GROUP" == type) && !StringUtils.hasText(name)) {
            log.warn("For types: [USER, ROLE, GROUP] name cannot be empty!")
            return null
        }
        ApproverAssociation result = new ApproverAssociation()
        result.setAssociationEntityId(roleId)
        result.setAssociationType(AssociationType.ROLE)
        result.setApproverLevel(level)
        switch (type) {
            case "USER":
                result.setApproverEntityType(AssociationType.USER)
                User user = getUserByLogin(name)
                if (user) {
                    result.setApproverEntityId(user.getId())
                } else {
                    log.warn("Cannot find User for getApproverAssociation method with login: ${name}")
                    result = null
                }
                break
            case "SUPERVISOR":
                result.setApproverEntityType(AssociationType.SUPERVISOR)
                if (StringUtils.hasText(name)) {
                    if (name == "Supervisor") {
                        result.setApproverEntityId("Supervisor")
                        break
                    }
                    MetadataType metadataType = getMetadataTypeByNameAndGrouping(name, MetadataTypeGrouping.SUPERVISOR_TYPE)
                    if (metadataType) {
                        result.setApproverEntityId(metadataType.getId())
                    } else {
                        log.warn("Cannot find Supervisor type for getApproverAssociation method with name: ${name}")
                        result = null
                    }
                }
                break
            case "ROLE":
                result.setApproverEntityType(AssociationType.ROLE)
                Role role = getRoleByName(name, config.getManagedSysId())
                if (role) {
                    result.setApproverEntityId(role.getId())
                } else {
                    log.warn("Cannot find Role for getApproverAssociation method with name: ${name}")
                    result = null
                }
                break
            case "GROUP":
                result.setApproverEntityType(AssociationType.GROUP)
                Group group = getGroupByName(name, config.getManagedSysId())
                if (group) {
                    result.setApproverEntityId(group.getId())
                } else {
                    log.warn("Cannot find Group for getApproverAssociation method with name: ${name}")
                    result = null
                }
                break
            case "OWNER":
                result.setApproverEntityType(AssociationType.OWNER)
                result.setApproverEntityId("Owner")
                break
            case "ADMIN":
                result.setApproverEntityType(AssociationType.ADMIN)
                result.setApproverEntityId("Admin")
                break
            default:
                log.warn("Unknown type: ${type}")
                result = null
                break
        }
        log.debug("==== execute getApproverAssociation method end. =====")
        return result
    }
}
```

{% endcode %}

<br>


---

# Agent Instructions: Querying This Documentation

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

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs-beta.openiam.com/application-onboarding/importing-entitlements.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
