From a1f5d27f3ec7675d0f21fc4d9274b8be41a18d86 Mon Sep 17 00:00:00 2001 From: hanna-g-sn Date: Tue, 21 Oct 2025 11:14:09 +0100 Subject: [PATCH 1/3] Add README for RESTMessageV2 GET with backoff and telemetry This README explains how to use the RESTMessageV2 GET helper for handling API throttling, telemetry, and pagination. --- .../README.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/README.md diff --git a/Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/README.md b/Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/README.md new file mode 100644 index 0000000000..58bd0bc209 --- /dev/null +++ b/Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/README.md @@ -0,0 +1,21 @@ +# RESTMessageV2 GET with backoff, telemetry, and simple pagination + +## What this solves +External APIs frequently throttle with HTTP 429 or intermittently return 5xx. This helper retries safely, honours Retry-After, logs simple telemetry, and follows a links.next pagination model. + +## Where to use +Script Include can be called from Scheduled Jobs, Flow Actions, Business Rules, or Background Scripts. + +## How it works +- Executes RESTMessageV2 requests +- On 429 or 5xx, sleeps using Retry-After or exponential backoff +- Collects minimal telemetry about attempts and total sleep time +- Appends items from json.items and follows json.links.next + +## References +- RESTMessageV2 API + https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/RESTMessageV2/concept/c_RESTMessageV2API.html +- Direct RESTMessageV2 example + https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/RESTMessageV2/reference/r_DirectRESTMessageV2Example.html +- Inbound rate limiting and Retry-After header + https://www.servicenow.com/docs/bundle/zurich-api-reference/page/integrate/inbound-rest/concept/inbound-REST-API-rate-limiting.html From 00e790f8d0d64a64356d33e83fcfd82651800f59 Mon Sep 17 00:00:00 2001 From: hanna-g-sn Date: Tue, 21 Oct 2025 11:16:08 +0100 Subject: [PATCH 2/3] Implement RestGetWithBackoff class for REST API calls This script includes a class for performing REST GET requests with retry handling, exponential backoff, and pagination support. It provides methods to fetch all pages of results and handle errors gracefully. --- .../RestGetWithBackoff.js | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/RestGetWithBackoff.js diff --git a/Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/RestGetWithBackoff.js b/Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/RestGetWithBackoff.js new file mode 100644 index 0000000000..0dace322d6 --- /dev/null +++ b/Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/RestGetWithBackoff.js @@ -0,0 +1,135 @@ +/** + * Script Include: RestGetWithBackoff + * Purpose: Safely perform RESTMessageV2 GET requests with retry handling, + * exponential backoff, and simple pagination support. + * + * Example usage (Background Script): + * var helper = new RestGetWithBackoff(); + * var data = helper.getAll({ + * endpoint: 'https://api.example.com/v1/items', + * headers: { 'Authorization': 'Bearer ${token}' }, + * maxRetries: 4, + * baseDelayMs: 750 + * }); + * gs.info('Fetched ' + data.length + ' records'); + */ + +var RestGetWithBackoff = Class.create(); +RestGetWithBackoff.prototype = { + initialize: function() {}, + + /** + * Main entry point to fetch all pages of results. + * Handles retries, pagination, and aggregates results. + * @param {Object} options - endpoint, headers, maxRetries, baseDelayMs + * @returns {Array} all items combined from paginated responses + */ + getAll: function(options) { + var url = options.endpoint; // Initial API endpoint + var headers = options.headers || {}; // Optional request headers + var maxRetries = options.maxRetries || 5; // Maximum retry attempts per page + var baseDelayMs = options.baseDelayMs || 500;// Base delay for exponential backoff + + var items = []; // Array to collect all items across pages + var attempts = 0; // Total number of REST calls + var totalSleepMs = 0; // Total delay time across retries + + // Continue fetching until there are no more pages (links.next = null) + while (url) { + // Execute the REST call (with internal retry logic) + var res = this._execute('get', url, headers, maxRetries, baseDelayMs); + attempts += res.attempts; // Count total attempts made + totalSleepMs += res.sleptMs; // Sum total sleep time used in retries + + // If non-success HTTP code, throw to stop execution + if (res.status < 200 || res.status >= 300) + throw 'HTTP ' + res.status + ' for ' + url + ': ' + res.body; + + // Parse and validate JSON body + var json = this._safeJson(res.body); + + // If body contains an 'items' array, append to results + if (Array.isArray(json.items)) items = items.concat(json.items); + + // Get next page link if available (standard 'links.next' pattern) + url = json && json.links && json.links.next ? json.links.next : null; + } + + // Log a completion summary + gs.info('REST helper complete. items=' + items.length + + ', attempts=' + attempts + + ', sleptMs=' + totalSleepMs); + + return items; + }, + + /** + * Executes a REST call with retry and exponential backoff. + * Retries on HTTP 429 (Too Many Requests) or 5xx errors. + * @returns {Object} status, body, attempts, sleptMs + */ + _execute: function(method, url, headers, maxRetries, baseDelayMs) { + var attempt = 0; + var sleptMs = 0; + + while (true) { + attempt++; + + // Build the RESTMessageV2 object + var r = new sn_ws.RESTMessageV2(); + r.setEndpoint(url); + r.setHttpMethod(method.toUpperCase()); + + // Apply custom headers (for example, auth tokens or content type) + Object.keys(headers).forEach(function(k) { r.setRequestHeader(k, headers[k]); }); + + // Execute the request + var resp = r.execute(); + var status = resp.getStatusCode(); + var body = resp.getBody(); + + // Success range (2xx) + if (status >= 200 && status < 300) { + return { status: status, body: body, attempts: attempt, sleptMs: sleptMs }; + } + + // Handle 429 (rate limit) or transient 5xx server errors + if (status === 429 || status >= 500) { + // Stop retrying if max reached + if (attempt >= maxRetries) { + return { status: status, body: body, attempts: attempt, sleptMs: sleptMs }; + } + + // Honour Retry-After header if present; otherwise exponential delay + var retryAfter = Number(resp.getHeader('Retry-After')) || 0; + var delayMs = retryAfter > 0 ? retryAfter * 1000 : Math.pow(2, attempt) * baseDelayMs; + + // Log retry details for visibility in system logs + gs.info('Retrying ' + url + ' after ' + delayMs + + ' ms due to HTTP ' + status + ' (attempt ' + attempt + ')'); + + gs.sleep(delayMs); // Wait before retrying + sleptMs += delayMs; + continue; + } + + // Non-retryable failure (e.g., 4xx not including 429) + return { status: status, body: body, attempts: attempt, sleptMs: sleptMs }; + } + }, + + /** + * Safe JSON parser that throws descriptive error on invalid JSON. + * @param {String} body - raw HTTP response text + * @returns {Object} parsed JSON + */ + _safeJson: function(body) { + try { + return JSON.parse(body || '{}'); + } catch (e) { + throw 'Invalid JSON: ' + e.message; + } + }, + + type: 'RestGetWithBackoff' +}; From bb669f09e25b2cf587df1597c8b49e0f9e44ece5 Mon Sep 17 00:00:00 2001 From: hanna-g-sn Date: Tue, 21 Oct 2025 11:16:29 +0100 Subject: [PATCH 3/3] Add example usage for RestGetWithBackoff Added a background script example for using RestGetWithBackoff. --- .../example_background_usage.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/example_background_usage.js diff --git a/Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/example_background_usage.js b/Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/example_background_usage.js new file mode 100644 index 0000000000..191116d457 --- /dev/null +++ b/Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/example_background_usage.js @@ -0,0 +1,11 @@ +// Background Script usage example for RestGetWithBackoff +(function() { + var helper = new RestGetWithBackoff(); + var data = helper.getAll({ + endpoint: 'https://api.example.com/v1/things?limit=100', + headers: { 'Authorization': 'Bearer ${token}' }, + maxRetries: 4, + baseDelayMs: 750 + }); + gs.info('Fetched ' + data.length + ' items total'); +})();