Skip to main content
This reference documents every property available in connector YAML files. Each property includes its purpose, allowed values, real-world examples, and whether it affects the UI (Hub, Dashboard), MCP (tool declarations), or both.
Impact Legend:
  • 🖥️ UI - Affects StackOne Hub, Dashboard, or Connect flows
  • 🤖 MCP - Used in MCP tool declarations (list tools)
  • ⚙️ Runtime - Affects API request execution

File Structure

Connectors use a modular file structure:
connectors/
└── {provider}/
    ├── {provider}.connector.s1.yaml      # Main connector file
    ├── {provider}.{resource1}.s1.partial.yaml  # Actions partial
    └── {provider}.{resource2}.s1.partial.yaml  # Actions partial
Partial files keep connectors maintainable by splitting actions into logical groups. The main connector file handles authentication and metadata, while partials contain resource-specific actions.How it works: The CLI merges all partials into a single connector definition during stackone push. The $ref syntax tells the merger which partials to include. File naming must follow the pattern {provider}.{resource}.s1.partial.yaml for auto-discovery.Example from BambooHR:
  • bamboohr.connector.s1.yaml - Auth config
  • bamboohr.employees.s1.partial.yaml - Employee actions
  • bamboohr.timeoff.s1.partial.yaml - Time-off actions

Root Properties

StackOne

Impact: ⚙️ Runtime The schema version for the connector file format.
ValueDescription
1.0.0Current and only supported version
StackOne: 1.0.0
The version determines which parser and validator the runtime uses. Currently only 1.0.0 is supported. Future versions may introduce new properties or change behavior. Always use 1.0.0 for new connectors.

info Section

Metadata about the connector displayed in the UI and used for identification.

info.title

Impact: 🖥️ UI Human-readable provider name displayed in the Hub and Dashboard.
info:
  title: BambooHR
The title appears in:
  • Integration Hub provider list
  • Dashboard connector cards
  • Account connection screens
  • Logs and audit trails
Use the official product name with proper capitalization (e.g., “BambooHR” not “Bamboo HR” or “bamboohr”).

info.key

Impact: 🖥️ UI | 🤖 MCP | ⚙️ Runtime Unique identifier for the connector. Used in API calls, MCP tool names, and internal routing.
info:
  key: bamboohr
ConstraintDescription
FormatLowercase alphanumeric with underscores
UniquenessMust be unique across all connectors
StabilityCannot change once deployed (breaks linked accounts)
The key is the primary identifier:MCP: Prefixes all action IDs → bamboohr_list_employeesAPI: Used in account connections:
POST /accounts
{ "provider": "bamboohr", ... }
Runtime: Routes requests to correct connector configuration.Warning: Changing a key after deployment breaks all existing linked accounts and API integrations.

info.version

Impact: 🖥️ UI | ⚙️ Runtime Connector version following semver format.
info:
  version: 1.0.0
Use semantic versioning:
  • Major (1.x.x): Breaking changes to actions or auth
  • Minor (x.1.x): New actions or optional parameters
  • Patch (x.x.1): Bug fixes, description updates
The version is displayed in the Dashboard and helps track deployed changes. The runtime uses it to validate compatibility during updates.

info.assets.icon

Impact: 🖥️ UI URL to the provider’s logo image. Displayed throughout the UI.
info:
  assets:
    icon: https://stackone-logos.com/api/bamboohr/filled/png
Recommended: Use StackOne’s logo service at https://stackone-logos.com/api/{provider}/filled/pngRequirements:
  • 24x24 pixels minimum
  • PNG or SVG format
  • Transparent background preferred
  • Hosted on HTTPS
The logo appears in Hub listings, Dashboard cards, and account connection flows.

info.description

Impact: 🖥️ UI | 🤖 MCP Brief description of the connector’s purpose.
info:
  description: BambooHR connector for HRIS actions including employee data, time-off, and reporting
UI: Shown in connector details panels and Hub listings.MCP: Included in connector metadata when clients query available integrations. Helps AI agents understand what the connector does.Best practices:
  • Keep under 200 characters
  • Mention key capabilities
  • Include category context (HRIS, CRM, etc.)

baseUrl

Impact: ⚙️ Runtime The root URL for all API requests. Supports static URLs and dynamic interpolation.

Static URL

baseUrl: 'https://api.bamboohr.com/api/gateway.php'

Dynamic URL with credentials

baseUrl: 'https://api.bamboohr.com/api/gateway.php/${credentials.subdomain}/v1'

Dynamic URL with config

baseUrl: 'https://${config.region}.api.provider.com/v2'
Dynamic URLs use ${...} string interpolation:Available contexts:
  • ${credentials.*} - Values from setupFields/configFields
  • ${config.*} - Configuration values
  • ${env.*} - Environment variables (limited)
Example from BambooHR:
baseUrl: 'https://api.bamboohr.com/api/gateway.php/${credentials.subdomain}/v1'
When a user enters subdomain acme-corp, the runtime resolves to:
https://api.bamboohr.com/api/gateway.php/acme-corp/v1
Important: Individual actions can override baseUrl in their step parameters for different API endpoints.

rateLimit

Impact: ⚙️ Runtime Configure rate limiting to respect provider API limits.
rateLimit:
  mainRatelimit: 10
