Skip to main content
Stateful emulation of a ServiceNow instance’s REST APIs. The Table API serves incident, change_request, problem, task, users, groups, roles, locations, companies, and custom u_ tables from seed config, with encoded queries (sysparm_query), dot-walked fields, display values, and Link and X-Total-Count paging headers. Incidents, changes, and problems follow ServiceNow’s choice values, numbering, priority lookup, and state rules. Basic auth with seeded users, roles checked by ACL-like rules, and OAuth at /oauth_token.do work as on an instance, and the Attachment and Aggregate APIs cover the basics. ServiceNow has no widely used official Node SDK; the tests drive the emulator with raw HTTP through a thin typed client, as integrations do with fetch or axios.

At a glance

Start

ServiceNow starts only when named in config or with --service. When it is the only enabled service, it listens on http://localhost:4000. Without seed config, the instance has the user admin with password admin and the base roles admin, itil, itil_admin, user_admin, snc_read_only, and snc_internal. Point your client’s instance URL at the emulator:

URL mapping

Auth

  • Basic: a seeded user’s user_name and password. Every instance has admin with password admin (sys_id 6816f79cc0a8016401c5a33be04be441, role admin). User names match without regard to case. Inactive and locked out users cannot sign in.
  • Bearer: an OAuth access token from /oauth_token.do, or a token the CLI accepts (for example test_token_admin), which acts as the user whose user_name is the token’s login (admin by default).
Without credentials the answer is 401 { "error": { "message": "User Not Authenticated", "detail": "Required to provide Auth information" }, "status": "failure" } with WWW-Authenticate: Basic realm="Service-now". Wrong credentials and unknown, revoked, or expired tokens answer 401 with User is not authenticated.

OAuth

POST /oauth_token.do takes a form body (or JSON) with client_id and client_secret (or HTTP Basic) from oauth.clients:
  • grant_type=password with username and password
  • grant_type=refresh_token with refresh_token; the answer carries a new access token and the same refresh token
  • grant_type=authorization_code with code, redirect_uri, and code_verifier when the authorize step sent a PKCE challenge (S256 or plain)
  • grant_type=client_credentials when oauth.client_credentials is true and the client names a client_credentials_user (no refresh token)
Answers look like { "access_token": "...", "refresh_token": "...", "scope": "useraccount", "token_type": "Bearer", "expires_in": 1799 }. Access tokens last 1800 seconds and refresh tokens 100 days unless the client sets access_token_lifespan and refresh_token_lifespan. A bad client, bad credentials, or a used or expired code answer 401 { "error_description": "access_denied", "error": "server_error" }, as an instance does; an unknown grant answers 400 unsupported_grant_type. GET /oauth_auth.do?response_type=code&client_id=...&redirect_uri=...&state=... shows a sign-in page listing active users. Choosing one redirects to the redirect URL with code and state; declining sends error=access_denied. POST /oauth_revoke_token.do with the client credentials and token revokes it (a refresh token takes the user’s access tokens with it).

Table API

Every route is served at /api/now/table/{table}, /api/now/v1/table/{table}, and /api/now/v2/table/{table}. Errors use ServiceNow’s body { "error": { "message", "detail" }, "status": "failure" }: Values are strings. Unknown fields in a body are ignored, read only fields (sys_*, number, computed fields) are ignored except sys_id and number on insert, booleans and numbers become strings, and a reference accepts a sys_id or a display value (user names too). Without sysparm_input_display_value, a choice label is stored as given, as GlideRecord does. Saving without a change keeps sys_mod_count.

Query parameters

References read as { "link": "<url>/api/now/table/sys_user/<sys_id>", "value": "<sys_id>" }, as { "display_value", "link" } with display values, and as { "display_value", "link", "value" } with all. An empty reference is "". Date/times are stored in UTC as yyyy-MM-dd HH:mm:ss and shown in the user’s time_zone (or the instance’s time_zone). Journal fields (comments, work_notes) read as ""; writing them adds a sys_journal_field entry, and their display value is the history, newest first. Link holds first, prev (after the first page), next (when more rows follow), and last, keeping the other parameters. X-Total-Count counts matching rows before row ACLs, so a user who may not read every row can get a short page, as on an instance.

Encoded queries

Operators: =, !=, >, >=, <, <=, LIKE, NOT LIKE (or NOTLIKE), STARTSWITH, ENDSWITH, IN, NOT IN, ISEMPTY, ISNOTEMPTY, ANYTHING, EMPTYSTRING, BETWEEN (low@high), ON and NOTON (Today@javascript:gs.beginningOfToday()@javascript:gs.endOfToday(), a label such as Last week, or a date), RELATIVEGT, RELATIVEGE, RELATIVELT, RELATIVELE, RELATIVEEE (@day@ago@3), SAMEAS, NSAMEAS, DYNAMIC (the “Me” filter 90d1921e5f510100a9ad2572f2b477fe), and INSTANCEOF on sys_class_name.
  • Integers and choice fields such as state and priority compare as numbers, date/times as instants, booleans as true/false, and strings without regard to case.
  • References match sys_ids with = and IN, and their display values with LIKE, STARTSWITH, ENDSWITH, and with = when the value is not a sys_id. Dot-walk through them: caller_id.company.name=ACME.
  • !=, NOT IN, NOT LIKE, and NOTON never match empty values (add ^ORfieldISEMPTY). field= matches empty values.
  • javascript: values recognize the common GlideSystem helpers, worked out in the user’s time zone: gs.getUserID(), gs.getUserName(), gs.now(), gs.nowDateTime(), gs.beginningOf...() and gs.endOf...() for Today, Yesterday, Tomorrow, This, Last, and Next Week, Month, Quarter, and Year, and Last 15, 30, or 45 Minutes and Last Hour or 2 Hours, gs.daysAgo(n), gs.daysAgoStart(n), gs.daysAgoEnd(n) and the same for minutes, hours, months, quarters, and years, and gs.dateGenerate('2026-01-01','start'). Weeks start on Monday. Any other script matches no rows.
  • A condition on an unknown field or a term that is not a condition is dropped, as on an instance. With invalid_query_returns_no_rows: true (the glide.invalid_query.returns_no_rows property) such a query returns no rows.

