# Spoke Generation

> Generate Integration Hub spokes for connecting external services including spoke actions, connection aliases, data transformations, error handling, and REST/SOAP integrations

- Skill: `happy-technologies-llc/spoke-generation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add happy-technologies-llc/spoke-generation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/happy-technologies-llc/spoke-generation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: Happy-Technologies-LLC (https://skillmd.com/u/happy-technologies-llc)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/happy-technologies-llc/spoke-generation

---


# Integration Hub Spoke Generation

## Overview

This skill covers generating ServiceNow Integration Hub spokes for connecting to external services:

- Creating spoke containers to organize related integration actions
- Building spoke actions with REST, SOAP, and scripted steps
- Configuring connection aliases for secure credential management
- Defining action inputs and outputs with proper data typing
- Implementing data transformations between ServiceNow and external formats
- Adding error handling and retry logic for resilient integrations
- Packaging spokes for deployment across instances

**When to use:** When building reusable integrations with external APIs, creating custom connectors for third-party services, or packaging integration logic into deployable spokes.

## Prerequisites

- **Roles:** `integration_admin`, `flow_designer`, or `admin`
- **Plugins:** `com.glide.hub.flow_designer` (Flow Designer), `com.glide.hub.integration` (Integration Hub)
- **Access:** sys_spoke, sys_hub_action_type_definition, sys_connection_alias tables
- **Knowledge:** REST/SOAP API fundamentals, JSON/XML data formats, OAuth and basic authentication
- **Related Skills:** `genai/flow-generation` for consuming spokes in flows

## Procedure

### Step 1: Analyze the Integration Requirements

Parse the integration request to identify:
- **External service**: What system or API to connect to (Slack, Jira, Salesforce, custom REST)
- **Operations**: What actions the spoke should support (create, read, update, delete, query)
- **Authentication**: How to authenticate (OAuth 2.0, Basic Auth, API Key, mutual TLS)
- **Data format**: Request/response format (JSON, XML, form-encoded)
- **Error handling**: Expected error codes and retry behavior

### Step 2: Query Existing Spokes for Patterns

**MCP Approach:**
```
Use SN-Query-Table on sys_spoke:
  - query: active=true
  - fields: sys_id,name,description,scope,logo,active
  - limit: 20
```

Query existing spoke actions:
```
Use SN-Query-Table on sys_hub_action_type_definition:
  - query: spoke=<spoke_sys_id>^active=true
  - fields: sys_id,name,description,action_type,inputs,outputs
  - limit: 20
```

**REST Approach:**
```
GET /api/now/table/sys_spoke
  ?sysparm_query=active=true
  &sysparm_fields=sys_id,name,description,scope,active
  &sysparm_limit=20
```

### Step 3: Create the Spoke Record

**MCP Approach:**
```
Use SN-Create-Record on sys_spoke:
  - name: "Acme CRM Spoke"
  - description: "Integration spoke for Acme CRM REST API - supports contacts, deals, and activities"
  - scope: "x_acme_crm"
  - logo: ""
  - active: true
  - version: "1.0.0"
```

**REST Approach:**
```
POST /api/now/table/sys_spoke
Body: {
  "name": "Acme CRM Spoke",
  "description": "Integration spoke for Acme CRM REST API - supports contacts, deals, and activities",
  "scope": "x_acme_crm",
  "active": true,
  "version": "1.0.0"
}
```

### Step 4: Create Connection Alias

Connection aliases provide secure, reusable credential storage.

**Connection Alias Types:**

| Type | Value | Use Case |
|------|-------|----------|
| HTTP Connection | http_connection | REST/SOAP API credentials |
| JDBC Connection | jdbc_connection | Database connections |
| LDAP Connection | ldap_connection | Directory services |
| Custom | custom | Non-standard authentication |

**MCP Approach:**
```
Use SN-Create-Record on sys_connection_alias:
  - name: "Acme CRM API"
  - connection_type: "http_connection"
  - description: "Connection alias for Acme CRM REST API"
  - active: true
  - scope: "x_acme_crm"
  - spoke: "<spoke_sys_id>"
```

**REST Approach:**
```
POST /api/now/table/sys_connection_alias
Body: {
  "name": "Acme CRM API",
  "connection_type": "http_connection",
  "description": "Connection alias for Acme CRM REST API",
  "active": true,
  "scope": "x_acme_crm",
  "spoke": "<spoke_sys_id>"
}
```

**Configure Credential (Basic Auth):**
```
Use SN-Create-Record on sys_alias_credential:
  - connection_alias: "<connection_alias_sys_id>"
  - type: "basic_auth"
  - user_name: "${connection.username}"
  - password: "${connection.password}"
  - active: true
```

**Configure Credential (OAuth 2.0):**
```
Use SN-Create-Record on sys_alias_credential:
  - connection_alias: "<connection_alias_sys_id>"
  - type: "oauth2"
  - oauth_profile: "<oauth_profile_sys_id>"
  - active: true
