> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Connector YAML Reference

> Complete reference for all connector YAML properties with examples and impact analysis

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**.

<Note>
  **Impact Legend:**

  * 🖥️ **UI** - Affects StackOne Hub, Dashboard, or Connect flows
  * 🤖 **MCP** - Used in MCP tool declarations (`list tools`)
  * ⚙️ **Runtime** - Affects API request execution
</Note>

***

## 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
```

<Accordion title="Why use partial files?">
  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
</Accordion>

***

## Root Properties

### `StackOne`

**Impact:** ⚙️ Runtime

The schema version for the connector file format.

| Value   | Description                        |
| ------- | ---------------------------------- |
| `1.0.0` | Current and only supported version |

```yaml theme={null}
StackOne: 1.0.0
```

<Accordion title="How version affects processing">
  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.
</Accordion>

***

## `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.

```yaml theme={null}
info:
  title: BambooHR
```

<Accordion title="UI display locations">
  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").
</Accordion>

### `info.key`

**Impact:** 🖥️ UI | 🤖 MCP | ⚙️ Runtime

Unique identifier for the connector. Used in API calls, MCP tool names, and internal routing.

```yaml theme={null}
info:
  key: bamboohr
```

| Constraint | Description                                          |
| ---------- | ---------------------------------------------------- |
| Format     | Lowercase alphanumeric with underscores              |
| Uniqueness | Must be unique across all connectors                 |
| Stability  | Cannot change once deployed (breaks linked accounts) |

<Accordion title="How key is used throughout the system">
  The key is the primary identifier:

  **MCP:** Prefixes all action IDs → `bamboohr_list_employees`

  **API:** Used in account connections:

  ```bash theme={null}
  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.
</Accordion>

<Accordion title="Forking connectors and inheriting updates">
  When forking an existing StackOne connector, you can customize the connector name (`info.title`). However, if you want to inherit future StackOne updates to that connector, the `info.key` field must match the StackOne connector key.

  **Example:**
  If you fork StackOne's BambooHR connector:

  * ✅ You can change `info.title` to "Custom BambooHR"
  * ⚠️ Keep `info.key: bamboohr` to receive future updates from StackOne
  * ❌ Changing `info.key` to `custom_bamboohr` prevents automatic update inheritance

  **Recommendation:** When forking existing StackOne connectors, keep `info.key` identical to the original StackOne connector key if you want to inherit future improvements and bug fixes.
</Accordion>

### `info.version`

**Impact:** 🖥️ UI | ⚙️ Runtime

Connector version following semver format.

```yaml theme={null}
info:
  version: 1.0.0
```

<Accordion title="Version management best practices">
  Use semantic versioning:

  * **Major (1.x.x):** Breaking changes — auth changes, removed/renamed actions, changed response or request shape, or behavioural changes consumers may have relied on
  * **Minor (x.1.x):** New actions, new optional parameters, or new optional response fields
  * **Patch (x.x.1):** Bug fixes, description updates, internal refactors

  The version is displayed in the Dashboard and helps track deployed changes. Auth configs resolve to a connector version when linked accounts make requests — see [Connector Versioning](/guides/connector-versioning) for selection rules, pinning, and immutable versioning.
</Accordion>

### `info.assets.icon`

**Impact:** 🖥️ UI

URL to the provider's logo image. Displayed throughout the UI.

```yaml theme={null}
info:
  assets:
    icon: https://stackone-logos.com/api/bamboohr/filled/png
```

<Accordion title="Logo requirements and hosting">
  **Recommended:** Use StackOne's logo service at `https://stackone-logos.com/api/{provider}/filled/png`

  **Requirements:**

  * 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.
</Accordion>

### `info.description`

**Impact:** 🖥️ UI | 🤖 MCP

Brief description of the connector's purpose.

```yaml theme={null}
info:
  description: BambooHR connector for HRIS actions including employee data, time-off, and reporting
```

<Accordion title="Where description appears">
  **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.)
</Accordion>

***

## `baseUrl`

**Impact:** ⚙️ Runtime

The root URL for all API requests. Supports static URLs and dynamic interpolation.

