ServiceNow REST API Integration Guide for Developers (2026)

Building a ServiceNow integration is fundamentally different from every other API integration you've built — because there is no single ServiceNow. Every customer runs their own instance at a unique subdomain, with their own OAuth endpoints, their own permission model, and their own table customisations. Guides written for ServiceNow developers working inside an instance won't help you. This one is written for developers building a product that connects to their customers' ServiceNow instances.

Quick answer: Use the Table API (/api/now/table/{tableName}) for reading and writing incidents, users, groups, and requests. Authenticate via OAuth 2.0 — but collect the customer's instance URL first, since every OAuth endpoint is instance-specific. The five tables that cover 90% of ITSM product integration use cases are incident, sys_user, sys_user_group, sc_request, and change_request.

This guide covers per-instance OAuth setup, Table API endpoints and query syntax, webhook configuration, rate limits, and three real-world integration patterns with working code — all from the perspective of an external developer connecting to a customer's ServiceNow instance.

If your product needs to support ServiceNow alongside other ITSM tools like Jira, Zendesk, or GitHub Issues, there's a unified approach worth knowing about — covered in the Building with Knit section.

The ServiceNow API: Table API, Scripted REST, and Import Sets

ServiceNow exposes several API surfaces. The right one for your integration depends on what you're doing:

What you want to do Recommended approach
Read/write incidents, users, groups, requests in real time Table API (/api/now/table/)
Receive real-time event notifications from ServiceNow Business Rules + Outbound REST Messages
Bulk import large datasets into ServiceNow Import Set API (/api/now/import/)
Expose custom endpoints inside ServiceNow Scripted REST API (requires ServiceNow dev access)
Query complex relationships across tables Table API with sysparm_query
Retrieve specific records by sys_id Table API GET by sys_id

The Table API is the right choice for the vast majority of product integrations. It provides CRUD access to any ServiceNow table through a consistent URL pattern:

https://{instance}.service-now.com/api/now/table/{tableName}

The Scripted REST API requires a ServiceNow developer to create custom endpoints inside the instance — you can't deploy these from outside. The Import Set API is for bulk historical data loads, not real-time integrations.

Authentication: Per-Instance OAuth and Why It's Different

ServiceNow OAuth is standard OAuth 2.0 in mechanics, but the endpoints are not standard — they're instance-specific. This is the detail that trips most developers up when building a multi-tenant integration.

For a typical API (Slack, GitHub, HubSpot), you hardcode a single OAuth endpoint:

https://slack.com/api/oauth.v2.access

For ServiceNow, every customer has their own:

https://{customer-instance}.service-now.com/oauth_token.do
https://{customer-instance}.service-now.com/oauth_auth.do

This means your integration must:

  1. Collect the customer's ServiceNow instance URL before initiating OAuth
  2. Construct the OAuth endpoints dynamically per customer
  3. Store per-customer OAuth credentials (access token, refresh token, instance URL)
  4. Handle token refresh per customer independently

Here's what that looks like in practice:

Step 1: Collect the Instance URL

Your onboarding UI needs to ask for the instance identifier — the [company] part of https://[company].service-now.com. This is what Knit's auth screen shows users when they connect ServiceNow.

def get_servicenow_endpoints(instance: str) -> dict:
    """
    Build instance-specific OAuth endpoints from the instance identifier.
    instance = "mycompany" (not the full URL)
    """
    base = f"https://{instance}.service-now.com"
    return {
        "base_url": base,
        "auth_url": f"{base}/oauth_auth.do",
        "token_url": f"{base}/oauth_token.do",
        "api_base": f"{base}/api/now/table"
    }

Step 2: Register an OAuth Provider in ServiceNow

Before any OAuth flow can happen, the customer's ServiceNow admin must register your application as an OAuth provider in their instance: System OAuth > Application Registry > New > Create an OAuth API endpoint for external clients.

Required fields:

  • Name: Your application name
  • Client ID: Auto-generated (give this to the customer)
  • Client Secret: Auto-generated (store securely)
  • Redirect URL: Your callback URL