```

### Step 5: Create Spoke Actions

Each action represents an operation the spoke can perform.

**MCP Approach:**
```
Use SN-Create-Record on sys_hub_action_type_definition:
  - name: "Create Contact"
  - description: "Creates a new contact in Acme CRM"
  - spoke: "<spoke_sys_id>"
  - action_type: "custom"
  - category: "x_acme_crm"
  - access: "public"
  - active: true
  - annotation: "Creates a contact record in external CRM system"
```

```
Use SN-Create-Record on sys_hub_action_type_definition:
  - name: "Get Contact"
  - description: "Retrieves a contact by ID from Acme CRM"
  - spoke: "<spoke_sys_id>"
  - action_type: "custom"
  - category: "x_acme_crm"
  - access: "public"
  - active: true
```

```
Use SN-Create-Record on sys_hub_action_type_definition:
  - name: "Search Contacts"
  - description: "Searches contacts in Acme CRM by criteria"
  - spoke: "<spoke_sys_id>"
  - action_type: "custom"
  - category: "x_acme_crm"
  - access: "public"
  - active: true
```

**REST Approach:**
```
POST /api/now/table/sys_hub_action_type_definition
Body: {
  "name": "Create Contact",
  "description": "Creates a new contact in Acme CRM",
  "spoke": "<spoke_sys_id>",
  "action_type": "custom",
  "category": "x_acme_crm",
  "access": "public",
  "active": true
}
```

### Step 6: Define Action Inputs

**MCP Approach:**
```
Use SN-Create-Record on sys_hub_action_input:
  - action_type: "<create_contact_action_sys_id>"
  - name: "first_name"
  - label: "First Name"
  - type: "string"
  - mandatory: true
  - order: 100
```

```
Use SN-Create-Record on sys_hub_action_input:
  - action_type: "<create_contact_action_sys_id>"
  - name: "last_name"
  - label: "Last Name"
  - type: "string"
  - mandatory: true
  - order: 200
```

```
Use SN-Create-Record on sys_hub_action_input:
  - action_type: "<create_contact_action_sys_id>"
  - name: "email"
  - label: "Email Address"
  - type: "string"
  - mandatory: true
  - order: 300
```

```
Use SN-Create-Record on sys_hub_action_input:
  - action_type: "<create_contact_action_sys_id>"
  - name: "company_id"
  - label: "Company ID"
  - type: "string"
  - mandatory: false
  - order: 400
```

**REST Approach:**
```
POST /api/now/table/sys_hub_action_input
Body: {
  "action_type": "<create_contact_action_sys_id>",
  "name": "email",
  "label": "Email Address",
  "type": "string",
  "mandatory": true,
  "order": 300
}
```

### Step 7: Add REST Steps to Actions

REST steps define the actual HTTP calls to external services.

**MCP Approach:**
```
Use SN-Create-Record on sys_hub_rest_step:
  - action_type: "<create_contact_action_sys_id>"
  - name: "Call Create Contact API"
  - order: 100
  - connection_alias: "<connection_alias_sys_id>"
  - http_method: "POST"
  - resource_path: "/api/v2/contacts"
  - content_type: "application/json"
  - request_body: '{
      "first_name": "${inputs.first_name}",
      "last_name": "${inputs.last_name}",
      "email": "${inputs.email}",
      "company_id": "${inputs.company_id}"
    }'
  - headers: '[{"name":"Accept","value":"application/json"}]'
```

**REST Approach:**
```
POST /api/now/table/sys_hub_rest_step
Body: {
  "action_type": "<create_contact_action_sys_id>",
  "name": "Call Create Contact API",
  "order": 100,
  "connection_alias": "<connection_alias_sys_id>",
  "http_method": "POST",
  "resource_path": "/api/v2/contacts",
  "content_type": "application/json",
  "request_body": "{\"first_name\":\"${inputs.first_name}\",\"last_name\":\"${inputs.last_name}\",\"email\":\"${inputs.email}\"}"
}
```

**GET Request Example:**
```
Use SN-Create-Record on sys_hub_rest_step:
  - action_type: "<get_contact_action_sys_id>"
  - name: "Call Get Contact API"
  - order: 100
  - connection_alias: "<connection_alias_sys_id>"
  - http_method: "GET"
  - resource_path: "/api/v2/contacts/${inputs.contact_id}"
  - content_type: "application/json"
  - headers: '[{"name":"Accept","value":"application/json"}]'
```

### Step 8: Define Action Outputs

**MCP Approach:**
```
Use SN-Create-Record on sys_hub_action_output:
  - action_type: "<create_contact_action_sys_id>"
  - name: "contact_id"
  - label: "Contact ID"
  - type: "string"
  - order: 100
```

```
Use SN-Create-Record on sys_hub_action_output:
  - action_type: "<create_contact_action_sys_id>"
  - name: "status_code"
  - label: "HTTP Status Code"
  - type: "integer"
  - order: 200
```

```
Use SN-Create-Record on sys_hub_action_output:
  - action_type: "<create_contact_action_sys_id>"
  - name: "response_body"
  - label: "Response Body"
  - type: "string"
  - order: 300