### Static URL

```yaml theme={null}
baseUrl: 'https://api.bamboohr.com/api/gateway.php'
```

### Dynamic URL with credentials

```yaml theme={null}
baseUrl: 'https://api.bamboohr.com/api/gateway.php/${credentials.subdomain}/v1'
```

### Dynamic URL with config

```yaml theme={null}
baseUrl: 'https://${config.region}.api.provider.com/v2'
```

<Accordion title="URL interpolation and runtime resolution">
  Dynamic URLs use `${...}` string interpolation:

  **Available contexts:**

  * `${credentials.*}` - Values from setupFields/configFields
  * `${config.*}` - Configuration values
  * `${env.*}` - Environment variables (limited)

  **Example from BambooHR:**

  ```yaml theme={null}
  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.
</Accordion>

***

## `rateLimit`

**Impact:** ⚙️ Runtime

Configure rate limiting to respect provider API limits.

```yaml theme={null}
rateLimit:
  mainRatelimit: 10
```

| Property        | Type   | Description             |
| --------------- | ------ | ----------------------- |
| `mainRatelimit` | number | Max requests per second |

<Accordion title="Rate limiting behavior">
  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.
</Accordion>

***

## `resources`

**Impact:** 🖥️ UI

URL to the provider's API documentation. Displayed as a help link in the UI.

```yaml theme={null}
resources: https://documentation.bamboohr.com/reference
```

<Accordion title="Resource link usage">
  The link appears in:

  * Connector configuration screens
  * Troubleshooting guides
  * Developer documentation references

  Use the most relevant documentation URL (API reference preferred over marketing pages).
</Accordion>

***

## `documentation`

**Impact:** 🖥️ UI · 🤖 Agent context

Structured external references for the connector. Each entry has a required `title` and `url`, and an optional `description`. Rendered as a card grid on the connector's docs page and surfaced in the `/actions` API response.

```yaml theme={null}
documentation:
  references:
    - title: Microsoft Graph API
      url: https://learn.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0
      description: Full REST API reference for Microsoft Graph.
    - title: Build a bot for Microsoft Teams
      url: https://learn.microsoft.com/en-us/microsoftteams/platform/bots/what-are-bots
      description: Guide for building bots to leverage the messaging API for agentic use cases.
```

<Accordion title="documentation.references fields">
  | Field         | Required | Description                                  |
  | ------------- | -------- | -------------------------------------------- |
  | `title`       | Yes      | Display name for the link                    |
  | `url`         | Yes      | Full URL to the external resource            |
  | `description` | No       | One-sentence summary shown beneath the title |

  Use `documentation.references` for links that benefit both developers reading the docs and agents consuming the `/actions` API. The `resources` field (plain string URL) remains valid for a single legacy link; use `documentation.references` for structured, multi-link documentation.
</Accordion>

***

## `authentication` Section

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

### Authentication Array Structure

```yaml theme={null}
authentication:
  - oauth2:
      type: oauth2
      label: OAuth 2.0
      # ... oauth config
  - api_key:
      type: custom
      label: API Key
      # ... api key config
