API Client Configuration
This guide covers HTTP client configuration for optimal Squid API performance. For background on why these settings matter, see API Throughput.
Disable Session Cookies
Why This Matters
ServiceNow enforces session synchronization, processing only one transaction per session at a time. When your HTTP client persists cookies, all requests serialize into a queue—each waiting for the previous one to complete.
| With Cookies | Without Cookies |
|---|---|
| All requests queue behind each other | Requests process in parallel |
| Single node handles all traffic | Load balancer distributes across cluster |
| Risk of HTTP 202 silent failures | Each request independent |
| Long-running queries block everything | Optimal throughput |
Squid queries routinely take minutes to complete. A single long-running query would block all other requests from your integration if session cookies are enabled.
HTTP 202: Silent Data Loss
When session queue thresholds are exceeded, ServiceNow returns HTTP 202 Accepted. Despite the seemingly positive status code, these requests are NOT processed—they are silently dropped.
ServiceNow triggers HTTP 202 when:
| Condition | Threshold |
|---|---|
| Queued transactions per session | >10 (Tokyo and later) |
| Current transaction duration | >30 seconds |
Squid queries frequently exceed 30 seconds, making HTTP 202 a real risk when reusing sessions.
In documented testing, 100 concurrent requests to the same session resulted in only 12 receiving HTTP 200 (processed), while 88 received HTTP 202 and were completely ignored. Do not treat HTTP 202 as success.
Code Examples
Configure your HTTP client to NOT persist cookies between requests:
- cURL
- Python
- Node.js
- .NET
- PowerShell
- Java
# No cookie jar = no session reuse (default behavior)
curl -u user:password "https://instance.service-now.com/api/x_a46gh_squidx/v1/data/cmdb_ci_server"
import requests
# Don't use a session object - each request gets a fresh session
response = requests.get(url, auth=(user, password))
# If you must use a session for connection pooling:
session = requests.Session()
session.cookies.clear() # Clear after each request
// Node.js does not persist cookies by default - no special configuration needed
const axios = require('axios');
const response = await axios.get(url, {
auth: { username, password }
});
var handler = new HttpClientHandler {
UseCookies = false // Critical: disable cookie container
};
var client = new HttpClient(handler);
.NET's HttpClient persists cookies by default. This has caused documented cases of 300+ seconds of session wait time for requests that individually take only seconds.
# Use -SessionVariable but don't reuse it across parallel requests
$cred = Get-Credential
Invoke-RestMethod -Uri $url -Credential $cred
# Each call creates a new session
// Java 11+ HttpClient - cookies disabled by default
HttpClient client = HttpClient.newBuilder()
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE))
.build();
OAuth Authentication
Enterprise integrations typically authenticate against a central identity provider (Active Directory, Entra ID, Okta, etc.) rather than using ServiceNow local accounts. Your identity provider issues an OAuth token that ServiceNow is configured to trust.
The examples below show Microsoft Entra ID. Your organization may use a different identity provider with different endpoints and parameters. Work with your identity and ServiceNow teams for the correct configuration.
Even with OAuth, ServiceNow returns session cookies. If your HTTP client persists these cookies, you'll hit the same session synchronization bottleneck. The cookie handling advice above still applies.
Obtaining an Access Token
Request an access token from your identity provider. The exact endpoint and parameters depend on your provider. This example shows Microsoft Entra ID (formerly Azure AD):
- cURL
- Python
- Node.js
- .NET
- PowerShell
- Java
# Microsoft Entra ID example
curl -X POST "https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "scope=YOUR_SERVICENOW_APP_ID/.default"
import requests
def get_oauth_token(tenant_id, client_id, client_secret, scope):
"""Get OAuth token from Microsoft Entra ID."""
response = requests.post(
f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
"scope": scope,
},
)
response.raise_for_status()
return response.json()["access_token"]
// Microsoft Entra ID example
async function getOAuthToken(tenantId, clientId, clientSecret, scope) {
const response = await axios.post(
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
new URLSearchParams({
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret,
scope: scope,
})
);
return response.data.access_token;
}
// Microsoft Entra ID example
public async Task<string> GetOAuthTokenAsync(
string tenantId, string clientId, string clientSecret, string scope)
{
var handler = new HttpClientHandler { UseCookies = false };
var client = new HttpClient(handler);
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = clientId,
["client_secret"] = clientSecret,
["scope"] = scope
});
var response = await client.PostAsync(
$"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", content);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadFromJsonAsync<JsonDocument>();
return json.RootElement.GetProperty("access_token").GetString();
}
# Microsoft Entra ID example
$tokenUrl = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
$body = @{
grant_type = "client_credentials"
client_id = $clientId
client_secret = $clientSecret
scope = $scope
}
$response = Invoke-RestMethod -Uri $tokenUrl -Method Post -Body $body
$accessToken = $response.access_token
// Microsoft Entra ID example
public String getOAuthToken(String tenantId, String clientId,
String clientSecret, String scope) throws Exception {
HttpClient client = HttpClient.newBuilder()
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE))
.build();
String body = "grant_type=client_credentials"
+ "&client_id=" + URLEncoder.encode(clientId, StandardCharsets.UTF_8)
+ "&client_secret=" + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8)
+ "&scope=" + URLEncoder.encode(scope, StandardCharsets.UTF_8);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Parse JSON response to extract access_token
ObjectMapper mapper = new ObjectMapper();
JsonNode json = mapper.readTree(response.body());
return json.get("access_token").asText();
}
Using the Token
Include the access token in the Authorization header:
- cURL
- Python
- Node.js
- .NET
- PowerShell
- Java
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
"https://instance.service-now.com/api/x_a46gh_squidx/v1/data/cmdb_ci_server"
# Don't use a Session object - it would persist cookies
response = requests.get(
f"{instance_url}/api/x_a46gh_squidx/v1/data/cmdb_ci_server",
headers={"Authorization": f"Bearer {token}"},
timeout=(30, 900),
)
const response = await axios.get(
`${instanceUrl}/api/x_a46gh_squidx/v1/data/cmdb_ci_server`,
{
headers: { Authorization: `Bearer ${token}` },
timeout: 900000,
}
);
var handler = new HttpClientHandler { UseCookies = false };
var client = new HttpClient(handler) { Timeout = TimeSpan.FromMinutes(15) };
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var response = await client.GetAsync(
$"{instanceUrl}/api/x_a46gh_squidx/v1/data/cmdb_ci_server");
$headers = @{
Authorization = "Bearer $accessToken"
}
Invoke-RestMethod -Uri "$instanceUrl/api/x_a46gh_squidx/v1/data/cmdb_ci_server" `
-Headers $headers -TimeoutSec 900
HttpClient client = HttpClient.newBuilder()
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE))
.connectTimeout(Duration.ofSeconds(30))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(instanceUrl + "/api/x_a46gh_squidx/v1/data/cmdb_ci_server"))
.header("Authorization", "Bearer " + token)
.timeout(Duration.ofMinutes(15))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
Token Refresh
OAuth tokens expire (typically after 60-90 minutes, depending on your identity provider configuration). Your integration should handle token refresh:
- Check expiration before each request, or
- Catch 401 responses and request a new token
The token response includes expires_in (seconds until expiration). Store the token acquisition time and refresh proactively before expiration.
Configure Appropriate Timeouts
Squid queries can take 10-15 minutes for large datasets. Configure your HTTP client timeouts accordingly:
| Timeout Type | Recommended Value | Reason |
|---|---|---|
| Connection timeout | 30 seconds | Establish connection quickly |
| Read timeout | 900+ seconds | Allow long-running queries to complete |
- cURL
- Python
- Node.js
- .NET
- PowerShell
- Java
# cURL uses --connect-timeout and --max-time (total time including transfer)
curl --connect-timeout 30 --max-time 900 \
-u user:password "https://instance.service-now.com/api/x_a46gh_squidx/v1/data/cmdb_ci_server"
response = requests.get(url, auth=auth, timeout=(30, 900))
# (connection_timeout, read_timeout)
const client = axios.create({
timeout: 900000 // 15 minutes in milliseconds
});
var client = new HttpClient(handler) {
Timeout = TimeSpan.FromMinutes(15)
};
# -TimeoutSec sets the total request timeout
Invoke-RestMethod -Uri $url -Credential $cred -TimeoutSec 900
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofMinutes(15)) // Request timeout
.GET()
.build();
Your ServiceNow administrator must also configure server-side transaction quota rules to allow long-running Squid queries. See ServiceNow Configuration.
HTTP Response Code Reference
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Request processed |
| 202 | Accepted | Request NOT processed — session queue full. Check cookie handling. |
| 401 | Unauthorized | Invalid credentials |
| 403 | Forbidden | Missing x_a46gh_squidx.rest role |
| 429 | Too Many Requests | Capacity exceeded. Review ServiceNow Configuration. |
| 503 | Service Unavailable | Platform overload. Contact ServiceNow administrator. |
Complete Example: Squid Client
- cURL
- Python
- Node.js
- .NET
- PowerShell
- Java
#!/bin/bash
# Squid API client with Entra ID authentication
TENANT_ID="your_entra_tenant_id"
CLIENT_ID="your_client_id"
CLIENT_SECRET="your_client_secret"
SCOPE="api://your_servicenow_app_id/.default"
INSTANCE_URL="https://instance.service-now.com"
# Get OAuth token from Entra ID
get_token() {
curl -s -X POST "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "scope=$SCOPE" | jq -r '.access_token'
}
# Call Squid API
TOKEN=$(get_token)
curl --connect-timeout 30 --max-time 900 \
-H "Authorization: Bearer $TOKEN" \
"$INSTANCE_URL/api/x_a46gh_squidx/v1/data/cmdb_ci_server?limit=1000"
import requests
import time
class SquidClient:
def __init__(self, instance_url, tenant_id, client_id, client_secret, scope):
self.instance_url = instance_url
self.base_url = f"{instance_url}/api/x_a46gh_squidx/v1/data"
self.tenant_id = tenant_id
self.client_id = client_id
self.client_secret = client_secret
self.scope = scope
self.token = None
self.token_expiry = 0
def _get_token(self):
if self.token and time.time() < self.token_expiry - 60:
return self.token
response = requests.post(
f"https://login.microsoftonline.com/{self.tenant_id}/oauth2/v2.0/token",
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": self.scope,
},
)
response.raise_for_status()
data = response.json()
self.token = data["access_token"]
self.token_expiry = time.time() + data["expires_in"]
return self.token
def get(self, config, params=None):
url = f"{self.base_url}/{config}"
# No session object = no cookie persistence
response = requests.get(
url,
headers={"Authorization": f"Bearer {self._get_token()}"},
params=params,
timeout=(30, 900),
)
response.raise_for_status()
return response.json()
# Usage
client = SquidClient(
instance_url="https://instance.service-now.com",
tenant_id="your_entra_tenant_id",
client_id="your_client_id",
client_secret="your_client_secret",
scope="api://your_servicenow_app_id/.default"
)
data = client.get("cmdb_ci_server", params={"limit": 1000})
const axios = require('axios');
class SquidClient {
constructor(instanceUrl, tenantId, clientId, clientSecret, scope) {
this.instanceUrl = instanceUrl;
this.baseUrl = `${instanceUrl}/api/x_a46gh_squidx/v1/data`;
this.tenantId = tenantId;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.scope = scope;
this.token = null;
this.tokenExpiry = 0;
}
async _getToken() {
if (this.token && Date.now() < this.tokenExpiry - 60000) {
return this.token;
}
const response = await axios.post(
`https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/token`,
new URLSearchParams({
grant_type: 'client_credentials',
client_id: this.clientId,
client_secret: this.clientSecret,
scope: this.scope,
})
);
this.token = response.data.access_token;
this.tokenExpiry = Date.now() + response.data.expires_in * 1000;
return this.token;
}
async get(config, params = {}) {
const token = await this._getToken();
const response = await axios.get(`${this.baseUrl}/${config}`, {
headers: { Authorization: `Bearer ${token}` },
params,
timeout: 900000,
});
return response.data;
}
}
// Usage
const client = new SquidClient(
'https://instance.service-now.com',
'your_entra_tenant_id',
'your_client_id',
'your_client_secret',
'api://your_servicenow_app_id/.default'
);
const data = await client.get('cmdb_ci_server', { limit: 1000 });
using System.Net.Http.Json;
using System.Text.Json;
public class SquidClient
{
private readonly string _instanceUrl;
private readonly string _tenantId;
private readonly string _clientId;
private readonly string _clientSecret;
private readonly string _scope;
private string? _token;
private DateTime _tokenExpiry;
public SquidClient(string instanceUrl, string tenantId,
string clientId, string clientSecret, string scope)
{
_instanceUrl = instanceUrl;
_tenantId = tenantId;
_clientId = clientId;
_clientSecret = clientSecret;
_scope = scope;
}
private async Task<string> GetTokenAsync()
{
if (_token != null && DateTime.UtcNow < _tokenExpiry.AddMinutes(-1))
return _token;
var handler = new HttpClientHandler { UseCookies = false };
var client = new HttpClient(handler);
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = _clientId,
["client_secret"] = _clientSecret,
["scope"] = _scope
});
var response = await client.PostAsync(
$"https://login.microsoftonline.com/{_tenantId}/oauth2/v2.0/token", content);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadFromJsonAsync<JsonDocument>();
_token = json!.RootElement.GetProperty("access_token").GetString()!;
var expiresIn = json.RootElement.GetProperty("expires_in").GetInt32();
_tokenExpiry = DateTime.UtcNow.AddSeconds(expiresIn);
return _token;
}
public async Task<JsonDocument> GetAsync(string config, Dictionary<string, string>? parameters = null)
{
var handler = new HttpClientHandler { UseCookies = false };
var client = new HttpClient(handler) { Timeout = TimeSpan.FromMinutes(15) };
var token = await GetTokenAsync();
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var query = parameters != null
? "?" + string.Join("&", parameters.Select(p => $"{p.Key}={p.Value}"))
: "";
var response = await client.GetAsync(
$"{_instanceUrl}/api/x_a46gh_squidx/v1/data/{config}{query}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<JsonDocument>();
}
}
// Usage
var client = new SquidClient(
"https://instance.service-now.com",
"your_entra_tenant_id",
"your_client_id",
"your_client_secret",
"api://your_servicenow_app_id/.default"
);
var data = await client.GetAsync("cmdb_ci_server", new() { ["limit"] = "1000" });
class SquidClient {
[string]$InstanceUrl
[string]$TenantId
[string]$ClientId
[string]$ClientSecret
[string]$Scope
[string]$Token
[datetime]$TokenExpiry
SquidClient([string]$instanceUrl, [string]$tenantId,
[string]$clientId, [string]$clientSecret, [string]$scope) {
$this.InstanceUrl = $instanceUrl
$this.TenantId = $tenantId
$this.ClientId = $clientId
$this.ClientSecret = $clientSecret
$this.Scope = $scope
$this.TokenExpiry = [datetime]::MinValue
}
[string] GetToken() {
if ($this.Token -and [datetime]::UtcNow -lt $this.TokenExpiry.AddMinutes(-1)) {
return $this.Token
}
$tokenUrl = "https://login.microsoftonline.com/$($this.TenantId)/oauth2/v2.0/token"
$body = @{
grant_type = "client_credentials"
client_id = $this.ClientId
client_secret = $this.ClientSecret
scope = $this.Scope
}
$response = Invoke-RestMethod -Uri $tokenUrl -Method Post -Body $body
$this.Token = $response.access_token
$this.TokenExpiry = [datetime]::UtcNow.AddSeconds($response.expires_in)
return $this.Token
}
[object] Get([string]$config, [hashtable]$params = @{}) {
$token = $this.GetToken()
$headers = @{ Authorization = "Bearer $token" }
$query = if ($params.Count -gt 0) {
"?" + (($params.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "&")
} else { "" }
$url = "$($this.InstanceUrl)/api/x_a46gh_squidx/v1/data/$config$query"
return Invoke-RestMethod -Uri $url -Headers $headers -TimeoutSec 900
}
}
# Usage
$client = [SquidClient]::new(
"https://instance.service-now.com",
"your_entra_tenant_id",
"your_client_id",
"your_client_secret",
"api://your_servicenow_app_id/.default"
)
$data = $client.Get("cmdb_ci_server", @{ limit = 1000 })
import java.net.*;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import com.fasterxml.jackson.databind.*;
public class SquidClient {
private final String instanceUrl;
private final String tenantId;
private final String clientId;
private final String clientSecret;
private final String scope;
private final ObjectMapper mapper = new ObjectMapper();
private String token;
private Instant tokenExpiry = Instant.MIN;
public SquidClient(String instanceUrl, String tenantId,
String clientId, String clientSecret, String scope) {
this.instanceUrl = instanceUrl;
this.tenantId = tenantId;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.scope = scope;
}
private String getToken() throws Exception {
if (token != null && Instant.now().isBefore(tokenExpiry.minusSeconds(60))) {
return token;
}
HttpClient client = HttpClient.newBuilder()
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE))
.build();
String body = "grant_type=client_credentials"
+ "&client_id=" + URLEncoder.encode(clientId, StandardCharsets.UTF_8)
+ "&client_secret=" + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8)
+ "&scope=" + URLEncoder.encode(scope, StandardCharsets.UTF_8);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode json = mapper.readTree(response.body());
token = json.get("access_token").asText();
int expiresIn = json.get("expires_in").asInt();
tokenExpiry = Instant.now().plusSeconds(expiresIn);
return token;
}
public JsonNode get(String config, Map<String, String> params) throws Exception {
HttpClient client = HttpClient.newBuilder()
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE))
.connectTimeout(Duration.ofSeconds(30))
.build();
String query = params != null && !params.isEmpty()
? "?" + params.entrySet().stream()
.map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"))
: "";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(instanceUrl + "/api/x_a46gh_squidx/v1/data/" + config + query))
.header("Authorization", "Bearer " + getToken())
.timeout(Duration.ofMinutes(15))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return mapper.readTree(response.body());
}
}
// Usage
SquidClient client = new SquidClient(
"https://instance.service-now.com",
"your_entra_tenant_id",
"your_client_id",
"your_client_secret",
"api://your_servicenow_app_id/.default"
);
JsonNode data = client.get("cmdb_ci_server", Map.of("limit", "1000"));
Related Topics
- API Throughput — Why these settings matter
- ServiceNow Configuration — Platform-side settings for administrators
- Authentication — Service account setup
- Give it a Spin — Try Squid quickly
- Install Squid — Production deployment