JavaScript / TypeScript
Complete code examples for integrating with the VelaFlows API using JavaScript
or TypeScript. All examples use the native fetch API (Node.js 18+ or browser).
Setup: API Client Helper
Create a reusable API client to handle authentication, error handling, and pagination:
// velaflows.ts
const BASE_URL = 'https://api.velaflows.com/api/v1'
interface VelaFlowsConfig {
token: string
workspaceId: string
}
interface ApiResponse<T> {
success: boolean
data: T
error?: {
code: string
message: string
details?: Record<string, unknown>
}
}
interface PaginatedData<T> {
items: T[]
pagination: {
page: number
limit: number
total: number
totalPages: number
}
}
class VelaFlowsClient {
private token: string
private workspaceId: string
constructor(config: VelaFlowsConfig) {
this.token = config.token
this.workspaceId = config.workspaceId
}
private get headers(): Record<string, string> {
return {
'Authorization': `Bearer ${this.token}`,
'x-workspace-id': this.workspaceId,
'Content-Type': 'application/json',
}
}
async get<T>(path: string, params?: Record<string, string>): Promise<T> {
const url = new URL(`${BASE_URL}${path}`)
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== '') {
url.searchParams.set(key, value)
}
})
}
const response = await fetch(url.toString(), { headers: this.headers })
return this.handleResponse<T>(response)
}
async post<T>(path: string, body?: unknown): Promise<T> {
const response = await fetch(`${BASE_URL}${path}`, {
method: 'POST',
headers: this.headers,
body: body ? JSON.stringify(body) : undefined,
})
return this.handleResponse<T>(response)
}
async patch<T>(path: string, body: unknown): Promise<T> {
const response = await fetch(`${BASE_URL}${path}`, {
method: 'PATCH',
headers: this.headers,
body: JSON.stringify(body),
})
return this.handleResponse<T>(response)
}
async delete<T>(path: string): Promise<T> {
const response = await fetch(`${BASE_URL}${path}`, {
method: 'DELETE',
headers: this.headers,
})
return this.handleResponse<T>(response)
}
private async handleResponse<T>(response: Response): Promise<T> {
const json = (await response.json()) as ApiResponse<T>
if (!response.ok || !json.success) {
const error = json.error || { code: 'UNKNOWN', message: 'Unknown error' }
throw new VelaFlowsError(response.status, error.code, error.message, error.details)
}
return json.data
}
}
class VelaFlowsError extends Error {
constructor(
public status: number,
public code: string,
message: string,
public details?: Record<string, unknown>
) {
super(message)
this.name = 'VelaFlowsError'
}
}
// Initialize the client
const velaflows = new VelaFlowsClient({
token: process.env.VELAFLOWS_API_TOKEN!,
workspaceId: process.env.VELAFLOWS_WORKSPACE_ID!,
})
export { velaflows, VelaFlowsClient, VelaFlowsError }Common Operations
List Conversations
interface Conversation {
_id: string
channelType: string
status: string
priority: string
assignedAgentId: string | null
lastMessageAt: string
unreadCount: number
createdAt: string
}
// List open conversations, page 1
const result = await velaflows.get<PaginatedData<Conversation>>(
'/inbox/conversations',
{ page: '1', limit: '20', status: 'open' }
)
console.log(`Total conversations: ${result.pagination.total}`)
result.items.forEach(conv => {
console.log(`[${conv.channelType}] ${conv.status} - ${conv.lastMessageAt}`)
})Create a CRM Lead
interface Lead {
_id: string
firstName: string
lastName: string
email: string
phone: string
pipelineId: string
stageId: string
createdAt: string
}
const lead = await velaflows.post<{ lead: Lead }>('/crm/leads', {
firstName: 'Jane',
lastName: 'Smith',
email: 'jane.smith@example.com',
phone: '+1234567890',
pipelineId: '65a1b2c3d4e5f6a7b8c9d0e1',
stageId: '65a1b2c3d4e5f6a7b8c9d0e2',
})
console.log(`Created lead: ${lead.lead._id}`)Send a Message
const message = await velaflows.post<{ message: { _id: string } }>(
'/inbox/conversations/65a1b2c3d4e5f6a7b8c9d0e1/messages',
{
content: 'Hello! How can I help you today?',
type: 'text',
}
)
console.log(`Sent message: ${message.message._id}`)Update a Customer
const customer = await velaflows.patch<{ lead: Lead }>(
'/customers/65a1b2c3d4e5f6a7b8c9d0e1',
{
firstName: 'Jane',
lastName: 'Doe',
email: 'jane.doe@newcompany.com',
}
)
console.log(`Updated customer: ${customer.lead._id}`)Subscribe to Webhooks
const subscription = await velaflows.post<{ subscription: { _id: string } }>(
'/webhooks/subscriptions',
{
url: 'https://your-app.com/webhooks/velaflows',
events: [
'conversation.created',
'conversation.message.received',
'lead.stage_changed',
],
secret: 'your-webhook-secret-for-verification',
}
)
console.log(`Webhook subscription: ${subscription.subscription._id}`)Error Handling
try {
const lead = await velaflows.post<{ lead: Lead }>('/crm/leads', {
email: 'invalid-email',
})
} catch (error) {
if (error instanceof VelaFlowsError) {
switch (error.code) {
case 'VALIDATION_ERROR':
console.error('Invalid data:', error.details)
break
case 'UNAUTHORIZED':
console.error('Check your API token')
break
case 'FORBIDDEN':
console.error('Token lacks required scope')
break
case 'TOO_MANY_REQUESTS':
console.error('Rate limited — retry later')
break
default:
console.error(`API error [${error.code}]: ${error.message}`)
}
} else {
console.error('Network error:', error)
}
}Pagination Helper
Fetch all pages of a paginated endpoint:
async function fetchAllPages<T>(
path: string,
params: Record<string, string> = {},
limit = 100
): Promise<T[]> {
const allItems: T[] = []
let page = 1
let totalPages = 1
do {
const result = await velaflows.get<PaginatedData<T>>(path, {
...params,
page: String(page),
limit: String(limit),
})
allItems.push(...result.items)
totalPages = result.pagination.totalPages
page++
} while (page <= totalPages)
return allItems
}
// Usage: fetch all open conversations
const allConversations = await fetchAllPages<Conversation>(
'/inbox/conversations',
{ status: 'open' }
)
console.log(`Fetched ${allConversations.length} conversations`)Retry with Exponential Backoff
Wrap the client with automatic retry logic:
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn()
} catch (error) {
if (error instanceof VelaFlowsError) {
// Don't retry client errors (except rate limits)
if (error.status < 500 && error.status !== 429) {
throw error
}
if (attempt === maxRetries) {
throw error
}
const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000
console.log(`Retrying in ${Math.round(delay)}ms (attempt ${attempt + 1})`)
await new Promise(resolve => setTimeout(resolve, delay))
} else {
throw error
}
}
}
throw new Error('Unreachable')
}
// Usage
const conversations = await withRetry(() =>
velaflows.get<PaginatedData<Conversation>>('/inbox/conversations')
)