REST API Integrations: Outbound and Inbound
Introduction
REST integrations are the backbone of modern enterprise connectivity. ServiceNow sits at the center of most IT ecosystems, needing to both consume external APIs (outbound) and expose its own data and processes to external systems (inbound). This guide covers both directions with production-ready code examples.
Outbound REST: Calling External APIs
RESTMessageV2 — The Modern API
var sm = new sn_ws.RESTMessageV2();
// Option 1: Direct URL (for ad-hoc calls)
sm.setHttpMethod('GET');
sm.setEndpoint('https://api.example.com/v2/incidents');
sm.setRequestHeader('Authorization', 'Bearer ' +
gs.getProperty('myapp.api.token'));
sm.setRequestHeader('Content-Type', 'application/json');
sm.setRequestHeader('Accept', 'application/json');
// Add query parameters
sm.setQueryParameter('status', 'open');
sm.setQueryParameter('limit', '100');
var response = sm.execute();
var statusCode = response.getStatusCode();
var body = response.getBody();
if (statusCode == 200) {
var data = JSON.parse(body);
// Process data
} else {
gs.error('API call failed. Status: ' + statusCode +
' Body: ' + body);
}
POST with JSON Body
var sm = new sn_ws.RESTMessageV2();
sm.setHttpMethod('POST');
sm.setEndpoint('https://api.example.com/v2/tickets');
sm.setRequestHeader('Content-Type', 'application/json');
sm.setRequestHeader('Authorization', 'Bearer ' + apiToken);
var payload = {
title: current.getValue('short_description'),
priority: current.getValue('priority'),
description: current.getValue('description'),
external_id: current.getValue('number')
};
sm.setRequestBody(JSON.stringify(payload));
var response = sm.execute();
var result = JSON.parse(response.getBody());
// Store external system ID back on our record
current.setValue('correlation_id', result.id);
current.update();
REST Message Records (Best Practice)
For reusable, maintainable integrations, define REST Messages in the platform:
System Web Services > Outbound > REST Message > New
// Cleaner: Reference the REST Message record
var sm = new sn_ws.RESTMessageV2(
'ExternalTicketSystem', // REST Message name
'Create Ticket' // HTTP Method name
);
sm.setStringParameterNoEscape('short_description',
current.getValue('short_description'));
sm.setStringParameterNoEscape('priority',
current.getValue('priority'));
var response = sm.execute();
This approach separates endpoint configuration from code, making environment changes (dev → prod URLs) manageable through records rather than code changes.
Asynchronous Execution
For integrations that shouldn't block the transaction:
// In an async Business Rule or scheduled job
var sm = new sn_ws.RESTMessageV2('ExternalSystem', 'Sync Incident');
sm.setStringParameterNoEscape('sys_id', current.getUniqueValue());
// executeAsync() returns immediately; response handled by callback
sm.executeAsync();
Error Handling and Retry Logic
function callWithRetry(restMessage, maxRetries) {
var attempts = 0;
var response;
while (attempts < maxRetries) {
try {
response = restMessage.execute();
if (response.getStatusCode() < 500) {
return response; // Success or 4xx (no retry)
}
} catch(e) {
gs.warn('REST call attempt ' + (attempts+1) +
' failed: ' + e.message);
}
attempts++;
// Exponential backoff
gs.sleep(Math.pow(2, attempts) * 1000);
}
throw new Error('REST call failed after ' + maxRetries + ' attempts');
}
Inbound REST: Exposing ServiceNow APIs
The Table API (OOTB)
ServiceNow exposes all tables through the built-in Table API:
GET /api/now/table/incident → List incidents
GET /api/now/table/incident/{sys_id} → Get specific incident
POST /api/now/table/incident → Create incident
PUT /api/now/table/incident/{sys_id} → Full update
PATCH /api/now/table/incident/{sys_id}→ Partial update
DELETE /api/now/table/incident/{sys_id}→ Delete incident
Powerful for generic integrations, but exposes all fields by default. Use query parameters to control output:
?sysparm_fields=number,short_description,state,priority
&sysparm_query=active=true^priority=1
&sysparm_limit=100
&sysparm_display_value=true
Scripted REST APIs: Custom Endpoints
For custom business logic or controlled data exposure:
System Web Services > Scripted REST APIs > New
Example: Create a custom incident intake endpoint
// Resource: POST /api/mycompany/v1/incident
(function process(request, response) {
try {
var body = JSON.parse(request.body.dataString);
// Validate required fields
if (!body.title || !body.caller_email) {
response.setStatus(400);
response.setBody({
error: 'Missing required fields: title, caller_email'
});
return;
}
// Find caller by email
var caller = new GlideRecord('sys_user');
if (!caller.get('email', body.caller_email)) {
response.setStatus(404);
response.setBody({
error: 'User not found: ' + body.caller_email
});
return;
}
// Create incident
var inc = new GlideRecord('incident');
inc.initialize();
inc.setValue('short_description', body.title);
inc.setValue('description', body.description || '');
inc.setValue('caller_id', caller.getUniqueValue());
inc.setValue('priority', body.priority || '3');
inc.setValue('category', body.category || 'inquiry');
var sysId = inc.insert();
// Fetch the created record for response
inc.get(sysId);
response.setStatus(201);
response.setBody({
sys_id: sysId,
number: inc.getValue('number'),
state: inc.getDisplayValue('state'),
link: gs.getProperty('glide.servlet.uri') +
'incident.do?sys_id=' + sysId
});
} catch(e) {
gs.error('Incident API error: ' + e.message);
response.setStatus(500);
response.setBody({ error: 'Internal server error' });
}
})(request, response);
Authentication Options
| Method | Use Case |
|---|---|
| Basic Auth | Simple integrations; avoid in production |
| OAuth 2.0 | Preferred for external applications |
| API Key (mutual auth) | Machine-to-machine integrations |
| SAML | Enterprise SSO-based integrations |
OAuth 2.0 Setup (Inbound):
System OAuth > Application Registry > New → OAuth API endpoint for external clients
External clients authenticate with client_id + client_secret to receive a token, then include Authorization: Bearer {token} in API calls.
Testing and Debugging REST Integrations
REST API Explorer
System Web Services > REST API Explorer
Test any Scripted REST API or Table API call directly from the browser with your current session's authentication.
Outbound HTTP Request Log
System Logs > Outbound HTTP Requests
Shows every outbound REST/SOAP call with full request/response detail. Invaluable for debugging failed integrations.
Integration Debugger
System Diagnostics > Session Debug > Outbound HTTP Logging
Enable detailed logging for the current session to capture integration traffic.
Best Practices
- Always use REST Message records for reusable integrations (not hardcoded URLs)
- Store API credentials in Credential records, not System Properties
- Implement error handling for every HTTP call (check status codes)
- Use async execution for non-critical integrations to avoid blocking
- Version your Scripted REST APIs (v1, v2)
- Validate input on inbound APIs before processing
- Return meaningful HTTP status codes (201 for creates, 404 for not found, etc.)
- Log integration failures with enough context to debug
- Set reasonable connection and read timeouts on outbound calls
- Use OAuth 2.0 for external application authentication
Conclusion
REST integrations are where ServiceNow earns its place as an enterprise platform hub. The RESTMessageV2 API gives you full control over outbound calls, while Scripted REST APIs let you expose precisely the right interface to external consumers. Invest in proper credential management, error handling, and logging from day one—integrations that fail silently are worse than integrations that fail loudly.