Best Practices
Best practices for the Builder API — security guidelines, rate limit handling, retry strategies, and recommended integration patterns for production deployments.
This guide outlines recommended practices for building secure, efficient, and maintainable integrations with the Synthreo Builder API. Following these guidelines will help ensure your applications are robust and provide a great user experience.
Security Best Practices
Section titled “Security Best Practices”Credential Management
Section titled “Credential Management”Never expose credentials in client-side code. Store your email, password, and user ID securely using environment variables or a dedicated secret management service.
// ❌ Bad: Hardcoded credentialsconst credentials = { email: "user@example.com", password: "mypassword123", userId: 12345};
// ✅ Good: Environment variablesconst credentials = { email: process.env.SYNTHREO_API_EMAIL, password: process.env.SYNTHREO_API_PASSWORD, userId: parseInt(process.env.SYNTHREO_USER_ID)};```text### Token Security
**Implement secure token storage and refresh logic**. JWT tokens expire after **24 hours** and must be refreshed by re-authenticating.
```javascriptclass TokenManager { constructor() { this.token = null; this.tokenExpiry = null; }
isTokenValid() { if (!this.token || !this.tokenExpiry) return false; // Refresh token 5 minutes before expiry (24-hour lifetime) return Date.now() < (this.tokenExpiry - 5 * 60 * 1000); }
async getValidToken() { if (!this.isTokenValid()) { await this.refreshToken(); } return this.token; }
async refreshToken() { // Note: No refresh endpoint exists - must re-authenticate const response = await this.authenticate(); this.token = response.token; // JWT tokens have 24-hour expiry this.tokenExpiry = Date.now() + (24 * 60 * 60 * 1000); }
// Critical: Never log or expose tokens logSafeInfo() { console.log('Token status:', { hasToken: !!this.token, isValid: this.isTokenValid(), expiresIn: this.tokenExpiry ? Math.floor((this.tokenExpiry - Date.now()) / 1000) : null }); }}```text### HTTPS and Data Protection
**Always use HTTPS** for all API communications to ensure data is encrypted in transit.
**Validate and sanitize input data** before sending it to the API to prevent injection attacks.
```javascriptfunction sanitizeUserInput(input) { if (typeof input !== 'string') { throw new Error('Input must be a string'); }
// Remove potentially harmful characters return input.replace(/[<>\"']/g, '').trim();}```text## Asynchronous Operations Best Practices
### Job Execution and Polling
**Choose the right execution method** based on expected operation duration:
- **Synchronous (`/Execute`)**: Quick operations under 30 seconds- **Asynchronous (`/ExecuteAsJob`)**: Long-running operations (5-15 minutes typical)
**Implement intelligent polling strategies**:
```javascriptclass JobManager { constructor() { this.activeJobs = new Map(); }
async executeAndPoll(diagramId, payload, options = {}) { const { initialInterval = 30000, // 30 seconds initial polling maxInterval = 300000, // 5 minutes maximum interval timeout = 3600000, // 1 hour timeout backoffMultiplier = 1.5 // Gradual increase in polling interval } = options;
// Start the job const jobResponse = await this.startJob(diagramId, payload); const jobId = jobResponse.job.id;
console.log(`Job ${jobId} started, polling every ${initialInterval/1000} seconds`);
let currentInterval = initialInterval; const startTime = Date.now();
while (Date.now() - startTime < timeout) { await new Promise(resolve => setTimeout(resolve, currentInterval));
try { const statusResponse = await this.checkJobStatus(jobId);
if (statusResponse.status === 202) { // Job still running - increase polling interval gradually currentInterval = Math.min(currentInterval * backoffMultiplier, maxInterval); console.log(`Job ${jobId} still running, next check in ${currentInterval/1000} seconds`); continue; }
if (statusResponse.status === 200) { console.log(`Job ${jobId} completed successfully`); return statusResponse.data; }
throw new Error(`Unexpected job status: ${statusResponse.status}`);
} catch (error) { console.error(`Error polling job ${jobId}:`, error.message); // Continue polling for transient errors, but with longer interval currentInterval = Math.min(currentInterval * 2, maxInterval); } }
throw new Error(`Job ${jobId} timed out after ${timeout/1000} seconds`); }}```text### Training Operations
**Handle training workflows with proper state monitoring**:
```javascriptclass TrainingManager { async triggerAndMonitorTraining(diagramId, nodeId, options = {}) { const { checkInterval = 60000, // 1 minute for training checks timeout = 3600000, // 1 hour timeout maxStateChanges = 10 // Prevent infinite loops } = options;
// Trigger training await this.triggerTraining(diagramId, nodeId); console.log(`Training initiated for diagram ${diagramId}`);
let lastState = null; let stateChangeCount = 0; const startTime = Date.now();
while (Date.now() - startTime < timeout) { const agentStatus = await this.getAgentStatus(diagramId); const currentState = agentStatus.stateId;
// Track state changes to detect issues if (currentState !== lastState) { stateChangeCount++; if (stateChangeCount > maxStateChanges) { throw new Error('Training failed: Too many state changes detected'); } console.log(`Training state changed: ${lastState} → ${currentState}`); lastState = currentState; }
switch (currentState) { case 6: // Training in progress console.log('Agent is training...'); await new Promise(resolve => setTimeout(resolve, checkInterval)); break;
case 2: // Idle/Ready console.log('Training completed successfully!'); return { success: true, finalState: currentState };
default: // Unexpected state - log but continue monitoring briefly console.warn(`Unexpected training state: ${currentState}`); await new Promise(resolve => setTimeout(resolve, checkInterval)); } }
throw new Error(`Training monitoring timed out after ${timeout/1000} seconds`); }}```text## Performance Optimization
### Connection Pooling and Reuse
**Reuse HTTP connections** when making multiple API calls to reduce latency and improve performance.
```javascript// Using node-fetch with keep-aliveconst fetch = require('node-fetch');const Agent = require('agentkeepalive');
const agent = new Agent({ maxSockets: 100, maxFreeSockets: 10, timeout: 60000, freeSocketTimeout: 30000});
const apiClient = { async makeRequest(url, options) { return fetch(url, { ...options, agent: agent }); }};```text### Intelligent Caching Strategies
**Cache responses** appropriately based on operation type:
```javascriptclass IntelligentCache { constructor() { this.cache = new Map(); // Different TTL for different operation types this.ttlMap = { 'agent_status': 30000, // 30 seconds - changes during training 'diagram_info': 300000, // 5 minutes - relatively static 'job_status': 5000, // 5 seconds - changes frequently 'auth_token': 86400000 // 24 hours - matches token expiry }; }
set(key, value, operationType = 'default') { const ttl = this.ttlMap[operationType] || 60000; // 1 minute default
this.cache.set(key, { value, timestamp: Date.now(), ttl }); }
get(key) { const item = this.cache.get(key); if (!item) return null;
if (Date.now() - item.timestamp > item.ttl) { this.cache.delete(key); return null; }
return item.value; }
// Clear cache for specific diagram when training starts invalidateTrainingCache(diagramId) { const keysToDelete = Array.from(this.cache.keys()) .filter(key => key.includes(`diagram_${diagramId}`));
keysToDelete.forEach(key => this.cache.delete(key)); console.log(`Invalidated ${keysToDelete.length} cache entries for diagram ${diagramId}`); }}```text### Rate Limiting Compliance
**Respect API rate limits** by implementing proper throttling and backoff strategies.
```javascriptclass RateLimiter { constructor(requestsPerSecond = 10) { this.requestsPerSecond = requestsPerSecond; this.requests = []; }
async throttle() { const now = Date.now();
// Remove requests older than 1 second this.requests = this.requests.filter(time => now - time < 1000);
if (this.requests.length >= this.requestsPerSecond) { const oldestRequest = Math.min(...this.requests); const waitTime = 1000 - (now - oldestRequest); await new Promise(resolve => setTimeout(resolve, waitTime)); }
this.requests.push(now); }}```text## Error Handling and Resilience
### Comprehensive Error Handling
**Handle both HTTP errors and cognitive diagram execution errors**:
```javascriptclass APIError extends Error { constructor(message, statusCode, errorType, context = {}) { super(message); this.name = 'APIError'; this.statusCode = statusCode; this.errorType = errorType; this.context = context; this.timestamp = new Date().toISOString(); }}
async function handleAPIResponse(response, context = {}) { if (!response.ok) { const errorBody = await response.json().catch(() => ({})); throw new APIError( errorBody.message || response.statusText, response.status, 'HTTP_ERROR', context ); }
const data = await response.json();
// Critical: errorData may contain info messages, not just errors if (data.errorData && data.errorData !== "[]") { try { const errorList = JSON.parse(data.errorData); const actualErrors = errorList.filter(item => item.type === 'ERROR' || (!item.type && item.message.toLowerCase().includes('error')) );
if (actualErrors.length > 0) { throw new APIError( actualErrors.map(e => `${e.node_name || 'Unknown'}: ${e.message}`).join('; '), 200, 'DIAGRAM_ERROR', { ...context, errorData: errorList } ); }
// Log info messages but don't treat as errors const infoMessages = errorList.filter(item => item.type === 'INFO'); if (infoMessages.length > 0) { console.log('Diagram info messages:', infoMessages.map(m => m.message)); }
} catch (parseError) { console.warn('Failed to parse errorData:', data.errorData); } }
return data;}```text### Circuit Breaker Pattern
**Implement circuit breaker pattern** to prevent cascading failures when the API is experiencing issues.
```javascriptclass CircuitBreaker { constructor(threshold = 5, timeout = 60000) { this.threshold = threshold; this.timeout = timeout; this.failureCount = 0; this.lastFailureTime = null; this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN }
async execute(operation) { if (this.state === 'OPEN') { if (Date.now() - this.lastFailureTime > this.timeout) { this.state = 'HALF_OPEN'; } else { throw new Error('Circuit breaker is OPEN'); } }
try { const result = await operation(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } }
onSuccess() { this.failureCount = 0; this.state = 'CLOSED'; }
onFailure() { this.failureCount++; this.lastFailureTime = Date.now();
if (this.failureCount >= this.threshold) { this.state = 'OPEN'; } }}```text## Concurrency and Resource Management
### Concurrency Control
**Manage concurrent operations** to avoid overwhelming the API or your application:
```javascriptclass ConcurrencyManager { constructor(maxConcurrent = 5) { this.maxConcurrent = maxConcurrent; this.running = 0; this.queue = []; }
async execute(operation) { return new Promise((resolve, reject) => { this.queue.push({ operation, resolve, reject }); this.processQueue(); }); }
async processQueue() { if (this.running >= this.maxConcurrent || this.queue.length === 0) { return; }
this.running++; const { operation, resolve, reject } = this.queue.shift();
try { const result = await operation(); resolve(result); } catch (error) { reject(error); } finally { this.running--; this.processQueue(); // Process next item in queue } }}
// Usage for multiple diagram executionsconst concurrencyManager = new ConcurrencyManager(3); // Max 3 concurrent requests
const results = await Promise.all( messages.map(message => concurrencyManager.execute(() => executeCognitiveDiagram(token, diagramId, message) ) ));```text### Resource Cleanup and Memory Management
**Implement proper cleanup** for long-running applications:
```javascriptclass ResourceManager { constructor() { this.timers = new Set(); this.intervals = new Set(); this.connections = new Set(); }
setTimeout(callback, delay) { const timer = setTimeout(() => { this.timers.delete(timer); callback(); }, delay); this.timers.add(timer); return timer; }
setInterval(callback, interval) { const timer = setInterval(callback, interval); this.intervals.add(timer); return timer; }
addConnection(connection) { this.connections.add(connection); }
cleanup() { // Clear all timers this.timers.forEach(timer => clearTimeout(timer)); this.intervals.forEach(timer => clearInterval(timer));
// Close all connections this.connections.forEach(conn => { if (conn.destroy) conn.destroy(); if (conn.close) conn.close(); });
// Clear collections this.timers.clear(); this.intervals.clear(); this.connections.clear();
console.log('Resources cleaned up successfully'); }}
// Global cleanup handlerconst resourceManager = new ResourceManager();
process.on('SIGINT', () => { console.log('Received SIGINT, cleaning up...'); resourceManager.cleanup(); process.exit(0);});```text## API Usage Patterns
### Batch Processing
**Process multiple requests efficiently** by implementing proper batching and concurrency control.
```javascriptasync function processBatch(items, processor, concurrency = 5) { const results = [];
for (let i = 0; i < items.length; i += concurrency) { const batch = items.slice(i, i + concurrency); const batchPromises = batch.map(processor); const batchResults = await Promise.allSettled(batchPromises); results.push(...batchResults); }
return results;}
// Usage exampleconst messages = ['Hello', 'How are you?', 'Goodbye'];const results = await processBatch( messages, message => executeCognitiveDiagram(token, diagramId, message), 3 // Process 3 at a time);```text### Conversation Management
**Maintain conversation context** effectively by using conversation IDs and proper state management.
```javascriptclass ConversationManager { constructor() { this.conversations = new Map(); }
createConversation(userId) { const conversationId = `conv_${userId}_${Date.now()}`; this.conversations.set(conversationId, { userId, messages: [], createdAt: new Date(), lastActivity: new Date() }); return conversationId; }
addMessage(conversationId, message, response) { const conversation = this.conversations.get(conversationId); if (conversation) { conversation.messages.push({ message, response, timestamp: new Date() }); conversation.lastActivity = new Date(); } }
getContext(conversationId, maxMessages = 5) { const conversation = this.conversations.get(conversationId); if (!conversation) return null;
return conversation.messages .slice(-maxMessages) .map(m => `User: ${m.message}\nAI: ${m.response}`) .join('\n\n'); }}```text## Monitoring and Logging
### Comprehensive Logging Strategy
**Implement structured logging** for debugging and monitoring:
```javascriptconst logger = { info: (message, metadata = {}) => { console.log(JSON.stringify({ level: 'INFO', message, timestamp: new Date().toISOString(), ...metadata })); },
error: (message, error, metadata = {}) => { console.error(JSON.stringify({ level: 'ERROR', message, error: error.message, stack: error.stack, timestamp: new Date().toISOString(), ...metadata })); },
// Special logging for job operations jobStatus: (jobId, status, metadata = {}) => { console.log(JSON.stringify({ level: 'INFO', message: 'Job status update', jobId, status, timestamp: new Date().toISOString(), ...metadata })); },
// Training-specific logging training: (diagramId, state, metadata = {}) => { console.log(JSON.stringify({ level: 'INFO', message: 'Training status update', diagramId, state, timestamp: new Date().toISOString(), ...metadata })); }};```text### Performance Monitoring
**Track API performance metrics** to identify bottlenecks and optimize your integration.
```javascriptclass PerformanceMonitor { constructor() { this.metrics = { requestCount: 0, totalResponseTime: 0, errorCount: 0, operationMetrics: new Map() // Track by operation type }; }
async trackRequest(operation, operationType = 'unknown') { const startTime = Date.now(); this.metrics.requestCount++;
// Initialize operation metrics if not exists if (!this.metrics.operationMetrics.has(operationType)) { this.metrics.operationMetrics.set(operationType, { count: 0, totalTime: 0, errors: 0 }); }
const opMetrics = this.metrics.operationMetrics.get(operationType); opMetrics.count++;
try { const result = await operation(); const responseTime = Date.now() - startTime;
this.metrics.totalResponseTime += responseTime; opMetrics.totalTime += responseTime;
logger.info('API request completed', { operationType, responseTime, avgResponseTime: this.metrics.totalResponseTime / this.metrics.requestCount, opAvgTime: opMetrics.totalTime / opMetrics.count });
return result; } catch (error) { this.metrics.errorCount++; opMetrics.errors++;
logger.error('API request failed', error, { operationType, responseTime: Date.now() - startTime, errorRate: this.metrics.errorCount / this.metrics.requestCount, opErrorRate: opMetrics.errors / opMetrics.count }); throw error; } }
getMetricsSummary() { const summary = { overall: { totalRequests: this.metrics.requestCount, totalErrors: this.metrics.errorCount, avgResponseTime: this.metrics.totalResponseTime / this.metrics.requestCount, errorRate: this.metrics.errorCount / this.metrics.requestCount }, byOperation: {} };
this.metrics.operationMetrics.forEach((metrics, operation) => { summary.byOperation[operation] = { count: metrics.count, avgTime: metrics.totalTime / metrics.count, errorRate: metrics.errors / metrics.count }; });
return summary; }}```text## Testing Strategies
### Unit Testing
**Write comprehensive tests** for your API integration logic.
```javascript// Example using Jestdescribe('Synthreo API Client', () => { let client;
beforeEach(() => { client = new SynthreoAPIClient(mockAuthClient); });
test('should handle authentication errors gracefully', async () => { mockAuthClient.getAccessToken.mockRejectedValue(new Error('Invalid credentials'));
await expect(client.executeDiagram('123', {})) .rejects .toThrow('Invalid credentials'); });
test('should parse successful responses correctly', async () => { const mockResponse = { result: 'OK', outputData: '[{"response": "Hello World"}]', errorData: '[]' };
fetch.mockResolvedValue({ ok: true, json: () => Promise.resolve(mockResponse) });
const result = await client.executeDiagram('123', {}); expect(result.outputData).toBe('[{"response": "Hello World"}]'); });
test('should handle job polling correctly', async () => { const jobId = 'test-job-123';
// Mock job still running, then completed fetch .mockResolvedValueOnce({ status: 202, json: () => Promise.resolve({ job: { id: jobId, status: 1 }}) }) .mockResolvedValueOnce({ status: 200, json: () => Promise.resolve({ result: 'OK', outputData: 'Job completed' }) });
const result = await client.pollJobStatus(jobId, 100); // Fast polling for test expect(result.outputData).toBe('Job completed'); });});```text## Environment-Specific Configurations
### Development vs Production Settings
**Configure your application** differently for different environments:
```javascriptconst config = { development: { polling: { jobInterval: 5000, // 5 seconds for faster development trainingInterval: 30000, // 30 seconds timeout: 300000 // 5 minutes }, logging: { level: 'debug', verbose: true }, retry: { maxAttempts: 3, baseDelay: 1000 } },
production: { polling: { jobInterval: 30000, // 30 seconds for production efficiency trainingInterval: 60000, // 1 minute timeout: 3600000 // 1 hour }, logging: { level: 'info', verbose: false }, retry: { maxAttempts: 5, baseDelay: 2000 } }};
const env = process.env.NODE_ENV || 'development';const currentConfig = config[env];```text## Security Checklist
### Pre-Production Security Review
Before deploying your Synthreo API integration to production:
- [ ] **Credentials are stored in environment variables or secure vaults**- [ ] **No API keys, passwords, or tokens in source code**- [ ] **All API communications use HTTPS**- [ ] **Token refresh logic is implemented and tested**- [ ] **Input validation and sanitization is in place**- [ ] **Error messages don't expose sensitive information**- [ ] **Logging excludes sensitive data (tokens, passwords)**- [ ] **Rate limiting and timeout configurations are appropriate**- [ ] **Circuit breaker patterns are implemented for resilience**- [ ] **Resource cleanup is properly handled**
By following these comprehensive best practices, you'll build more reliable, secure, and maintainable integrations with the Synthreo Builder API that can handle the complexities of real-world production environments.