```

### Step 9: Add Error Handling with Script Steps

**MCP Approach:**
```
Use SN-Create-Record on sys_hub_script_step:
  - action_type: "<create_contact_action_sys_id>"
  - name: "Handle Response"
  - order: 200
  - script: |
      (function execute(inputs, outputs) {
        var statusCode = inputs.rest_step_status_code;
        var responseBody = inputs.rest_step_response_body;

        if (statusCode == 201) {
          var response = JSON.parse(responseBody);
          outputs.contact_id = response.id;
          outputs.status_code = statusCode;
        } else if (statusCode == 409) {
          throw new Error('Contact already exists: ' + responseBody);
        } else if (statusCode == 429) {
          throw new Error('Rate limit exceeded. Retry after cooldown.');
        } else {
          throw new Error('API error ' + statusCode + ': ' + responseBody);
        }
      })(inputs, outputs);
```

### Step 10: Test and Publish the Spoke

**MCP Approach:**
```
Use SN-Update-Record on sys_spoke:
  - sys_id: "<spoke_sys_id>"
  - active: true
```

Verify all actions are properly configured:
```
Use SN-Query-Table on sys_hub_action_type_definition:
  - query: spoke=<spoke_sys_id>
  - fields: sys_id,name,active,action_type
  - limit: 50
```

Verify connection alias is available:
```
Use SN-Query-Table on sys_connection_alias:
  - query: spoke=<spoke_sys_id>
  - fields: sys_id,name,connection_type,active
```

## Tool Usage

| Tool | Purpose | When to Use |
|------|---------|-------------|
| SN-Query-Table | Find existing spokes and actions | Pattern discovery and validation |
| SN-Create-Record | Create spokes, actions, inputs, outputs, steps | Building spoke components |
| SN-Update-Record | Modify spoke settings, activate spokes | Editing and publishing |
| SN-Get-Table-Schema | Discover spoke table fields | Understanding available configurations |

## Best Practices

1. **One spoke per external service** -- avoid mixing multiple APIs in a single spoke
2. **Use connection aliases** for all credentials -- never hardcode secrets in scripts or REST steps
3. **Define comprehensive inputs/outputs** with clear labels and proper data types
4. **Always add error handling** -- parse HTTP status codes and provide meaningful error messages
5. **Implement retry logic** for transient failures (429, 503) using script steps
6. **Use pagination** for search/list actions to handle large result sets
7. **Version your API paths** in resource_path to support API evolution
8. **Log integration calls** for debugging with appropriate verbosity levels
9. **Test with mock data** before connecting to production external services
10. **Document rate limits** and throttling behavior in the spoke description

## Troubleshooting

| Issue | Cause | Resolution |
|-------|-------|------------|
| Connection test fails | Invalid credentials or URL in connection alias | Verify connection alias configuration and test connectivity |
| 401 Unauthorized | OAuth token expired or invalid credentials | Refresh OAuth token or update credential record |
| 403 Forbidden | API key lacks required permissions | Check external service permissions for the integration user |
| REST step timeout | External service slow or unreachable | Increase timeout on REST step or check network connectivity |
| Response parsing error | Unexpected response format from API | Add defensive JSON parsing in script step with try/catch |
| Action not visible in Flow Designer | Action access set to private or spoke inactive | Set access to "public" and verify spoke is active |
| Input data truncated | String field length limits | Use multi-line text type for large payloads |

## Examples

### Example 1: Slack Notification Spoke

Generate a spoke for sending Slack messages:
- **Action 1:** Send Message -- POST to /api/chat.postMessage with channel, text, attachments
- **Action 2:** Create Channel -- POST to /api/conversations.create with name, is_private
- **Action 3:** Upload File -- POST to /api/files.upload with channels, file content
- **Connection:** OAuth 2.0 with Slack Bot Token
- **Error Handling:** Handle rate limits (429), channel not found (channel_not_found)

### Example 2: Jira Integration Spoke

Generate a spoke for Jira Cloud operations:
- **Action 1:** Create Issue -- POST to /rest/api/3/issue with project, summary, description, issuetype
- **Action 2:** Get Issue -- GET /rest/api/3/issue/{issueKey} returning status, assignee, priority
- **Action 3:** Add Comment -- POST to /rest/api/3/issue/{issueKey}/comment with body
- **Action 4:** Transition Issue -- POST to /rest/api/3/issue/{issueKey}/transitions
- **Connection:** Basic Auth with API token, base URL configurable per instance

### Example 3: Custom REST API Spoke

Generate a spoke for a custom internal microservice:
- **Action 1:** Submit Order -- POST /api/v1/orders with line items, customer, shipping address
- **Action 2:** Check Order Status -- GET /api/v1/orders/{orderId} returning status, tracking number
- **Action 3:** Cancel Order -- DELETE /api/v1/orders/{orderId} with reason
- **Connection:** API Key authentication via custom header
- **Data Transform:** Map ServiceNow catalog variables to API request format

## Related Skills

- `genai/flow-generation` - Flow Designer flows that consume spoke actions
- `genai/playbook-generation` - Playbooks that call spoke actions as activities
- `catalog/approval-workflows` - Approval flows that may trigger external notifications
- `admin/instance-management` - Connection alias and credential configuration

