Integrate deployed ComfyUI workflows into JavaScript and TypeScript applications using the FlowScale SDK.
Production Security Best PracticesYour AI workflows are now business infrastructure—protect them accordingly. When integrating into client-side applications, your API keys become visible to users, which could lead to unauthorized usage and unexpected costs.Recommended Architecture:
  • Backend Integration: Use this SDK in Node.js servers where API keys remain secure
  • Proxy Pattern: Create your own API endpoints that internally call FlowScale AI
  • Rate Limiting: Control usage to prevent abuse and manage costs
If you must use client-side: Set allowDangerouslyExposeApiKey: true to acknowledge the security implications.

Installation

npm install flowscale

Quick Start

Connect to your deployed workflows:
import { FlowscaleAPI } from 'flowscale';

// Store API keys in environment variables
const apiKey = process.env.FLOWSCALE_API_KEY;
const apiUrl = process.env.FLOWSCALE_API_URL;

const flowscale = new FlowscaleAPI({
  apiKey,
  baseUrl: apiUrl
});

Environment Variables

Add the following to your .env file:
FLOWSCALE_API_KEY=your-api-key
FLOWSCALE_API_URL=https://your-api-url.pod.flowscale.ai

Architecture Patterns for Production Applications

Choose the integration pattern that matches your application’s needs:

Configuration

const flowscale = new FlowscaleAPI({
  apiKey: process.env.FLOWSCALE_API_KEY,
  baseUrl: process.env.FLOWSCALE_API_URL,
  timeout: 60000,
  retries: 3
});

Core Methods

Health Check

const health = await flowscale.checkHealth();

Get Queue Status

Retrieve the current workflow queue status:
const queue = await flowscale.getQueue();
console.log('Queue Details:', queue);

Workflow Management

List Available Workflows

Get all deployed workflows in your cluster:
const workflows = await flowscale.getWorkflows();
console.log('Available Workflows:', workflows);

Executing Workflows

Execute Workflow (Synchronous)

Trigger a workflow execution and get immediate response:
const workflowId = "550e8400-e29b-41d4-a716-446655440000";
const groupId = "test_group"; // Optional for organizing runs

const inputs = {
  "text_51536": "A beautiful sunset over mountains",
  "image_1234": fileOrBlob, // File or Blob object
  "video_1239": videoFile   // File or Blob object
};

const result = await flowscale.executeWorkflow(workflowId, inputs, groupId);
console.log('Workflow Result:', result);

Execute Workflow (Asynchronous with Auto-Polling)

Execute a workflow and automatically wait for the result:
const workflowId = "550e8400-e29b-41d4-a716-446655440000";
const inputs = {
  "text_51536": "A serene lake at dawn",
  "image_1234": imageFile
};

try {
  // Auto-polls every 2 seconds, times out after 10 minutes by default
  const result = await flowscale.executeWorkflowAsync(
    workflowId,
    inputs,
    "my_group",     // groupId (optional)
    2000,           // pollIntervalMs (optional)
    600000          // timeoutMs (optional)
  );
  console.log('Workflow Result:', result);
} catch (error) {
  if (error.message.includes('timed out')) {
    console.error('Workflow took too long to complete');
  } else {
    console.error('Workflow error:', error);
  }
}
Migration Notice - executeWorkflowAsync Response FormatStarting from v1.0.18, executeWorkflowAsync automatically detects single vs multiple outputs and returns the appropriate format for backward compatibility:
  • Single Output: Returns legacy format with deprecation warning
  • Multiple Outputs: Returns new array format
For new applications, use the new array format which supports multiple outputs.

Managing Workflow Runs

Get Run Details

Retrieve detailed information about a specific run:
const runDetails = await flowscale.getRun('808f34d0-ef97-4b78-a00f-1268077ea6db');
console.log('Run Details:', runDetails);

Get Multiple Runs

Retrieve runs by group ID or get all runs:
// Get runs for a specific group
const runs = await flowscale.getRuns('test_group');
console.log('Runs for Group:', runs);

// Get all runs for the team
const allRuns = await flowscale.getRuns();
console.log('All Runs:', allRuns);

Cancel Running Workflow

Cancel a workflow execution using its run ID:
const result = await flowscale.cancelRun('808f34d0-ef97-4b78-a00f-1268077ea6db');
console.log('Cancellation Result:', result);

Retrieving Outputs

Get Workflow Output

Fetch the output of a completed workflow:
const output = await flowscale.getOutput('filename_prefix_58358_5WWF7GQUYF.png');
console.log('Workflow Output:', output);

TypeScript Support

Type Definitions

The SDK includes comprehensive TypeScript definitions:
interface WorkflowInput {
  [key: string]: string | number | boolean | File | Blob;
}