```

<Accordion title="Multiple auth methods">
  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).
</Accordion>

<Tabs>
  <Tab title="OAuth 2.0">
    ## OAuth 2.0 Authentication

    **Impact:** 🖥️ UI | ⚙️ Runtime

    ```yaml theme={null}
    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`

    | Value    | Description                             |
    | -------- | --------------------------------------- |
    | `oauth2` | OAuth 2.0 authorization code flow       |
    | `custom` | API Key, Basic Auth, or other non-OAuth |

    #### `label`

    **Impact:** 🖥️ UI

    Display name for this auth method in the Hub.

    ```yaml theme={null}
    label: OAuth 2.0
    ```

    #### `support`

    **Impact:** 🖥️ UI

    Help text and links shown during connection flow.

    ```yaml theme={null}
    support:
      description: Login to your BambooHR account to connect your data.
      link: https://documentation.bamboohr.com/docs/getting-started
    ```

    <Accordion title="Support section display">
      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.
    </Accordion>

    #### `authorization`

    **Impact:** ⚙️ Runtime

    OAuth flow configuration.

    ```yaml theme={null}
    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
    ```

    | Property              | Type     | Description                                  |
    | --------------------- | -------- | -------------------------------------------- |
    | `authorizationUrl`    | string   | Provider's OAuth authorize endpoint          |
    | `authorizationParams` | object   | Query params for authorization request       |
    | `tokenUrl`            | string   | Provider's token exchange endpoint           |
    | `token`               | JSONPath | Path to access token in credentials          |
    | `includeBearer`       | boolean  | Add "Bearer " prefix to Authorization header |
    | `pkce`                | boolean  | Enable PKCE (Proof Key for Code Exchange)    |

    <Accordion title="OAuth flow internals">
      **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.
    </Accordion>

    #### `setupFields`

    **Impact:** 🖥️ UI | ⚙️ Runtime

    Fields collected when configuring the connector (T1 - your app's credentials).

    ```yaml theme={null}
    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
    ```

    | Property      | Type    | Description                                                  |
    | ------------- | ------- | ------------------------------------------------------------ |
    | `key`         | string  | Credential storage key (referenced as `$.credentials.{key}`) |
    | `label`       | string  | 🖥️ Field label in UI                                        |
    | `type`        | enum    | `text`, `password`, `url`                                    |
    | `required`    | boolean | Field must have a value                                      |
    | `secret`      | boolean | Encrypt at rest, mask in UI                                  |
    | `placeholder` | string  | 🖥️ Example value shown in empty field                       |
    | `description` | string  | 🖥️ Help text below field                                    |
    | `tooltip`     | string  | 🖥️ Hover/info icon text                                     |

    <Accordion title="setupFields vs configFields">
      **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
    </Accordion>

    #### `configFields`

    **Impact:** 🖥️ UI | ⚙️ Runtime

    Fields collected from end-users during connection (T2 - their credentials).

    ```yaml theme={null}
    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:** ⚙️ Runtime

    Embedded action for refreshing expired OAuth tokens.

    ```yaml theme={null}
    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
    ```

    <Accordion title="Token refresh mechanics">
      **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.
    </Accordion>

    #### `environments`

    **Impact:** 🖥️ UI

    Available deployment environments for this auth method.

    ```yaml theme={null}
    environments:
      - key: production
        name: Production
      - key: sandbox
        name: Sandbox
    ```

    <Accordion title="Multi-environment support">
      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`.
    </Accordion>

    #### `testActions`

    **Impact:** 🖥️ UI | ⚙️ Runtime

    Actions executed to validate a connection after OAuth completes.

    ```yaml theme={null}
    testActions:
      - action: list_employees
        required: true
    ```

    | Property   | Type    | Description                      |
    | ---------- | ------- | -------------------------------- |
    | `action`   | string  | Action ID to execute             |
    | `required` | boolean | Connection fails if action fails |

    <Accordion title="Connection validation flow">
      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
    </Accordion>
  </Tab>

  <Tab title="Custom Auth (API Key, Basic)">
    ## Custom Authentication

    **Impact:** 🖥️ UI | ⚙️ Runtime

    ```yaml theme={null}
    authentication:
      - api_key:
          type: custom
          label: API Key
          support:
            description: Enter your BambooHR API key to connect.
          authorization:
            type: bearer
            token: $.credentials.apiKey
          configFields:
            - key: subdomain
              label: Subdomain
              type: text
              required: true
            - key: apiKey
              label: API Key
              type: password
              required: true
              secret: true
          environments:
            - key: production
              name: Production
          testActions:
            - action: list_employees
              required: true
    ```

    #### Authorization Types for Custom Auth

    ```yaml theme={null}
    # Bearer token
    authorization:
      type: bearer
      token: $.credentials.apiKey

    # Basic auth
    authorization:
      type: basic
      username: $.credentials.username
      password: $.credentials.password

    # Custom header
    authorization:
      type: bearer
      token: $.credentials.apiKey
      includeBearer: false  # Sends raw token without "Bearer " prefix
      customHeaders:
        X-Api-Key: $.credentials.apiKey
    ```

    | Type                              | Header Format                                        |
    | --------------------------------- | ---------------------------------------------------- |
    | `bearer`                          | `Authorization: Bearer {token}`                      |
    | `bearer` + `includeBearer: false` | `Authorization: {token}`                             |
    | `basic`                           | `Authorization: Basic base64({username}:{password})` |
    | `none`                            | No Authorization header                              |

    <Accordion title="Custom header injection">
      Use `customHeaders` for providers with non-standard auth:

      ```yaml theme={null}
      authorization:
        type: bearer
        token: $.credentials.accessToken
        customHeaders:
          Authorization: 'Bearer {{$.credentials.accessToken}}'
          X-Custom-Auth: '{{$.credentials.apiKey}}'
      ```

      Headers support both JSONPath (`$.credentials.*`) and JEXL (`'{{...}}'`) expressions.
    </Accordion>
  </Tab>
</Tabs>

***

## `actions` Section

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

### Action References

```yaml theme={null}
actions:
  $ref: bamboohr.employees
  $ref: bamboohr.timeoff
  $ref: bamboohr.reports
