Skip to main content

Overview

The APIMember class makes HTTP/REST API calls with built-in retry, timeout, authentication, and response transformation capabilities.
import { APIMember } from '@ensemble-edge/conductor';

const api = new APIMember({
  name: 'fetch-weather',
  config: {
    url: 'https://api.weather.com/forecast',
    method: 'GET',
    params: {
      city: '${input.city}',
      apiKey: '${env.WEATHER_API_KEY}'
    }
  }
});

const result = await api.execute({ city: 'San Francisco' });

Constructor

new APIMember(options: APIMemberOptions)
options
APIMemberOptions
required
API member configuration (extends MemberOptions)
options.name
string
required
Member name
options.config
APIConfig
required
API configuration
options.config.url
string
required
API endpoint URL
options.config.method
string
default:"GET"
HTTP method: GET, POST, PUT, PATCH, DELETE
options.config.headers
object
Request headers
options.config.params
object
Query parameters (for GET)
options.config.body
any
Request body (for POST/PUT/PATCH)
options.config.auth
AuthConfig
Authentication configuration
options.config.responseType
string
default:"json"
Response type: json, text, blob, arrayBuffer
options.config.timeout
number
default:"30000"
Request timeout in milliseconds
interface APIConfig {
  url: string;
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
  headers?: Record<string, string>;
  params?: Record<string, any>;
  body?: any;
  auth?: AuthConfig;
  responseType?: 'json' | 'text' | 'blob' | 'arrayBuffer';
  timeout?: number;
  validateStatus?: (status: number) => boolean;
}

Methods

execute()

Execute the API call.
async execute(input: any): Promise<APIResponse>
input
any
required
Input data (used in URL/body expressions)
Returns: Promise<APIResponse>
interface APIResponse {
  status: number;
  statusText: string;
  headers: Record<string, string>;
  data: any;
  duration: number;
}
Example:
const result = await api.execute({ userId: 'user_123' });

console.log(result.status); // 200
console.log(result.data); // { id: 'user_123', name: 'Alice' }
console.log(result.duration); // 234

HTTP Methods

GET Request

- member: get-user
  type: API
  config:
    url: https://api.example.com/users/${input.userId}
    method: GET
    headers:
      Authorization: Bearer ${env.API_KEY}
  input:
    userId: user_123

POST Request

- member: create-user
  type: API
  config:
    url: https://api.example.com/users
    method: POST
    headers:
      Content-Type: application/json
      Authorization: Bearer ${env.API_KEY}
    body:
      email: ${input.email}
      name: ${input.name}
      role: user
  input:
    email: newuser@example.com
    name: New User

PUT Request

- member: update-user
  type: API
  config:
    url: https://api.example.com/users/${input.userId}
    method: PUT
    body:
      name: ${input.name}
      email: ${input.email}

PATCH Request

- member: partial-update
  type: API
  config:
    url: https://api.example.com/users/${input.userId}
    method: PATCH
    body:
      name: ${input.name}

DELETE Request

- member: delete-user
  type: API
  config:
    url: https://api.example.com/users/${input.userId}
    method: DELETE

Authentication

Bearer Token

- member: authenticated-request
  type: API
  config:
    url: https://api.example.com/protected
    method: GET
    auth:
      type: bearer
      token: ${env.API_TOKEN}

Basic Auth

- member: basic-auth-request
  type: API
  config:
    url: https://api.example.com/protected
    method: GET
    auth:
      type: basic
      username: ${env.API_USERNAME}
      password: ${env.API_PASSWORD}

API Key (Header)

- member: api-key-header
  type: API
  config:
    url: https://api.example.com/data
    method: GET
    headers:
      X-API-Key: ${env.API_KEY}

API Key (Query)

- member: api-key-query
  type: API
  config:
    url: https://api.example.com/data
    method: GET
    params:
      api_key: ${env.API_KEY}

OAuth 2.0

- member: oauth-request
  type: API
  config:
    url: https://api.example.com/protected
    method: GET
    auth:
      type: oauth2
      accessToken: ${get-access-token.output.token}

