Skip to main content

Basic Usage

operations:
  - name: fetch-data
    operation: http
    config:
      url: https://api.example.com/data
      method: GET
      headers:
        Authorization: Bearer ${env.API_KEY}

Configuration

config:
  url: string              # Request URL (required)
  method: string           # GET, POST, PUT, DELETE, PATCH (default: GET)
  headers: object          # Request headers
  body: object|string      # Request body
  timeout: number          # Timeout in ms (default: 30000)

HTTP Methods

GET Request

operations:
  - name: get-user
    operation: http
    config:
      url: https://api.example.com/users/${input.userId}
      method: GET
      headers:
        Authorization: Bearer ${env.API_KEY}
Output:
{
  status: number         // HTTP status code
  data: any             // Parsed response body
  headers: object       // Response headers
}

POST Request

operations:
  - name: create-user
    operation: http
    config:
      url: https://api.example.com/users
      method: POST
      headers:
        Authorization: Bearer ${env.API_KEY}
        Content-Type: application/json
      body:
        name: ${input.name}
        email: ${input.email}
        role: user

PUT Request

operations:
  - name: update-user
    operation: http
    config:
      url: https://api.example.com/users/${input.userId}
      method: PUT
      headers:
        Authorization: Bearer ${env.API_KEY}
        Content-Type: application/json
      body:
        name: ${input.name}
        email: ${input.email}

DELETE Request

operations:
  - name: delete-user
    operation: http
    config:
      url: https://api.example.com/users/${input.userId}
      method: DELETE
      headers:
        Authorization: Bearer ${env.API_KEY}

PATCH Request

operations:
  - name: partial-update
    operation: http
    config:
      url: https://api.example.com/users/${input.userId}
      method: PATCH
      headers:
        Authorization: Bearer ${env.API_KEY}
        Content-Type: application/json
      body:
        status: ${input.status}

URL Interpolation

Static URL

config:
  url: https://api.example.com/pricing

Path Parameters

config:
  url: https://api.example.com/users/${input.userId}/orders/${input.orderId}

Query Parameters

config:
  url: https://api.example.com/search?q=${input.query}&limit=${input.limit}&page=${input.page}

Dynamic URL from Input

config:
  url: ${input.apiUrl}

URL with Previous Operation Output

operations:
  - name: get-endpoint
    operation: storage
    config:
      type: kv
      action: get
      key: api-endpoint

  - name: call-api
    operation: http
    config:
      url: ${get-endpoint.output.value}/users

Headers

Static Headers

config:
  headers:
    Content-Type: application/json
    Accept: application/json
    User-Agent: Conductor/1.0

With Environment Variables

config:
  headers:
    Authorization: Bearer ${env.API_KEY}
    X-API-Version: ${env.API_VERSION}

With Input Values

config:
  headers:
    X-User-ID: ${input.userId}
    X-Request-ID: ${input.requestId}
    X-Tenant: ${input.tenantId}

Dynamic Headers from Previous Operations

operations:
  - name: get-token
    operation: http
    config:
      url: https://api.example.com/auth
      method: POST
      body:
        username: ${env.USERNAME}
        password: ${env.PASSWORD}

  - name: call-api
    operation: http
    config:
      url: https://api.example.com/data
      headers:
        Authorization: Bearer ${get-token.output.data.access_token}

Request Body

JSON Body

operations:
  - name: create-order
    operation: http
    config:
      url: https://api.example.com/orders
      method: POST
      headers:
        Content-Type: application/json
      body:
        customerId: ${input.customerId}
        items: ${input.items}
        total: ${calculate-total.output.amount}
        currency: USD

String Body

operations:
  - name: send-xml
    operation: http
    config:
      url: https://api.example.com/xml
      method: POST
      headers:
        Content-Type: application/xml
      body: |
        <order>
          <customer>${input.customerId}</customer>
          <total>${input.total}</total>
        </order>

Form Data