interface WorkflowResult {
  id: string;
  status: 'completed' | 'failed' | 'running';
  images?: Array<{
    url: string;
    width: number;
    height: number;
    format: string;
  }>;
  outputs?: { [key: string]: any };
  metadata?: {
    executionTime: number;
    gpuType: string;
    cost: number;
  };
}

interface ExecutionOptions {
  timeout?: number;
  webhookUrl?: string;
  priority?: 'low' | 'normal' | 'high';
  metadata?: { [key: string]: any };
}

Workflow-Specific Types

Generate types for your specific workflows:
// Define types for your workflow
interface TextToImageInputs {
  prompt: string;
  negative_prompt?: string;
  width: number;
  height: number;
  steps: number;
  cfg_scale: number;
  seed?: number;
}

interface TextToImageResult extends WorkflowResult {
  images: Array<{
    url: string;
    width: number;
    height: number;
    seed: number;
  }>;
}

// Use with the SDK
const result = await flowscale.executeWorkflow('text-to-image-v2', {
  prompt: 'A majestic mountain range',
  width: 1024,
  height: 1024,
  steps: 20,
  cfg_scale: 7.5
});

// TypeScript knows the shape of the result
const imageUrl: string = result.images[0].url;

File Handling

Uploading Files

Handle image and file uploads seamlessly across different environments:
// Browser file upload from input element
const fileInput = document.getElementById('imageUpload') as HTMLInputElement;
const file = fileInput.files?.[0];

if (file) {
  const result = await flowscale.executeWorkflow('image-to-image', {
    image: file,              // Direct file upload
    prompt: 'Make it artistic',
    strength: 0.7
  });
}

Downloading Results

// Get output URL from workflow result
const output = await flowscale.getOutput('filename_prefix_58358_5WWF7GQUYF.png');
const imageUrl = output.data.download_url;

// Download image as blob (browser)
const response = await fetch(imageUrl);
const imageBlob = await response.blob();

// Save to file (Node.js)
import fs from 'fs';
const buffer = Buffer.from(await imageBlob.arrayBuffer());
fs.writeFileSync('generated-image.png', buffer);

// Display in browser
const objectUrl = URL.createObjectURL(imageBlob);
document.getElementById('result-image').src = objectUrl;

Error Handling

Comprehensive Error Handling

import { FlowScaleError, NetworkError, ValidationError } from 'flowscale';

try {
  const result = await flowscale.executeWorkflow('my-workflow', {
    prompt: 'A beautiful landscape'
  });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Invalid inputs:', error.details);
    // Handle validation errors (e.g., show user-friendly messages)
  } else if (error instanceof NetworkError) {
    console.error('Network error:', error.message);
    // Handle network issues (e.g., retry logic)
  } else if (error instanceof FlowScaleError) {
    console.error('FlowScale API error:', error.message, error.code);
    // Handle API-specific errors
  } else {
    console.error('Unexpected error:', error);
  }
}

Retry Logic

async function executeWithRetry(workflowId: string, inputs: any, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await flowscale.executeWorkflow(workflowId, inputs);
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      // Exponential backoff
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
      
      console.log(`Retry attempt ${attempt + 1}/${maxRetries}`);
    }
  }
}

React Integration

Custom Hook

Create a React hook for easy workflow execution:
import { useState, useCallback } from 'react';
import { FlowscaleAPI } from 'flowscale';

const flowscale = new FlowscaleAPI({
  apiKey: process.env.REACT_APP_FLOWSCALE_API_KEY,
  baseUrl: process.env.REACT_APP_FLOWSCALE_API_URL
});

export function useFlowScale() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const executeWorkflow = useCallback(async (workflowId: string, inputs: any, groupId?: string) => {
    setLoading(true);
    setError(null);
    
    try {
      const result = await flowscale.executeWorkflowAsync(workflowId, inputs, groupId);
      return result;
    } catch (err) {
      setError(err as Error);
      throw err;
    } finally {
      setLoading(false);
    }
  }, []);

  return { executeWorkflow, loading, error };
}

Component Example

import React, { useState } from 'react';
import { useFlowScale } from './hooks/useFlowScale';

export function ImageGenerator() {
  const [prompt, setPrompt] = useState('');
  const [result, setResult] = useState<any>(null);
  const { executeWorkflow, loading, error } = useFlowScale();

  const generateImage = async () => {
    try {
      const result = await executeWorkflow('text-to-image', {
        text_51536: prompt
      });
      setResult(result);
    } catch (error) {
      console.error('Generation failed:', error);
    }
  };

  return (
    <div>
      <input
        value={prompt}
        onChange={(e) => setPrompt(e.target.value)}
        placeholder="Enter your prompt..."
      />
      <button onClick={generateImage} disabled={loading}>
        {loading ? 'Generating...' : 'Generate Image'}
      </button>
      
      {error && <div className="error">{error.message}</div>}
      
      {result?.data?.download_url && (
        <img src={result.data.download_url} alt="Generated" />
      )}
    </div>
  );
}