PropertyTypeDescription
mainRatelimitnumberMax requests per second
The runtime tracks requests per linked account and throttles when limits are reached. Requests exceeding the limit are queued and retried with exponential backoff.Best practices:
  • Set slightly below provider’s documented limit
  • Check provider API docs for per-endpoint limits
  • Some providers have different limits for different endpoints
Note: Rate limits apply per account, not globally across all accounts.

resources

Impact: 🖥️ UI URL to the provider’s API documentation. Displayed as a help link in the UI.
resources: https://documentation.bamboohr.com/reference

authentication Section

Defines how end-users authenticate with the provider. Supports multiple auth methods per connector.

Authentication Array Structure

authentication:
  - oauth2:
      type: oauth2
      label: OAuth 2.0
      # ... oauth config
  - api_key:
      type: custom
      label: API Key
      # ... api key config
When a connector has multiple authentication options:
  1. Users see all options in the Hub during connection
  2. Each method has independent credentials and setup flows
  3. Linked accounts store which method was used
  4. Runtime uses the appropriate auth handler based on account config
Example: Slack offers both OAuth 2.0 (for apps) and Bot Token (for direct API access).

OAuth 2.0 Authentication

Impact: 🖥️ UI | ⚙️ Runtime
authentication:
  - oauth2:
      type: oauth2
      label: OAuth 2.0
      support:
        description: Login to your BambooHR account to connect.
        link: https://documentation.bamboohr.com/docs/getting-started
      authorization:
        type: oauth2
        authorizationUrl: 'https://bamboohr.com/oauth/authorize'
        authorizationParams:
          scope: '{{$.credentials.scopes ?? "employees:read"}}'
          client_id: $.credentials.clientId
          redirect_uri: '${apiHostUri}/connect/oauth2/bamboohr/callback'
        tokenUrl: 'https://bamboohr.com/oauth/token'
        token: $.credentials.accessToken
        includeBearer: true
        pkce: true
      setupFields:
        - key: clientId
          label: Client ID
          type: text
          required: true
          secret: false
      configFields:
        - key: subdomain
          label: Subdomain
          type: text
          required: true
      refreshAuthentication:
        action: # embedded refresh action
      environments:
        - key: production
          name: Production
      testActions:
        - action: list_employees
          required: true

type

ValueDescription
oauth2OAuth 2.0 authorization code flow
customAPI Key, Basic Auth, or other non-OAuth

label

Impact: 🖥️ UIDisplay name for this auth method in the Hub.
label: OAuth 2.0

support

Impact: 🖥️ UIHelp text and links shown during connection flow.
support:
  description: Login to your BambooHR account to connect your data.
  link: https://documentation.bamboohr.com/docs/getting-started
The support section helps users during the connection flow:
  • description appears as instructional text
  • link creates a “Learn more” button
Use this to guide users who may not know how to set up the integration or where to find credentials.

authorization

Impact: ⚙️ RuntimeOAuth flow configuration.
authorization:
  type: oauth2
  authorizationUrl: 'https://provider.com/oauth/authorize'
  authorizationParams:
    scope: '{{$.credentials.scopes ?? "default:scope"}}'
    client_id: $.credentials.clientId
    redirect_uri: '${apiHostUri}/connect/oauth2/provider/callback'
  tokenUrl: 'https://provider.com/oauth/token'
  token: $.credentials.accessToken
  includeBearer: true
  pkce: true
PropertyTypeDescription
authorizationUrlstringProvider’s OAuth authorize endpoint
authorizationParamsobjectQuery params for authorization request
tokenUrlstringProvider’s token exchange endpoint
tokenJSONPathPath to access token in credentials
includeBearerbooleanAdd “Bearer ” prefix to Authorization header
pkcebooleanEnable PKCE (Proof Key for Code Exchange)
Authorization Flow:
  1. User clicks “Connect” in Hub
  2. Runtime builds authorization URL with authorizationParams
  3. User redirects to provider, logs in, grants permissions
  4. Provider redirects back with authorization code
  5. Runtime exchanges code for tokens via tokenUrl
  6. Tokens stored in credentials for linked account
Expression types in authorizationParams:
  • $.credentials.* - JSONPath to credential values
  • ${apiHostUri} - StackOne callback URL base
  • '{{expression ?? default}}' - JEXL with fallback
PKCE: Required by many providers for security. Generates code_verifier and code_challenge automatically.Scopes: The example '{{$.credentials.scopes ?? "default:scope"}}' lets users customize scopes in setupFields while providing sensible defaults.

setupFields

Impact: 🖥️ UI | ⚙️ RuntimeFields collected when configuring the connector (T1 - your app’s credentials).
setupFields:
  - key: clientId
    label: Client ID
    type: text
    required: true
    secret: false
    placeholder: 'your-client-id'
    description: Found in your app's OAuth settings.
    tooltip: Navigate to Settings > API > OAuth to find this.
  - key: clientSecret
    label: Client Secret
    type: password
    required: true
    secret: true
PropertyTypeDescription
keystringCredential storage key (referenced as $.credentials.{key})
labelstring🖥️ Field label in UI
typeenumtext, password, url
requiredbooleanField must have a value
secretbooleanEncrypt at rest, mask in UI
placeholderstring🖥️ Example value shown in empty field
descriptionstring🖥️ Help text below field
tooltipstring🖥️ Hover/info icon text
setupFields - Credentials your team enters once when enabling the connector:
  • OAuth Client ID/Secret
  • API keys for your platform
  • Application-level settings