This is a one-time admin step per customer instance. Document it clearly in your onboarding instructions.

Step 3: OAuth Authorization Flow

import requests
from urllib.parse import urlencode

def get_auth_url(instance: str, client_id: str, redirect_uri: str, state: str) -> str:
    """Redirect the customer's admin to this URL to initiate OAuth consent."""
    endpoints = get_servicenow_endpoints(instance)
    params = {
        "response_type": "code",
        "client_id": client_id,
        "redirect_uri": redirect_uri,
        "state": state  # CSRF protection — always validate on callback
    }
    return f"{endpoints['auth_url']}?{urlencode(params)}"


def exchange_code_for_tokens(instance: str, client_id: str, client_secret: str,
                              code: str, redirect_uri: str) -> dict:
    """Exchange the authorization code for access + refresh tokens."""
    endpoints = get_servicenow_endpoints(instance)
    response = requests.post(
        endpoints["token_url"],
        data={
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": redirect_uri,
            "client_id": client_id,
            "client_secret": client_secret
        }
    )
    response.raise_for_status()
    tokens = response.json()
    # Store tokens["access_token"], tokens["refresh_token"], and instance per customer
    return tokens

Step 4: Token Refresh

ServiceNow access tokens expire after 30 minutes by default (configurable by the admin). Build refresh logic before you hit your first expiry:

def refresh_access_token(instance: str, client_id: str, client_secret: str,
                          refresh_token: str) -> dict:
    endpoints = get_servicenow_endpoints(instance)
    response = requests.post(
        endpoints["token_url"],
        data={
            "grant_type": "refresh_token",
            "client_secret": client_secret,
            "client_id": client_id,
            "refresh_token": refresh_token
        }
    )
    response.raise_for_status()
    return response.json()  # New access_token and refresh_token
If you're building a product that integrates with ServiceNow alongside other ITSM tools — Jira, Zendesk, GitHub, Linear — building and maintaining per-instance OAuth for each one is significant infrastructure overhead. Knit handles ServiceNow's instance URL collection and OAuth flow per customer, so you get a single integration layer across all your supported tools. → getknit.dev/integration/servicenow

Key Table API Endpoints and the Five Tables That Matter

The Tables

ServiceNow has hundreds of tables. For a B2B product integration, these five cover the vast majority of use cases:

Table name What it contains Common use cases
incident IT incidents and support tickets Sync tickets, create incidents from your product, update status
sys_user All users in the instance User lookup, assignee resolution, member sync
sys_user_group Teams and groups Group-based routing, access control mapping
sc_request Service catalog requests Read service requests submitted by users
change_request Change management records Monitor or create change requests

All Table API requests follow the same pattern:

GET https://{instance}.service-now.com/api/now/table/{table}
Authorization: Bearer {access_token}
Accept: application/json
Content-Type: application/json
X-no-response-body: false

The sysparm Parameters

ServiceNow's Table API uses sysparm_ prefixed query parameters for filtering, field selection, and pagination. Understanding these is essential — without them you'll either pull the entire table or struggle with pagination.

Parameter Purpose Example
sysparm_query Filter records using encoded query syntax state=1^assigned_toISNOTEMPTY
sysparm_fields Return only specific fields (comma-separated) sys_id,number,short_description,state
sysparm_limit Max records per page (default: 10, max: 10,000) 100
sysparm_offset Pagination offset 100 (page 2 with limit 100)
sysparm_display_value Return display values instead of raw values true or all
sysparm_exclude_reference_link Remove reference links from response (smaller payload) true

Reading Incidents

