2xx Success codes
- 200 OK — request succeeded, body contains the result. Standard for GET and successful PATCH/PUT responses.
- 201 Created — resource was created. Standard for POST. Check the Location header for the URL of the new resource. The created record is typically in the response body.
- 204 No Content — success with no response body. Standard for DELETE. Do not try to parse the body — there is none.
response.getBody()returns an empty string.
4xx Client error codes
- 400 Bad Request — your request is malformed. Check: JSON syntax errors, missing required fields, invalid field value formats. This is a code error — fix the request, do not retry.
- 401 Unauthorized — authentication failed. Token is missing, expired, or invalid. If using OAuth, refresh the token. If using Basic Auth, check credentials. Retry with fresh credentials.
- 403 Forbidden — authenticated but not permitted. The user/service has no ACL access to this resource. Not fixable with retry — requires a permission change.
- 404 Not Found — the resource does not exist. The sys_id was deleted, the URL has a typo, or you are querying the wrong instance. Do not retry.
- 422 Unprocessable Entity — request syntax is correct but semantic validation failed. A required reference does not exist, a field value is not in the allowed list. Fix the data, do not retry.
- 429 Too Many Requests — rate limited. Check the Retry-After header. Implement exponential backoff. Do not hammer the endpoint — that makes it worse.
5xx Server error codes
- 500 Internal Server Error — the remote server crashed processing your request. Usually a bug on the remote end. Log it, retry with backoff after a delay.
- 503 Service Unavailable — service is down or overloaded. Retry with exponential backoff. Alert your team if it persists.
- 504 Gateway Timeout — the request took too long. May indicate a server performance issue or your request is too complex. Retry with backoff.
Handling codes in ServiceNow scripts
try {
var response = rm.execute();
var status = response.getStatusCode();
var body = response.getBody();
if (status >= 200 && status < 300) {
// Success — process response
return JSON.parse(body);
} else if (status == 401) {
// Re-authenticate
gs.warn('Token expired, refreshing', 'MyIntegration');
refreshOAuthToken();
return null; // Caller should retry
} else if (status == 429) {
// Rate limited — queue for retry
gs.warn('Rate limited — will retry', 'MyIntegration');
gs.eventQueue('my.integration.retry', current, '', '');
return null;
} else if (status >= 500) {
// Server error — log and retry logic
gs.error('External server error: ' + status + ' | ' + body, 'MyIntegration');
return null; // Implement retry elsewhere
} else {
// 4xx client error — log, do NOT retry
gs.error('Request error: ' + status + ' | ' + body.substring(0, 200), 'MyIntegration');
return null;
}
} catch (e) {
// Network failure — connection refused, timeout
gs.error('REST call failed: ' + e.getMessage(), 'MyIntegration');
return null;
}
Related: RESTMessageV2 guide · OAuth 2.0 · Flow Designer error handling · IntegrationHub spokes
Handling status codes in RESTMessageV2
Every RESTMessageV2 response has a status code. Your integration logic must handle the full range — not just 200. The minimal correct pattern:
var rm = new sn_ws.RESTMessageV2('MyAPIMessage', 'GET Record');
rm.setStringParameterNoEscape('sys_id', recordSysId);
try {
var response = rm.execute();
var status = response.getStatusCode();
var body = response.getBody();
if (status == 200) {
var data = JSON.parse(body);
processSuccess(data);
} else if (status == 404) {
gs.warn('Record not found in external system: ' + recordSysId);
// don't throw — log and continue
} else if (status == 429) {
// Rate limited — queue for retry
scheduleRetry(recordSysId, 'Rate limited by API');
} else if (status >= 500) {
// Server error — log as error, queue for retry
gs.error('API server error ' + status + ' for record ' + recordSysId);
scheduleRetry(recordSysId, 'API server error ' + status);
} else {
gs.error('Unexpected status ' + status + ': ' + body.substring(0, 200));
}
} catch(e) {
// Network failure, timeout, DNS failure
gs.error('Integration network error: ' + e.getMessage());
scheduleRetry(recordSysId, 'Network error: ' + e.getMessage());
}
The retry queue pattern
For production integrations, transient failures (429, 503, timeouts) should not result in lost data — they should result in a retry. Build a lightweight queue table: custom fields for the record sys_id, the operation, the retry count, the next retry time, and the last error message. A Scheduled Job runs every few minutes, queries the queue for records past their retry time with retry count under the maximum, and attempts the operation again. Each failure increments retry count and sets next retry time with exponential backoff (1 min, 5 min, 15 min, 60 min). This pattern is the difference between an integration that loses records under load and one that is reliably eventually-consistent.
Related: RESTMessageV2 · OAuth 2.0 · Flow Designer error handling · IntegrationHub spokes · Scheduled Jobs
Status codes that require special handling in ServiceNow integrations
Beyond the basics, several status codes require specific handling patterns in production ServiceNow integrations:
401 Unauthorized — Authentication failed or token expired. For OAuth integrations, attempt a token refresh first before treating as a hard failure. If the refresh also returns 401, the credentials are invalid and the integration needs human intervention.
403 Forbidden — The credentials are valid but the user/application does not have permission for this operation. This is a configuration issue on the external system side — no amount of retry will fix it. Log as an error requiring investigation, do not retry automatically.
404 Not Found — The specific record does not exist in the external system. This may be expected (the record was deleted externally) or unexpected (a sync issue). Handle based on your integration's logic — for a one-way sync pushing data to the external system, a 404 may mean you need to create the record rather than update it.
409 Conflict — A concurrent modification conflict. Common in optimistic locking scenarios. Retry with the latest version of the record after a brief pause.
422 Unprocessable Entity — The request was well-formed but contained semantic errors (a required field was missing, a value failed validation). Log the response body — it usually contains specific validation error details that tell you exactly what was wrong with the payload.
503 Service Unavailable — The external system is down or overloaded. Always retry with backoff. Add the retry time in your response logging so you can measure how long external systems are unavailable when troubleshooting integration SLAs.
Logging status codes for operational visibility
Every integration in production should log outbound call outcomes in a structured way that enables monitoring and SLA tracking. At minimum, log: the target endpoint, the HTTP method, the status code received, the response time in milliseconds, and the outcome (success, retry, failure). Write these to a custom integration log table rather than just the System Log — the System Log fills quickly in high-volume environments and lacks the queryable structure needed for integration health dashboards. A custom table lets you query "what percentage of calls to the Salesforce API succeeded in the last 24 hours?" or "how many 429s did we receive from the HR system today?" — operational questions that the System Log cannot answer efficiently.