packages/opencode/src/bundled-skills/integration-hub/SKILL.md
This skill should be used when the user asks to "IntegrationHub", "spoke", "flow action", "integration", "subflow", "connection alias", "credential alias", "REST step", or any ServiceNow IntegrationHub development.
npx skillsauth add groeimetai/snow-flow integration-hubInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
IntegrationHub provides reusable integration components for Flow Designer and workflows.
Spoke (sn_ih_spoke)
├── Actions (sn_ih_action)
│ ├── Inputs
│ ├── Steps
│ └── Outputs
├── Connection Alias
└── Credential Alias
Flow Designer
└── Uses Spoke Actions
| Table | Purpose |
| ------------------- | ----------------------------- |
| sn_ih_spoke | Spoke definitions |
| sn_ih_action | Spoke actions |
| sn_ih_step_config | Action step configuration |
| sys_alias | Connection/credential aliases |
// Create connection alias (ES5 ONLY!)
var alias = new GlideRecord("sys_alias")
alias.initialize()
alias.setValue("name", "Jira Connection")
alias.setValue("id", "x_myapp_jira_connection")
alias.setValue("type", "connection")
alias.setValue("connection_type", "http")
// Connection attributes
alias.setValue(
"attributes",
JSON.stringify({
base_url: "https://mycompany.atlassian.net/rest/api/3",
timeout: 30000,
}),
)
alias.insert()
// Create credential alias (ES5 ONLY!)
var credAlias = new GlideRecord("sys_alias")
credAlias.initialize()
credAlias.setValue("name", "Jira API Token")
credAlias.setValue("id", "x_myapp_jira_credential")
credAlias.setValue("type", "credential")
credAlias.insert()
// Link to actual credential
var credential = new GlideRecord("basic_auth_credentials")
credential.initialize()
credential.setValue("name", "Jira API Token Credential")
credential.setValue("user_name", "[email protected]")
credential.setValue("password", "") // Set via secure method
credential.insert()
// Create alias-credential mapping
var mapping = new GlideRecord("sys_alias_credential")
mapping.initialize()
mapping.setValue("alias", credAlias.getUniqueValue())
mapping.setValue("credential", credential.getUniqueValue())
mapping.insert()
// Create spoke (ES5 ONLY!)
var spoke = new GlideRecord("sn_ih_spoke")
spoke.initialize()
spoke.setValue("name", "Custom ITSM Connector")
spoke.setValue("label", "Custom ITSM Connector")
spoke.setValue("description", "Integration with external ITSM system")
spoke.setValue("vendor", "My Company")
spoke.setValue("version", "1.0.0")
// Scope
spoke.setValue("scope", "x_myapp_itsm")
// Logo
spoke.setValue("logo", "attachment_sys_id")
spoke.insert()
// Create action for spoke (ES5 ONLY!)
var action = new GlideRecord("sn_ih_action")
action.initialize()
action.setValue("name", "Create External Ticket")
action.setValue("label", "Create External Ticket")
action.setValue("spoke", spokeSysId)
action.setValue("description", "Creates a ticket in external ITSM system")
// Category
action.setValue("category", "record_operations")
// Accessibility
action.setValue("accessible_from", "flow_designer")
action.insert()
// Add inputs
addActionInput(action.getUniqueValue(), "summary", "String", true, "Ticket summary")
addActionInput(action.getUniqueValue(), "description", "String", false, "Ticket description")
addActionInput(action.getUniqueValue(), "priority", "String", false, "Priority level")
// Add outputs
addActionOutput(action.getUniqueValue(), "ticket_id", "String", "Created ticket ID")
addActionOutput(action.getUniqueValue(), "ticket_url", "String", "URL to ticket")
// Add action input (ES5 ONLY!)
function addActionInput(actionSysId, name, type, mandatory, label) {
var input = new GlideRecord("sn_ih_input")
input.initialize()
input.setValue("action", actionSysId)
input.setValue("name", name)
input.setValue("label", label)
input.setValue("type", type)
input.setValue("mandatory", mandatory)
input.setValue("order", getNextOrder(actionSysId, "input"))
return input.insert()
}
// Add action output (ES5 ONLY!)
function addActionOutput(actionSysId, name, type, label) {
var output = new GlideRecord("sn_ih_output")
output.initialize()
output.setValue("action", actionSysId)
output.setValue("name", name)
output.setValue("label", label)
output.setValue("type", type)
output.setValue("order", getNextOrder(actionSysId, "output"))
return output.insert()
}
// Create REST step for action (ES5 ONLY!)
var step = new GlideRecord("sn_ih_step_config")
step.initialize()
step.setValue("action", actionSysId)
step.setValue("name", "Call External API")
step.setValue("order", 100)
step.setValue("step_type", "rest")
// REST configuration
step.setValue(
"rest_config",
JSON.stringify({
connection_alias: "x_myapp_jira_connection",
credential_alias: "x_myapp_jira_credential",
http_method: "POST",
resource_path: "/issue",
request_body: {
fields: {
project: { key: "${inputs.project_key}" },
summary: "${inputs.summary}",
description: "${inputs.description}",
issuetype: { name: "Task" },
},
},
headers: {
"Content-Type": "application/json",
},
}),
)
step.insert()
// Create script step (ES5 ONLY!)
var scriptStep = new GlideRecord("sn_ih_step_config")
scriptStep.initialize()
scriptStep.setValue("action", actionSysId)
scriptStep.setValue("name", "Process Response")
scriptStep.setValue("order", 200)
scriptStep.setValue("step_type", "script")
// Script (ES5 ONLY!)
scriptStep.setValue(
"script",
"(function execute(inputs, outputs) {\n" +
" // Get REST response from previous step\n" +
" var response = inputs.rest_response;\n" +
" \n" +
" if (response.status_code === 201) {\n" +
" var body = JSON.parse(response.body);\n" +
" outputs.ticket_id = body.id;\n" +
" outputs.ticket_url = body.self;\n" +
" outputs.success = true;\n" +
" } else {\n" +
" outputs.success = false;\n" +
' outputs.error_message = "Failed: " + response.status_code;\n' +
" }\n" +
"})(inputs, outputs);",
)
scriptStep.insert()
// Subflows encapsulate reusable integration logic
// Created via Flow Designer UI, but can be invoked via script
// Invoke subflow from script (ES5 ONLY!)
var inputs = {
ticket_id: "INC0010001",
action: "update",
fields: {
status: "resolved",
resolution: "Fixed",
},
}
// Start subflow
sn_fd.FlowAPI.startSubflow("x_myapp_update_external_ticket", inputs)
// Execute spoke action from script (ES5 ONLY!)
var actionInputs = {
summary: "New ticket from ServiceNow",
description: "Created via integration",
priority: "Medium",
}
try {
var result = sn_fd.FlowAPI.executeAction("x_myapp_itsm.create_external_ticket", actionInputs)
if (result.outputs.success) {
gs.info("Created ticket: " + result.outputs.ticket_id)
} else {
gs.error("Failed: " + result.outputs.error_message)
}
} catch (e) {
gs.error("Action execution failed: " + e.message)
}
// Error handling in action script (ES5 ONLY!)
;(function execute(inputs, outputs) {
try {
// Main logic
var response = callExternalAPI(inputs)
if (response.status_code >= 400) {
throw new Error("API error: " + response.status_code + " - " + response.body)
}
outputs.result = JSON.parse(response.body)
outputs.success = true
} catch (e) {
outputs.success = false
outputs.error_code = "INTEGRATION_ERROR"
outputs.error_message = e.message
// Log for debugging
gs.error("IntegrationHub action failed: " + e.message)
// Optionally throw to trigger Flow Designer error handling
// throw e;
}
})(inputs, outputs)
// Retry wrapper for transient failures (ES5 ONLY!)
function executeWithRetry(fn, maxRetries, delayMs) {
var attempts = 0
var lastError = null
while (attempts < maxRetries) {
try {
return fn()
} catch (e) {
lastError = e
attempts++
if (attempts < maxRetries) {
gs.info("Retry " + attempts + " of " + maxRetries + " after error: " + e.message)
gs.sleep(delayMs * attempts) // Exponential backoff
}
}
}
throw new Error("Failed after " + maxRetries + " attempts: " + lastError.message)
}
| Tool | Purpose |
| --------------------------------- | ------------------------ |
| snow_query_table | Query spokes and actions |
| snow_find_artifact | Find integration configs |
| snow_test_rest_connection | Test connections |
| snow_execute_script_with_output | Test action scripts |
// 1. Find available spokes
await snow_query_table({
table: "sn_ih_spoke",
query: "active=true",
fields: "name,label,vendor,version",
})
// 2. Get spoke actions
await snow_query_table({
table: "sn_ih_action",
query: "spoke.name=Jira Spoke",
fields: "name,label,description,category",
})
// 3. Test connection
await snow_test_rest_connection({
connection_alias: "x_myapp_jira_connection",
credential_alias: "x_myapp_jira_credential",
})
development
This skill should be used when the user asks to "App Engine Studio", "workspace builder", "custom workspace", "AES", "low code", "app development", "studio", or any ServiceNow App Engine Studio development.
tools
This skill should be used when the user asks to "create a widget", "build a widget", "service portal widget", "sp_widget", "fix widget", "widget not working", "ng-click not working", or any Service Portal widget development.
development
This skill should be used when the user asks to "create chatbot", "virtual agent", "VA topic", "NLU", "conversation", "chat flow", "topic block", or any ServiceNow Virtual Agent development.
development
This skill should be used when the user asks to "vendor", "supplier", "contract", "procurement", "SLA", "vendor risk", "vendor performance", or any ServiceNow Vendor Management development.