def get_incidents(instance: str, token: str,
                  state: str = None, assigned_to: str = None,
                  limit: int = 100, offset: int = 0) -> dict:
    """
    Fetch incidents from a ServiceNow instance.
    state codes: 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed
    """
    query_parts = []
    if state:
        query_parts.append(f"state={state}")
    if assigned_to:
        query_parts.append(f"assigned_to.user_name={assigned_to}")

    params = {
        "sysparm_limit": limit,
        "sysparm_offset": offset,
        "sysparm_fields": "sys_id,number,short_description,description,state,"
                          "priority,assigned_to,assignment_group,opened_at,"
                          "resolved_at,sys_created_on,sys_updated_on",
        "sysparm_exclude_reference_link": "true",
        "sysparm_display_value": "false"  # Raw values are easier to work with
    }
    if query_parts:
        params["sysparm_query"] = "^".join(query_parts)

    response = requests.get(
        f"https://{instance}.service-now.com/api/now/table/incident",
        headers={
            "Authorization": f"Bearer {token}",
            "Accept": "application/json"
        },
        params=params
    )
    response.raise_for_status()

    # Pagination: check X-Total-Count header for total record count
    total = int(response.headers.get("X-Total-Count", 0))
    return {
        "records": response.json()["result"],
        "total": total,
        "has_more": (offset + limit) < total
    }

Creating an Incident

def create_incident(instance: str, token: str,
                    short_description: str, description: str,
                    caller_id: str = None, priority: int = 3,
                    assignment_group: str = None) -> dict:
    """
    Creates an incident. Priority: 1=Critical, 2=High, 3=Moderate, 4=Low.
    caller_id and assignment_group are sys_id values from sys_user/sys_user_group.
    """
    payload = {
        "short_description": short_description,
        "description": description,
        "priority": str(priority),
        "impact": str(priority),    # Often mirrors priority
        "urgency": str(priority)
    }
    if caller_id:
        payload["caller_id"] = caller_id
    if assignment_group:
        payload["assignment_group"] = assignment_group

    response = requests.post(
        f"https://{instance}.service-now.com/api/now/table/incident",
        headers={
            "Authorization": f"Bearer {token}",
            "Accept": "application/json",
            "Content-Type": "application/json"
        },
        json=payload
    )
    response.raise_for_status()
    result = response.json()["result"]
    return {
        "sys_id": result["sys_id"],       # Use this for future updates
        "number": result["number"],        # Human-readable e.g. INC0012345
        "state": result["state"],
        "url": f"https://{instance}.service-now.com/nav_to.do?uri=incident.do?sys_id={result['sys_id']}"
    }

Updating an Incident

def update_incident(instance: str, token: str,
                    sys_id: str, **fields) -> dict:
    """
    Update any incident fields by sys_id.
    Common fields: state, assigned_to, assignment_group, work_notes, close_notes
    """
    response = requests.patch(
        f"https://{instance}.service-now.com/api/now/table/incident/{sys_id}",
        headers={
            "Authorization": f"Bearer {token}",
            "Accept": "application/json",
            "Content-Type": "application/json"
        },
        json=fields
    )
    response.raise_for_status()
    return response.json()["result"]

Users and Groups

# Get a user by their email address (common lookup pattern)
def get_user_by_email(instance: str, token: str, email: str) -> dict | None:
    response = requests.get(
        f"https://{instance}.service-now.com/api/now/table/sys_user",
        headers={"Authorization": f"Bearer {token}", "Accept": "application/json"},
        params={
            "sysparm_query": f"email={email}^active=true",
            "sysparm_fields": "sys_id,name,email,user_name",
            "sysparm_limit": 1,
            "sysparm_exclude_reference_link": "true"
        }
    )
    response.raise_for_status()
    results = response.json()["result"]
    return results[0] if results else None

# List all active groups
def list_groups(instance: str, token: str) -> list:
    response = requests.get(
        f"https://{instance}.service-now.com/api/now/table/sys_user_group",
        headers={"Authorization": f"Bearer {token}", "Accept": "application/json"},
        params={
            "sysparm_query": "active=true",
            "sysparm_fields": "sys_id,name,description,manager",
            "sysparm_limit": 1000,
            "sysparm_exclude_reference_link": "true"
        }
    )
    response.raise_for_status()
    return response.json()["result"]

Webhooks: Business Rules and Outbound REST Messages

