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)
API member configuration (extends MemberOptions)API configurationHTTP method: GET, POST, PUT, PATCH, DELETE
Query parameters (for GET)
Request body (for POST/PUT/PATCH)
Authentication configuration
options.config.responseType
Response type: json, text, blob, arrayBuffer
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 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}
- 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
- 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
- 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
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()}
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
- Set appropriate timeouts - Prevent hanging requests
- Use retry with backoff - Handle transient failures
- Validate status codes - Don’t trust 2xx only
- Cache when possible - Reduce API calls
- Handle rate limits - Respect API quotas
- Use authentication - Secure API access
- Transform responses - Extract what you need
- Log requests - Debug API issues
- Test with mocks - Avoid real API calls in tests
- Monitor API usage - Track performance and costs