configFields - Credentials each end-user enters during connection:
  • Their API keys
  • Account-specific settings (subdomain, region)
  • Personal access tokens
Storage:
  • setupFields → Stored per auth configuration
  • configFields → Stored per linked account
Security:
  • secret: true → Encrypted at rest, never exposed in API responses
  • type: password → Masked in UI during entry

configFields

Impact: 🖥️ UI | ⚙️ RuntimeFields collected from end-users during connection (T2 - their credentials).
configFields:
  - key: subdomain
    label: Company Subdomain
    type: text
    required: true
    placeholder: 'acme-corp'
    description: Your BambooHR subdomain (from your login URL)
Same property options as setupFields.

refreshAuthentication

Impact: ⚙️ RuntimeEmbedded action for refreshing expired OAuth tokens.
refreshAuthentication:
  action:
    actionId: refresh_token_bamboohr
    categories:
      - internal
    actionType: refresh_token
    label: Refresh Token
    description: Refresh BambooHR OAuth2 token
    steps:
      - stepId: refresh_request
        stepFunction:
          functionName: request
          parameters:
            url: '/oauth/token'
            method: post
            args:
              - name: grant_type
                value: refresh_token
                in: body
              - name: refresh_token
                value: $.credentials.refreshToken
                in: body
      - stepId: map_response
        stepFunction:
          functionName: map_fields
          version: '2'
          parameters:
            fields:
              - targetFieldKey: accessToken
                expression: $.access_token
              - targetFieldKey: refreshToken
                expression: $.refresh_token
            dataSource: $.steps.refresh_request.output.data
    result:
      data: $.steps.map_response.output.data
When refresh happens:
  1. An action fails with 401 Unauthorized
  2. Runtime checks if refreshAuthentication is configured
  3. Executes the embedded refresh action
  4. Updates stored credentials with new tokens
  5. Retries the original action
The refresh action must:
  • Call the provider’s token refresh endpoint
  • Map the response to credential format (accessToken, refreshToken, expiresIn)
  • Return data in result.data
Note: The categories: [internal] hides this from MCP tool listings.

environments

Impact: 🖥️ UIAvailable deployment environments for this auth method.
environments:
  - key: production
    name: Production
  - key: sandbox
    name: Sandbox
Environments let you offer sandbox/production toggles in the Hub. Some providers (like Salesforce) have separate OAuth apps and endpoints for sandbox vs production.The selected environment is available in expressions as $.environment.key.

testActions

Impact: 🖥️ UI | ⚙️ RuntimeActions executed to validate a connection after OAuth completes.
testActions:
  - action: list_employees
    required: true
PropertyTypeDescription
actionstringAction ID to execute
requiredbooleanConnection fails if action fails
After OAuth token exchange:
  1. Runtime executes each testAction in order
  2. If required: true and action fails → connection marked as failed
  3. If required: false and action fails → warning logged but connection succeeds
Best practices:
  • Use a simple read action (list, get)
  • Test the most common use case
  • Avoid actions that modify data

actions Section

Actions define the operations available through the connector. Use $ref to include partials.

Action References

actions:
  $ref: bamboohr.employees
  $ref: bamboohr.timeoff
  $ref: bamboohr.reports
The CLI resolves $ref at build time:
  1. $ref: bamboohr.employees looks for bamboohr.employees.s1.partial.yaml
  2. Partial file must be in same directory as main connector
  3. Actions from partial are merged into main connector
  4. Multiple $ref statements combine all partials
Partial file structure:
# bamboohr.employees.s1.partial.yaml
- actionId: list_employees
  # ... action config
- actionId: get_employee
  # ... action config
Note: Partials start with - (array items), not actions:.

Action Properties

Each action defines a single operation.

actionId

Impact: 🤖 MCP | ⚙️ Runtime Unique identifier for the action. Becomes the MCP tool name with provider prefix.
actionId: list_employees
MCP tool name: bamboohr_list_employees
ConstraintDescription
Formatsnake_case, lowercase
UniquenessMust be unique within connector
Convention{verb}_{resource} or {verb}_{resource}_{variant}
Standard verbs:
  • list_* - Get multiple records (paginated)
  • get_* - Get single record by ID
  • create_* - Create new record
  • update_* - Modify existing record
  • delete_* - Remove record
  • search_* - Query with filters
Examples:
  • list_employees - List all employees
  • get_employee - Get employee by ID
  • create_employee - Create new employee
  • search_employees_by_department - Filtered search
MCP impact: The full tool name is {provider_key}_{actionId}, so bamboohr + list_employees = bamboohr_list_employees.

categories

Impact: 🖥️ UI Categories for filtering in the UI. Does not affect MCP.
categories:
  - hris
  - employees
CategoryDescription
hrisHR Information Systems
atsApplicant Tracking Systems
crmCustomer Relationship Management
lmsLearning Management Systems
marketingMarketing automation
filestorageFile storage and documents
ticketingSupport ticketing
messagingChat and messaging
internalHidden from UI (used for refresh tokens)
Categories enable filtering in:
  • Actions Explorer in Dashboard
  • AI Playground action selection
  • SDK fetchTools({ categories: ['hris'] })