operations:
  - name: submit-form
    operation: http
    config:
      url: https://api.example.com/form
      method: POST
      headers:
        Content-Type: application/x-www-form-urlencoded
      body: name=${input.name}&email=${input.email}&message=${input.message}

Timeout Configuration

Default Timeout (30s)

config:
  url: https://api.example.com
  # Uses default 30000ms timeout

Short Timeout for Fast APIs

config:
  url: https://fast-api.example.com
  timeout: 5000  # 5 seconds

Long Timeout for Slow Operations

config:
  url: https://slow-api.example.com
  timeout: 120000  # 2 minutes

Retry Configuration

Basic Retry

operations:
  - name: fetch-data
    operation: http
    config:
      url: https://api.example.com
    retry:
      maxAttempts: 3
      backoff: exponential
Backoff Pattern:
  • Attempt 1: immediate
  • Attempt 2: 1s delay
  • Attempt 3: 2s delay
  • Attempt 4: 4s delay

Conditional Retry

operations:
  - name: fetch-data
    operation: http
    config:
      url: https://api.example.com
    retry:
      maxAttempts: 5
      backoff: exponential
      retryIf: |
        (error) => {
          // Retry on 5xx errors and network issues
          return error.status >= 500 || error.code === 'NETWORK_ERROR';
        }

No Retries

config:
  url: https://api.example.com
# No retry config = fail immediately on error

Response Handling

Automatic JSON Parsing

operations:
  - name: fetch-api
    operation: http
    config:
      url: https://api.example.com/data

outputs:
  data: ${fetch-api.output.data}
  status: ${fetch-api.output.status}

Access Response Headers

operations:
  - name: fetch-api
    operation: http
    config:
      url: https://api.example.com

outputs:
  contentType: ${fetch-api.output.headers['content-type']}
  rateLimit: ${fetch-api.output.headers['x-rate-limit-remaining']}
  rateLimitReset: ${fetch-api.output.headers['x-rate-limit-reset']}

Check Status Code

operations:
  - name: call-api
    operation: http
    config:
      url: https://api.example.com

  - name: handle-success
    condition: ${call-api.output.status === 200}
    operation: code
    config:
      script: scripts/handle-api-success

  - name: handle-error
    condition: ${call-api.output.status >= 400}
    operation: code
    config:
      script: scripts/handle-api-error
    input:
      status: ${call-api.output.status}
      message: ${call-api.output.data.message}
// scripts/handle-api-success.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function handleApiSuccess(context: AgentExecutionContext) {
  return { success: true }
}
// scripts/handle-api-error.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function handleApiError(context: AgentExecutionContext) {
  const { status, message } = context.input

  return {
    error: true,
    status,
    message
  }
}
### Transform Response
operations:
  - name: fetch-api
    operation: http
    config:
      url: https://api.example.com/users

  - name: transform
    operation: code
    config:
      script: scripts/transform-users
    input:
      users: ${fetch-api.output.data}
// scripts/transform-users.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function transformUsers(context: AgentExecutionContext) {
  const { users } = context.input

  return {
    users: users.map(u => ({
      id: u.user_id,
      name: u.full_name,
      email: u.email_address
    }))
  }
}

## Common API Integrations

### REST API with Pagination
operations:
  - name: fetch-page
    operation: http
    config:
      url: https://api.example.com/users?page=${input.page}&limit=100
      headers:
        Authorization: Bearer ${env.API_KEY}

  - name: has-more
    operation: code
    config:
      script: scripts/check-has-more-pages
    input:
      data_length: ${fetch-page.output.data.length}
// scripts/check-has-more-pages.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function checkHasMorePages(context: AgentExecutionContext) {
  const { data_length } = context.input
  return {
    hasMore: data_length === 100
  }
}

GraphQL API

operations:
  - name: graphql-query
    operation: http
    config:
      url: https://api.example.com/graphql
      method: POST
      headers:
        Authorization: Bearer ${env.API_KEY}
        Content-Type: application/json
      body:
        query: |
          query GetUser($id: ID!) {
            user(id: $id) {
              id
              name
              email
              orders {
                id
                total
              }
            }
          }
        variables:
          id: ${input.userId}

