ServiceNow ITSM Deep Management Skill
Manage and analyze ServiceNow incidents, changes, CMDB, knowledge base, and SLA compliance.
API Conventions
Authentication
All API calls use Basic Auth or OAuth token — injected automatically via Authorization header. Never hardcode credentials.
Base URL
https://{{instance}}.service-now.com/api
Core Helper Function
#!/bin/bash
snow_api() {
local method="$1"
local endpoint="$2"
local data="${3:-}"
if [ -n "$data" ]; then
curl -s -X "$method" \
-H "Authorization: Bearer $SERVICENOW_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
"${SERVICENOW_INSTANCE_URL}/api${endpoint}" \
-d "$data"
else
curl -s -X "$method" \
-H "Authorization: Bearer $SERVICENOW_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
"${SERVICENOW_INSTANCE_URL}/api${endpoint}"
fi
}
Output Rules
- TOKEN EFFICIENCY: Extract only needed fields with
jq - Target <=50 lines per script output
- Use
sysparm_fieldsto limit returned columns - Use
sysparm_queryfor server-side filtering - Never dump full API responses
Common Operations
Incident Management
#!/bin/bash
echo "=== Open Incidents by Priority ==="
snow_api GET "/now/table/incident?sysparm_query=active=true^ORDERBYDESCpriority&sysparm_fields=number,short_description,priority,state,assigned_to,sys_created_on&sysparm_limit=25" \
| jq -r '.result[] | "\(.number)\t\(.priority)\t\(.state)\t\(.short_description[0:60])"' \
| column -t
echo ""
echo "=== Incident Count by Priority ==="
for p in 1 2 3 4 5; do
count=$(snow_api GET "/now/stats/incident?sysparm_query=active=true^priority=$p&sysparm_count=true" | jq -r '.result.stats.count')
echo "P${p}: ${count}"
done
echo ""
echo "=== Unassigned Incidents ==="
snow_api GET "/now/table/incident?sysparm_query=active=true^assigned_toISEMPTY&sysparm_fields=number,short_description,priority,sys_created_on&sysparm_limit=10" \
| jq -r '.result[] | "\(.number)\t\(.priority)\t\(.sys_created_on)\t\(.short_description[0:50])"' \
| column -t
Change Request Management
#!/bin/bash
echo "=== Pending Change Requests ==="
snow_api GET "/now/table/change_request?sysparm_query=approval=requested&sysparm_fields=number,short_description,type,risk,start_date,end_date&sysparm_limit=20" \
| jq -r '.result[] | "\(.number)\t\(.type)\t\(.risk)\t\(.start_date)\t\(.short_description[0:50])"' \
| column -t
echo ""
echo "=== Upcoming Scheduled Changes (next 7 days) ==="
snow_api GET "/now/table/change_request?sysparm_query=state=scheduled^start_dateONNext 7 days@javascript:gs.beginningOfNext7Days()@javascript:gs.endOfNext7Days()&sysparm_fields=number,short_description,start_date,end_date,assigned_to&sysparm_limit=20" \
| jq -r '.result[] | "\(.number)\t\(.start_date)\t\(.short_description[0:50])"' \
| column -t
CMDB Queries
#!/bin/bash
echo "=== Configuration Items by Class ==="
snow_api GET "/now/table/cmdb_ci?sysparm_query=ORDERBYsys_class_name&sysparm_fields=sys_class_name&sysparm_limit=500" \
| jq -r '.result[].sys_class_name' | sort | uniq -c | sort -rn | head -15
echo ""
echo "=== CI Relationships for a Server ==="
CI_SYS_ID="${1:?CI sys_id required}"
snow_api GET "/now/table/cmdb_rel_ci?sysparm_query=parent=${CI_SYS_ID}^ORchild=${CI_SYS_ID}&sysparm_fields=parent,child,type&sysparm_limit=25" \
| jq -r '.result[] | "\(.type.display_value)\t\(.parent.display_value)\t->\t\(.child.display_value)"' \
| column -t
Knowledge Base Management
#!/bin/bash
echo "=== Knowledge Base Articles (recently updated) ==="
snow_api GET "/now/table/kb_knowledge?sysparm_query=workflow_state=published^ORDERBYDESCsys_updated_on&sysparm_fields=number,short_description,kb_category,sys_updated_on,view_count&sysparm_limit=20" \
| jq -r '.result[] | "\(.number)\t\(.view_count) views\t\(.sys_updated_on[0:10])\t\(.short_description[0:50])"' \
| column -t
echo ""
echo "=== Draft Articles Pending Review ==="
snow_api GET "/now/table/kb_knowledge?sysparm_query=workflow_state=draft&sysparm_fields=number,short_description,author,sys_created_on&sysparm_limit=15" \
| jq -r '.result[] | "\(.number)\t\(.sys_created_on[0:10])\t\(.short_description[0:50])"' \
| column -t
SLA Tracking
#!/bin/bash
echo "=== SLA Breached or At Risk ==="
snow_api GET "/now/table/task_sla?sysparm_query=has_breached=true^ORstage=in_progress^percentage>=75&sysparm_fields=task,sla,percentage,has_breached,stage,planned_end_time&sysparm_limit=20" \
| jq -r '.result[] | "\(.task.display_value)\t\(.sla.display_value)\t\(.percentage)%\tbreached:\(.has_breached)\t\(.planned_end_time)"' \
| column -t
echo ""
echo "=== SLA Compliance Rate (last 30 days) ==="
TOTAL=$(snow_api GET "/now/stats/task_sla?sysparm_query=sys_created_onONLast 30 days@javascript:gs.beginningOfLast30Days()@javascript:gs.endOfLast30Days()&sysparm_count=true" | jq -r '.result.stats.count')
BREACHED=$(snow_api GET "/now/stats/task_sla?sysparm_query=has_breached=true^sys_created_onONLast 30 days@javascript:gs.beginningOfLast30Days()@javascript:gs.endOfLast30Days()&sysparm_count=true" | jq -r '.result.stats.count')
echo "Total SLAs: $TOTAL | Breached: $BREACHED | Compliance: $(echo "scale=1; (($TOTAL - $BREACHED) * 100) / $TOTAL" | bc)%"
Output Format
Present results as a structured report:
Managing Servicenow Deep Report
═══════════════════════════════
Resources discovered: [count]
Resource Status Key Metric Issues
──────────────────────────────────────────────
[name] [ok/warn] [value] [findings]
Summary: [total] resources | [ok] healthy | [warn] warnings | [crit] critical
Action Items: [list of prioritized findings]
Target ≤50 lines of output. Use tables for multi-resource comparisons.
Anti-Hallucination Rules
- NEVER assume resource names — always discover via CLI/API in Phase 1 before referencing in Phase 2.
- NEVER fabricate metric names or dimensions — verify against the service documentation or
--helpoutput. - NEVER mix CLI commands between service versions — confirm which version/API you are targeting.
- ALWAYS use the discovery → verify → analyze chain — every resource referenced must have been discovered first.
- ALWAYS handle empty results gracefully — an empty response is valid data, not an error to retry.
Counter-Rationalizations
| Shortcut | Counter | Why |
|---|---|---|
| "I'll skip discovery and check known resources" | Always run Phase 1 discovery first | Resource names change, new resources appear — assumed names cause errors |
| "The user only asked for a quick check" | Follow the full discovery → analysis flow | Quick checks miss critical issues; structured analysis catches silent failures |
| "Default configuration is probably fine" | Audit configuration explicitly | Defaults often leave logging, security, and optimization features disabled |
| "Metrics aren't needed for this" | Always check relevant metrics when available | API/CLI responses show current state; metrics reveal trends and intermittent issues |
| "I don't have access to that" | Try the command and report the actual error | Assumed permission failures prevent useful investigation; actual errors are informative |
Common Pitfalls
- Encoded queries: ServiceNow uses encoded query strings with
^as AND operator — always URL-encode special characters - sys_id references: Related fields return sys_id by default — use
sysparm_display_value=trueto get display names - Pagination: Default limit is 10000 but always set
sysparm_limit— usesysparm_offsetfor pagination - Date format: ServiceNow uses
yyyy-MM-dd HH:mm:ssformat in UTC — not ISO 8601 - ACLs: API results respect user ACLs — missing records may be a permissions issue, not missing data
- Rate limits: Respect instance rate limits — stagger bulk operations
- Table API vs Aggregate API: Use
/now/stats/for counts and aggregations instead of fetching all records