```

<Accordion title="How $ref resolution works">
  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:**

  ```yaml theme={null}
  # bamboohr.employees.s1.partial.yaml
  - actionId: list_employees
    # ... action config
  - actionId: get_employee
    # ... action config
  ```

  Note: Partials start with `-` (array items), not `actions:`.
</Accordion>

***

## 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.

```yaml theme={null}
actionId: list_employees
```

**MCP tool name:** `bamboohr_list_employees`

| Constraint | Description                                          |
| ---------- | ---------------------------------------------------- |
| Format     | snake\_case, lowercase                               |
| Uniqueness | Must be unique within connector                      |
| Convention | `{verb}_{resource}` or `{verb}_{resource}_{variant}` |

<Accordion title="Action ID naming conventions">
  **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`.
</Accordion>

### `categories`

**Impact:** 🖥️ UI

Categories for filtering in the UI. Does not affect MCP.

```yaml theme={null}
categories:
  - hris
  - employees
```

| Category      | Description                              |
| ------------- | ---------------------------------------- |
| `hris`        | HR Information Systems                   |
| `ats`         | Applicant Tracking Systems               |
| `crm`         | Customer Relationship Management         |
| `lms`         | Learning Management Systems              |
| `marketing`   | Marketing automation                     |
| `filestorage` | File storage and documents               |
| `ticketing`   | Support ticketing                        |
| `messaging`   | Chat and messaging                       |
| `internal`    | Hidden from UI (used for refresh tokens) |

<Accordion title="Category filtering behavior">
  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
</Accordion>

### `actionType`

**Impact:** 🤖 MCP | ⚙️ Runtime

Determines the action's behavior pattern and response schema.

```yaml theme={null}
actionType: list
```

| Type            | Description              | Response Schema              |
| --------------- | ------------------------ | ---------------------------- |
| `custom`        | Provider-specific action | Raw provider response        |
| `list`          | Paginated list (unified) | `{ data: [], next: string }` |
| `get`           | Single record (unified)  | `{ data: object }`           |
| `create`        | Create record (unified)  | `{ data: object }`           |
| `update`        | Update record (unified)  | `{ data: object }`           |
| `delete`        | Delete record (unified)  | `{ success: boolean }`       |
| `refresh_token` | Token refresh (internal) | Credential object            |

<Accordion title="Unified vs Custom action types">
  **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:

  ```json theme={null}
  {
    "data": [...],
    "next": "cursor_token_or_null"
  }
  ```

  While a `custom` action returns whatever the provider returns.
</Accordion>

### `label`

**Impact:** 🖥️ UI

Human-readable name displayed in the UI.

```yaml theme={null}
label: List Employees
```

<Accordion title="Label display locations">
  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)
</Accordion>

### `description`

**Impact:** 🖥️ UI | 🤖 MCP

Short description of what the action does. Used in both UI and MCP tool descriptions.

```yaml theme={null}
description: List all employees from BambooHR with optional filtering
```

