Restrict ritm creation via table API

Mujahid Sharief
Tera Contributor

The above requirement is not getting achieved with before insert BR, I need to block ritm creation via table api and only authorized channels like service catalog/approved API should be able to create.
Please help, if anyone has implemented such requirement. 

8 REPLIES 8

vaishali231
Kilo Sage

Hey @Mujahid Sharief 

 Before Insert Business Rule alone cannot reliably block RITM creation via Table API. The reason is that Table API requests are already authorized at the platform level before the Business Rule executes.

To properly enforce this requirement, you need a layered control approach.

 

 Root Cause

The Table API allows record creation if the user has required roles/ACL access

Business Rules execute after access is granted, so they are not a secure control point

There is no built-in differentiation between:

  • Service Catalog (UI/Portal)
  • External API calls

 Solution

1. Restrict Table API Access via ACL (Primary Control)

Create a Create ACL on sc_req_item to strictly control who can insert records.

Example:

Table: sc_req_item

Operation: create

answer = gs.hasRole('your_integration_role');

 Only users with this role can create RITM via API
Remove unnecessary roles like rest_service from general users

 

2. Use Scripted REST API for Approved Integrations

Instead of exposing the Table API:

Create a Scripted REST API

Add validation (headers, tokens, source system)

Example:

(function process(request, response) {
   var source = request.headers['x-source'];
   if (source != 'approved_system') {
       response.setStatus(403);
       response.setBody({ error: 'Unauthorized source' });
       return;
   }
   var ritm = new GlideRecord('sc_req_item');
   ritm.initialize();
   ritm.short_description = request.body.data.short_description;
   var sysId = ritm.insert();
   response.setBody({ result: 'success', sys_id: sysId });
})();

Full control over who can create records
  Ability to validate payload and enforce business logic

 

3. Keep Business Rule as Defensive Layer (Optional)

You can still use a Business Rule as a fallback safeguard:

if (!gs.getSession().isInteractive() && !gs.hasRole('your_integration_role')) {

   gs.addErrorMessage('Unauthorized RITM creation');

   current.setAbortAction(true);

}

 Helps catch misconfigurations
  Should not be the primary control

 

 How to Differentiate Channels

gs.getSession().isInteractive()

true - UI / Service Portal

false - API / background

Use this only as a supporting check, not a security control.

*************************************************************************************************************************************

If this response helps, please mark it as Accept as Solution and Helpful.

Doing so helps others in the community and encourages me to keep contributing.

Regards

Vaishali Singh

Servicenow Developer
Linkedin - https://www.linkedin.com/in/vaishali-singh-2273361bb





 

Thank you for the solution, let me try one of these and confirm.

hey @Mujahid Sharief 

 

 

Hope you are doing well.

Did my previous reply answer your question?

If it was helpful, please mark it as correct ✓ and close the thread . This will help other readers find the solution more easily.

 

Thankyou & Regards

Vaishali Singh

Servicenow Developer
Linkedin - https://www.linkedin.com/in/vaishali-singh-2273361bb





(function executeRule(current, previous /*null when async*/) {

    if (!current.subcategory.nil()) {
        valCatSubcat(current.category, current.subcategory);
    } else {
        valCat(current.category);
    }

    function valCatSubcat(category, subcategory) {
        var qry = gs.getMessage(
            "name=incident^element=category^ORelement=subcategory^dependent_value={0}^value={1}",
            [category, subcategory]
        );

        var ch = new GlideRecord('sys_choice');
        ch.addEncodedQuery(qry);
        ch.query();

        var result = ch.hasNext();

        if (!result) {
            // Allow transition from Pending → Work in Progress even if invalid
            if (!(previous.incident_state == 'On Hold' && current.incident_state == 'Work in Progress')) {
                current.setAbortAction(true);
            }
        }
    }

    function valCat(category) {
        var qry = gs.getMessage(
            "name=incident^element=category^value={0}",
            [category]
        );

        var ch = new GlideRecord('sys_choice');
        ch.addEncodedQuery(qry);
        ch.query();

        var result = ch.hasNext();

        if (!result) {
            // Allow transition from Pending → Work in Progress even if invalid
            if (!(previous.incident_state == 'On Hold' && current.incident_state == 'Work in Progress')) {
                current.setAbortAction(true);
            }
        }
    }

})(current, previous);