Tables

Every row has sys_id (32 hex characters), sys_created_on, sys_created_by, sys_updated_on, sys_updated_by, sys_mod_count, and sys_tags. Impact and urgency are 1 High, 2 Medium, 3 Low; priority is 1 Critical to 5 Planning.

Business rules

  • Incident: priority follows impact and urgency through the default priority lookup and cannot be set directly. Resolving (6) or closing (7) needs close_code and close_notes (Resolution code and Resolution notes), then stamps resolved_at and resolved_by; closing also stamps closed_at and closed_by. active is false when Closed or Canceled. Leaving Resolved counts reopen_count and clears the resolution stamps. Leaving On Hold clears hold_reason. Changing the assignment group counts reassignment_count. incident_state follows state.
  • Change request: types normal (default), standard, and emergency follow ServiceNow’s state models: normal New, Assess, Authorize, Scheduled, Implement, Review, Closed; emergency skips Assess; standard goes New, Scheduled, Implement, Review, Closed. Assess, Authorize, and Scheduled may go back to New, and any open state may be Canceled. Other moves answer 403 Invalid state transition .... Scheduling a normal or emergency change needs approval approved. Closing needs close_code (successful, successful_issues, unsuccessful) and close_notes. A planned end before the planned start is refused.
  • Problem: priority follows impact and urgency. Resolving (106) or closing (107) needs resolution_code (fix_applied, risk_accepted, canceled, duplicate). Closed problems are inactive.

Access

  • admin passes every check. snc_read_only denies every write, even for admin.
  • Task tables: itil reads and writes; users without it read incidents they are the caller of or opened, and create incidents. Closed and canceled incidents, closed problems, and closed or canceled changes are read only except for admin. Only admin deletes task rows.
  • Users, groups, roles, and memberships: everyone reads; user_admin writes. Locations and companies: everyone reads; admin writes.
  • Journal entries and attachments are readable with the record they belong to.
  • Custom tables: read_roles (default: any signed in user) and write_roles (default itil).
  • Roles come from sys_user_has_role, from groups through sys_group_has_role, and from roles they contain (itil_admin contains itil).
A hidden row answers 404 on read; a denied write answers 403 ACL Exception ... Failed due to security constraints.

Attachment API

  • POST /api/now/attachment/file?table_name=incident&table_sys_id=<sys_id>&file_name=log.txt with the raw bytes and their Content-Type
  • POST /api/now/attachment/upload with multipart form fields table_name, table_sys_id, and a file part
  • GET /api/now/attachment with sysparm_query, sysparm_limit, and sysparm_offset
  • GET /api/now/attachment/{sys_id} for metadata (file_name, content_type, size_bytes, hash, table_name, table_sys_id, download_link, …)
  • GET /api/now/attachment/{sys_id}/file for the bytes, with X-Attachment-Metadata
  • DELETE /api/now/attachment/{sys_id}
Attachments go with their record when it is deleted.

Aggregate API

GET /api/now/stats/{table} (also /api/now/v1/stats/{table}) takes sysparm_query, sysparm_count=true, sysparm_avg_fields, sysparm_sum_fields, sysparm_min_fields, sysparm_max_fields, sysparm_group_by (or GROUPBYfield in the query), sysparm_order_by (COUNT or COUNT^DESC), and sysparm_display_value. Without grouping it answers { "result": { "stats": { "count": "12", "avg": { ... } } } }; with grouping, { "result": [{ "stats": { "count": "3" }, "groupby_fields": [{ "field": "priority", "value": "1", "display_value": "1 - Critical" }] }] }.

Seed config

  • time_zone: users without their own time_zone see date/times here
  • Custom tables and fields must start with u_. Tables may extend another table (task puts their rows in the task table and gives them task fields and rules). Field type is one of string (default), integer, decimal, boolean, glide_date_time, glide_date, reference (with reference), email, url, phone_number, glide_list, or journal_input.
  • records values are read as display values: choice labels, user names, and names of referenced rows work, and date/times are in the instance’s time zone. Business rules run, so a resolved incident needs its resolution fields.
  • Seeded rows get stable sys_ids, so seeding again on saved state adds nothing.
  • Without config the instance has admin / admin and the base roles admin, itil, itil_admin, user_admin, snc_read_only, and snc_internal.

Inspector

GET /_inspector shows incidents, changes, problems, users with their effective roles, groups, tables with row counts, attachments, and OAuth clients.

Limits

Outbound webhooks and business rule scripts (ServiceNow has no standard signed webhook), the Import Set, CMDB, Service Catalog, and Scripted REST APIs, approvals (sysapproval_approver), SLAs, workflows and flows, UI policies, domain separation, XML responses, and sysparm_view. ACLs are the model above rather than configurable rules.

Conformance

See CONFORMANCE.md for every provider.