Building Scripted REST APIs in ServiceNow: A Complete Guide

How to design, build, and secure custom REST endpoints in ServiceNow — resource design, request/response handling, versioning, and authentication patterns.

The Table API covers most data access scenarios, but sometimes you need a custom endpoint — one that runs business logic, aggregates data from multiple tables, or exposes an operation that doesn't map cleanly to CRUD. Scripted REST APIs are how you build those endpoints on the ServiceNow platform.

When to Build a Scripted REST API

Build a Scripted REST API when:

  • You need to execute business logic as part of the API call (not just data retrieval)
  • You need to aggregate data from multiple tables into a single response
  • You're exposing ServiceNow functionality to an external system that expects a specific API contract
  • You need custom authentication or rate limiting beyond what the Table API provides

Creating a Scripted REST API

Navigate to System Web Services > Scripted REST APIs > New

Key settings:

  • API ID — used in the URL path: /api/<scope>/<api_id>
  • API Namespace — your application scope prefix
  • Requires Authentication — almost always true for production APIs

Resource Design

A Scripted REST API is made up of Resources — individual endpoints with their own path and HTTP method handling.

// API: /api/nowspec/incident_metrics
// Resources:
//   GET  /summary          → overall metrics
//   GET  /by_group/{id}    → metrics for specific group
//   POST /refresh          → trigger metrics recalculation

Use path parameters for resource identifiers and query parameters for filters:

// Path parameter: /api/nowspec/incidents/{sys_id}
var sys_id = request.pathParams.sys_id;

// Query parameter: /api/nowspec/incidents?priority=1&limit=50
var priority = request.queryParams.priority;
var limit = parseInt(request.queryParams.limit) || 25;

Request and Response Handling

(function process(request, response) {
    
    // Read request body (for POST/PUT)
    var body = JSON.parse(request.body.dataString);
    
    // Set response status
    response.setStatus(200); // or 201, 400, 404, 500
    
    // Set content type
    response.setContentType('application/json');
    
    // Build response object
    var result = {
        status: 'success',
        data: {},
        meta: {
            timestamp: new Date().toISOString(),
            version: '1.0'
        }
    };
    
    // Execute business logic
    var gr = new GlideRecord('incident');
    gr.addEncodedQuery('active=true^priority=1');
    gr.query();
    
    var incidents = [];
    while (gr.next()) {
        incidents.push({
            sys_id: gr.getUniqueValue(),
            number: gr.getValue('number'),
            short_description: gr.getValue('short_description'),
            assigned_to: gr.getDisplayValue('assigned_to')
        });
    }
    
    result.data = incidents;
    result.meta.count = incidents.length;
    
    response.setBody(JSON.stringify(result));
    
})(request, response);

Error Handling Pattern

(function process(request, response) {
    
    try {
        var sys_id = request.pathParams.sys_id;
        
        if (!sys_id) {
            response.setStatus(400);
            response.setBody(JSON.stringify({
                error: 'sys_id is required',
                code: 'MISSING_PARAMETER'
            }));
            return;
        }
        
        var gr = new GlideRecord('incident');
        if (!gr.get(sys_id)) {
            response.setStatus(404);
            response.setBody(JSON.stringify({
                error: 'Incident not found',
                code: 'RECORD_NOT_FOUND'
            }));
            return;
        }
        
        // Success response
        response.setStatus(200);
        response.setBody(JSON.stringify({
            sys_id: gr.getUniqueValue(),
            number: gr.getValue('number')
        }));
        
    } catch(e) {
        gs.error('Scripted REST API error: ' + e.message);
        response.setStatus(500);
        response.setBody(JSON.stringify({
            error: 'Internal server error',
            code: 'SERVER_ERROR'
        }));
    }
    
})(request, response);

Versioning

ServiceNow Scripted REST APIs support versioning through the API definition. When you need to make a breaking change:

  1. Create a new version of the API (v2)
  2. Build the new version alongside the existing one
  3. Communicate a deprecation timeline to API consumers
  4. Remove v1 only after all consumers have migrated

The URL includes the version: /api/nowspec/incidents/v2/summary

Security Considerations

  • Always require authentication — never expose a Scripted REST API without it
  • Validate all input parameters before using them in queries
  • Never concatenate user input directly into encoded queries — use addQuery() instead
  • Limit what data is returned based on the calling user's roles, not just authentication
  • Log API calls including the caller's user ID for audit purposes

Want the complete reference?

This article is part of the NowSpectrum knowledge library. Browse all products for cheat sheets, interview prep, and deep-dive reference guides.

Browse All Products →
← Back to all posts