Webhook Handler

operations:
  - name: send-webhook
    operation: http
    config:
      url: ${env.WEBHOOK_URL}
      method: POST
      headers:
        Content-Type: application/json
        X-Webhook-Secret: ${env.WEBHOOK_SECRET}
      body:
        event: user.created
        data:
          userId: ${input.userId}
          email: ${input.email}
        timestamp: ${Date.now()}

OAuth Token Refresh

operations:
  - name: refresh-token
    operation: http
    config:
      url: https://api.example.com/oauth/token
      method: POST
      headers:
        Content-Type: application/x-www-form-urlencoded
      body: |
        grant_type=refresh_token&
        refresh_token=${env.REFRESH_TOKEN}&
        client_id=${env.CLIENT_ID}&
        client_secret=${env.CLIENT_SECRET}

  - name: save-token
    operation: storage
    config:
      type: kv
      action: put
      key: access-token
      value: ${refresh-token.output.data.access_token}
      expirationTtl: ${refresh-token.output.data.expires_in}

Third-Party Integrations

Stripe Payment

operations:
  - name: create-payment-intent
    operation: http
    config:
      url: https://api.stripe.com/v1/payment_intents
      method: POST
      headers:
        Authorization: Bearer ${env.STRIPE_SECRET_KEY}
        Content-Type: application/x-www-form-urlencoded
      body: amount=${input.amount}&currency=usd&customer=${input.customerId}

SendGrid Email

operations:
  - name: send-email
    operation: http
    config:
      url: https://api.sendgrid.com/v3/mail/send
      method: POST
      headers:
        Authorization: Bearer ${env.SENDGRID_API_KEY}
        Content-Type: application/json
      body:
        personalizations:
          - to:
              - email: ${input.email}
        from:
          email: [email protected]
        subject: ${input.subject}
        content:
          - type: text/html
            value: ${input.html}

Slack Notification

operations:
  - name: post-to-slack
    operation: http
    config:
      url: ${env.SLACK_WEBHOOK_URL}
      method: POST
      headers:
        Content-Type: application/json
      body:
        text: ${input.message}
        username: Conductor Bot
        icon_emoji: ":robot_face:"
        channel: "#notifications"

Twilio SMS

operations:
  - name: send-sms
    operation: http
    config:
      url: https://api.twilio.com/2010-04-01/Accounts/${env.TWILIO_ACCOUNT_SID}/Messages.json
      method: POST
      headers:
        Authorization: Basic ${env.TWILIO_AUTH_TOKEN}
        Content-Type: application/x-www-form-urlencoded
      body: To=${input.phone}&From=${env.TWILIO_PHONE}&Body=${input.message}

GitHub API

operations:
  - name: create-issue
    operation: http
    config:
      url: https://api.github.com/repos/${input.owner}/${input.repo}/issues
      method: POST
      headers:
        Authorization: token ${env.GITHUB_TOKEN}
        Content-Type: application/json
        Accept: application/vnd.github.v3+json
      body:
        title: ${input.title}
        body: ${input.body}
        labels: ${input.labels}

Error Handling

Check Response Status

operations:
  - name: call-api
    operation: http
    config:
      url: https://api.example.com

  - name: handle-success
    condition: ${call-api.output.status >= 200 && call-api.output.status < 300}
    operation: code
    config:
      script: scripts/handle-http-success
    input:
      data: ${call-api.output.data}

  - name: handle-client-error
    condition: ${call-api.output.status >= 400 && call-api.output.status < 500}
    operation: code
    config:
      script: scripts/handle-client-error
    input:
      status: ${call-api.output.status}
      error: ${call-api.output.data.error}

  - name: handle-server-error
    condition: ${call-api.output.status >= 500}
    operation: code
    config:
      script: scripts/handle-server-error
