Skip to main content
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();
{
  "status": "success",
  "data": [
    {
      "container": "container #1",
      "status": "idle"
    },
    {
      "container": "container #2", 
      "status": "running"
    }
  ]
}

Get Queue Status

Retrieve the current workflow queue status:
const queue = await flowscale.getQueue();
console.log('Queue Details:', queue);
{
  "status": "success",
  "data": [
    {
      "container": "container #1",
      "queue": {
        "queue_running": [
          [0, "2a0babc4-acce-4521-9576-00fa0e6ecc91"]
        ],
        "queue_pending": [
          [1, "5d60718a-7e89-4c64-b32d-0d1366b44e2a"]
        ]
      }
    }
  ]
}

Workflow Management

List Available Workflows

Get all deployed workflows in your cluster:
const workflows = await flowscale.getWorkflows();
console.log('Available Workflows:', workflows);
{
  "status": "success",
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Text to Image",
      "description": "Generate an image from text input",
      "inputs": "{ \"text_51536\": \"string\" }",
      "outputs": "{ \"image_output\": \"image\" }"
    },
    {
      "id": "660f9411-f30c-52e5-b827-557766551111", 
      "name": "Image to Image",
      "description": "Transform an existing image",
      "inputs": "{ \"image_input\": \"image\", \"prompt\": \"string\" }",
      "outputs": "{ \"transformed_image\": \"image\" }"
    }
  ]
}

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);
{
  "status": "success",
  "data": {
    "run_id": "808f34d0-ef97-4b78-a00f-1268077ea6db",
    "workflow_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

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.
{
  "status": "success",
  "data": [
    {
      "filename": "output_image_1.png",
      "download_url": "https://runs.s3.amazonaws.com/generations/...", 
      "generation_status": "success"
    },
    {
      "filename": "output_image_2.png",
      "download_url": "https://runs.s3.amazonaws.com/generations/...",
      "generation_status": "success"
    }
  ]
}

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);
{
  "status": "success",
  "data": {
    "_id": "808f34d0-ef97-4b78-a00f-1268077ea6db",
    "status": "completed",
    "inputs": [
      {
        "path": "text_51536",
        "value": "a man riding a bike"
      }
    ],
    "outputs": [
      {
        "filename": "filename_prefix_58358_5WWF7GQUYF.png",
        "url": "https://runs.s3.amazonaws.com/generations/..."
      }
    ]
  }
}

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);
{
  "status": "success",
  "data": {
    "group_id": "test_group",
    "count": 2,
    "runs": [
      {
        "_id": "cc29a72d-75b9-4c7b-b991-ccaf2a04d6ea",
        "status": "completed",
        "outputs": [
          {
            "filename": "filename_prefix_58358_G3DRLIVVYP.png",
            "url": "https://runs.s3.amazonaws.com/generations/..."
          }
        ]
      }
    ]
  }
}

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);
{
  "status": "success",
  "data": "Run cancelled successfully"
}

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);
{
  "status": "success",
  "data": {
    "download_url": "https://runs.s3.amazonaws.com/generations/...",
    "generation_status": "success"
  }
}

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
  • URL Input
  • Base64 Input
// 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?
I