Multiple categories: An action can belong to multiple categories. It appears when any matching filter is applied.internal category: Actions with categories: [internal] are:
  • Hidden from UI listings
  • Not returned in MCP list tools
  • Still executable via direct API calls
  • Used for token refresh and internal operations

actionType

Impact: 🤖 MCP | ⚙️ Runtime Determines the action’s behavior pattern and response schema.
actionType: list
TypeDescriptionResponse Schema
customProvider-specific actionRaw provider response
listPaginated list (unified){ data: [], next: string }
getSingle record (unified){ data: object }
createCreate record (unified){ data: object }
updateUpdate record (unified){ data: object }
deleteDelete record (unified){ success: boolean }
refresh_tokenToken refresh (internal)Credential object
Unified types (list, get, create, update, delete):
  • Enforce consistent response schemas across providers
  • Enable cross-provider compatibility
  • Support automatic pagination handling
  • Normalize error responses
Custom type:
  • Returns raw provider response
  • Use for provider-specific features
  • No schema normalization
  • Full flexibility for unique endpoints
Example: A list action always returns:
{
  "data": [...],
  "next": "cursor_token_or_null"
}
While a custom action returns whatever the provider returns.

label

Impact: 🖥️ UI Human-readable name displayed in the UI.
label: List Employees
The label appears in:
  • Actions Explorer
  • AI Playground action selector
  • Request logs
  • Error messages
Best practices:
  • Use title case
  • Start with verb (List, Get, Create, etc.)
  • Keep concise (under 30 characters)

description

Impact: 🖥️ UI | 🤖 MCP Short description of what the action does. Used in both UI and MCP tool descriptions.
description: List all employees from BambooHR with optional filtering
When MCP clients call list tools, the description becomes the tool’s description:
{
  "name": "bamboohr_list_employees",
  "description": "List all employees from BambooHR with optional filtering",
  "inputSchema": { ... }
}
AI agent impact: LLMs use this description to decide when to invoke the tool. A good description helps agents:
  • Understand the action’s purpose
  • Know what data it returns
  • Decide which action fits the user’s request
Best practices:
  • Keep under 200 characters
  • Mention key capabilities
  • Be specific about what’s returned

details

Impact: 🤖 MCP Extended description with full context. Used as the complete tool description in MCP.
details: |
  Retrieves a paginated list of all employees from BambooHR.

  Returns employee profiles including:
  - Personal information (name, email, phone)
  - Employment details (department, job title, hire date)
  - Custom fields configured in your BambooHR account

  Use the 'fields' parameter to specify which fields to include.
  Supports filtering by employment status and department.
When both are present, MCP tool declarations use:
  • description as a brief summary
  • details as the full tool description
When only description exists: It’s used for both.Recommended approach:
  • description: One-line summary (~100 chars)
  • details: Full context for AI agents (~500 chars)
The details help AI agents understand nuances like:
  • What fields are returned
  • How pagination works
  • What filters are available
  • Edge cases and limitations

resources

Impact: 🖥️ UI Link to provider documentation for this specific action.
resources: https://documentation.bamboohr.com/reference/get-employees-directory
Override or supplement the connector-level resources link with action-specific documentation. Displayed in the Actions Explorer and helps developers understand the underlying API.

inputs

Impact: 🤖 MCP | ⚙️ Runtime Define parameters the action accepts. Become MCP tool input schema.
inputs:
  - name: employee_id
    type: string
    description: The unique identifier of the employee
    required: true
    in: path
  - name: fields
    type: string
    description: Comma-separated list of fields to include
    required: false
    default: 'firstName,lastName,email'
    in: query
  - name: page_size
    type: number
    description: Number of results per page (max 100)
    required: false
    default: 25
    in: query

Input Properties

PropertyTypeDescription
namestringParameter name (used in expressions as $.inputs.{name})
typeenumData type (see below)
descriptionstring🤖 MCP - Shown in tool schema
requiredbooleanWhether parameter is mandatory
defaultanyDefault value if not provided
inenumWhere to send: path, query, body, header
arraybooleanParameter accepts array of values
propertiesarrayNested properties for type: object (see accordion below)

Input Types

TypeDescriptionExample
stringText value"john@example.com"
numberNumeric value42, 3.14
booleanTrue/falsetrue, false
datetime_stringISO 8601 date/time"2024-01-15T10:30:00Z"
objectJSON object{ "key": "value" }
enumPredefined optionsSee below

Enum Type

inputs:
  - name: status
    type: enum
    description: Filter by employment status
    required: false
    values:
      - key: active
        label: Active
        description: Currently employed
      - key: inactive
        label: Inactive
        description: Former employees
    default: active
    in: query
Inputs are converted to JSON Schema for MCP:
inputs:
  - name: employee_id
    type: string
    description: Employee unique identifier
    required: true
  - name: include_terminated
    type: boolean
    description: Include terminated employees
    required: false
    default: false
Becomes:
{
  "inputSchema": {
    "type": "object",
    "properties": {
      "employee_id": {
        "type": "string",
        "description": "Employee unique identifier"
      },
      "include_terminated": {
        "type": "boolean",
        "description": "Include terminated employees",
        "default": false
      }
    },
    "required": ["employee_id"]
  }
}
AI impact: LLMs use this schema to:
  • Generate valid tool calls
  • Understand parameter types
  • Apply default values
  • Validate inputs before calling