<Accordion title="Description in MCP tool declarations">
  When MCP clients call `list tools`, the description becomes the tool's description:

  ```json theme={null}
  {
    "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
</Accordion>

### `details`

**Impact:** 🤖 MCP

Extended description with full context. Used as the complete tool description in MCP.

```yaml theme={null}
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.
```

<Accordion title="Details vs Description in MCP">
  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
</Accordion>

### `resources`

**Impact:** 🖥️ UI

Link to provider documentation for this specific action.

```yaml theme={null}
resources: https://documentation.bamboohr.com/reference/get-employees-directory
```

<Accordion title="Action-level resources">
  Override or supplement the connector-level resources link with action-specific documentation. Displayed in the Actions Explorer and helps developers understand the underlying API.
</Accordion>

***

## `inputs`

**Impact:** 🤖 MCP | ⚙️ Runtime

Define parameters the action accepts. Become MCP tool input schema.

```yaml theme={null}
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

| Property      | Type    | Description                                                |
| ------------- | ------- | ---------------------------------------------------------- |
| `name`        | string  | Parameter name (used in expressions as `$.inputs.{name}`)  |
| `type`        | enum    | Data type (see below)                                      |
| `description` | string  | 🤖 MCP - Shown in tool schema                              |
| `required`    | boolean | Whether parameter is mandatory                             |
| `default`     | any     | Default value if not provided                              |
| `in`          | enum    | Where to send: `path`, `query`, `body`, `header`           |
| `array`       | boolean | Parameter accepts array of values                          |
| `properties`  | array   | Nested properties for `type: object` (see accordion below) |

### Input Types

| Type              | Description        | Example                  |
| ----------------- | ------------------ | ------------------------ |
| `string`          | Text value         | `"john@example.com"`     |
| `number`          | Numeric value      | `42`, `3.14`             |
| `boolean`         | True/false         | `true`, `false`          |
| `datetime_string` | ISO 8601 date/time | `"2024-01-15T10:30:00Z"` |
| `object`          | JSON object        | `{ "key": "value" }`     |
| `enum`            | Predefined options | See below                |

### Enum Type

```yaml theme={null}
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
```

<Accordion title="MCP input schema generation">
  Inputs are converted to JSON Schema for MCP:

  ```yaml theme={null}
  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:

  ```json theme={null}
  {
    "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
</Accordion>

<Accordion title="Array inputs">
  Use `array: true` for parameters accepting multiple values:

  ```yaml theme={null}
  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" }`
</Accordion>

<Accordion title="Object type with nested properties">
  For complex inputs, use `type: object` with a nested `properties` array to define the object's structure:

  ```yaml theme={null}
  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:

  ```json theme={null}
  {
    "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" }
          }
        }
      }
    }
  }
  ```
</Accordion>

***

## `steps`

**Impact:** ⚙️ Runtime

Define the execution flow for the action. Steps run sequentially.

```yaml theme={null}
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

