Node.js SDK

@seenn/node — Full TypeScript support with fluent API

npm version GitHub stars MIT License

#Installation

npm install @seenn/node

Or with other package managers:

# yarn
yarn add @seenn/node

# pnpm
pnpm add @seenn/node

#SeennClient

The main client class for interacting with Seenn.

new SeennClient(config: SeennConfig)

Creates a new Seenn client instance.

Config Options
apiKey string Your secret API key (sk_live_xxx or sk_test_xxx)required
baseUrl string API base URL (default: https://api.seenn.io)
timeout number Request timeout in ms (default: 30000)
maxRetries number Max retry attempts (default: 3)
debug boolean Enable debug logging (default: false)
import { SeennClient } from '@seenn/node';

const seenn = new SeennClient({
  apiKey: process.env.SEENN_SECRET_KEY,
  debug: process.env.NODE_ENV === 'development',
});

#seenn.jobs.start()

seenn.jobs.start(params: StartJobParams): Promise<Job>

Start a new job. Returns a Job instance for fluent updates.

Parameters
userId string User ID who owns this jobrequired
jobType string Job type identifier (e.g., 'video-generation')required
title string Human-readable title shown to userrequired
metadata object Custom metadata (max 10KB)
queue QueueInfo Initial queue position info
stage StageInfo Initial stage info
estimatedCompletionAt string ISO 8601 timestamp for estimated completion
ttlSeconds number Time-to-live in seconds (default: 30 days)
const job = await seenn.jobs.start({
  userId: 'user_123',
  jobType: 'video-generation',
  title: 'Creating your video...',
  metadata: {
    prompt: 'A cat playing piano',
    quality: 'high',
  },
  stage: {
    name: 'initializing',
    current: 1,
    total: 3,
  },
});

console.log(job.id);  // 'job_abc123'

#seenn.jobs.get()

seenn.jobs.get(jobId: string): Promise<Job>

Get a job by ID. Useful for resuming updates in a different process.

// In a worker process
const job = await seenn.jobs.get('job_abc123');

console.log(job.status);    // 'running'
console.log(job.progress);  // 25

#seenn.jobs.list()

seenn.jobs.list(userId: string, options?): Promise<{ jobs: Job[], nextCursor?: string }>

List jobs for a user with pagination.

Options
limit number Max jobs to return (default: 20, max: 100)
cursor string Pagination cursor from previous response
const { jobs, nextCursor } = await seenn.jobs.list('user_123', {
  limit: 10,
});

for (const job of jobs) {
  console.log(job.title, job.status);
}

// Fetch next page
if (nextCursor) {
  const nextPage = await seenn.jobs.list('user_123', { cursor: nextCursor });
}

#Parent-Child Jobs

For batch processing workflows, you can create parent jobs that contain multiple child jobs. The parent's progress is automatically calculated from its children.

Use Cases: Batch image processing (e.g., 5-image packs), multi-stage video pipelines, multi-model AI workflows.

#seenn.jobs.createParent()

seenn.jobs.createParent(params: CreateParentParams): Promise<Job>

Create a parent job that will contain child jobs.

Parameters
userId string User ID who owns this jobrequired
jobType string Job type identifierrequired
title string Human-readable titlerequired
childCount number Total number of children (1-1000)required
childProgressMode 'average' | 'weighted' | 'sequential' How to calculate parent progress (default: 'average')
const parent = await seenn.jobs.createParent({
  userId: 'user_123',
  jobType: 'batch-processing',
  title: 'Christmas Image Pack (5 images)',
  childCount: 5,
  childProgressMode: 'average',
});

console.log(parent.isParent);  // true
console.log(parent.children);   // { total: 5, completed: 0, ... }

#seenn.jobs.createChild()

seenn.jobs.createChild(params: CreateChildParams): Promise<Job>

Create a child job under a parent.

Parameters
parentJobId string Parent job IDrequired
childIndex number 0-based index within parentrequired
userId string User ID (must match parent)required
jobType string Job type identifierrequired
title string Human-readable titlerequired
const child = await seenn.jobs.createChild({
  parentJobId: parent.id,
  childIndex: 0,
  userId: 'user_123',
  jobType: 'image-generation',
  title: 'Snowflake Image',
});

console.log(child.isChild);  // true
console.log(child.parent);   // { parentJobId: '...', childIndex: 0 }

// Update child progress - parent auto-updates!
await child.setProgress(50);
await child.complete();

#seenn.jobs.createBatch()

seenn.jobs.createBatch(params: CreateBatchParams): Promise<{ parent: Job, children: Job[] }>

Create a parent job and all children in one call. Best for batch processing workflows.

Parameters
userId string User ID who owns these jobsrequired
jobType string Job type identifier (same for parent and children)required
parentTitle string Title for the parent jobrequired
childTitles string[] Titles for each child job (array length = child count)required
childProgressMode 'average' | 'sequential' How to calculate parent progress (default: 'average')
metadata object Metadata for parent job

Complete Example: Glow Image Pack

This example shows a complete batch processing workflow for generating a 5-image Christmas pack:

// 1. Create batch job (parent + 5 children)
const { parent, children } = await seenn.jobs.createBatch({
  userId: 'user_123',
  jobType: 'image-pack',
  parentTitle: 'Christmas Pack (5 images)',
  childTitles: [
    'Snowflake',
    'Christmas Tree',
    'Santa Claus',
    'Reindeer',
    'Gift Box',
  ],
  childProgressMode: 'average',
  metadata: { packId: 'christmas-2026', style: 'watercolor' },
});

console.log('Parent ID:', parent.id);          // '01HXY...'
console.log('Children:', children.length);     // 5
console.log('Parent status:', parent.status); // 'pending'

// 2. Process children in parallel with your AI model
await Promise.all(children.map(async (child, index) => {
  try {
    // Update progress as AI processes
    await child.setProgress(10, { message: 'Starting generation...' });

    // Call your AI model
    const imageUrl = await generateImage(child.title, {
      onProgress: (pct) => child.setProgress(pct),
    });

    // Complete with result URL
    await child.complete({
      result: { type: 'image', url: imageUrl },
      message: 'Image ready!',
    });
  } catch (error) {
    // Fail individual child - parent continues!
    await child.fail({
      error: { code: 'GENERATION_FAILED', message: error.message },
      retryable: true,
    });
  }
}));

// 3. Check final results
await parent.refresh();
console.log('Parent status:', parent.status);       // 'completed' or 'failed'
console.log('Completed:', parent.children.completed); // 4
console.log('Failed:', parent.children.failed);       // 1
Auto-updates: When children update, the parent's progress and status automatically recalculate. Your mobile app receives SSE events for both child and parent changes.

#seenn.jobs.getWithChildren()

seenn.jobs.getWithChildren(parentJobId: string): Promise<{ parent: Job, children: ChildJobSummary[] }>

Get a parent job with all its children.

const { parent, children } = await seenn.jobs.getWithChildren(parentId);

console.log(parent.progress);            // 60 (average of children)
console.log(parent.childProgress);      // { completed: 3, running: 1, pending: 1, ... }

for (const child of children) {
  console.log(`Child ${child.childIndex}: ${child.status} (${child.progress}%)`);
}

#childProgressMode

The childProgressMode option determines how the parent job's progress is calculated from its children:

Mode Calculation Best For
'average' Sum of all child progress / total children Equal-weight tasks (e.g., batch image generation)
'sequential' (Completed children / total) × 100 Pipeline stages where partial progress doesn't matter
'weighted' Same as average (custom weights coming soon) Variable-size tasks (future feature)
// Example: 5 children, 3 at 100%, 1 at 50%, 1 at 0%
// Total progress: 100 + 100 + 100 + 50 + 0 = 350

// 'average' mode: 350 / 5 = 70%
// 'sequential' mode: 3 completed / 5 total = 60%

#Failed Child Behavior

Seenn allows partial success - not every child needs to complete for the parent to succeed.

Parent Status Rules

Scenario Parent Status Example
All children completed completed 5/5 completed, 0 failed
Some children failed, some completed completed 4/5 completed, 1 failed (partial success)
ALL children failed failed 0/5 completed, 5 failed
Still processing running 2 completed, 1 running, 2 pending
Key Rule: Parent fails ONLY when completed === 0 AND all children are in terminal state. If even ONE child completes, the parent completes (with partial results).

Progress Calculation with Failed Children

  • Failed children count toward progress - Their last progress value is used
  • Example: 3 at 100%, 1 at 50% (failed at 50%), 1 at 0% (failed immediately) = (100+100+100+50+0)/5 = 70%
  • Use parent.children.failed to check how many failed

Handling Partial Failures

// After batch processing completes
const { parent, children } = await seenn.jobs.getWithChildren(parentId);

const { completed, failed, total } = parent.children;

if (parent.status === 'completed' && failed > 0) {
  // Partial success - some children failed
  console.log(`Pack completed with ${completed}/${total} images`);

  // Get successful results
  const successfulImages = children
    .filter(c => c.status === 'completed')
    .map(c => c.result?.url);

  // Get failed children for retry
  const failedChildren = children.filter(c => c.status === 'failed');
  for (const child of failedChildren) {
    console.log(`"${child.title}" failed: ${child.error?.message}`);
    // Optionally offer retry to user
  }
} else if (parent.status === 'failed') {
  // Total failure - ALL children failed
  console.log('Pack generation completely failed');
}

#job.setProgress()

job.setProgress(progress: number, options?: ProgressOptions): Promise<Job>

Update job progress. Automatically sets status to running if pending.

Parameters
progress number Progress percentage (0-100)required
message string Status message to show user
stage StageInfo Current stage info { name, current, total, description? }
queue QueueInfo Queue position { position, total?, queueName? }
estimatedCompletionAt string Updated ETA (ISO 8601)
metadata object Additional metadata (merged with existing)
// Simple progress update
await job.setProgress(50);

// With message
await job.setProgress(50, { message: 'Processing frames...' });

// With stage info
await job.setProgress(66, {
  message: 'Rendering video...',
  stage: {
    name: 'rendering',
    current: 2,
    total: 3,
    description: 'Combining frames into video',
  },
});

// Fluent chaining
await job
  .setProgress(25, { message: 'Step 1...' })
  .then(j => j.setProgress(50, { message: 'Step 2...' }))
  .then(j => j.setProgress(75, { message: 'Step 3...' }));

#job.complete()

job.complete(options?: CompleteOptions): Promise<Job>

Mark job as completed with optional result data.

Options
result JobResult Result data { type?, url?, data? } (max 100KB)
message string Completion message
// Simple completion
await job.complete();

// With result URL
await job.complete({
  result: {
    type: 'video',
    url: 'https://cdn.example.com/output.mp4',
  },
});

// With custom data
await job.complete({
  result: {
    type: 'analysis',
    data: {
      sentiment: 'positive',
      confidence: 0.95,
      keywords: ['happy', 'success'],
    },
  },
  message: 'Analysis complete!',
});

#job.fail()

job.fail(options: FailOptions): Promise<Job>

Mark job as failed with error details.

Options
error JobError Error details { code, message, details? }required
retryable boolean Whether the job can be retried (default: false)
try {
  // Processing...
} catch (err) {
  await job.fail({
    error: {
      code: 'PROCESSING_ERROR',
      message: 'Failed to generate video',
      details: { reason: err.message },
    },
    retryable: true,
  });
}

#Self-Hosted Backend

Using your own backend instead of Seenn Cloud? Configure the SDK to point to your API:

const seenn = new SeennClient({
  apiKey: process.env.SEENN_SECRET_KEY,
  // Point to your own backend
  baseUrl: 'https://api.yourcompany.com',
});
Open Source: The SDK is MIT licensed. You can use it with Seenn Cloud or your own backend implementation. Check GitHub for the API spec your backend needs to implement.

#Backend Requirements

Your self-hosted backend must implement these endpoints:

Endpoint Description
POST /v1/jobs Create a job (supports parent-child params)
GET /v1/jobs/:id Get job by ID
GET /v1/jobs/:parentId/children Get parent with all children
POST /v1/jobs/:id/progress Update job progress
POST /v1/jobs/:id/complete Mark job as completed
POST /v1/jobs/:id/fail Mark job as failed

#Error Handling

The SDK throws typed errors for different scenarios:

import {
  SeennError,
  ValidationError,
  NotFoundError,
  RateLimitError,
} from '@seenn/node';

try {
  await seenn.jobs.get('nonexistent');
} catch (err) {
  if (err instanceof NotFoundError) {
    console.log('Job not found');
  } else if (err instanceof RateLimitError) {
    console.log('Rate limited, retry after:', err.retryAfter);
  } else if (err instanceof ValidationError) {
    console.log('Invalid input:', err.message);
  }
}