From stackone-unified-connectors
Baseline skill for building unified/schema-based connectors that transform provider data into standardized schemas. Use alongside domain-specific schema skills (e.g., unified-hris-schema, unified-crm-schema) that define your organization's standard schemas. Use when user says "start unified build for [provider]", "build a schema-based connector", "map fields to schema", "test unified connector", or asks about field mapping, enum mapping, pagination configuration, or scope decisions. This skill provides implementation patterns; schema skills provide field definitions. Do NOT use for agentic/custom connectors (use stackone-cli), discovering existing connectors (use stackone-connectors), or building AI agents (use stackone-agents).
How this skill is triggered — by the user, by Claude, or both
Slash command
/stackone-unified-connectors:stackone-unified-connectorsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Build connectors that transform provider-specific data into standardized schemas with consistent field names, enum values, and pagination.
Build connectors that transform provider-specific data into standardized schemas with consistent field names, enum values, and pagination.
This is a baseline skill that provides the core workflow and patterns for building unified connectors. It is designed to work alongside domain-specific schema skills that you create for your organization's specific use cases.
Recommended approach:
unified-hris-schema, unified-messaging-schema, unified-crm-schema)This separation allows you to maintain consistent schemas across all providers within a category while leveraging the shared technical patterns from this baseline skill.
Before building unified connectors:
stackone help <command> for command-specific details--debug before configuring mappingsThese principles apply to ALL unified connector work. Violations cause silent failures or broken mappings.
Every YAML configuration field uses camelCase, not snake_case:
# CORRECT
scopeDefinitions: fieldConfigs: targetFieldKey:
enumMapper: matchExpression: dataKey:
nextKey: pageSize: indexField:
stepFunction: functionName: dataSource:
# WRONG - causes validation errors or silent failures
scope_definitions: field_configs: target_field_key:
While config fields are camelCase, targetFieldKey values match YOUR schema (often snake_case):
fieldConfigs:
- targetFieldKey: first_name # YOUR schema field
expression: $.firstName # Provider's field
stepFunction:
functionName: map_fields
version: '2' # REQUIRED - omitting causes empty results
Pass fields directly in map_fields step parameters rather than action-level fieldConfigs. This avoids schema inference issues that cause build failures.
# RECOMMENDED - Inline fields
- stepId: map_data
stepFunction:
functionName: map_fields
version: '2'
parameters:
fields:
- targetFieldKey: email
expression: $.email # Direct reference, NO step prefix
type: string
dataSource: $.steps.get_data.output.data
| Location | Expression Format | Example |
|---|---|---|
Inline in parameters.fields | Direct field reference | $.email, $.work.department |
Action-level fieldConfigs | Step ID prefix required | $.get_employees.email |
The entire purpose of unified connectors is standardized output. Never suggest users handle mapping in application code.
Never assume response structure. Always run with --debug first:
stackone run --debug --connector <file> --credentials <file> --action-id <action>
Check for a domain-specific schema skill first.
Domain-specific schema skills (e.g., unified-hris-schema, unified-crm-schema) should define your organization's standard schema for that category. These skills complement this baseline skill by providing:
If schema skill exists:
If no schema skill exists:
What a schema skill should contain:
# Example: unified-hris-schema skill structure
# - Field definitions with types
# - Enum values (e.g., employment_status: active, inactive, terminated)
# - Required fields marked
# - Nested structures documented
Creating domain-specific schema skills prevents drift between providers and reduces repeated schema discussions.
Do not skip this step. Research ALL available endpoints before proceeding.
For each endpoint, document:
Present a comparison table and get explicit user approval before implementing:
| Option | Endpoint | Field Coverage | Permissions | Status |
|--------|----------|----------------|-------------|--------|
| A | GET /v2/employees | 70% | Narrow | Active |
| B | POST /reports | 100% | Moderate | Active |
| C | POST /v1/data | 100% | Broad | Deprecated |
Recommendation: Option B - Full coverage, not deprecated
Do not proceed without user selection.
Use scopeDefinitions (not scope_definitions):
scopeDefinitions:
employees:read:
description: Read employee data
employees:extended:read:
description: Extended employee data
includes: employees:read # Scope inheritance
Principles:
See references/scope-patterns.md for detailed patterns.
Use inline fields in map_fields parameters:
steps:
- stepId: map_data
stepFunction:
functionName: map_fields
version: '2'
parameters:
fields:
- targetFieldKey: id
expression: $.id
type: string
- targetFieldKey: email
expression: $.email
type: string
- targetFieldKey: department
expression: $.work.department # Nested field
type: string
- targetFieldKey: status
expression: $.status
type: enum
enumMapper:
matcher:
- matchExpression: '{{$.status == "Active"}}'
value: active
- matchExpression: '{{$.status == "Inactive"}}'
value: inactive
- matchExpression: '{{$.status == null}}'
value: unknown
dataSource: $.steps.get_data.output.data
- stepId: typecast_data
stepFunction:
functionName: typecast
version: '2'
parameters:
fields:
- targetFieldKey: id
type: string
- targetFieldKey: email
type: string
- targetFieldKey: department
type: string
- targetFieldKey: status
type: enum
dataSource: $.steps.map_data.output.data
result:
data: $.steps.typecast_data.output.data
See references/field-mapping-patterns.md for enum mapping, nested objects, and transformations.
For list endpoints, use cursor pagination with the request function:
cursor:
enabled: true
pageSize: 50
inputs:
- name: page_size
type: number
in: query
required: false
- name: cursor
type: string
in: query
required: false
steps:
- stepId: get_data
stepFunction:
functionName: request
parameters:
url: /items
method: get
args:
# Dual-condition pattern for defaults
- name: limit
value: $.inputs.page_size
in: query
condition: "{{present(inputs.page_size)}}"
- name: limit
value: 50
in: query
condition: "{{!present(inputs.page_size)}}"
- name: cursor
value: $.inputs.cursor
in: query
condition: "{{present(inputs.cursor)}}"
result:
data: $.steps.get_data.output.data
next: $.steps.get_data.output.data.meta.nextCursor
Important: Use request function (not paginated_request) when you need dynamic inputs like page_size. The paginated_request function can have issues with $.inputs.* resolving to undefined.
See references/pagination-patterns.md for detailed configuration.
stackone validate connectors/<provider>/<provider>.connector.s1.yaml
Phase 1: Raw Response
stackone run --debug --connector <file> --credentials <file> --action-id <action>
Phase 2: Field Mapping - Verify all fields use YOUR schema names, not provider names
Phase 3: Pagination - Test first page, next page, last page, empty results
Phase 4: Schema Completeness - All required fields present and populated
Create a coverage document listing:
User says: "start unified build for BambooHR"
Actions:
unified-hris-schema)unified-hris-schema skill for consistency across HRIS providers/v1/employees, /v1/employees/directory, custom reports--debug, verify field names match schemaResult: Working unified connector with standardized employee schema that matches other HRIS connectors.
User says: "build a unified messaging connector for Slack"
Actions:
unified-messaging-schema skill - none existsunified-messaging-schema skill so future messaging connectors (Teams, Discord) use the same schema."Result: New schema skill created, connector built, future messaging connectors will use same schema.
User says: "My unified connector returns provider field names instead of my schema"
Actions:
targetFieldKey uses YOUR schema names (not provider names)version: '2' is specified on map_fields and typecast--debug to see raw response structureResult: Fields correctly mapped to user's schema.
User says: "Pagination cursor isn't being passed correctly"
Actions:
--debug to see raw response structuredataKey path matches actual response (e.g., data.employees not just employees)nextKey path points to cursor valuepaginated_request with dynamic inputs - switch to request with dual-condition patternresult.next returns the cursor valueResult: Working pagination with correct cursor handling.
Cause: Missing or incorrect field mapping configuration.
Fix: Ensure targetFieldKey uses YOUR schema field names, not provider names. Verify map_fields step is present and dataSource is correct.
Cause: Missing version: '2' or wrong expression context.
Fix: Add version: '2' to map_fields and typecast. For inline fields, use direct references ($.email) without step prefix.
Cause: matchExpression doesn't match provider values (case-sensitive).
Fix: Check exact provider values with --debug. Use .toLowerCase() for case-insensitive matching. Always include null/unknown fallback.
Cause: Cursor not being sent or extracted correctly.
Fix: Verify iterator.key matches API's expected parameter name. Check nextKey path against raw response. Verify iterator.in is correct (query/body/headers).
Cause: Action-level fieldConfigs triggering unwanted schema inference.
Fix: Use inline fields in map_fields parameters instead of action-level fieldConfigs.
Cause: Using paginated_request which doesn't handle $.inputs.* well.
Fix: Use standard request function with dual-condition pattern for defaults.
| Resource | URL |
|---|---|
| CLI Package | https://www.npmjs.com/package/@stackone/cli |
| Connector Engine Docs | https://docs.stackone.com/guides/connector-engine |
| CLI Reference | https://docs.stackone.com/guides/connector-engine/cli-reference |
To maintain consistency across providers, create schema skills for each category you work with. A domain-specific schema skill should include:
Required content:
string, number, boolean, datetime_string, enum)Example skill structure:
# Unified HRIS Schema
## Employee Resource
### Required Fields
| Field | Type | Description |
|-------|------|-------------|
| id | string | Unique identifier |
| email | string | Primary email address |
| first_name | string | Employee first name |
| last_name | string | Employee last name |
| employment_status | enum | Current employment status |
### Enum: employment_status
| Value | Description |
|-------|-------------|
| active | Currently employed |
| inactive | On leave or suspended |
| terminated | No longer employed |
### Optional Fields
| Field | Type | Description |
|-------|------|-------------|
| department | string | Department name |
| hire_date | datetime_string | Date of hire |
Naming convention: unified-{category}-schema (e.g., unified-hris-schema, unified-crm-schema, unified-messaging-schema)
npx claudepluginhub stackonehq/agent-plugins --plugin stackone-unified-connectorsGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.