| Property        | Type     | Description                                                                         |
| --------------- | -------- | ----------------------------------------------------------------------------------- |
| `stepId`        | string   | Unique identifier within action                                                     |
| `description`   | string   | What this step does (for debugging)                                                 |
| `condition`     | JEXL     | Skip step if condition is false                                                     |
| `ignoreError`   | boolean  | Continue execution if this step fails (default: `false`)                            |
| `stepFunction`  | object   | Function to execute (for simple steps)                                              |
| `iterator`      | JSONPath | Array expression — makes this a foreach step. See [Iterator Steps](#iterator-steps) |
| `stepFunctions` | array    | Multiple functions to execute per iteration (use with `iterator`)                   |

### Conditional Steps

```yaml theme={null}
steps:
  - stepId: fetch_with_filter
    condition: '{{present(inputs.department)}}'
    stepFunction:
      functionName: request
      parameters:
        url: '/employees'
        args:
          - name: department
            value: $.inputs.department
            in: query
```

<Accordion title="Condition expressions">
  Conditions use JEXL syntax with helper functions:

  | Function     | Description                        | Example                  |
  | ------------ | ---------------------------------- | ------------------------ |
  | `present(x)` | Value exists and not null          | `present(inputs.filter)` |
  | `blank(x)`   | Value is null, undefined, or empty | `blank(inputs.cursor)`   |

  **Common patterns:**

  ```yaml theme={null}
  # 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)}}'
  ```
</Accordion>

***

## Step Functions

### `request`

**Impact:** ⚙️ Runtime

Make an HTTP request to the provider API.

```yaml theme={null}
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
```

| Parameter             | Type    | Description                                    |
| --------------------- | ------- | ---------------------------------------------- |
| `baseUrl`             | string  | Override connector baseUrl                     |
| `url`                 | string  | Endpoint path (appended to baseUrl)            |
| `method`              | enum    | `get`, `post`, `put`, `patch`, `delete`        |
| `authorization`       | object  | Override connector auth                        |
| `args`                | array   | Request parameters                             |
| `response.collection` | boolean | Whether response is an array (default: `true`) |
| `response.dataKey`    | string  | Extract data from this key in response         |
| `response.indexField` | string  | Field to use as index for keyed results        |
| `customErrors`        | array   | Remap provider error responses (see below)     |

#### Args Location (`in`)

| Value     | Description                           |
| --------- | ------------------------------------- |
| `path`    | URL path parameter: `/employees/{id}` |
| `query`   | Query string: `?field=value`          |
| `body`    | Request body (JSON)                   |
| `headers` | HTTP header                           |

<Accordion title="Request construction internals">
  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:**

  ```yaml theme={null}
  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/456`

  **Body construction:**

  ```yaml theme={null}
  args:
    - name: firstName
      value: $.inputs.first_name
      in: body
    - name: lastName
      value: $.inputs.last_name
      in: body
  ```

  Sends: `{ "firstName": "John", "lastName": "Doe" }`
</Accordion>

<Accordion title="Custom error handling">
  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

  ```yaml theme={null}
  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:**

  | Property         | Type   | Description                                  |
  | ---------------- | ------ | -------------------------------------------- |
  | `receivedStatus` | number | HTTP status to match from provider           |
  | `targetStatus`   | number | HTTP status to return instead                |
  | `condition`      | string | JEXL expression (only trigger error if true) |
  | `message`        | string | Custom error message                         |

  The `condition` property is useful for GraphQL APIs where errors are returned in the response body with a 200 status code.
</Accordion>

### `paginated_request`

**Impact:** ⚙️ Runtime

Automatically handle pagination for list endpoints.

```yaml theme={null}
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

```yaml theme={null}
# 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
```

<Accordion title="Pagination handling mechanics">
  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:**

  ```json theme={null}
  {
    "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.
</Accordion>

### `map_fields`

**Impact:** ⚙️ Runtime

Transform data between formats.

```yaml theme={null}
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
```

| Parameter                 | Type     | Description              |
| ------------------------- | -------- | ------------------------ |
| `fields`                  | array    | Mapping definitions      |
| `dataSource`              | JSONPath | Source data to transform |
| `fields[].targetFieldKey` | string   | Output field name        |
| `fields[].expression`     | JEXL     | Value expression         |
| `fields[].type`           | enum     | Output type              |

<Accordion title="Field mapping expressions">
  Expressions support JEXL syntax with the source object as context:

  **Simple path:**

  ```yaml theme={null}
  expression: $.employeeId
  ```

  **Concatenation:**

  ```yaml theme={null}
  expression: $.firstName + ' ' + $.lastName
  ```

  **Conditional:**

  ```yaml theme={null}
  expression: $.status == 'Active' ? 'employed' : 'terminated'
  ```

  **Null coalescing:**

  ```yaml theme={null}
  expression: $.middleName ?? ''
  ```

  **Nested access:**

  ```yaml theme={null}
  expression: $.department.name
  ```

  **Array operations:**

  ```yaml theme={null}
  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
</Accordion>

### `group_data`

**Impact:** ⚙️ Runtime

Group array data by a field.

```yaml theme={null}
stepFunction:
  functionName: group_data
  parameters:
    groupByField: department_id
    dataSource: $.steps.fetch_employees.output.data
```

<Accordion title="Group data usage">
  Transforms flat array into grouped structure:

  **Input:**

  ```json theme={null}
  [
    { "name": "John", "department_id": "eng" },
    { "name": "Jane", "department_id": "eng" },
    { "name": "Bob", "department_id": "sales" }
  ]
  ```

  **Output:**

  ```json theme={null}
  {
    "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.
</Accordion>

### `typecast`

**Impact:** ⚙️ Runtime

Convert data types.

```yaml theme={null}
stepFunction:
  functionName: typecast
  parameters:
    type: number
    dataSource: $.steps.previous.output.data.count
```

| Type      | Description                 |
| --------- | --------------------------- |
| `string`  | Convert to string           |
| `number`  | Parse as number             |
| `boolean` | Convert to boolean          |
| `json`    | Parse JSON string to object |

### `soap_request`

**Impact:** ⚙️ Runtime

Make SOAP API requests. Used primarily for enterprise providers like Workday.

```yaml theme={null}
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}}'
```

| Parameter        | Type    | Description                         |
| ---------------- | ------- | ----------------------------------- |
| `baseUrl`        | string  | SOAP service base URL               |
| `url`            | string  | SOAP endpoint path                  |
| `method`         | enum    | HTTP method (typically `post`)      |
| `authorization`  | object  | Auth config (same as `request`)     |
| `soapOperation`  | string  | SOAP operation name                 |
| `useSoapContext` | boolean | Use SOAP context (default: `false`) |
| `namespaces`     | array   | XML namespace definitions           |
| `args`           | array   | Request parameters                  |

<Accordion title="SOAP namespace configuration">
  Namespaces map XML prefixes to URIs:

  ```yaml theme={null}
  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:

  ```yaml theme={null}
  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.
</Accordion>

### `static_values`

**Impact:** ⚙️ Runtime

Return predefined static values without making an API request. Useful for enum lookups or constant data.

```yaml theme={null}
stepFunction:
  functionName: static_values
  parameters:
    values:
      - id: question
        name: Question
      - id: incident
        name: Incident
      - id: problem
        name: Problem
      - id: task
        name: Task