// scripts/handle-http-success.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function handleHttpSuccess(context: AgentExecutionContext) {
  const { data } = context.input
  return { success: true, data }
}
// scripts/handle-client-error.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function handleClientError(context: AgentExecutionContext) {
  const { status, error } = context.input

  return {
    error: 'Client error',
    status,
    message: error
  }
}
// scripts/handle-server-error.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function handleServerError(context: AgentExecutionContext) {
  return { error: 'Server error', retry: true }
}

### Retry on Failure
operations:
  - name: unreliable-api
    operation: http
    config:
      url: https://flaky-api.example.com
    retry:
      maxAttempts: 5
      backoff: exponential
      initialDelay: 1000

Fallback API

operations:
  - name: primary-api
    operation: http
    config:
      url: https://primary-api.example.com

  - name: fallback-api
    condition: ${!primary-api.output || primary-api.output.status >= 500}
    operation: http
    config:
      url: https://fallback-api.example.com

Handle Timeout

operations:
  - name: slow-api
    operation: http
    config:
      url: https://slow-api.example.com
      timeout: 10000

  - name: handle-timeout
    condition: ${!slow-api.output}
    operation: code
    config:
      script: scripts/handle-timeout
// scripts/handle-timeout.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function handleTimeout(context: AgentExecutionContext) {
  return { error: 'Request timed out', useCache: true }
}

## Caching

### Cache API Responses
operations:
  - name: fetch-pricing
    operation: http
    config:
      url: https://api.example.com/pricing
    cache:
      ttl: 3600  # Cache for 1 hour
      key: pricing-${input.plan}

Conditional Caching

operations:
  - name: fetch-data
    operation: http
    config:
      url: https://api.example.com/data
    cache:
      ttl: ${input.cached ? 3600 : 0}
      key: data-${input.id}

Cache Based on Response

operations:
  - name: fetch-api
    operation: http
    config:
      url: https://api.example.com

  - name: cache-if-success
    condition: ${fetch-api.output.status === 200}
    operation: storage
    config:
      type: kv
      action: put
      key: cached-${input.id}
      value: ${fetch-api.output.data}
      expirationTtl: 3600

Performance Tips

Parallel Requests

Execute multiple independent HTTP requests in parallel:
operations:
  - name: fetch-user
    operation: http
    config:
      url: https://api.example.com/users/${input.userId}

  - name: fetch-orders
    operation: http
    config:
      url: https://api.example.com/orders?userId=${input.userId}

  - name: fetch-settings
    operation: http
    config:
      url: https://api.example.com/settings/${input.userId}

# All three run in parallel automatically

  - name: combine-results
    operation: code
    config:
      script: scripts/combine-api-results
    input:
      user_data: ${fetch-user.output.data}
      orders_data: ${fetch-orders.output.data}
      settings_data: ${fetch-settings.output.data}
// scripts/combine-api-results.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function combineApiResults(context: AgentExecutionContext) {
  const { user_data, orders_data, settings_data } = context.input

  return {
    user: user_data,
    orders: orders_data,
    settings: settings_data
  }
}

Set Appropriate Timeouts

# Fast APIs (< 1s expected)
operations:
  - name: fast-api
    operation: http
    config:
      url: https://fast-api.example.com
      timeout: 5000

# Normal APIs (1-5s expected)
  - name: normal-api
    operation: http
    config:
      url: https://api.example.com
      timeout: 30000

# Slow APIs (5-30s expected)
  - name: slow-api
    operation: http
    config:
      url: https://slow-api.example.com
      timeout: 60000

Cache Aggressively

operations:
  - name: fetch-static-data
    operation: http
    config:
      url: https://api.example.com/config
    cache:
      ttl: 86400  # 24 hours for static data

Batch Requests

operations:
  - name: batch-create
    operation: http
    config:
      url: https://api.example.com/batch
      method: POST
      body:
        operations: ${input.items}

Testing

import { TestConductor } from '@ensemble/conductor/testing';