ServiceNow does not have native outbound webhooks that you configure from outside the instance. Real-time event notifications require a ServiceNow admin on the customer side to set up two things: a Business Rule (which triggers on record events) and an Outbound REST Message (which sends the payload to your server).

This is a key difference from APIs like GitHub or Slack where you register a webhook URL programmatically. For ServiceNow, you need to provide your customers' IT teams with setup instructions.

What the customer's admin configures:

Business Rule (System Definition > Business Rules):

  • Table: incident
  • When to run: after insert/update
  • Condition: (whatever triggers the notification — e.g., state changes)
  • Script:
// ServiceNow Business Rule script
var message = new sn_ws.RESTMessageV2('Your Integration', 'POST incident');
message.setStringParameterNoEscape('sys_id', current.sys_id);
message.setStringParameterNoEscape('number', current.number);
message.setStringParameterNoEscape('state', current.state);
message.setStringParameterNoEscape('updated_at', current.sys_updated_on);
var response = message.execute();

Outbound REST Message (System Web Services > Outbound > REST Message):

  • Endpoint: your server's webhook URL
  • HTTP Method: POST
  • Authentication: Basic or OAuth (your server's credentials)

On your server, receive and process the payload:

from flask import Flask, request, abort
import hmac, hashlib

app = Flask(__name__)

@app.route("/webhook/servicenow", methods=["POST"])
def handle_servicenow_event():
    # ServiceNow doesn't send a standard signature header —
    # secure your endpoint via IP allowlisting or a shared secret
    # passed as a query param or custom header agreed with the admin
    payload = request.json
    sys_id = payload.get("sys_id")
    state = payload.get("state")

    # State codes: 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed
    if state in ("6", "7"):
        close_linked_item_in_your_product(sys_id)

    return "", 200

Because webhook setup requires admin access on the customer's instance, build your integration to work without webhooks first (polling) and offer webhook setup as an enhancement for customers whose admins can configure it.

Rate Limits

ServiceNow rate limits are instance-configured, not globally fixed — your customer's IT admin controls them. This creates a situation you won't face with other APIs: two customers on the same plan can have different rate limits.

Configuration Value
Default rate limit ~5,000 requests/hour per user account (instance-configured)
Default max records per query 10,000 records (governed by glide.db.max_view_records, adjustable by admin)
Max sysparm_limit per request 10,000
Token expiry (default) 30 minutes
Rate limit headers Not returned — watch for 429 Too Many Requests
Response format JSON (default) or XML

Unlike GitHub or Slack, ServiceNow does not return rate limit headers (X-RateLimit-Remaining etc.) on every response. You'll receive a 429 Too Many Requests when you hit the limit — build retry logic with exponential backoff:

import time

def servicenow_request(url: str, token: str, max_retries: int = 3, **kwargs) -> requests.Response:
    for attempt in range(max_retries):
        response = requests.get(url, headers={
            "Authorization": f"Bearer {token}",
            "Accept": "application/json"
        }, **kwargs)

        if response.status_code == 429:
            wait = 2 ** attempt * 10  # 10s, 20s, 40s
            time.sleep(wait)
            continue

        if response.status_code == 401:
            # Token likely expired — trigger refresh and retry once
            raise TokenExpiredError("Access token expired")

        response.raise_for_status()
        return response

    raise Exception(f"Max retries exceeded for {url}")

For sustained high-volume integrations, use a dedicated integration user account in ServiceNow rather than a human user's account — this ensures your rate limit isn't shared with the user's other API activity.

3 Common ServiceNow Integration Patterns

Pattern 1: Sync Incidents into Your Product

Pull all open incidents and keep them in sync with periodic polling:

def full_incident_sync(instance: str, token: str) -> list:
    """
    Full sync of all open and in-progress incidents.
    Run on initial connection; switch to delta sync (updatedAfter) for ongoing.
    """
    all_incidents = []
    offset = 0
    limit = 100

    while True:
        page = get_incidents(
            instance=instance,
            token=token,
            limit=limit,
            offset=offset
        )
        all_incidents.extend(page["records"])

        if not page["has_more"]:
            break
        offset += limit

    # Normalise ServiceNow state codes to your product's status model
    status_map = {
        "1": "open", "2": "in_progress", "3": "on_hold",
        "6": "resolved", "7": "closed"
    }

    return [
        {
            "external_id": i["sys_id"],
            "reference": i["number"],
            "title": i["short_description"],
            "status": status_map.get(str(i["state"]), "unknown"),
            "priority": i["priority"],
            "assignee_id": i.get("assigned_to"),
            "created_at": i["sys_created_on"],
            "updated_at": i["sys_updated_on"]
        }
        for i in all_incidents
    ]

Pattern 2: Create an Incident from Your Product

The common "escalate to IT" pattern — a user triggers an action in your product and it creates a ServiceNow incident:

Raw ServiceNow approach — you need to resolve the user's sys_id first, look up the right assignment group sys_id, then create the incident:

# Step 1: resolve caller sys_id from user's email
caller = get_user_by_email(instance, token, user_email)
caller_sys_id = caller["sys_id"] if caller else None

# Step 2: look up assignment group sys_id
groups = list_groups(instance, token)
group = next((g for g in groups if g["name"] == "IT Help Desk"), None)
group_sys_id = group["sys_id"] if group else None

# Step 3: create the incident
incident = create_incident(
    instance=instance,
    token=token,
    short_description=f"Alert from {your_product}: {alert_title}",
    description=alert_details,
    caller_id=caller_sys_id,
    assignment_group=group_sys_id,
    priority=2  # High
)
# Store incident["sys_id"] in your DB for future status sync

With Knit — skip the sys_id resolution steps. Knit's normalised endpoints return consistent IDs you can use directly:

# Get incidents already filtered and paginated
incidents = requests.get(
    "https://api.getknit.dev/v1.0/ticketing/tickets.list",
    headers={
        "Authorization": f"Bearer {knit_token}",
        "X-Knit-Integration-Id": integration_id
    },
    params={"status": "OPEN", "assignedToId": user_id}
)
# Update an incident's status
requests.post(
    "https://api.getknit.dev/v1.0/ticketing/ticket.update",
    headers={
        "Authorization": f"Bearer {knit_token}",
        "X-Knit-Integration-Id": integration_id
    },
    json={"ticketId": ticket_id, "status": "IN_PROGRESS", "assignedToId": agent_id}
)

Pattern 3: User and Group Sync for Access Control

Many products need to know which ServiceNow users and groups a customer has, to map them to your product's access model:

def sync_users_and_groups(instance: str, token: str) -> dict:
    """
    Sync all active users and groups from ServiceNow.
    Used to populate assignee pickers and map access levels.
    """
    # Fetch users — paginate if the instance has many
    users_response = requests.get(
        f"https://{instance}.service-now.com/api/now/table/sys_user",
        headers={"Authorization": f"Bearer {token}", "Accept": "application/json"},
        params={
            "sysparm_query": "active=true",
            "sysparm_fields": "sys_id,name,email,user_name,department",
            "sysparm_limit": 1000,
            "sysparm_exclude_reference_link": "true"
        }
    )
    users = users_response.json()["result"]

    # Fetch groups
    groups = list_groups(instance, token)

    return {
        "users": [
            {"id": u["sys_id"], "name": u["name"],
             "email": u["email"], "username": u["user_name"]}
            for u in users
        ],
        "groups": [
            {"id": g["sys_id"], "name": g["name"]}
            for g in groups
        ]
    }

Building ServiceNow Integrations with Knit

The two hardest parts of a ServiceNow product integration are both auth-related: collecting the instance URL from each customer, constructing per-instance OAuth endpoints, and managing token refresh independently per customer installation. These are real engineering problems that have nothing to do with the value you're delivering to users.

Knit handles ServiceNow authentication — including instance URL collection and per-customer OAuth — so your integration starts from a normalised API call rather than an auth infrastructure build. The same Knit headers work across all your ticketing integrations:

Authorization: Bearer {your-knit-token}
X-Knit-Integration-Id: {customer-integration-id}

This is especially valuable if your product also supports Jira, Zendesk, GitHub Issues, Linear, or Asana — Knit's same API surface covers all of them, so you write the integration logic once.

The Knit APIs available for ServiceNow:

Knit API Endpoint Maps to in ServiceNow Use cases
Get Tickets GET /ticketing/tickets.list incident table List incidents with 11 filters: accountId, contactId, assignedToId, status, ticketType, date ranges
Update Ticket POST /ticketing/ticket.update incident PATCH Update status, assignee, priority, group, due date
Get Accounts GET /ticketing/accounts Customer accounts / companies List accounts linked to incidents
Get Account By Id GET /ticketing/account?accountId= Single account record Fetch account details for a specific incident
Get Contacts GET /ticketing/contacts sys_user (contact view) List contacts/callers with email and phone
Get Contact By Id GET /ticketing/contact?contactId= Single contact record Returns id, name, email, phone, accountId
Get Users GET /ticketing/users?accountId= sys_user (agent view) Build assignee picker; map to your user directory
Get User By Id GET /ticketing/user?userId= Single sys_user Resolve a specific user for display or routing
Get Groups GET /ticketing/groups?accountId= sys_user_group List assignment groups; map to your access model
Get Group By Id GET /ticketing/group?groupId= Single group record Fetch group details for routing or display

Example: list open high-priority incidents via Knit

/

import requests

def get_open_high_priority_incidents(knit_token: str, integration_id: str) -> list:
    """
    No instance URL handling. No token refresh. No sysparm syntax.
    Works the same way for ServiceNow, Jira, Zendesk, and every other Knit-supported tool.
    """
    all_tickets = []
    cursor = None

    while True:
        params = {"status": "OPEN"}
        if cursor:
            params["cursor"] = cursor

        response = requests.get(
            "https://api.getknit.dev/v1.0/ticketing/tickets.list",
            headers={
                "Authorization": f"Bearer {knit_token}",
                "X-Knit-Integration-Id": integration_id
            },
            params=params
        )
        response.raise_for_status()
        data = response.json()["data"]
        all_tickets.extend(data["tickets"])

        cursor = data["pagination"].get("next")
        if not cursor:
            break

    return all_tickets

→ See the full ServiceNow integration on Knit: getknit.dev/integration/servicenow

→ Knit's ticketing API docs: developers.getknit.dev

What to Build First

  1. Build your instance URL collection UI — a simple input field asking for the ServiceNow instance identifier. This unlocks everything else. Document clearly what format you expect (mycompany, not https://mycompany.service-now.com).
  2. Write your dynamic OAuth endpoint constructor — a utility function that builds token and auth URLs from the instance identifier. Every other piece of your auth layer depends on this.
  3. Prepare your onboarding documentation for customer admins — ServiceNow OAuth requires the customer's IT admin to register your application. Write a clear step-by-step guide before any customer goes through onboarding.
  4. Build token storage with per-customer isolation — access token, refresh token, instance URL, and expiry time per customer. Implement token refresh before your first expiry, not after.
  5. Implement incident list and create endpoints — these cover the primary use case for 80%+ of ServiceNow integrations. Use sysparm_fields from the start to avoid pulling data you don't need.
  6. Build user and group sync — fetch sys_user and sys_user_group on integration setup and cache the results. These change infrequently and are needed to populate assignee pickers and resolve group names.
  7. Add delta sync for incident updates — poll incident with sysparm_query=sys_updated_on>javascript:gs.dateGenerate('YYYY-MM-DD','HH:mm:ss') to fetch only records changed since your last sync rather than re-pulling everything.
  8. Document the webhook setup process — provide your customers' admins with a Business Rule + Outbound REST Message template they can deploy, enabling real-time sync without polling.

Summary

Topic Key fact
Primary API Table API: https://{instance}.service-now.com/api/now/table/{tableName}
Auth approach OAuth 2.0 — but endpoints are per-instance, not global
Token expiry 30 minutes by default — build refresh logic before first use
Key tables incident, sys_user, sys_user_group, sc_request, change_request
State codes (incident) 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed
Filtering sysparm_query with ServiceNow encoded query syntax
Max records per query 10,000 (default) — paginate with sysparm_offset
Rate limits Instance-configured (typically ~5,000 req/hr) — no standard headers
Webhooks Business Rules + Outbound REST Messages — requires customer admin
Multi-integration shortcut Knit handles instance URL collection, OAuth, and normalises across Jira, Zendesk, GitHub, and more

Frequently Asked Questions

What is the ServiceNow Table API?

The ServiceNow Table API is the primary REST interface for reading and writing records across any ServiceNow table. It exposes endpoints at https://{instance}.service-now.com/api/now/table/{tableName} and supports GET, POST, PUT, PATCH, and DELETE operations. For product integrations, the most relevant tables are incident, sys_user, sys_user_group, sc_request, and change_request. The Table API supports powerful query filtering via the sysparm_query parameter.

How do I authenticate with the ServiceNow REST API?

ServiceNow supports OAuth 2.0 (recommended for production) and Basic Auth. For OAuth, the token endpoint is https://{instance}.service-now.com/oauth_token.do and the authorization endpoint is https://{instance}.service-now.com/oauth_auth.do — both are instance-specific, so you must collect the customer's instance URL before initiating the OAuth flow. Tokens expire after 30 minutes by default; use the refresh token to obtain new ones without user interaction.

What is sysparm_query in ServiceNow?

sysparm_query is the ServiceNow Table API's parameter for filtering records. It uses ServiceNow's encoded query syntax: field operators joined with ^ (AND) or ^OR (OR). Common operators include =, !=, IN, STARTSWITH, CONTAINS. Example: state=1^assigned_toISNOTEMPTY^opened_at>=javascript:gs.beginningOfLast30Days(). Build queries in the ServiceNow Filter Builder UI first, then copy the encoded query string to use in your API calls.

What are the ServiceNow API rate limits?

ServiceNow API rate limits are configured per instance by the customer's admin, not fixed globally. The default is typically 5,000 API requests per hour per user account, but enterprise instances can have this set differently. ServiceNow does not return standard rate limit headers on every response — watch for 429 Too Many Requests and implement exponential backoff. The API defaults to a maximum of 10,000 records per single Table API query (controlled by the glide.db.max_view_records system property — most instances leave this at the default).

How do ServiceNow webhooks work?

ServiceNow does not have native outbound webhooks that you register from outside the instance. Real-time event notifications are built using Business Rules (server-side scripts that fire on table record events) combined with Outbound REST Messages. This requires a ServiceNow admin on the customer's side to configure. For integrations where webhook setup isn't feasible, use delta polling: query the incident table with a sys_updated_on> filter on a schedule.

What is the difference between the ServiceNow Table API and Import Set API?

The Table API directly reads and writes records with immediate effect — the right choice for most product integrations. The Import Set API stages data in a temporary table first, then a transform map processes it into the target table. Use Import Sets only for bulk historical data migration. For real-time integrations involving incidents, users, and groups, always use the Table API.

Which ServiceNow tables should I use for an ITSM integration?

Focus on five tables: incident for IT incidents, sys_user for user records, sys_user_group for team assignments, sc_request for service catalog requests, and change_request for change management. The incident table's state field uses numeric codes — 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed — always map these explicitly in your code rather than relying on display values.

Is there a simpler way to integrate with ServiceNow without building per-instance OAuth for each customer?

Yes. Knit provides a unified ticketing API that handles ServiceNow authentication — including collecting the instance URL and managing the per-instance OAuth flow per customer. Instead of building dynamic OAuth endpoint logic, token refresh, and per-customer credential storage, your customers connect their ServiceNow instance once through Knit's auth layer. You then call Knit's normalised endpoints for incidents, accounts, contacts, users, and groups — the same interface that works across Jira, GitHub, Zendesk, and more. → getknit.dev/integration/servicenow

#1 in Ease of Integrations

Trusted by businesses to streamline and simplify integrations seamlessly with GetKnit.