Use array: true for parameters accepting multiple values:
inputs:
  - name: employee_ids
    type: string
    array: true
    description: List of employee IDs to fetch
    in: query
In MCP schema: "type": "array", "items": { "type": "string" }
For complex inputs, use type: object with a nested properties array to define the object’s structure:
inputs:
  - name: filter
    type: object
    description: Filter criteria for the query
    required: false
    in: body
    properties:
      - name: status
        type: string
        description: Filter by status
      - name: created_after
        type: datetime_string
        description: Filter by creation date
      - name: assignee
        type: object
        description: Filter by assignee
        properties:
          - name: id
            type: string
            description: Assignee user ID
Properties array: Each item in properties supports the same fields as top-level inputs (name, type, description, required, default, properties for deeper nesting).This generates proper JSON Schema for MCP, helping AI agents understand the expected object structure:
{
  "filter": {
    "type": "object",
    "properties": {
      "status": { "type": "string", "description": "Filter by status" },
      "created_after": { "type": "string", "description": "Filter by creation date" },
      "assignee": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Assignee user ID" }
        }
      }
    }
  }
}

steps

Impact: ⚙️ Runtime Define the execution flow for the action. Steps run sequentially.
steps:
  - stepId: fetch_employees
    description: Fetch employee data from API
    stepFunction:
      functionName: request
      parameters:
        url: '/employees/directory'
        method: get
        args:
          - name: fields
            value: '{{$.inputs.fields ?? "firstName,lastName"}}'
            in: query
  - stepId: transform_response
    stepFunction:
      functionName: map_fields
      version: '2'
      parameters:
        fields:
          - targetFieldKey: employees
            expression: $.employees
        dataSource: $.steps.fetch_employees.output.data

Step Properties

PropertyTypeDescription
stepIdstringUnique identifier within action
descriptionstringWhat this step does (for debugging)
conditionJEXLSkip step if condition is false
ignoreErrorbooleanContinue execution if this step fails (default: false)
stepFunctionobjectFunction to execute (for simple steps)
iteratorJSONPathArray expression — makes this a foreach step. See Iterator Steps
stepFunctionsarrayMultiple functions to execute per iteration (use with iterator)

Conditional Steps

steps:
  - stepId: fetch_with_filter
    condition: '{{present(inputs.department)}}'
    stepFunction:
      functionName: request
      parameters:
        url: '/employees'
        args:
          - name: department
            value: $.inputs.department
            in: query
Conditions use JEXL syntax with helper functions:
FunctionDescriptionExample
present(x)Value exists and not nullpresent(inputs.filter)
blank(x)Value is null, undefined, or emptyblank(inputs.cursor)
Common patterns:
# Only run if parameter provided
condition: '{{present(inputs.employee_id)}}'

# Only run on first page
condition: '{{blank(inputs.cursor)}}'

# Multiple conditions
condition: '{{present(inputs.start_date) && present(inputs.end_date)}}'

Step Functions

request

Impact: ⚙️ Runtime Make an HTTP request to the provider API.
stepFunction:
  functionName: request
  parameters:
    url: '/employees/{employee_id}'
    method: get
    args:
      - name: employee_id
        value: $.inputs.employee_id
        in: path
      - name: fields
        value: '{{$.inputs.fields ?? "all"}}'
        in: query
      - name: Content-Type
        value: application/json
        in: headers
ParameterTypeDescription
baseUrlstringOverride connector baseUrl
urlstringEndpoint path (appended to baseUrl)
methodenumget, post, put, patch, delete
authorizationobjectOverride connector auth
argsarrayRequest parameters
response.collectionbooleanWhether response is an array (default: true)
response.dataKeystringExtract data from this key in response
response.indexFieldstringField to use as index for keyed results
customErrorsarrayRemap provider error responses (see below)

Args Location (in)

ValueDescription
pathURL path parameter: /employees/{id}
queryQuery string: ?field=value
bodyRequest body (JSON)
headersHTTP header
The runtime builds requests as follows:
  1. URL: baseUrl + url with path params interpolated
  2. Query: All in: query args joined with &
  3. Body: All in: body args merged into JSON object
  4. Headers: Connector auth headers + custom headers + in: headers args
Path parameter example:
url: '/employees/{employee_id}/time-off/{request_id}'
args:
  - name: employee_id
    value: $.inputs.employee_id
    in: path
  - name: request_id
    value: $.inputs.request_id
    in: path
Resolves to: /employees/123/time-off/456Body construction:
args:
  - name: firstName
    value: $.inputs.first_name
    in: body
  - name: lastName
    value: $.inputs.last_name
    in: body
Sends: { "firstName": "John", "lastName": "Doe" }
Use customErrors to remap provider error responses. This is useful for:
  • GraphQL APIs that return errors with 200 status codes
  • Provider-specific error formats that need normalization
  • Custom error messages for better AI agent understanding
stepFunction:
  functionName: request
  parameters:
    url: /graphql
    method: post
    customErrors:
      - receivedStatus: 200
        targetStatus: 400
        condition: '{{present(data.errors)}}'
        message: GraphQL query failed
      - receivedStatus: 404
        targetStatus: 400
        message: Resource not found
