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

# baton-http: REST API integration

> Integrate any REST API without writing Go code. Configuration only.

Baton-HTTP is a configuration-driven connector that lets you write YAML instead of Go code. Instead of implementing the `ResourceSyncer` interface, you describe how to map an API to C1's resource model. Your ops team can own integrations directly — no engineering queue.

## Resources

* [Official download center](https://dist.conductorone.com/ConductorOne/baton-http): Stable binaries (Windows/Linux/macOS) and container images

## When to use baton-http

**Use baton-http when:**

* The target system has a REST API
* You need a quick integration without writing Go
* The access model maps to user/group/resource patterns
* You want non-developers to maintain the integration

**Use a custom connector when:**

* The API requires authentication not supported by baton-http (e.g., Kerberos, SAML)
* You need heavy data transformation or business logic
* You need maximum performance optimization

## Command-line options

| Flag                       | Description                                              |
| :------------------------- | :------------------------------------------------------- |
| `--config-path`            | **(Required)** Path to the YAML configuration file       |
| `--validate-config-only`   | Validate the configuration file and exit without running |
| `--enable-command-actions` | Enable command actions (shell script execution)          |
| `--client-id`              | C1 client ID for service mode                            |
| `--client-secret`          | C1 client secret for service mode                        |

<Note>
  Authentication credentials for the target API are configured in the YAML file using environment variable interpolation (e.g., `${API_TOKEN}`), not via command-line flags.
</Note>

## Configuration structure

Every baton-http configuration file must include these core elements:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
version: "1"                    # Required, must be "1"
app_name: "Your Application"    # Required
app_description: "Optional description"

connect:
  base_url: "https://api.example.com/v1"
  auth:
    type: "bearer"
    token: "${API_TOKEN}"

resource_types:
  user:
    # Resource configuration...
```

### Top-level fields

| Field             | Required | Description                                         |
| :---------------- | :------- | :-------------------------------------------------- |
| `version`         | Yes      | Schema version, must be `"1"`                       |
| `app_name`        | Yes      | Application name for this connector                 |
| `app_description` | No       | Description of the application                      |
| `vars`            | No       | Global variables available throughout configuration |
| `connect`         | Yes      | API connection and authentication settings          |
| `http`            | No       | HTTP client settings (timeouts, retries)            |
| `resource_types`  | Yes      | Resource type definitions (minimum 1 required)      |
| `error_handling`  | No       | Global error handling configuration                 |
| `actions`         | No       | Custom action definitions                           |

## Connection configuration

The `connect` section defines how to connect to your HTTP API:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
connect:
  base_url: "https://api.example.com/v1"

  auth:
    type: "bearer"
    token: "${API_TOKEN}"

  request_defaults:
    content_type: "application/json"
    headers:
      Accept: "application/json"
    query_params:
      limit: "100"

  pagination:
    strategy: "offset"
    limit_param: "limit"
    offset_param: "offset"
    page_size: 25
```

## Authentication methods

Baton-HTTP supports multiple authentication methods. Configure authentication under `connect.auth`.

### No authentication

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
auth:
  type: none
```

### Bearer token

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
auth:
  type: bearer
  token: "${API_TOKEN}"
```

### Basic authentication

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
auth:
  type: basic
  username: "${API_USERNAME}"
  password: "${API_PASSWORD}"
```

### API key

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
auth:
  type: api_key
  api_key:
    header: "X-API-Key"       # Header name
    prefix: "ApiKey"          # Optional prefix
    key: "${API_KEY}"         # The key value
```

### OAuth2 client credentials

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
auth:
  type: oauth2_client_credentials
  oauth2_client_credentials:
    token_url: "https://api.example.com/oauth/token"
    # Or use OIDC discovery:
    # issuer: "https://login.example.com"
    client_id: "${CLIENT_ID}"
    client_secret: "${CLIENT_SECRET}"
    scope: "read:users read:groups"
    token_expiry_padding: 60  # Refresh 60 seconds before expiry
```

### OAuth2 password (ROPC)

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
auth:
  type: oauth2_password
  oauth2_password:
    token_url: "https://api.example.com/oauth/token"
    client_id: "${CLIENT_ID}"
    client_secret: "${CLIENT_SECRET}"
    username: "${USERNAME}"
    password: "${PASSWORD}"
    scope: "api"
```

### Bearer dynamic

Use for APIs that issue tokens via a login endpoint:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
auth:
  type: bearer_dynamic
  bearer_dynamic:
    token_url: "https://api.example.com/login"
    username: "${USERNAME}"
    password: "${PASSWORD}"
    token_field: "access_token"      # JSON field containing token
    expiry_field: "expires_at"       # Optional, auto-parses JWT if omitted
    token_expiry_padding: 60
```

## Resource type configuration

Resource types define the entities you want to sync. Each resource type specifies how to list resources and map API responses to C1 resources.

### Basic structure

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
resource_types:
  user:
    name: "User"
    description: "User accounts"
    traits:
      - user

    list:
      request:
        method: GET
        url: /users
      response:
        items_path: items
        mapping_type: resource
        resource_mapping:
          id: cel:item.id
          display_name: cel:item.name
          user_traits:
            email: cel:item.email
            login: cel:item.username
            status: 'cel:item.active ? "enabled" : "disabled"'

    skip_entitlements_and_grants: true
```

### Resource type fields

| Field                          | Required | Description                                               |
| :----------------------------- | :------- | :-------------------------------------------------------- |
| `name`                         | Yes      | Human-readable name                                       |
| `description`                  | No       | Description of this resource type                         |
| `traits`                       | No       | Traits for this type (`user`, `group`, `role`, `app`)     |
| `depends_on`                   | No       | Resource types that must be processed first               |
| `parent_type`                  | No       | Parent resource type for hierarchical resources           |
| `child_types`                  | No       | Child resource types                                      |
| `list`                         | Yes\*    | How to list resources (\*unless using `static_resources`) |
| `static_resources`             | No       | Statically defined resources                              |
| `static_entitlements`          | No       | Predefined entitlements                                   |
| `entitlements`                 | No       | Dynamic entitlement configuration                         |
| `grants`                       | No       | Grant discovery configuration                             |
| `skip_entitlements_and_grants` | No       | Skip entitlement/grant processing                         |

## Data mapping with CEL expressions

Baton-HTTP uses [Common Expression Language (CEL)](https://github.com/google/cel-spec) for data mapping. CEL expressions are prefixed with `cel:`.

### Response mapping

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
response:
  items_path: data.users           # Path to array in response
  mapping_type: resource           # resource, grant, or entitlement
  resource_mapping:
    id: cel:item.id
    display_name: cel:item.firstName + " " + item.lastName
    description: cel:item.email
    user_traits:
      email: cel:item.email
      login: cel:item.username
      status: 'cel:item.status == "active" ? "enabled" : "disabled"'
      profile:
        first_name: cel:item.firstName
        last_name: cel:item.lastName
        department: cel:item.department
```

### Common CEL patterns

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
# Conditional expressions
status: 'cel:item.active ? "enabled" : "disabled"'

# String concatenation
display_name: cel:item.firstName + " " + item.lastName

# Null-safe access with has()
parent_id: "cel:has(item.parent.id) ? item.parent.id : null"

# Array access
primary_email: cel:item.emails[0].address
```

## URL templates

Use Go template syntax (prefixed with `tmpl:`) for dynamic URLs:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
# URL templating
url: tmpl:/groups/{{.resource.id}}/members

# With parent resource
url: tmpl:/orgs/{{.parent_resource.id}}/teams/{{.resource.id}}

# Request body templating
body: |
  tmpl:{
    "user_id": "{{.principal.id}}",
    "role": "member"
  }
```

### Available template variables

| Variable                       | Description                     |
| :----------------------------- | :------------------------------ |
| `.resource.id`                 | Current resource ID             |
| `.resource.display_name`       | Current resource display name   |
| `.parent_resource.id`          | Parent resource ID              |
| `.principal.id`                | Principal ID (for provisioning) |
| `.principal.traits.user.email` | Principal's email               |
| `.pre_requests.<name>.body`    | Response from a pre-request     |

## Pagination

Configure pagination under `connect.pagination` (global) or per-resource under `list.pagination`.

### Offset-based

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
pagination:
  strategy: offset
  limit_param: limit
  offset_param: offset
  page_size: 100
  total_path: totalResults     # Optional: path to total count
```

### Cursor-based

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
pagination:
  strategy: cursor
  limit_param: limit
  cursor_param: cursor
  cursor_path: meta.nextCursor  # Path to next cursor in response
  page_size: 100
```

### Page-based

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
pagination:
  strategy: page
  limit_param: per_page
  page_param: page
  page_start: 1               # Starting page number
  page_size: 100
```

### Link-based

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
pagination:
  strategy: link
  link_next_path: links.next   # Path to next URL in response
```

### No pagination

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
pagination:
  strategy: none
```

## Entitlements

Entitlements define permissions that can be granted to resources.

### Static entitlements

Use for predefined entitlements like group membership:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
static_entitlements:
  - id: member
    display_name: cel:resource.display_name + " Member"
    description: cel:"Member of " + resource.display_name
    slug: member
    purpose: assignment      # assignment, or permission
    grantable_to:
      - user
```

### Dynamic entitlements

Fetch entitlements from an API:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
entitlements:
  - request:
      url: tmpl:/spaces/{{.resource.id}}/permissions
    response:
      items_path: items
      mapping_type: entitlement
      entitlement_mapping:
        id: cel:resource.id + "-" + item.operation
        display_name: cel:"Can " + item.operation + " on " + resource.display_name
        slug: cel:item.operation
        purpose: permission
        grantable_to:
          - user
          - group
```

## Grants

Grants define which principals have which entitlements:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
grants:
  - request:
      url: tmpl:/groups/{{.resource.id}}/members
    response:
      items_path: members
      mapping_type: grant
      grant_mapping:
        principal_id: cel:item.userId
        principal_type: user
        entitlement_name: member
```

### Conditional grants

Use `skip_if` to filter grants:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
grants:
  - request:
      url: tmpl:/resources/{{.resource.id}}/permissions
    response:
      items_path: items
      skip_if: cel:item.subject.type == "anonymous"
      mapping_type: grant
      grant_mapping:
        principal_id: cel:item.subject.id
        principal_type: cel:item.subject.type
        entitlement_name: cel:item.permission
```

## Provisioning

Enable provisioning to grant and revoke access through C1.

### Grant and revoke

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
static_entitlements:
  - id: member
    display_name: "Group Member"
    purpose: assignment
    grantable_to:
      - user
    provisioning:
      grant:
        request:
          method: POST
          url: tmpl:/groups/{{.resource.id}}/members
          body: |
            tmpl:{
              "user_id": "{{.principal.id}}"
            }
        success_condition: cel:response.status_code == 201
      revoke:
        request:
          method: DELETE
          url: tmpl:/groups/{{.resource.id}}/members/{{.principal.id}}
        success_condition: cel:response.status_code == 204
```

### Pre-requests

Use pre-requests when you need data from another API call:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
provisioning:
  grant:
    request:
      pre_requests:
        user_details:
          url: tmpl:/users/{{.principal.id}}
          method: GET
      method: PUT
      url: tmpl:/groups/{{.resource.id}}/members/{{.pre_requests.user_details.body.username}}
```

## HTTP client settings

Configure timeouts and retries under the `http` section:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
http:
  timeout: 30s
  headers:
    Accept: "application/json"
  retry:
    max_attempts: 3
    initial_backoff: 1s
    max_backoff: 10s
```

## Error handling

Configure error handling globally or per-request:

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
error_handling:
  error_status_codes:
    "429":
      action: retry
      retry_after: 30s
      max_retries: 3
    "404":
      action: warn
      message: "Resource not found"
    "500":
      action: retry
      retry_after: 5s
```

Error actions: `fail` (default), `retry`, `warn`, `ignore`

## Running baton-http

### Validate configuration

```bash theme={"theme":{"light":"css-variables","dark":"css-variables"}}
baton-http --config-path ./config.yaml --validate-config-only
```

### One-shot mode (local testing)

```bash theme={"theme":{"light":"css-variables","dark":"css-variables"}}
baton-http --config-path ./config.yaml -f sync.c1z
baton resources -f sync.c1z
baton grants -f sync.c1z
```

### Service mode (production)

```bash theme={"theme":{"light":"css-variables","dark":"css-variables"}}
baton-http --config-path ./config.yaml \
  --client-id "$C1_CLIENT_ID" \
  --client-secret "$C1_CLIENT_SECRET"
```

## Deploying to Kubernetes

### Step 1: Set up a new connector in C1

<Steps>
  <Step>
    In C1, navigate to **Connectors** > **Add connector**.
  </Step>

  <Step>
    Search for **Baton** and click **Add**.
  </Step>

  <Step>
    Choose how to set up the new connector:

    * Add the connector to a currently unmanaged app
    * Add the connector to a managed app
    * Create a new managed app
  </Step>

  <Step>
    Set the owner for this connector and click **Next**.
  </Step>

  <Step>
    In the **Settings** area, click **Edit**, then click **Rotate** to generate a new Client ID and Secret. Save these credentials.
  </Step>
</Steps>

### Step 2: Create Kubernetes configuration

#### Secret

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
apiVersion: v1
kind: Secret
metadata:
  name: baton-http-secrets
type: Opaque
stringData:
  C1_CLIENT_ID: <C1 client ID>
  C1_CLIENT_SECRET: <C1 client secret>
  API_TOKEN: <Your API token>
```

#### ConfigMap

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
apiVersion: v1
kind: ConfigMap
metadata:
  name: baton-http-config
data:
  config.yaml: |
    version: "1"
    app_name: My Application

    connect:
      base_url: "https://api.example.com/v1"
      auth:
        type: bearer
        token: "${API_TOKEN}"

    resource_types:
      user:
        name: User
        traits:
          - user
        list:
          request:
            url: /users
          response:
            items_path: data
            mapping_type: resource
            resource_mapping:
              id: cel:item.id
              display_name: cel:item.name
              user_traits:
                email: cel:item.email
                status: 'cel:item.active ? "enabled" : "disabled"'
        skip_entitlements_and_grants: true
```

#### Deployment

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: baton-http
spec:
  selector:
    matchLabels:
      app: baton-http
  template:
    metadata:
      labels:
        app: baton-http
    spec:
      containers:
      - name: baton-http
        image: ghcr.io/conductorone/baton-http:latest
        args:
          - "--config-path=/config/config.yaml"
        envFrom:
        - secretRef:
            name: baton-http-secrets
        volumeMounts:
        - name: config
          mountPath: /config
      volumes:
      - name: config
        configMap:
          name: baton-http-config
```

### Step 3: Deploy

Apply the configuration files to your Kubernetes cluster and verify the connector appears in C1 under **Applications** > **Managed apps**.

## Example: GitHub integration

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
version: "1"
app_name: Github
app_description: Github Organization

connect:
  base_url: https://api.github.com
  auth:
    type: bearer
    token: "${GITHUB_TOKEN}"
  request_defaults:
    headers:
      Accept: application/json
    query_params:
      per_page: 100

resource_types:
  org:
    name: Organization
    traits:
      - app
    child_types:
      - user
      - team
    list:
      request:
        url: /user/orgs
      response:
        items_path: items
        mapping_type: resource
        resource_mapping:
          id: cel:item.id
          display_name: cel:item.login

  user:
    name: User
    depends_on:
      - org
    parent_type: org
    traits:
      - user
    list:
      request:
        url: tmpl:/orgs/{{.parent_resource.id}}/members
      response:
        items_path: items
        mapping_type: resource
        resource_mapping:
          id: cel:item.login
          display_name: cel:item.login
          user_traits:
            login: cel:item.login
    skip_entitlements_and_grants: true

  team:
    name: Team
    depends_on:
      - org
    parent_type: org
    traits:
      - group
    list:
      request:
        url: tmpl:/orgs/{{.parent_resource.id}}/teams
      response:
        items_path: items
        mapping_type: resource
        resource_mapping:
          id: cel:item.id
          display_name: cel:item.name
          group_traits:
            profile:
              description: cel:item.description
    static_entitlements:
      - id: member
        display_name: "Team Member"
        purpose: assignment
        grantable_to:
          - user
    grants:
      - request:
          url: tmpl:/orgs/{{.parent_resource.id}}/team/{{.resource.id}}/members
        response:
          items_path: items
          mapping_type: grant
          grant_mapping:
            principal_id: cel:item.login
            principal_type: user
            entitlement_name: member
```

## Example: OAuth2 with provisioning

```yaml theme={"theme":{"light":"css-variables","dark":"css-variables"}}
version: "1"
app_name: ConductorOne
app_description: ConductorOne API Integration

connect:
  base_url: https://${C1_ENDPOINT}/api/v1
  auth:
    type: oauth2_client_credentials
    oauth2_client_credentials:
      token_url: https://${C1_ENDPOINT}/auth/v1/token
      client_id: "${C1_CLIENT_ID}"
      client_secret: "${C1_CLIENT_SECRET}"
  request_defaults:
    headers:
      Accept: application/json

resource_types:
  user:
    name: User
    traits:
      - user
    list:
      request:
        url: /users
      response:
        items_path: list
        mapping_type: resource
        resource_mapping:
          id: cel:item.user.id
          display_name: cel:item.user.displayName
          user_traits:
            login: cel:item.user.username
            email: cel:item.user.email
    skip_entitlements_and_grants: true
```

## Troubleshooting

### Authentication errors

* Verify credentials are correct and environment variables are set
* Check token hasn't expired
* For OAuth2, verify token URL and scopes

### Resources not syncing

* Use `--validate-config-only` to check configuration
* Verify `items_path` matches your API response structure
* Check CEL expressions with sample data

### Pagination issues

* Confirm pagination `strategy` matches your API
* Verify parameter names (`limit_param`, `offset_param`, etc.)
* Check `cursor_path` or `link_next_path` for cursor/link pagination

### Mapping errors

* Test CEL expressions against sample API responses
* Use `has()` for optional fields to avoid null errors
* Check for typos in field names

For more information, see the [official download center](https://dist.conductorone.com/ConductorOne/baton-http).
