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

# YAML Schema

> 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 — authentication changes, removed/renamed actions, changed response or request shape, or behavioral 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](/connector-building/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>

***

## `scopeDefinitions`

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

Declares the requirements an action can depend on, keyed by scope name. Commonly OAuth or API scopes, but the same mechanism represents any prerequisite: a pricing tier, a platform feature, a configuration flag.

```yaml theme={null}
scopeDefinitions:
  employees:read:
    description: Read employee data
  employees:write:
    description: Create and update employee records
    includes: employees:read
```

| Field         | Required | Description                    |
| ------------- | -------- | ------------------------------ |
| `description` | Yes      | What the scope grants          |
| `includes`    | No       | Another scope this one implies |

Actions and inputs reference these names through `requiredScopes`, and guide sections through `applicableScopes`. Both are validated against the keys defined here, so a reference to an undeclared scope fails validation.

<Accordion title="Hierarchical scopes and naming">
  Scope names are arbitrary strings, so use whatever the provider uses. Google's are full URLs:

  ```yaml theme={null}
  scopeDefinitions:
    https://www.googleapis.com/auth/drive:
      description: View and manage all Drive files
      includes: https://www.googleapis.com/auth/drive.readonly
    https://www.googleapis.com/auth/drive.readonly:
      description: View all Drive files (read-only)
  ```

  `includes` should only reflect relationships the provider actually documents. A write scope typically includes its read counterpart, and a broader scope includes a narrower one, but a read-only scope must never include a write scope.
</Accordion>

<Tip>
  Scopes aren't limited to API permissions. Use them for pricing tiers (`plan:enterprise`), platform features (`feature:advanced_reporting`), or configuration prerequisites (`config:sso_enabled`).
</Tip>

***

## `authentication` Section

Defines how end-users authenticate with the provider. Supports multiple authentication 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 authentication 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 authentication 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 authentication 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 (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 connector profile
      * 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 (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 authentication 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 authentication:

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

***

## `support` and Guides

**Impact:** 🖥️ UI

Each authentication method can carry its own instructions as structured data, under `support.guides`. For StackOne authored connectors, these are published as the guide pages under [Connectors](/connectors/introduction). You can also fetch the same content from the API and render it in your own product, which is covered in [Rendering Guides](/connector-building/rendering-guides).

```yaml theme={null}
authentication:
  - custom:
      type: custom
      label: API Key
      support:
        description: Admin privileges required
        guides:
          config:
            warning: Admin access required to generate an API key.
            sections:
              - title: Generating an API Key
                content: |
                  Instructions for finding the API key.
                steps:
                  - title: Navigate to Settings
                    content: |
                      Go to Settings > API in your account.
                  - title: Create Key
                    content: |
                      Click Generate API Key and copy it.
      authorization:
        type: bearer
        token: $.credentials.apiKey
      configFields:
        - key: apiKey
          label: API Key
          type: password
          required: true
          secret: true
```

### Which guide to write

| Guide           | Pairs with     | Explains                                          | Audience                                 |
| --------------- | -------------- | ------------------------------------------------- | ---------------------------------------- |
| `guides.config` | `configFields` | How to obtain the credentials the fields ask for  | End-users linking an account             |
| `guides.setup`  | `setupFields`  | How to register an app and get client credentials | Whoever configures the connector profile |

Only include the guide whose fields exist. A `guides.config` with no `configFields` documents inputs nobody is asked for.

<Note>
  Events have their own equivalent at `events.guides.setup`, for instructions on configuring a webhook in the provider.
</Note>

### Section and step fields

Both guides take a `warning` and a required array of sections. Sections and steps share the same shape, and steps nest one level inside a section.

```yaml theme={null}
sections:
  - title: Section Title
    content: |
      Section overview text.
    list:
      - Bullet one
      - Bullet two
    image:
      src: https://example.com/screenshot.png
      alt: The API settings page
    applicableScopes: employees:read
    steps:
      - title: Step Title
        content: |
          Step instructions.
        displayScopes: true
```

| Field              | Required | Applies to    | Description                                                                                        |
| ------------------ | -------- | ------------- | -------------------------------------------------------------------------------------------------- |
| `title`            | Yes      | Section, step | Heading                                                                                            |
| `content`          | Yes      | Section, step | Body text                                                                                          |
| `list`             | No       | Section, step | Bullet points                                                                                      |
| `image`            | No       | Section, step | `src` and `alt`, both required when present                                                        |
| `steps`            | No       | Section       | Nested steps                                                                                       |
| `applicableScopes` | No       | Section, step | Space-separated scope names. Renderers show the section or step only when one of these is selected |
| `displayScopes`    | No       | Step          | Shows which actions the step's scopes enable                                                       |
| `warning`          | No       | Guide         | Callout shown above the sections                                                                   |

<Warning>
  `content` is required on every section and step. A section with only a `title` and `list` fails validation.
</Warning>

<Accordion title="Scope-aware guide content">
  `applicableScopes` filters instructions down to the configuration the user actually chose, so a read-only integration doesn't get told to grant write permissions:

  ```yaml theme={null}
  guides:
    config:
      sections:
        - title: Read Permissions
          content: |
            Grant read access so the integration can list employees.
          applicableScopes: employees:read
        - title: Write Permissions
          content: |
            Grant write access so the integration can create employees.
          applicableScopes: employees:write
  ```

  Every name used here must exist in [`scopeDefinitions`](#scopedefinitions), and validation fails if it doesn't.

  `displayScopes: true` on a step renders badges for the actions those scopes unlock, which is useful on the step where a user picks scopes in the provider's consent screen:

  ```yaml theme={null}
  steps:
    - title: Select API Scopes
      content: |
        Choose the scopes you need based on your use case.
      displayScopes: true
      applicableScopes: employees:read employees:write departments:read
  ```
</Accordion>

<Accordion title="support.link and where the Hub sends users">
  `support.link` points at your own rendered version of these guides. The Hub sends users there instead of to StackOne's pages.

  ```yaml theme={null}
  support:
    link: https://docs.yourcompany.com/integrations/provider-setup
    description: Admin privileges required
  ```

  Leave it out and, as long as `guides.config` has sections, StackOne fills it in with that connector's own generated guide page, built from the connector key and the authentication label:

  ```
  https://docs.stackone.com/connectors/[key]/guides/link-account/[authentication-label]
  ```

  So the fallback is the guide you wrote, not generic documentation. Set `link` when you want users in your product rather than in StackOne's docs.
</Accordion>

<Tip>
  `content` and `list` are plain strings, and the API returns them with markdown syntax intact. Anything rendering them needs a markdown parser, plus sanitizing. See [Rendering Guides](/connector-building/rendering-guides).
</Tip>

***

## `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)                                                                                                                                   |
| `variants`    | array   | Multiple accepted shapes for multi-type fields (see accordion below). When present, omit top-level `type`, `array`, `properties`, `values`, and `default` (define these within each variant) |

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

<Accordion title="Variant inputs">
  When a field accepts multiple distinct shapes (e.g., a string OR an array of objects), use `variants` instead of a single `type`. Each variant is a complete shape specification.

  ```yaml theme={null}
  inputs:
    - name: value
      description: The value for this operation
      required: false
      in: body
      variants:
        - type: string
          description: A string value (e.g. name, status)
        - type: boolean
          description: A boolean value
        - type: object
          array: true
          description: Role references by ID
          properties:
            - name: id
              type: string
              required: true
              description: The unique identifier
        - type: object
          array: true
          description: Group references with provider type
          properties:
            - name: name
              type: string
              required: true
              description: The group name
            - name: providerType
              type: enum
              required: true
              description: The provider type
              oneOf:
                values:
                  - idp
                  - custom
  ```

  **Rules:**

  * When `variants` is present, top-level `type`, `array`, `properties`, `oneOf`, and `rules` must be absent — each variant carries its own complete schema
  * `variants` must contain at least 2 entries
  * Each variant must declare a `type`

  **MCP impact:** Variant fields are exposed to AI agents with type hints describing each accepted shape, enabling correct tool call generation.
</Accordion>

***

## `steps` and Step Functions

The `steps` array and the available step functions (`request`, `paginated_request`, `map_fields`, `group_data`, `typecast`, `soap_request`, `static_values`, `merge_collections`, `code_execution_lambda`, and Iterator Steps) are documented on a dedicated page. See [Step Functions](/connector-yaml-reference/step-functions).

***

## `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](/connector-yaml-reference/expression-syntax) 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="/connector-yaml-reference/expression-syntax">
    Full reference for JSONPath, JEXL, and built-in functions
  </Card>

  <Card title="CLI Reference" icon="terminal" href="/connector-building/stackone-cli">
    Validate and deploy connectors
  </Card>

  <Card title="Build Workflow" icon="robot" href="/connector-building/build-workflow">
    AI-assisted connector development
  </Card>

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