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

# Build Your First Connector

> Step-by-step guide to building, testing, and deploying your first StackOne connector

A StackOne connector is a YAML configuration that turns a third-party API into a set of agent-ready actions. By the end of this guide, you'll have a working connector pushed to your StackOne project — built with the StackOne CLI, an AI assistant, and the [connectors-template](https://github.com/StackOneHQ/connectors-template).

You'll set up your environment, generate an API key, build and test authentication against a provider, then add and validate your first actions.

<Warning>
  Custom connectors require Enterprise access — contact [support@stackone.com](mailto:support@stackone.com) to enable.
</Warning>

***

## Setup

<Steps>
  <Step title="Generate a StackOne API key">
    1. In the [StackOne dashboard](https://app.stackone.com/), go to **Project Settings → API keys**.
    2. Click **Create API key**.
    3. Ensure the following **Scopes** are enabled (some are not selected by default):
       * `connectors:read` — download connectors from registry
       * `connectors:write` — push and delete connectors
       * `credentials:read` — use linked account credentials with `stackone run`
    4. Copy the key (format: `v1.{region}.xxxxx`) and store it somewhere safe — you won't be able to view it again.

    See [API Keys](/guides/api-keys#configure-api-key-scopes-optional) for more on scopes.
  </Step>

  <Step title="Open a Terminal & Install the StackOne CLI">
    You'll run all StackOne CLI commands from a terminal window.

    <Tabs>
      <Tab title="macOS">
        1. Press `Cmd + Space` to open Spotlight, type **Terminal**, and press Enter.
        2. Confirm Node.js 18+ is installed by running:
           ```bash theme={null}
           node --version
           ```
           If the command isn't found or the version is below 18, install Node.js from [nodejs.org](https://nodejs.org/).
        3. Confirm Git is installed:
           ```bash theme={null}
           git --version
           ```
           If the command isn't found, install the Xcode Command Line Tools (`xcode-select --install`) or download Git from [git-scm.com](https://git-scm.com/downloads/mac).
        4. Install the StackOne CLI globally via npm:
           ```bash theme={null}
           npm install -g @stackone/cli
           ```
           The `-g` flag installs the CLI globally so the `stackone` command is available in any directory.
        5. Verify the install:
           ```bash theme={null}
           stackone --version
           ```
      </Tab>

      <Tab title="Windows">
        1. Open **Command Prompt** or **PowerShell** from the Start menu.
        2. Confirm Node.js 18+ is installed (`node --version`). If missing, install from [nodejs.org](https://nodejs.org/).
        3. Confirm Git is installed (`git --version`). If missing, install [Git for Windows](https://git-scm.com/downloads/win).
        4. Install the StackOne CLI: `npm install -g @stackone/cli`
        5. Verify with `stackone --version`.
      </Tab>

      <Tab title="Linux">
        1. Open your terminal application.
        2. Confirm Node.js 18+ is installed (`node --version`). Install via your package manager if needed.
        3. Confirm Git is installed (`git --version`). If missing, install via your package manager (e.g., `sudo apt install git` on Debian/Ubuntu, `sudo dnf install git` on Fedora).
        4. Install the StackOne CLI: `npm install -g @stackone/cli`
        5. Verify with `stackone --version`.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Set Up Your Working Directory">
    Create your own private GitHub repository by following the [connectors-template setup guide](https://github.com/StackOneHQ/connectors-template#set-up-your-private-repository).

    Clone your new private repo into a `my-connector` folder and install dependencies:

    ```bash theme={null}
    git clone https://github.com/<your-org>/<your-repo>.git my-connector
    cd my-connector
    npm install
    ```

    All subsequent CLI commands run from inside `my-connector`.

    <Tip>
      The template ships with agent skills (including `/on-boarding`), CI/CD workflows, a pre-configured `CLAUDE.md`, and example connector structures.
    </Tip>

    <Accordion title="Starting without connectors-template">
      <Warning>
        You can build connectors without the template, but you'll skip the bundled agent skills (like `/on-boarding`), pre-configured `CLAUDE.md`, and CI/CD workflows.
      </Warning>

      Create an empty folder, move into it, then pull an existing connector as a reference:

      ```bash theme={null}
      mkdir my-connector
      cd my-connector
      stackone pull --connector provider --output ./connectors/
      ```
    </Accordion>
  </Step>

  <Step title="Configure the StackOne CLI">
    From inside your working directory, authenticate the StackOne CLI:

    ```bash theme={null}
    # Authenticate globally (stores credentials in ~/.stackone)
    stackone agent setup --global

    # Or scope to current project only
    stackone agent setup --local
    ```

    This configures MCP tools for AI-assisted development and stores your access token.
  </Step>

  <Step title="Create a StackOne CLI Profile">
    Create a named profile so deployment commands like `stackone push` can authenticate without you pasting your API key each time:

    ```bash theme={null}
    stackone init
    ```

    You'll be prompted for a profile label and an API key — paste the StackOne API key you generated in [Step 1](#generate-a-stackone-api-key). Create separate profiles for staging vs. production and reference them later with `--profile <profile-label>`.
  </Step>
</Steps>

***

## Starting the Build

The connectors-template includes agent skills and a pre-configured `CLAUDE.md`, so the recommended path is to open your project in an AI coding assistant and start a chat from inside the `my-connector` directory.

<Tabs>
  <Tab title="Claude Code">
    From inside `my-connector`, launch Claude Code:

    ```bash theme={null}
    claude
    ```

    The template's `CLAUDE.md` and bundled skills (including `/on-boarding`) load automatically.
  </Tab>

  <Tab title="Cursor / VS Code">
    Open the `my-connector` folder in Cursor or VS Code with the Claude extension, then open the AI chat panel.
  </Tab>
</Tabs>

Once the chat is open, run the onboarding skill:

```
/on-boarding
```

This launches an interactive flow that guides you through:

1. **Connector type selection** — Agentic Actions (raw provider data) vs Schema-Based (unified output)
2. **Provider details** — Name, API version, and pulling any existing connector
3. **Authentication setup** — Configuring and validating auth before building actions
4. **Schema definition** (for unified connectors) — Defining your target output format
5. **Endpoint research** — Discovering available API endpoints and their trade-offs
6. **Action building** — Implementing and testing each action

The onboarding flow ensures you complete each step before moving to the next, reducing errors and rework.

<Tip>
  **Experienced users:** Skip onboarding and tell the agent directly what you need:

  * "Build a connector for \[Provider] with employee management actions"
  * "Start unified build for \[Provider]" (for schema-based connectors)
</Tip>

***

## Build Workflow

The recommended workflow ensures you validate each step before moving to the next:

```mermaid theme={null}
flowchart LR
    A[Fork/Pull] --> B[Build Auth]
    B --> C[Connect Account]
    C --> D[Build Actions]
    D --> E[Test & Debug]
    E --> F[Iterate]
    F --> D
```

### Step 1: Fork or Pull Existing Connector

Always start by checking if a connector already exists:

```bash theme={null}
# Pull existing connector from StackOne registry
stackone pull --connector provider --output ./connectors/

# Check what was downloaded
ls connectors/provider/
```

If the pull succeeds, you have a foundation to build on. If it fails, create a new connector structure:

```
connectors/provider/
├── provider.connector.s1.yaml       # Main file (auth, metadata)
└── provider.employees.s1.partial.yaml  # Actions by resource
```

### Step 2: Build Authentication

<Warning>
  **Do not proceed until authentication works.** All other work depends on valid auth.
</Warning>

Configure authentication in the main connector file:

<Tabs>
  <Tab title="API Key (Bearer)">
    ```yaml theme={null}
    authentication:
      - custom:
          type: custom
          label: API Key
          authorization:
            type: bearer
            token: $.credentials.apiKey
          configFields:
            - key: apiKey
              label: API Key
              type: password
              required: true
              secret: true
          testActionsIds:
            - list_users  # Simple action to test auth
    ```
  </Tab>

  <Tab title="API Key (Header)">
    ```yaml theme={null}
    authentication:
      - custom:
          type: custom
          label: API Key
          authorization:
            type: none
          customHeaders:
            X-API-Key: $.credentials.apiKey
          configFields:
            - key: apiKey
              label: API Key
              type: password
              required: true
              secret: true
    ```
  </Tab>

  <Tab title="Basic Auth">
    ```yaml theme={null}
    authentication:
      - custom:
          type: custom
          label: Basic Auth
          authorization:
            type: basic
            username: $.credentials.username
            password: $.credentials.password
          configFields:
            - key: username
              label: Username
              type: text
              required: true
            - key: password
              label: Password
              type: password
              required: true
              secret: true
    ```
  </Tab>
</Tabs>

Create a simple test action (e.g., `list_users`) and verify auth works before building more actions.

### Step 3: Connect Account & Test Auth

Most providers offer sandbox or developer accounts for testing — check the provider's developer portal for signup, then generate API credentials (API key, OAuth app, etc.). Note any rate limits or restrictions on sandbox accounts.

Create test files for local development:

```bash theme={null}
# Create account config
echo '{"environment": "production", "provider": "provider"}' > account.json

# Create credentials (gitignore this!)
echo '{"apiKey": "your_sandbox_api_key"}' > credentials.json
```

<Tip>
  Keep sandbox credentials separate from production. Store them in a `credentials.json` file that's gitignored.
</Tip>

Test authentication:

```bash theme={null}
stackone run \
  --connector connectors/provider/provider.connector.s1.yaml \
  --account account.json \
  --credentials credentials.json \
  --action-id list_users \
  --debug
```

<Accordion title="Alternative: push and link an account">
  If you prefer, push the connector and create a linked account in the StackOne dashboard (see [Getting Started](/guides/introduction) guide). Then use `--account-id` instead of local credential files:

  ```bash theme={null}
  stackone push connectors/provider/ --profile <profile-label>
  # Create the linked account in the dashboard, then:
  stackone run \
    --connector connectors/provider/provider.connector.s1.yaml \
    --account-id your-linked-account-id \
    --action-id list_users
  ```

  `<profile-label>` is the name you set when running `stackone init` (see [Setup](#setup)).
</Accordion>

### Step 4: Build Actions

With auth working, build out your actions. See [Common Step Functions](#common-step-functions) for patterns.

### Step 5: Iterate

1. Validate syntax: `stackone validate connectors/provider/`
2. Test actions: `stackone run --debug ...`
3. Fix issues and repeat

***

## Common Step Functions

### `request` - Single HTTP Request

Use for GET single resource, POST create, PUT update operations:

```yaml theme={null}
steps:
  - stepId: get_user
    stepFunction:
      functionName: request
      parameters:
        url: /users/${inputs.id}
        method: get
        response:
          collection: false
          dataKey: data.user  # Extract nested response
```

### `paginated_request` - Cursor Pagination

Use for list endpoints with cursor-based pagination:

```yaml theme={null}
steps:
  - stepId: list_users
    stepFunction:
      functionName: paginated_request
      parameters:
        url: /users
        method: get
        response:
          dataKey: data.users      # Path to data array
          nextKey: meta.next_cursor # Path to pagination cursor
          indexField: id            # Unique ID field in each record
        iterator:
          key: cursor              # Parameter name API expects
          in: query                # Send cursor as query param
        args:
          - name: limit
            value: "100"
            in: query
```

<Warning>
  **Verify paths with `--debug`** before assuming response structure. Common mistake: using `dataKey: users` when the actual path is `dataKey: data.users`.
</Warning>

### `map_fields` - Transform Data (Unified Connectors)

Transform provider response to your schema:

```yaml theme={null}
steps:
  - stepId: map_data
    stepFunction:
      functionName: map_fields
      version: "2"
      parameters:
        dataSource: $.steps.get_users.output.data
        fields:
          - targetFieldKey: id
            expression: $.user_id
            type: string
          - targetFieldKey: email
            expression: $.email_address
            type: string
          - targetFieldKey: full_name
            expression: "{{$.first_name + ' ' + $.last_name}}"
            type: string
          - targetFieldKey: status
            expression: $.account_status
            type: enum
            enumMapper:
              matcher:
                - matchExpression: '{{$.account_status == "ACTIVE"}}'
                  value: active
                - matchExpression: '{{$.account_status == "INACTIVE"}}'
                  value: inactive
```

<Note>
  Always use `version: "2"` for `map_fields` and `typecast` steps.
</Note>

### `typecast` - Apply Type Conversions

Apply after `map_fields` to ensure correct types:

```yaml theme={null}
steps:
  - stepId: typecast_data
    stepFunction:
      functionName: typecast
      version: "2"
      parameters:
        dataSource: $.steps.map_data.output.data
        fields:
          - targetFieldKey: id
            type: string
          - targetFieldKey: created_at
            type: datetime_string
          - targetFieldKey: is_active
            type: boolean
```

### Cursor Pagination with Dynamic Page Size

For list actions where callers can specify page size, use the dual-condition pattern:

```yaml theme={null}
inputs:
  - name: page_size
    type: number
    in: query
    required: false
    description: Results per page (max 100)
  - name: cursor
    type: string
    in: query
    required: false
    description: Pagination cursor

cursor:
  enabled: true
  pageSize: 50

steps:
  - stepId: get_data
    stepFunction:
      functionName: request
      parameters:
        url: /items
        method: get
        args:
          # Use page_size if provided
          - name: limit
            value: $.inputs.page_size
            in: query
            condition: "{{present(inputs.page_size)}}"
          # Otherwise use default
          - name: limit
            value: 50
            in: query
            condition: "{{!present(inputs.page_size)}}"
          # Pass cursor when present
          - name: cursor
            value: $.inputs.cursor
            in: query
            condition: "{{present(inputs.cursor)}}"

result:
  data: $.steps.get_data.output.data.items
  next: $.steps.get_data.output.data.meta.next_cursor
```

***

## StackOne CLI Examples

For the full command list, see the [CLI Reference](/guides/connector-engine/cli-reference). The examples below cover the patterns you'll use most while iterating on your first connector.

### Validate as you build

Watch mode re-runs validation on every save:

```bash theme={null}
stackone validate connectors/provider/ --watch
```

### Test actions with parameters

`--params` accepts a structured JSON object with these top-level keys, each corresponding to an input's `in:` location in the action's YAML:

* `path` — `in: path` inputs (URL path parameters)
* `queryParams` — `in: query` inputs
* `header` — `in: headers` inputs (note: singular `header`)
* `body` — `in: body` inputs

Place every value under the key that matches the input's `in:` location:

```bash theme={null}
stackone run \
  --connector connectors/provider/provider.connector.s1.yaml \
  --account-id linked-account-id \
  --action-id get_employee \
  --params '{"path": {"id": "emp_123"}, "queryParams": {"include": "manager"}}'
```

### Push when ready

```bash theme={null}
stackone push connectors/provider/ --profile <profile-label>
```

`<profile-label>` is the name you set when running `stackone init` (see the [setup section](#setup)). Create separate profiles for staging vs. production and swap the label as needed.

***

## Debugging

### Enable Debug Mode

Add `--debug` to any `stackone run` command to surface:

* The raw HTTP request sent to the provider (URL, method, headers, body)
* The full response body before any mapping is applied
* The inputs and outputs of each step in the connector
* The resolved values of any JSONPath or JEXL expressions

```bash theme={null}
stackone run \
  --connector connectors/provider/provider.connector.s1.yaml \
  --account-id linked-account-id \
  --action-id list_employees \
  --debug
```

### Debug Empty or Incorrect Results

When mapping produces unexpected results:

<Steps>
  <Step title="Test raw response first">
    Temporarily modify your `result` block to return raw data:

    ```yaml theme={null}
    result:
      data: $.steps.get_employees.output.data  # Raw, before map_fields
    ```
  </Step>

  <Step title="Verify response structure">
    Run with `--debug` and examine the actual paths:

    ```bash theme={null}
    stackone run --debug ... | jq '.steps.get_employees.output'
    ```
  </Step>

  <Step title="Add fields incrementally">
    Start with one field in `map_fields`, verify it works, then add more.
  </Step>

  <Step title="Check expression context">
    For inline fields in `map_fields`, expressions reference fields directly (`$.email`), not with step prefix (`$.get_employees.email`).
  </Step>
</Steps>

### Common Issues

| Symptom                      | Likely Cause                    | Fix                                               |
| ---------------------------- | ------------------------------- | ------------------------------------------------- |
| 401 Unauthorized             | Invalid credentials             | Check API key, regenerate if needed               |
| Empty data array             | Wrong `dataKey` path            | Use `--debug` to verify actual response structure |
| Null field values            | Incorrect expression path       | Verify nested paths match actual response         |
| Pagination returns same data | Wrong `iterator.key`            | Check API docs for expected cursor parameter name |
| Enum not translated          | `matchExpression` case mismatch | Provider might return `"ACTIVE"` not `"Active"`   |

***

## Error Mapping

Map provider-specific errors to meaningful responses using error handlers:

```yaml theme={null}
errorHandlers:
  - condition: '{{response.status == 404}}'
    message: "Resource not found"
    code: NOT_FOUND
  - condition: '{{response.status == 429}}'
    message: "Rate limit exceeded. Retry after {{response.headers.retry-after}} seconds"
    code: RATE_LIMITED
  - condition: '{{response.status == 403}}'
    message: "Insufficient permissions for this operation"
    code: FORBIDDEN
```

For unified connectors, map provider error codes to standard error responses:

```yaml theme={null}
errorHandlers:
  - condition: '{{response.body.error.code == "INVALID_TOKEN"}}'
    message: "Authentication failed - token may be expired"
    code: UNAUTHORIZED
  - condition: '{{response.body.error.code == "MISSING_SCOPE"}}'
    message: "Missing required permission: {{response.body.error.required_scope}}"
    code: FORBIDDEN
```

***

## Manual Testing Methods

Beyond `stackone run`, you have additional testing options:

### Action Request Tester (Dashboard)

The StackOne dashboard includes an Action Request Tester for testing deployed connectors:

1. Navigate to your project in the dashboard
2. Select a linked account
3. Choose an action and provide parameters
4. Execute and view results

This tests the deployed connector version against real credentials.

### RPC Calls via API

Test actions programmatically using the StackOne API:

```bash theme={null}
curl -X POST "https://api.stackone.com/unified/actions/execute" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "x-account-id: your-linked-account-id" \
  -d '{
    "action_id": "provider_list_employees",
    "params": {
      "page_size": 10
    }
  }'
```

<Warning>
  Actions only appear in the Request Tester if they're enabled in your auth config. If you don't see an action, edit your auth config and enable it.
</Warning>

### MCP Testing (AI Agents)

If using Claude Code or similar, test actions conversationally:

```
Test the list_employees action for the provider connector.
Use page_size of 5 and verify the response includes id, name, and email fields.
```

The AI agent will execute the action and validate results.

***

## Unified Connector Checklist

For schema-based connectors, verify:

<AccordionGroup>
  <Accordion title="Schema Definition">
    * [ ] Target schema documented before building
    * [ ] All required fields identified
    * [ ] Field types specified (string, number, enum, datetime\_string)
    * [ ] Enum values defined with mappings
  </Accordion>

  <Accordion title="Field Mapping">
    * [ ] `fieldConfigs` map all schema fields
    * [ ] `targetFieldKey` uses YOUR schema names (not provider names)
    * [ ] Nested paths verified against actual response
    * [ ] Enum mappings handle all provider values + default case
  </Accordion>

  <Accordion title="Pagination">
    * [ ] `cursor.enabled: true` for list actions
    * [ ] `dataKey` path verified with `--debug`
    * [ ] `nextKey` path verified with `--debug`
    * [ ] `result.next` returns cursor for next page
    * [ ] Tested: first page, next page, last page, empty results
  </Accordion>

  <Accordion title="Steps">
    * [ ] `map_fields` step with `version: "2"`
    * [ ] `typecast` step with `version: "2"`
    * [ ] Correct `dataSource` references between steps
  </Accordion>
</AccordionGroup>

***

## Optimizing Agent Performance

The connector building agent starts with baseline capabilities. As you build connectors, you can improve agent performance by creating **custom skills** that encode your specific use cases and schemas.

### The Optimization Workflow

```mermaid theme={null}
flowchart LR
    A[Build First Connectors] --> B[Identify Patterns]
    B --> C[Create Skills]
    C --> D[Agent Uses Skills]
    D --> E[Faster, More Accurate Builds]
```

### Why Create Custom Skills?

| Without Skills                         | With Skills                                   |
| -------------------------------------- | --------------------------------------------- |
| Agent asks for schema every time       | Agent auto-loads your schema skill            |
| Generic action structures              | Actions match your specific requirements      |
| Manual field mapping decisions         | Consistent mapping patterns across connectors |
| Repeated explanations of your use case | Agent understands context immediately         |

### Creating Schema Skills

After building your first few connectors, extract your schema into a reusable skill:

```
.claude/skills/schemas/
└── my-use-case-schema.md
```

**Example schema skill:**

```markdown theme={null}
---
name: Employee Sync Schema
description: Target schema for employee data synchronization
category: hris
---

# Employee Sync Schema

## Target Schema

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| id | string | yes | Unique identifier |
| email | string | yes | Primary email |
| first_name | string | yes | |
| last_name | string | yes | |
| status | enum | yes | Values: active, inactive, terminated |
| department | string | no | |
| hire_date | datetime_string | no | ISO 8601 format |

## Enum Mappings

### status
| Provider Value | Schema Value |
|----------------|--------------|
| Active, active, ACTIVE | active |
| Inactive, inactive, INACTIVE | inactive |
| Terminated, terminated, TERMINATED | terminated |
| * (default) | unknown |
```

Once created, the agent automatically uses this schema for future unified builds — no questions needed.

### Recommended Progression

<Steps>
  <Step title="Build 2-3 Connectors">
    Use the baseline agent to build your first connectors. Note patterns in your requirements.
  </Step>

  <Step title="Create Schema Skills">
    Extract your target schemas into `.claude/skills/schemas/`. Include field definitions, types, and enum mappings.
  </Step>

  <Step title="Document Use-Case Patterns">
    Create skills that describe your specific integration patterns, endpoint preferences, or data transformation rules.
  </Step>

  <Step title="Iterate and Refine">
    As you build more connectors, update skills based on edge cases and lessons learned.
  </Step>
</Steps>

<Tip>
  Skills are stored in `.claude/skills/` in the connectors-template repository. The agent reads these automatically when relevant to the current task.
</Tip>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Connector Structure" icon="book" href="/guides/connector-engine/connector-structure">
    Detailed YAML structure reference
  </Card>

  <Card title="Expression Language" icon="function" href="/guides/connector-engine/expression-language">
    JSONPath, JEXL, and string interpolation
  </Card>

  <Card title="YAML Reference" icon="file-code" href="/guides/connector-engine/connector-yaml-reference">
    Complete property documentation
  </Card>

  <Card title="CI/CD Setup" icon="github" href="/guides/connector-engine/github-workflow">
    Automated deployment with GitHub Actions
  </Card>
</CardGroup>