Custom Auth

- member: custom-auth
  type: API
  config:
    url: https://api.example.com/protected
    method: GET
    headers:
      Authorization: Custom ${env.API_KEY}
      X-Request-ID: ${context.executionId}
      X-Timestamp: ${Date.now()}

Query Parameters

Static Parameters

- member: search-users
  type: API
  config:
    url: https://api.example.com/users
    method: GET
    params:
      status: active
      role: admin
      limit: 50

Dynamic Parameters

- member: search-dynamic
  type: API
  config:
    url: https://api.example.com/search
    method: GET
    params:
      q: ${input.query}
      page: ${input.page}
      limit: ${input.limit || 10}
      sort: ${input.sortBy}

Array Parameters

- member: filter-by-tags
  type: API
  config:
    url: https://api.example.com/posts
    method: GET
    params:
      tags: ${input.tags}  # Array: ?tags=a&tags=b&tags=c

Request Bodies

JSON Body

- member: json-request
  type: API
  config:
    url: https://api.example.com/data
    method: POST
    headers:
      Content-Type: application/json
    body:
      user:
        id: ${input.userId}
        name: ${input.name}
      metadata:
        timestamp: ${Date.now()}
        source: conductor

Form Data

- member: form-request
  type: API
  config:
    url: https://api.example.com/submit
    method: POST
    headers:
      Content-Type: application/x-www-form-urlencoded
    body:
      name: ${input.name}
      email: ${input.email}

Multipart Upload

- member: upload-file
  type: API
  config:
    url: https://api.example.com/upload
    method: POST
    headers:
      Content-Type: multipart/form-data
    body:
      file: ${input.fileData}
      filename: ${input.filename}
      description: ${input.description}

Response Handling

JSON Response

- member: fetch-json
  type: API
  config:
    url: https://api.example.com/data
    method: GET
    responseType: json

Text Response

- member: fetch-text
  type: API
  config:
    url: https://api.example.com/text
    method: GET
    responseType: text

Binary Response

- member: download-file
  type: API
  config:
    url: https://api.example.com/download
    method: GET
    responseType: blob

Response Transformation

- member: transform-response
  type: API
  config:
    url: https://api.example.com/data
    method: GET
    transform:
      # Extract specific fields
      output:
        id: ${response.data.user.id}
        name: ${response.data.user.profile.fullName}
        email: ${response.data.user.contact.email}

Status Code Validation

- member: validate-status
  type: API
  config:
    url: https://api.example.com/data
    method: GET
    validateStatus: (status) => status >= 200 && status < 300

Error Handling

Retry Configuration

- member: retry-on-failure
  type: API
  config:
    url: https://api.example.com/flaky-endpoint
    method: GET
  retry:
    maxAttempts: 3
    backoff: exponential
    initialDelay: 1000
    retryIf: |
      (error) => {
        // Retry on network errors or 5xx
        return error.code === 'NETWORK_ERROR' ||
               (error.status >= 500 && error.status < 600);
      }

Timeout Handling

- member: with-timeout
  type: API
  config:
    url: https://api.example.com/slow-endpoint
    method: GET
    timeout: 5000  # 5 seconds
  retry:
    maxAttempts: 2
    retryIf: (error) => error.code === 'TIMEOUT'

Fallback Value

flow:
  - member: try-api
    type: API
    config:
      url: https://api.example.com/data
      method: GET

  - member: use-fallback
    condition: ${try-api.output.status !== 200}
    type: Function
    config:
      handler: () => ({ data: [], source: 'fallback' })

Advanced Patterns

Pagination

flow:
  - member: fetch-page-1
    type: API
    config:
      url: https://api.example.com/users
      method: GET
      params:
        page: 1
        limit: 100

  - member: fetch-page-2
    condition: ${fetch-page-1.output.data.hasMore}
    type: API
    config:
      url: https://api.example.com/users
      method: GET
      params:
        page: 2
        limit: 100