```

| Parameter | Type  | Description                       |
| --------- | ----- | --------------------------------- |
| `values`  | array | Array of static objects to return |

<Accordion title="Static values use cases">
  Use `static_values` when:

  1. **Provider doesn't have an API endpoint** for enum values:

  ```yaml theme={null}
  # Ticket types are hardcoded in Zendesk
  stepFunction:
    functionName: static_values
    parameters:
      values:
        - id: question
          name: Question
        - id: incident
          name: Incident
  ```

  2. **Combining with dynamic data** via multi-step actions:

  ```yaml theme={null}
  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
  ```
</Accordion>

### `merge_collections`

**Impact:** ⚙️ Runtime

Merge multiple data collections into a single array.

```yaml theme={null}
stepFunction:
  functionName: merge_collections
  parameters:
    collections:
      - $.steps.static_defaults.output.data
      - $.steps.api_results.output.data
```

| Parameter     | Type  | Description                            |
| ------------- | ----- | -------------------------------------- |
| `collections` | array | JSONPath references to arrays to merge |

<Accordion title="Merge collections example">
  Combine static defaults with API-fetched data:

  ```yaml theme={null}
  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.
</Accordion>

### 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.

```yaml theme={null}
- 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
```

| Property        | Type     | Description                                                                     |
| --------------- | -------- | ------------------------------------------------------------------------------- |
| `iterator`      | JSONPath | Expression that evaluates to an array                                           |
| `stepFunction`  | object   | Single function to execute per item                                             |
| `stepFunctions` | array    | Multiple functions to execute per item (mutually exclusive with `stepFunction`) |