customErrors properties:
PropertyTypeDescription
receivedStatusnumberHTTP status to match from provider
targetStatusnumberHTTP status to return instead
conditionstringJEXL expression (only trigger error if true)
messagestringCustom error message
The condition property is useful for GraphQL APIs where errors are returned in the response body with a 200 status code.

paginated_request

Impact: ⚙️ Runtime Automatically handle pagination for list endpoints.
stepFunction:
  functionName: paginated_request
  parameters:
    url: '/employees'
    method: get
    args:
      - name: page_size
        value: '{{$.inputs.page_size ?? 25}}'
        in: query
    pagination:
      type: cursor
      request:
        cursor_field: cursor
        cursor_position: query
      response:
        cursor_path: $.next_cursor
        data_path: $.employees

Pagination Types

# Cursor-based
pagination:
  type: cursor
  request:
    cursor_field: cursor
    cursor_position: query
  response:
    cursor_path: $.next_cursor
    data_path: $.data

# Offset-based
pagination:
  type: offset
  request:
    offset_field: offset
    limit_field: limit
    offset_position: query
  response:
    data_path: $.results
    total_path: $.total_count

# Page number
pagination:
  type: page_number
  request:
    page_field: page
    page_position: query
  response:
    data_path: $.items
    total_pages_path: $.total_pages
The runtime handles pagination automatically:Cursor pagination:
  1. Make initial request without cursor
  2. Read cursor_path from response
  3. If cursor exists, make next request with cursor in cursor_position
  4. Repeat until no cursor returned
  5. Aggregate all data_path results
Result format:
{
  "data": [...all records...],
  "next": "cursor_for_next_page_or_null"
}
MCP behavior: When an agent calls a list action, they receive the first page. The next cursor allows fetching subsequent pages by passing it back as an input parameter.

map_fields

Impact: ⚙️ Runtime Transform data between formats.
stepFunction:
  functionName: map_fields
  version: '2'
  parameters:
    fields:
      - targetFieldKey: id
        expression: $.employeeId
        type: string
      - targetFieldKey: full_name
        expression: $.firstName + ' ' + $.lastName
        type: string
      - targetFieldKey: is_active
        expression: $.status == 'Active'
        type: boolean
      - targetFieldKey: hire_date
        expression: $.hireDate
        type: datetime_string
    dataSource: $.steps.fetch_employees.output.data
ParameterTypeDescription
fieldsarrayMapping definitions
dataSourceJSONPathSource data to transform
fields[].targetFieldKeystringOutput field name
fields[].expressionJEXLValue expression
fields[].typeenumOutput type
Expressions support JEXL syntax with the source object as context:Simple path:
expression: $.employeeId
Concatenation:
expression: $.firstName + ' ' + $.lastName
Conditional:
expression: $.status == 'Active' ? 'employed' : 'terminated'
Null coalescing:
expression: $.middleName ?? ''
Nested access:
expression: $.department.name
Array operations:
expression: $.tags|join(',')
Type coercion: The type field ensures output matches expected type:
  • string - Converts to string
  • number - Converts to number
  • boolean - Converts to boolean
  • datetime_string - Formats as ISO 8601

group_data

Impact: ⚙️ Runtime Group array data by a field.
stepFunction:
  functionName: group_data
  parameters:
    groupByField: department_id
    dataSource: $.steps.fetch_employees.output.data
Transforms flat array into grouped structure:Input:
[
  { "name": "John", "department_id": "eng" },
  { "name": "Jane", "department_id": "eng" },
  { "name": "Bob", "department_id": "sales" }
]
Output:
{
  "eng": [
    { "name": "John", "department_id": "eng" },
    { "name": "Jane", "department_id": "eng" }
  ],
  "sales": [
    { "name": "Bob", "department_id": "sales" }
  ]
}
Useful for restructuring provider responses into more usable formats.

typecast

Impact: ⚙️ Runtime Convert data types.
stepFunction:
  functionName: typecast
  parameters:
    type: number
    dataSource: $.steps.previous.output.data.count
TypeDescription
stringConvert to string
numberParse as number
booleanConvert to boolean
jsonParse JSON string to object

soap_request

Impact: ⚙️ Runtime Make SOAP API requests. Used primarily for enterprise providers like Workday.
stepFunction:
  functionName: soap_request
  parameters:
    baseUrl: 'https://${credentials.workday_host}'
    url: '/ccx/service/${credentials.tenant}/Human_Resources'
    method: post
    authorization:
      type: bearer
      token: ${credentials.accessToken}
      includeBearer: true
    soapOperation: Get_Workers
    useSoapContext: false
    namespaces:
      - namespaceIdentifier: bsvc
        namespace: 'urn:com.workday/bsvc'
    args:
      - name: '@_bsvc:version'
        in: body
        value: v45.1
      - name: bsvc:Request_Criteria
        in: body
        value:
          'bsvc:Transaction_Log_Criteria_Data':
            - 'bsvc:Transaction_Date_Range_Data':
                'bsvc:Updated_From': '{{$.inputs.updated_after}}'