WebSocket Support

The SDK provides WebSocket functionality for real-time communication:

Connecting to WebSocket

// Connect to WebSocket and set up event handlers
const disconnect = flowscale.connectWebSocket({
  onOpen: () => {
    console.log('WebSocket connected!');
  },
  onMessage: (message) => {
    console.log('Received message:', message);
    if (message.type === 'run_status_update') {
      console.log('Run status updated:', message.data);
    }
  },
  onClose: (event) => {
    console.log('WebSocket closed:', event.code, event.reason);
  },
  onError: (error) => {
    console.error('WebSocket error:', error);
  }
});

// Later, to disconnect:
disconnect();

Sending Messages

const success = flowscale.sendWebSocketMessage({
  type: 'client_event',
  data: {
    action: 'subscribe',
    runId: '808f34d0-ef97-4b78-a00f-1268077ea6db'
  }
});

if (success) {
  console.log('Message sent successfully');
} else {
  console.error('Failed to send message');
}

Security Features

Proxy Mode Configuration

For secure frontend usage, use proxy mode to keep your API key safe:
// ✅ SECURE: No API key exposed to frontend users
const flowscale = new FlowscaleAPI({
  baseUrl: 'https://your-backend-proxy.com', // Your secure backend proxy
  proxyMode: true, // Enable proxy mode
  customHeaders: {
    'Authorization': 'Bearer jwt-token-here' // Use JWT tokens instead
  }
});

Dynamic Header Management

// Update JWT token without recreating SDK instance
flowscale.updateCustomHeaders({
  'Authorization': 'Bearer new-jwt-token'
});

// Get current headers
const headers = flowscale.getCustomHeaders();

// Clear all custom headers
flowscale.clearCustomHeaders();

Best Practices

Environment Configuration

Always store sensitive information such as API keys in environment variables:
# .env file
FLOWSCALE_API_KEY=your-api-key
FLOWSCALE_API_URL=https://your-api-url.pod.flowscale.ai
// Use environment variables
const flowscale = new FlowscaleAPI({
  apiKey: process.env.FLOWSCALE_API_KEY,
  baseUrl: process.env.FLOWSCALE_API_URL
});

Performance Optimization

// Reuse client instances
const flowscale = new FlowscaleAPI({ 
  apiKey: process.env.FLOWSCALE_API_KEY,
  baseUrl: process.env.FLOWSCALE_API_URL 
});

// Use connection pooling for high-volume applications
const flowscale = new FlowscaleAPI({
  apiKey: process.env.FLOWSCALE_API_KEY,
  baseUrl: process.env.FLOWSCALE_API_URL,
  httpAgent: new https.Agent({ keepAlive: true })
});

// Cache results when appropriate
const cache = new Map();

async function getCachedResult(cacheKey: string, workflowId: string, inputs: any) {
  if (cache.has(cacheKey)) {
    return cache.get(cacheKey);
  }
  
  const result = await flowscale.executeWorkflowAsync(workflowId, inputs);
  cache.set(cacheKey, result);
  return result;
}

Security

// Never expose API keys in client-side code
// Use environment variables or secure key management

// Validate inputs on both client and server
function validateInputs(inputs: any) {
  if (!inputs.prompt || inputs.prompt.length > 1000) {
    throw new Error('Invalid prompt');
  }
  // Add more validation...
}

// Always use HTTPS
const flowscale = new FlowscaleAPI({
  apiKey: process.env.FLOWSCALE_API_KEY,
  baseUrl: 'https://your-api-url.pod.flowscale.ai' // Always use HTTPS
});

Testing and Debugging

  • Test workflows in a development environment before deploying to production
  • Validate inputs to ensure they match the workflow requirements
  • Wrap API calls in try-catch blocks to handle errors gracefully
  • Use the SDK’s logging functionality for debugging:
// Enable debug logging for development
const flowscale = new FlowscaleAPI({
  apiKey: process.env.FLOWSCALE_API_KEY,
  baseUrl: process.env.FLOWSCALE_API_URL,
  enableLogging: true,
  logLevel: 'debug'
});

Batch Processing

Process multiple inputs efficiently:
const prompts = [
  'A sunset over the ocean',
  'A mountain landscape', 
  'A city skyline at night'
];

const results = await Promise.all(
  prompts.map(prompt => 
    flowscale.executeWorkflowAsync('text-to-image', {
      text_51536: prompt
    })
  )
);

console.log('Generated', results.length, 'images');

Examples & Recipes

Support

Need help with the JavaScript SDK?