<Accordion title="Iterator context variables">
  Inside an iterator step, these additional variables are available:

  | Variable             | Description                                                    |
  | -------------------- | -------------------------------------------------------------- |
  | `$.iterator.item`    | Current array element                                          |
  | `$.iterator.index`   | Current iteration index (0-based)                              |
  | `$.iterator.current` | Output from previous step function in `stepFunctions` sequence |
  | `${iterator.item}`   | String interpolation of current item (for URLs)                |

  **Multiple step functions per iteration:**

  ```yaml theme={null}
  - 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.
</Accordion>

***

## `result`

**Impact:** 🤖 MCP | ⚙️ Runtime

Define the action's output. Becomes the tool's return value.

```yaml theme={null}
result:
  data: $.steps.transform_response.output.data
  rawRequest: $.steps.fetch_employees.output.request
  rawResponse: $.steps.fetch_employees.output.response
```

| Property      | Description                                |
| ------------- | ------------------------------------------ |
| `data`        | Primary response data (returned to caller) |
| `rawRequest`  | Original HTTP request (for debugging)      |
| `rawResponse` | Original HTTP response (for debugging)     |

<Accordion title="Result in MCP responses">
  When an MCP client calls a tool, the result determines the response:

  ```yaml theme={null}
  result:
    data: $.steps.map_employees.output.data
  ```

  The tool returns:

  ```json theme={null}
  {
    "content": [
      {
        "type": "text",
        "text": "{ \"data\": [...employees...] }"
      }
    ]
  }
  ```

  For list actions with pagination:

  ```json theme={null}
  {
    "data": [...],
    "next": "cursor_token"
  }
  ```

  **rawRequest/rawResponse:** Included for debugging when enabled. Helps troubleshoot API issues without diving into logs.
</Accordion>

***

## Expression Formats

Three expression formats are available throughout connector YAML. For the complete reference including all built-in functions, see the [Expression Language](/guides/connector-engine/expression-language) page.

### JSONPath (`$.path`)

Access data from context objects.

```yaml theme={null}
value: $.credentials.apiKey
value: $.inputs.employee_id
value: $.steps.fetch_data.output.data.employees
```

| Context                   | Description             |
| ------------------------- | ----------------------- |
| `$.credentials`           | Auth credentials        |
| `$.inputs`                | Action input parameters |
| `$.config`                | Connector configuration |
| `$.steps.{stepId}.output` | Previous step output    |

### String Interpolation (`${...}`)

Embed values in strings.

```yaml theme={null}
url: '/employees/${inputs.employee_id}/profile'
baseUrl: 'https://api.${credentials.subdomain}.provider.com'
```

### JEXL (`'{{...}}'`)

Complex expressions with logic.

```yaml theme={null}
value: '{{$.inputs.status ?? "active"}}'
value: '{{present(inputs.filter) ? inputs.filter : "all"}}'
condition: '{{$.inputs.page_size > 100}}'
```

<Accordion title="Expression format selection guide">
  **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:**

  ```yaml theme={null}
  # 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 : "*"}}'
  ```
</Accordion>

***

## GraphQL Actions

For GraphQL APIs, use the `request` function with POST method and query in body.

```yaml theme={null}
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
```

<Accordion title="GraphQL best practices">
  **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:

  ```graphql theme={null}
  query {
    users(first: 50, after: "cursor") {
      nodes { ... }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
  ```

  **Error handling:**
  GraphQL returns 200 even for errors. Check `errors` field in response:

  ```yaml theme={null}
  - 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:

  ```yaml theme={null}
  args:
    - name: query
      value: 'query GetUser($id: ID!) { user(id: $id) { name } }'
      in: body
    - name: variables
      value:
        id: $.inputs.user_id
      in: body
  ```
</Accordion>

***

## Complete Action Example

Here's a full action demonstrating all concepts:

```yaml theme={null}
- 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

<CardGroup cols={2}>
  <Card title="Expression Language" icon="function" href="/guides/connector-engine/expression-language">
    Full reference for JSONPath, JEXL, and built-in functions
  </Card>

  <Card title="CLI Reference" icon="terminal" href="/guides/connector-engine/cli-reference">
    Validate and deploy connectors
  </Card>

  <Card title="AI Builder" icon="robot" href="/guides/connector-engine/ai-builder">
    AI-assisted connector development
  </Card>

  <Card title="Connector Examples" icon="code" href="https://github.com/StackOneHQ/connectors-template">
    Browse real connector implementations
  </Card>
</CardGroup>