ParameterTypeDescription
baseUrlstringSOAP service base URL
urlstringSOAP endpoint path
methodenumHTTP method (typically post)
authorizationobjectAuth config (same as request)
soapOperationstringSOAP operation name
useSoapContextbooleanUse SOAP context (default: false)
namespacesarrayXML namespace definitions
argsarrayRequest parameters
Namespaces map XML prefixes to URIs:
namespaces:
  - namespaceIdentifier: bsvc
    namespace: 'urn:com.workday/bsvc'
  - namespaceIdentifier: wd
    namespace: 'urn:com.workday/bsvc/wd'
Reference namespaces in field names with the identifier prefix:
args:
  - name: 'bsvc:Worker_Reference'
    in: body
    value:
      'bsvc:ID':
        '@_bsvc:type': 'Employee_ID'
        '#text': '{{$.inputs.employee_id}}'
XML attribute syntax: Use @_ prefix for XML attributes and #text for text content.

static_values

Impact: ⚙️ Runtime Return predefined static values without making an API request. Useful for enum lookups or constant data.
stepFunction:
  functionName: static_values
  parameters:
    values:
      - id: question
        name: Question
      - id: incident
        name: Incident
      - id: problem
        name: Problem
      - id: task
        name: Task
ParameterTypeDescription
valuesarrayArray of static objects to return
Use static_values when:
  1. Provider doesn’t have an API endpoint for enum values:
# Ticket types are hardcoded in Zendesk
stepFunction:
  functionName: static_values
  parameters:
    values:
      - id: question
        name: Question
      - id: incident
        name: Incident
  1. Combining with dynamic data via multi-step actions:
steps:
  static_types:
    stepFunction:
      functionName: static_values
      parameters:
        values:
          - id: default
            name: Default Type

  api_types:
    stepFunction:
      functionName: request
      parameters:
        url: /custom-types

  merge_types:
    stepFunction:
      functionName: merge_collections
      parameters:
        collections:
          - $.steps.static_types.output.data
          - $.steps.api_types.output.data

merge_collections

Impact: ⚙️ Runtime Merge multiple data collections into a single array.
stepFunction:
  functionName: merge_collections
  parameters:
    collections:
      - $.steps.static_defaults.output.data
      - $.steps.api_results.output.data
ParameterTypeDescription
collectionsarrayJSONPath references to arrays to merge
Combine static defaults with API-fetched data:
steps:
  default_statuses:
    stepFunction:
      functionName: static_values
      parameters:
        values:
          - id: open
            name: Open
          - id: closed
            name: Closed

  custom_statuses:
    stepFunction:
      functionName: request
      parameters:
        url: /custom-statuses

  all_statuses:
    stepFunction:
      functionName: merge_collections
      parameters:
        collections:
          - $.steps.default_statuses.output.data
          - $.steps.custom_statuses.output.data

result:
  outputDataPath: $.steps.all_statuses.output.data
Collections are merged in order—items from the first collection appear first in the result.

Iterator Steps

Impact: ⚙️ Runtime Iterator steps (foreach) execute step function(s) for each item in an array. Define by adding the iterator property to a step.
- stepId: get_employee_details
  description: Fetch detailed data for each employee
  iterator: $.steps.list_employees.output.data.employees[*].id
  stepFunction:
    functionName: request
    parameters:
      url: /employees/${iterator.item}
      method: get
PropertyTypeDescription
iteratorJSONPathExpression that evaluates to an array
stepFunctionobjectSingle function to execute per item
stepFunctionsarrayMultiple functions to execute per item (mutually exclusive with stepFunction)
Inside an iterator step, these additional variables are available:
VariableDescription
$.iterator.itemCurrent array element
$.iterator.indexCurrent iteration index (0-based)
$.iterator.currentOutput from previous step function in stepFunctions sequence
${iterator.item}String interpolation of current item (for URLs)
Multiple step functions per iteration:
- stepId: enrich_employees
  iterator: $.steps.list.output.data[*].id
  stepFunctions:
    - functionName: request
      parameters:
        url: /employees/${iterator.item}/details
        method: get
    - functionName: request
      parameters:
        url: /employees/${iterator.item}/compensation
        method: get
Each iteration collects the final step function output. All iteration results are combined into $.steps.{stepId}.output.data as an array.

result

Impact: 🤖 MCP | ⚙️ Runtime Define the action’s output. Becomes the tool’s return value.
result:
  data: $.steps.transform_response.output.data
  rawRequest: $.steps.fetch_employees.output.request
  rawResponse: $.steps.fetch_employees.output.response
PropertyDescription
dataPrimary response data (returned to caller)
rawRequestOriginal HTTP request (for debugging)
rawResponseOriginal HTTP response (for debugging)
When an MCP client calls a tool, the result determines the response:
result:
  data: $.steps.map_employees.output.data
The tool returns:
{
  "content": [
    {
      "type": "text",
      "text": "{ \"data\": [...employees...] }"
    }
  ]
}
For list actions with pagination:
{
  "data": [...],
  "next": "cursor_token"
}
rawRequest/rawResponse: Included for debugging when enabled. Helps troubleshoot API issues without diving into logs.

Expression Formats

Three expression formats are available throughout connector YAML. For the complete reference including all built-in functions, see the Expression Language page.

JSONPath ($.path)

Access data from context objects.
value: $.credentials.apiKey
value: $.inputs.employee_id
value: $.steps.fetch_data.output.data.employees
ContextDescription
$.credentialsAuth credentials
$.inputsAction input parameters
$.configConnector configuration
$.steps.{stepId}.outputPrevious step output