describe('http operations', () => {
  it('should fetch from API', async () => {
    const conductor = await TestConductor.create({
      projectPath: './conductor',
      mocks: {
        http: {
          'https://api.example.com/users/1': {
            status: 200,
            data: {
              id: 1,
              name: 'Alice',
              email: '[email protected]'
            }
          }
        }
      }
    });

    const result = await conductor.executeAgent('get-user', {
      userId: 1
    });

    expect(result.output.user.name).toBe('Alice');
  });

  it('should handle API errors', async () => {
    const conductor = await TestConductor.create({
      mocks: {
        http: {
          'https://api.example.com/users/1': {
            status: 404,
            data: { error: 'Not found' }
          }
        }
      }
    });

    const result = await conductor.executeAgent('get-user', {
      userId: 1
    });

    expect(result.output.error).toBe('Not found');
  });
});

Best Practices

1. Always Set Timeouts
# Good: Timeout configured
config:
  timeout: 30000

# Bad: No timeout (hangs forever)
config:
  url: https://api.example.com
2. Use Retries for Transient Failures
# Good: Retry on failure
retry:
  maxAttempts: 3
  backoff: exponential
3. Secure API Keys
# Good: Environment variable
headers:
  Authorization: Bearer ${env.API_KEY}

# Bad: Hardcoded (NEVER DO THIS)
headers:
  Authorization: Bearer sk-1234567890
4. Cache Responses
# Good: Cache for appropriate TTL
cache:
  ttl: 3600
5. Handle Errors Gracefully
# Good: Check status and handle errors
operations:
  - name: call-api
    operation: http

  - name: handle-error
    condition: ${call-api.output.status >= 400}
6. Use HTTPS
# Good: HTTPS
url: https://api.example.com

# Bad: HTTP for sensitive data
url: http://api.example.com
7. Validate Responses
# Good: Validate response structure
operations:
  - name: fetch
    operation: http

  - name: validate
    operation: code
    config:
      script: scripts/validate-response-structure
    input:
      data: ${fetch.output.data}
// scripts/validate-response-structure.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function validateResponseStructure(context: AgentExecutionContext) {
  const { data } = context.input

  if (!data.id || !data.name) {
    throw new Error('Invalid response structure')
  }

  return data
}

**8. Rate Limit Awareness**
# Good: Respect rate limits
operations:
  - name: check-rate-limit
    operation: code
    config:
      script: scripts/check-rate-limit
    input:
      rateLimit: ${fetch.output.headers["x-rate-limit-remaining"]}
// scripts/check-rate-limit.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function checkRateLimit(context: AgentExecutionContext) {
  const { rateLimit } = context.input
  const remaining = parseInt(rateLimit)

  if (remaining < 10) {
    // Slow down or pause
  }
}

## Common Pitfalls

### Pitfall: No Error Handling
# Bad: No error handling
operations:
  - name: call-api
    operation: http
    config:
      url: https://api.example.com

# Good: Handle errors
operations:
  - name: call-api
    operation: http
    config:
      url: https://api.example.com
    retry:
      maxAttempts: 3

  - name: handle-error
    condition: ${!call-api.output || call-api.output.status >= 400}
    operation: code
    config:
      script: scripts/handle-error-simple
// scripts/handle-error-simple.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default function handleErrorSimple(context: AgentExecutionContext) {
  return { error: true }
}

### Pitfall: Missing Content-Type
# Bad: No Content-Type for JSON
operations:
  - name: post
    operation: http
    config:
      method: POST
      body: { data: "value" }

# Good: Explicit Content-Type
operations:
  - name: post
    operation: http
    config:
      method: POST
      headers:
        Content-Type: application/json
      body: { data: "value" }

Pitfall: Hardcoded URLs

# Bad: Hardcoded URL
config:
  url: https://prod-api.example.com

# Good: Environment-based URL
config:
  url: ${env.API_BASE_URL}/endpoint

Next Steps