Rate Limiting

- member: rate-limited-request
  type: API
  config:
    url: https://api.example.com/data
    method: GET
    rateLimit:
      maxRequests: 100
      perMilliseconds: 60000  # 100 requests per minute

Parallel Requests

flow:
  - parallel:
      - member: fetch-users
        type: API
        config:
          url: https://api.example.com/users

      - member: fetch-products
        type: API
        config:
          url: https://api.example.com/products

      - member: fetch-orders
        type: API
        config:
          url: https://api.example.com/orders

  - member: combine-results
    type: Function
    config:
      handler: |
        (input) => ({
          users: input.fetchUsers.data,
          products: input.fetchProducts.data,
          orders: input.fetchOrders.data
        })

Request Chaining

flow:
  - member: get-user-id
    type: API
    config:
      url: https://api.example.com/auth/me
      method: GET

  - member: get-user-profile
    type: API
    config:
      url: https://api.example.com/users/${get-user-id.output.data.id}/profile
      method: GET

  - member: get-user-orders
    type: API
    config:
      url: https://api.example.com/users/${get-user-id.output.data.id}/orders
      method: GET

Conditional Requests

flow:
  - member: check-cache
    type: Data
    config:
      type: kv
      operation: get
      key: api-cache:${input.key}

  - member: call-api
    condition: ${!check-cache.output.value}
    type: API
    config:
      url: https://api.example.com/expensive-operation
      method: POST

  - member: cache-result
    condition: ${call-api.output.status === 200}
    type: Data
    config:
      type: kv
      operation: put
      key: api-cache:${input.key}
      value: ${call-api.output.data}
      expirationTtl: 3600

Webhook Integration

- member: trigger-webhook
  type: API
  config:
    url: ${env.WEBHOOK_URL}
    method: POST
    headers:
      Content-Type: application/json
      X-Webhook-Signature: ${computeSignature(input)}
    body:
      event: workflow_completed
      data: ${output}
      timestamp: ${Date.now()}

Performance Optimization

Connection Pooling

const api = new APIMember({
  name: 'pooled-api',
  config: {
    url: 'https://api.example.com/data',
    method: 'GET',
    keepAlive: true,
    maxSockets: 10
  }
});

Response Compression

- member: compressed-request
  type: API
  config:
    url: https://api.example.com/large-data
    method: GET
    headers:
      Accept-Encoding: gzip, deflate, br

Caching

- member: cached-api-call
  type: API
  config:
    url: https://api.example.com/static-data
    method: GET
  cache:
    enabled: true
    ttl: 3600000  # 1 hour
    key: ${config.url}

Testing

import { APIMember } from '@ensemble-edge/conductor';
import { describe, it, expect, vi } from 'vitest';

describe('APIMember', () => {
  it('makes GET request', async () => {
    const api = new APIMember({
      name: 'test-api',
      config: {
        url: 'https://api.example.com/users/123',
        method: 'GET'
      }
    });

    // Mock fetch
    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ id: '123', name: 'Test' })
    });

    const result = await api.execute({});

    expect(result.status).toBe(200);
    expect(result.data.name).toBe('Test');
  });

  it('handles errors', async () => {
    const api = new APIMember({
      name: 'error-api',
      config: {
        url: 'https://api.example.com/error',
        method: 'GET'
      }
    });

    global.fetch = vi.fn().mockRejectedValue(
      new Error('Network error')
    );

    await expect(api.execute({})).rejects.toThrow('Network error');
  });
});

Best Practices

  1. Set appropriate timeouts - Prevent hanging requests
  2. Use retry with backoff - Handle transient failures
  3. Validate status codes - Don’t trust 2xx only
  4. Cache when possible - Reduce API calls
  5. Handle rate limits - Respect API quotas
  6. Use authentication - Secure API access
  7. Transform responses - Extract what you need
  8. Log requests - Debug API issues
  9. Test with mocks - Avoid real API calls in tests
  10. Monitor API usage - Track performance and costs