String Interpolation (${...})

Embed values in strings.
url: '/employees/${inputs.employee_id}/profile'
baseUrl: 'https://api.${credentials.subdomain}.provider.com'

JEXL ('{{...}}')

Complex expressions with logic.
value: '{{$.inputs.status ?? "active"}}'
value: '{{present(inputs.filter) ? inputs.filter : "all"}}'
condition: '{{$.inputs.page_size > 100}}'
Use JSONPath when:
  • Simple value access
  • No transformation needed
  • Used alone (not in string)
Use String Interpolation when:
  • Building URLs or strings
  • Concatenating with static text
  • Simple variable substitution
Use JEXL when:
  • Default values needed (?? operator)
  • Conditional logic required
  • Complex transformations
  • In condition fields
Examples:
# JSONPath - direct value
token: $.credentials.accessToken

# String interpolation - URL building
url: '/api/v1/users/${inputs.user_id}'

# JEXL - default value
value: '{{$.inputs.limit ?? 25}}'

# JEXL - conditional
value: '{{present(inputs.filter) ? inputs.filter : "*"}}'

GraphQL Actions

For GraphQL APIs, use the request function with POST method and query in body.
steps:
  - stepId: graphql_query
    stepFunction:
      functionName: request
      parameters:
        url: '/graphql'
        method: post
        args:
          - name: query
            value: |
              query ListUsers($first: Int, $after: String) {
                users(first: $first, after: $after) {
                  nodes {
                    id
                    name
                    email
                  }
                  pageInfo {
                    hasNextPage
                    endCursor
                  }
                }
              }
            in: body
          - name: variables
            value:
              first: '{{$.inputs.page_size ?? 50}}'
              after: '{{$.inputs.cursor ?? null}}'
            in: body
Query structure:
  • Define variables for all dynamic values
  • Use fragments for reusable field selections
  • Keep queries focused (request only needed fields)
Pagination: GraphQL often uses cursor-based pagination:
query {
  users(first: 50, after: "cursor") {
    nodes { ... }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
Error handling: GraphQL returns 200 even for errors. Check errors field in response:
- stepId: check_errors
  condition: '{{present(steps.graphql_query.output.data.errors)}}'
  stepFunction:
    # Handle error case
Variables: Pass variables as separate variables object, not inline in query:
args:
  - name: query
    value: 'query GetUser($id: ID!) { user(id: $id) { name } }'
    in: body
  - name: variables
    value:
      id: $.inputs.user_id
    in: body

Complete Action Example

Here’s a full action demonstrating all concepts:
- actionId: list_employees
  categories:
    - hris
    - employees
  actionType: list
  label: List Employees
  description: Retrieve a paginated list of employees from BambooHR
  details: |
    Fetches employee records with optional filtering by status and department.

    Returns:
    - Employee ID and display name
    - Email and phone
    - Department and job title
    - Employment status and dates

    Pagination: Returns 25 records per page by default. Use 'next' cursor for more.
  resources: https://documentation.bamboohr.com/reference/get-employees-directory
  inputs:
    - name: status
      type: enum
      description: Filter by employment status
      required: false
      values:
        - key: active
          label: Active
          description: Currently employed
        - key: inactive
          label: Inactive
          description: Former employees
      default: active
      in: query
    - name: department
      type: string
      description: Filter by department name
      required: false
      in: query
    - name: page_size
      type: number
      description: Number of results per page (max 100)
      required: false
      default: 25
      in: query
    - name: cursor
      type: string
      description: Pagination cursor from previous response
      required: false
      in: query
  steps:
    - stepId: fetch_employees
      description: Fetch employee directory from BambooHR API
      stepFunction:
        functionName: paginated_request
        parameters:
          url: '/employees/directory'
          method: get
          args:
            - name: status
              value: '{{$.inputs.status ?? "active"}}'
              in: query
            - name: department
              value: $.inputs.department
              in: query
              condition: '{{present(inputs.department)}}'
            - name: page_size
              value: '{{$.inputs.page_size ?? 25}}'
              in: query
            - name: cursor
              value: $.inputs.cursor
              in: query
              condition: '{{present(inputs.cursor)}}'
          pagination:
            type: cursor
            request:
              cursor_field: cursor
              cursor_position: query
            response:
              cursor_path: $.paging.next
              data_path: $.employees
    - stepId: transform_employees
      description: Map BambooHR fields to standard format
      stepFunction:
        functionName: map_fields
        version: '2'
        parameters:
          fields:
            - targetFieldKey: id
              expression: $.id
              type: string
            - targetFieldKey: display_name
              expression: $.displayName
              type: string
            - targetFieldKey: email
              expression: $.workEmail
              type: string
            - targetFieldKey: department
              expression: $.department
              type: string
            - targetFieldKey: job_title
              expression: $.jobTitle
              type: string
            - targetFieldKey: status
              expression: $.status
              type: string
            - targetFieldKey: hire_date
              expression: $.hireDate
              type: datetime_string
          dataSource: $.steps.fetch_employees.output.data
  result:
    data: $.steps.transform_employees.output.data
    rawRequest: $.steps.fetch_employees.output.request
    rawResponse: $.steps.fetch_employees.output.response

Next Steps