npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sogni-ai/sogni-client

v5.58.1

Published

Sogni SDK - AI image, video & audio generation plus LLM chat with vision via the Sogni Supernet (Stable Diffusion, Flux, WAN, LTX-2, Seedance, HappyHorse, Qwen VLM)

Readme

Sogni SDK for JavaScript & Node.js

This library provides an easy way to interact with the Sogni Supernet - a DePIN protocol for creative AI inference. It is written in TypeScript and can be used in both TypeScript and JavaScript projects such as backend Node.js and browser environments.

Behind the scenes this SDK uses a WebSocket connection for communication between clients, server, and workers. It harnesses an event-based API to interact with Supernet to make things super efficient.

Features

  • 🎨 Image Generation - Create images with the latest frontier Open Source models like Stable Diffusion, Qwen Image, Z-Image Turbo, and Flux
  • 🎨 Image Edit - Modify, merge, restyle, and transform images using prompts and/or multiple reference images using powerful models like Qwen Image Edit.
  • 🎬 Video Generation - Generate videos using Wan 2.2 14B FP8 models with five workflow types:
    • Text-to-Video (t2v) - Generate videos from text prompts
    • Image-to-Video (i2v) - Animate static images
    • Sound-to-Video (s2v) - Generate videos synchronized with audio
    • Animate-Move - Transfer motion from reference video to image subject
    • Animate-Replace - Replace subjects in videos while preserving motion
  • ⚡ Fast & Relaxed Networks - Choose between high-speed GPU network or cost-effective Mac network
  • 🔄 Real-time Progress - Event-based API with progress tracking and live updates
  • 🎯 Advanced Controls - Fine-tune generation with samplers, schedulers, ControlNets, and more
  • 🤖 LLM Text Generation - Chat completions with streaming, multi-turn conversations, and thinking/reasoning mode via OpenAI-compatible API
  • 🔧 LLM Tool Calling - Define custom tools (functions) that the LLM can invoke during conversations for real-time data and actions
  • 🎨🎬🎵 Sogni Platform Tools - Generate images, reference-guided image edits, videos, audio-driven videos, video transforms, and music through natural language chat
  • 🤖 Hosted Creative Tools - Use the default creative-tools surface for media generation, editing, analysis, metadata, prompt enhancement, script writing, lyrics, and instrumental structures; set sogni_tools: "creative-agent" to add workflow control and asset-manifest tools
  • ⏱️ Durable Creative Workflows - Persistent server-side multi-step workflows with SSE event streaming, Last-Event-ID resume, and cooperative cancellation via /v1/creative-agent/workflows
  • 👁️ Vision Chat - Multimodal image understanding with scene description, OCR, object detection, visual analysis, and multi-image comparison via Qwen3.6 VLM

Migration notes

v3.x.x to v4.x.x

Version 4 adds support for video generation, including the new Wan 2.2 14B FP8 model family with five workflow types (text-to-video, image-to-video, sound-to-video, animate-move, and animate-replace). There are the following breaking changes:

  • type is required when calling sogni.projects.create(params), valid values are image, video and audio. See code examples below.
  • numberOfImages renamed to numberOfMedia
  • hasResultImage in Job class is now hasResultMedia
  • Job and Project classes now have type property that can be image, video or audio

Installation

Add library to your project using npm or yarn:

npm install @sogni-ai/sogni-client

or

yarn add @sogni-ai/sogni-client

Core concepts

In order to use Sogni Supernet, you need an active Sogni account with a positive SOGNI or Spark token balance. You can authenticate using either an API key (recommended) or username and password. You can create a free account in our Web App or Mac App which will give you tokens just for signing up and confirming your email. Each email-verified account is allowed 400 free Spark render credits per month. On the API, free credits can be used with Krea 2 Turbo; paid credits can access all models and features.

To get your API key: Log in to dashboard.sogni.ai, click your Username dropdown in the top-right corner, and provision your API key.

Spark tokens can be purchased with a credit card in a Mac or Web app.

Your account is tied to a Base Wallet that is created during signup.

Supernet Types

There are 2 worker network types available:

  • fast - this network runs on high-end GPUs and is optimized for speed. It is more expensive than relaxed network. Required for video generation.
  • relaxed - this network runs on Apple Mac devices and is optimized for cost. It is cheaper than fast network. Supports image generation only.

In both options, the more complex your query is (the more steps), the higher the cost in tokens.

Inference definitions: Projects and Jobs

One request for image or video generation is called a Project. A project can generate one or more images or videos. Each generated image or video is represented by a Job.

When you send a project to Supernet, it will be processed by one or more workers. The resulting media will be encrypted and uploaded to Sogni servers where it will be stored for 24 hours. After this period, media files will be auto-deleted.

Client initialization

To initialize a WebSocket client, provide a stable appId and account credentials. Generate the ID once per application installation and persist it across process restarts or page reloads.

Option 1: API Key Authentication (Recommended)

API key authentication is the simplest way to connect. The client auto-authenticates via the WebSocket connection — no separate login() call is needed.

Get your API key: Log in to dashboard.sogni.ai and click your Username dropdown in the top-right corner to provision your key.

import { SogniClient } from '@sogni-ai/sogni-client';

const sogni = await SogniClient.createInstance({
  appId: 'my-app-installation', // Stable across restarts; do not generate a new value per run
  appSource: 'my-app',
  network: 'fast', // Network to use, 'fast' or 'relaxed'
  apiKey: 'your-api-key' // API key for authentication
});

// No login() call needed — the client is authenticated automatically
const models = await sogni.projects.waitForModels();

Note: With API key auth, most REST API calls (balance, profile, etc.) are available. Sensitive account operations (withdrawals, staking, 2FA) are not available with API key auth.

Option 2: Username & Password Authentication

import { SogniClient } from '@sogni-ai/sogni-client';

const sogni = await SogniClient.createInstance({
  appId: 'my-app-installation',
  appSource: 'my-app',
  network: 'fast'
});

await sogni.account.login('your-username', 'your-password');
const models = await sogni.projects.waitForModels();

Important Note:

  • These samples assume you are using ES modules, which allow await on the top level, if you are CommonJS you will need to wrap await calls in an async function.
  • appId identifies one application installation. Reuse the same value across restarts and reloads.
  • If you generate an appId, generate it once and save it (for example in browser localStorage). Do not call randomUUID() every time the application starts.
  • Only one connection per appId is allowed. If you try to connect with the same appId multiple times, the previous connection will be closed.

REST-only clients

Set disableSocket: true when a process uses only REST APIs. This mode opens no artist WebSocket and does not require an appId:

const sogni = await SogniClient.createInstance({
  apiKey: 'your-api-key',
  disableSocket: true
});

Socket-backed project generation and chat completions are unavailable in REST-only mode. Hosted chat, durable workflows, replay records, announcements, and account REST APIs remain available.

Connection metadata and event subscriptions

Use appSource to identify the product or integration behind a client connection. The SDK forwards it during authentication and on socket-backed project/chat requests so server-side reporting can attribute usage consistently.

By default, the SDK receives the standard socket event stream, including live model worker counts through swarmModels and swarmLLMModels. Proxy, server-side, or headless clients that do not need ongoing worker count updates can opt out of the grouped model availability stream:

const sogni = await SogniClient.createInstance({
  appId: 'your-app-id',
  appSource: 'my-integration',
  network: 'fast',
  apiKey: 'your-api-key',
  socketEventSubscriptions: {
    modelAvailability: false
  }
});

If your process needs the initial model list before submitting work, keep the default subscription, wait for models, then unsubscribe from future count updates:

const models = await sogni.projects.waitForModels();

await sogni.setSocketEventSubscriptions({
  modelAvailability: false
});

modelAvailability is a subscription group covering swarmModels and swarmLLMModels. You can also opt in or out of individual socket event names with the same boolean map, and omitted subscriptions preserve default server behavior.

Group flags dominate individual flags. Disabling a group (e.g. modelAvailability: false) suppresses every event in the group, and re-enabling a single event under that group later (e.g. swarmModels: true) does not override the group-level suppression — the group flag still wins. To re-enable a single event, re-enable the group it belongs to (or clear it via setSocketEventSubscriptions({ reset: true }) before reapplying selective subscriptions). Subscriptions you do not list keep their current server-side state.

Runtime subscription changes made via setSocketEventSubscriptions are remembered locally and re-applied on every reconnect, so a long-lived client only needs to express its preference once.

Current project queue explanations subscribe automatically on supported servers. Use the dedicated event for explanations about subscription slots, payment confirmation, or worker availability:

sogni.projects.on('queueChanged', ({ projectId, waitingReason, jobWaitingReasons }) => {
  console.info(projectId, waitingReason?.message ?? '');
  // Each entry identifies one queued result by its zero-based jobIndex.
  // imgID is optional until a worker assigns it.
  for (const entry of jobWaitingReasons) {
    console.info(entry.jobIndex, entry.waitingReason.message);
  }
});

The same current fields are available on Project.waitingReason, Project.jobWaitingReasons, and their serialized snapshot; known pending jobs also expose Job.waitingReason. The result list is complete, so removed entries clear earlier explanations. These details also cover the remaining queued results in a partially running batch, without changing its status or creating jobs early. Display messages as plain text. Prefer them to queueStatus when explaining a wait: the older worker estimate does not account for subscription or payment constraints. Older servers provide no explanation, and a free slot does not promise immediate processing.

Set socketEventSubscriptions: { projectQueue: false } to opt out. A runtime subscription reset disables this optional stream; subscribe again with { projectQueue: true } if needed.

User-facing subscription limit notices are opt-in. Enable the event when a client needs live queue, concurrency, or fair-use messaging:

await sogni.setSocketEventSubscriptions({ subscriptionLimitNotice: true });

sogni.apiClient.socket.on('subscriptionLimitNotice', (notice) => {
  console.info(notice.message);
});

Agent integrations can optionally declare connection and workload attribution without changing appSource. Defaults are immutable and per-request overrides are isolated, so concurrent operations cannot leak lineage into one another:

const sogni = await SogniClient.createInstance({
  appId: 'your-app-id',
  appSource: 'my-agent-integration',
  apiKey: 'your-api-key',
  attribution: {
    connection: {
      interactionKind: 'external_agent',
      agentFramework: 'codex',
      agentSurface: 'plugin'
    },
    workload: {
      workloadKind: 'agent_mediated',
      agentFramework: 'codex',
      agentSurface: 'plugin',
      executionMode: 'server'
    }
  }
});

await sogni.projects.create({
  type: 'image',
  modelId: 'z_image_turbo_bf16',
  positivePrompt: 'A cinematic mountain observatory',
  numberOfMedia: 1,
  attribution: {
    operationScope: 'child',
    rootOperationId: 'turn-123',
    parentOperationId: 'tool-call-456'
  }
});

The SDK supplies the project/job operation ID when it is omitted. Standalone attributed calls default to top_level; child calls should provide their stable root and immediate parent IDs. All attribution is optional, normalized again by the server, and excluded entirely from the wire for callers that do not configure it.

Usage

After authentication, the client will have an active WebSocket connection to Sogni Supernet. Within a short period of time the client will receive the current balance and list of available models. After this you can start using the client to generate images or videos.

It is advised to watch for connected and disconnected events on the client instance to be notified when the connection is established or lost:

// Will be triggered when the client is connected to Supernet
sogni.client.on('connected', ({ network }) => {
  console.log('Connected to Supernet:', network);
});

// Will be triggered when websocket connection is lost or the client is disconnected from Supernet
sogni.client.on('disconnected', ({ code, reason }) => {
  console.log('Disconnected from Supernet:', code, reason);
});

Subscription entitlements

The account API exposes the public subscription plan catalog and the current wallet's entitlement snapshot:

const plans = await sogni.account.getSubscriptionPlans();

const subscription = await sogni.account.refreshSubscription();
if (sogni.account.currentAccount.isUnlimited) {
  console.log('Unlimited tier:', subscription.tier);
}

currentAccount.isUnlimited is true when the latest entitlement snapshot has active: true and tier is either unlimited or unlimited_pro. The server keeps active true for entitled states until access actually ends: trials and cancel-at-period-end windows remain entitled, with currentPeriodEnd reflecting the paid-through date. Canceling during a free trial stops renewal and keeps trial access until its original currentPeriodEnd, with trial limits still in effect: the snapshot stays trialing with cancelAtPeriodEnd: true and active: true until expiry. A grace_period snapshot is never entitled and returns active: false: it means the provider (Apple billing grace / Google Play grace / Stripe retries) is retrying the renewal payment, and unlimited render access is paused while the retry is in progress — render submissions under the plan return a specific error from the platform explaining that the renewal payment is being retried and that unlimited access resumes once it succeeds. Renders can still be paid with Spark/SOGNI in the meantime, and unlimited access resumes automatically when the renewal succeeds. During grace the snapshot's effective period end indicates the payment-retry window, not access. Period dates are ISO timestamp strings.

A canceled-but-still-paid subscription carries cancelAtPeriodEnd: true and keeps access until currentPeriodEnd. When a downgrade or plan switch is scheduled for the next renewal, the snapshot may also carry scheduledTier, scheduledTerm, and scheduledChangeAt (ISO timestamp) — absent when no change is pending — so UIs can render "Your plan will change to X on date" messaging while the current tier keeps its benefits.

When a job is explicitly submitted with billingMode: 'subscription' and the subscription cannot cover it, the platform rejects the job with a subscription-specific error code, exported as SUBSCRIPTION_ERROR_CODES from the package root:

  • 4078 (NOT_ENTITLED) — no active subscription entitlement covers the job.
  • 4079 (QUEUE_CAP) — the subscription's concurrent job queue cap was reached.
  • 4080 (GRACE_RETRY) — the subscription is in its billing-grace window: the renewal payment is being retried and unlimited access is paused until it succeeds. On 4080, offer the user a "pay with Spark/SOGNI" fallback (token billing) instead of auto-retrying the subscription job in a loop — it will keep failing until the renewal succeeds.

billingMode ('auto' | 'subscription' | 'tokens', exported as BillingMode) is accepted by project params, creative workflows (sogni.workflows.start(), resume(), and reseed(), where it serializes as billing_mode), and all three chat transports: sogni.chat.completions.create() (socket), sogni.chat.hosted.create() (REST /v1/chat/completions), and sogni.chat.runs.create() (durable runs, where it serializes as billing_mode).

Chat job failures preserve this error contract. Streamed and non-streaming chat completions, hosted REST chat, and durable chat runs fail with a ChatJobError (exported from the package root): .message stays the human-readable server message, while code/errorCode carry the wire code string (e.g. '4080'), errorType carries the server's tag (e.g. 'subscription_unavailable'), and the subscriptionErrorCode getter maps the code back to the numeric SUBSCRIPTION_ERROR_CODES value when applicable — so apps can branch without string-matching.

Unlimited fair-use accounting and enforcement remain dynamic and server-authoritative. While a monthly Fast-network limit is active, the entitlement snapshot includes an ephemeral fairUse object with the subscriber's current usage, plan reference price, reset timestamp, effective Fast queue/concurrency limits, Relaxed-network availability, and upgrade availability. The field is absent when no limit is active. Clients should use it for current user-facing messaging only, never persist it as policy, infer a threshold from it, or use it to authorize work.

To start Stripe checkout, use a plan's planId (unlimited or unlimited_pro) and term (monthly or annual). Checkout and portal sessions require user authentication; API-key auth is rejected for those browser redirect operations.

const { url } = await sogni.account.createSubscriptionCheckout('unlimited_pro', 'annual', {
  redirectType: 'web',
  appSource: 'my-integration'
});
window.location.href = url;

const portal = await sogni.account.createSubscriptionPortalSession();
window.location.href = portal.url;

Image Generation

Sogni supports a wide range of models for image generation. You can find a list of available models in sogni.projects.availableModels property during runtime or query it using sogni.projects.getAvailableModels() method.

For a start, you can try FLUX.1 [schnell] with the following parameters:

const fluxDefaults = {
  modelId: 'flux1-schnell-fp8',
  steps: 4,
  guidance: 1
};

Creating an image project

// Find model that has the most workers
const mostPopularModel = sogni.projects.availableModels.reduce((a, b) =>
  a.workerCount > b.workerCount ? a : b
);
// Create a project using the most popular model
const project = await sogni.projects.create({
  type: 'image',
  modelId: mostPopularModel.id,
  positivePrompt: 'A cat wearing a hat',
  negativePrompt:
    'malformation, bad anatomy, bad hands, missing fingers, cropped, low quality, bad quality, jpeg artifacts, watermark',
  stylePrompt: 'anime',
  steps: 20,
  guidance: 7.5,
  numberOfMedia: 1,
  outputFormat: 'jpg', // Can be 'png' or 'jpg', defaults to 'png'
  tokenType: 'spark', // 'sogni' or 'spark'
  network: 'fast' // 'fast' or 'relaxed'
});

Note: Full project parameter list can be found in ProjectParams docs.

Getting project status and results

In general, there are 2 ways to work with API:

  1. Using promises or async/await syntax.
  2. Listening to events on Project and Job class instances.

Using promises

const project = await sogni.projects.create({
  type: 'image',
  modelId: mostPopularModel.id,
  steps: 20,
  guidance: 7.5,
  positivePrompt: 'A cat wearing a hat',
  negativePrompt:
    'malformation, bad anatomy, bad hands, missing fingers, cropped, low quality, bad quality, jpeg artifacts, watermark',
  stylePrompt: 'anime',
  numberOfMedia: 4,
  tokenType: 'spark', // 'sogni' or 'spark'
  network: 'fast' // 'fast' or 'relaxed'
});

project.on('progress', (progress) => {
  console.log('Project progress:', progress);
});

const imageUrls = await project.waitForCompletion();
// Now you can use image URLs to download images.
// Note that images will be available for 24 hours only!
console.log('Image URLs:', imageUrls);

Using events

const project = await sogni.projects.create({
  type: 'image',
  modelId: mostPopularModel.id,
  steps: 20,
  guidance: 7.5,
  positivePrompt: 'A cat wearing a hat',
  negativePrompt:
    'malformation, bad anatomy, bad hands, missing fingers, cropped, low quality, bad quality, jpeg artifacts, watermark',
  stylePrompt: 'anime',
  numberOfMedia: 4,
  tokenType: 'spark', // 'sogni' or 'spark'
  network: 'fast' // 'fast' or 'relaxed'
});

// Fired when one of project jobs completed, you can get the resultUrl from the job
// without waiting for the entire project to complete
project.on('jobCompleted', (job) => {
  console.log('Job completed:', job.id, job.resultUrl);
});

// Fired when one of project jobs failed
project.on('jobFailed', (job) => {
  console.log('Job failed:', job.id, job.error);
});

// Receive project completion percentage in real-time
project.on('progress', (progress) => {
  // console.log('Project progress:', progress);
});

// Fired when the project is fully completed
project.on('completed', async (images) => {
  console.log('Project completed:', images);
});

// Fired when the project failed
project.on('failed', async (errorData) => {
  console.log('Project failed:', errorData);
});

External API-backed jobs may not report diffusion steps. For those jobs, SDK progress uses provider progress or ETA-derived progress and remains a finite 0-100 number. GPT Image 2 and Seedance results can arrive as direct hosted URLs; the SDK preserves those URLs on job.resultUrl and job.getResultUrl() returns the cached URL without requesting a Sogni signed download URL.

Resuming projects after a refresh or reconnect

Generation keeps running on the Supernet while your socket is down. The SDK treats a dropped connection as a transport gap, not a failure: tracked projects stay alive, the client reconnects with capped exponential backoff for as long as the session is authenticated, and on every authenticated handshake it reconciles with the server. Whatever the client missed is replayed through the normal project / job events, so listeners attached before the gap simply keep receiving updates, and waitForCompletion() still resolves.

Projects the server knows about but this client does not (a page refresh, a second tab sharing the socket, cleared local state) are rebuilt as tracked Project instances with project.recovered === true. Their params are reconstructed from the original request; asset inputs are not recoverable.

// Every reconciliation — after reconnect, after a shared-socket tab connects, or after a manual
// sync() — reports what changed. `snapshot` is the raw server view, for apps that keep their own
// project store.
sogni.projects.on('projectsSynced', ({ reason, active, completed, lost, recoveredActive, recoveredCompleted }) => {
  console.log(reason, { active, completed, lost });
});

// In-flight projects this client was not tracking; they are tracked now, so `project` / `job`
// events follow as usual.
sogni.projects.on('activeProjectsRecovered', (projects) => {
  for (const recovered of projects) {
    const project = sogni.projects.trackedProjects.find((p) => p.id === recovered.id);
    project?.on('completed', (urls) => console.log('Finished after resume:', urls));
  }
});

// Projects that finished while this client was away, with result URLs already resolved.
sogni.projects.on('completedProjectsRecovered', (projects) => {
  for (const project of projects) console.log(project.id, project.resultUrls);
});

// Ask for a fresh reconciliation yourself, e.g. when the page returns to the foreground.
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible') void sogni.projects.sync();
});

A project that the server no longer lists is looked up on the REST API (which only stores finished projects) a few times before it is declared lost; it then fails with an error where isProjectLostError(error) is true. Before failing it, the SDK also asks the account's live project lookup, so a project that is only slow to be picked up stays active instead. Apps that persist project ids themselves can run the same lookup with sogni.projects.resolveMissing(ids).

When the status lookup confirms failure or cancellation without a full result record, resolveMissing() returns state: 'terminal' with a compact project snapshot whose status is failed or canceled. Apps with their own stores should finish any remaining jobs accordingly and preserve results already received; optional model and cost fields may be absent. Tracked Project instances receive the usual failure/cancellation events automatically. A successful completion without its full result record remains unknown until the result data is available.

Socket server restarts

A Sogni platform release restarts the socket server: every connection closes with code 1001 for a few seconds. The SDK is built so apps need no special handling for it:

  • create() and chat requests made during the gap wait (up to 30 seconds) for the reconnected, authenticated socket instead of failing.
  • A project request that reached the server while it was shutting down is refused by id; the SDK sends the same request again after reconnecting, once. Projects created moments before a reconnect are re-checked when they become old enough to judge, rather than minutes later.
  • LLM jobs are not carried across a restart. The server refunds them, and a stream that was open fails with a ChatJobError whose retryable is true (errorType 'server_restarting' or 'transport_lost') rather than waiting forever. After a plain network blip the server keeps the job for 30 seconds and the stream simply continues. Re-issue retryable failures as new requests:
import { isRetryableChatError } from '@sogni-ai/sogni-client';

async function completeWithRetry(params) {
  try {
    return await sogni.chat.completions.create(params);
  } catch (error) {
    if (!isRetryableChatError(error)) throw error;
    return sogni.chat.completions.create(params); // waits for the reconnect
  }
}

To read one of your own projects while it is still queued or rendering, use sogni.projects.getStatus(id). It needs an authenticated client and returns normalized statuses (pending, queued, processing, completed, failed, canceled) with a finished flag. sogni.projects.get(id) is unchanged: it returns the stored record of a finished project and 404s until then.

const { status, finished } = await sogni.projects.getStatus(projectId);
if (!finished) console.log(`Still ${status}`);

The same snapshot also answers "is anything rendering elsewhere on this account?" — another tab in a different Sogni app, another device, a headless client. sogni.projects.listProjectsElsewhere() returns those in-flight projects read-only (appSource, status, model, per-job step counts) so an app can point at them without tracking them; their results reach the account's project history when they finish.

Recovery is per app instance: the server hands projects back to the appId that created them, so persist your appId (browsers: localStorage) and reuse it across reloads.

Results after you stopped waiting

The socket holds a project that finished while its client was disconnected for one hour. A client that restarts, or a script or agent that exits before its projects finish, can still collect them:

  • sogni.projects.getResult(id) returns a project's state and its renders at any time: while it is queued (with the server's waitingReason, which says whether the account's own plan concurrency is holding it or it is waiting for a worker) and after it finished, with signed download URLs for the completed renders. Pass { kind: 'video' } (or image, audio, model) when you know what it produces and the model is not in this client's catalog.
  • sogni.projects.listRecent({ since }) lists this account's recently completed media projects, newest first, from the durable history (up to 7 days back, 24 hours by default), including ones that finished while no client was connected.
for (const project of await sogni.projects.listRecent({ since: Date.now() - 6 * 3600_000 })) {
  const result = await sogni.projects.getResult(project.id);
  for (const job of result.jobs) if (job.url) console.log(project.modelName, job.url);
}

const pending = await sogni.projects.getResult(projectId);
if (!pending.finished) console.log(pending.status, pending.waitingReason?.message);

Project parameters

Here is a full list of project parameters that you can use:

  • modelId - ID of the model to use for image generation.
  • positivePrompt - text prompt that describes what you want to see in the image. Can be an empty string.
  • negativePrompt - text prompt that describes what you don't want to see in the image. Can be an empty string.
  • stylePrompt - text prompt that describes the style of the image. Can be an empty string.
  • numberOfImages - number of images to generate.
  • tokenType - select token type to pay for render. Can be either sogni or spark. External API-backed models such as GPT Image 2 and Seedance are Spark-only.
  • sizePreset - optionally pass the ID of a size preset to use. If not passed, the default output is a square at either 512x512, 768x768 or 1024x1024 (SDXL and Flux) based on the default resolution of the selected model. See Detecting available output presets section below for available presets for your model. The token cost and render time of the job is heavily influenced by total pixel count where a 2048x2048 image is 4x the cost and render time of a 1024x1024 image as it is 4x the generated pixel count. You may also pass custom along with width and height project parameters to request a custom dimension. Note that not all size presets and custom aspect ratios produce consistently good results with all models. If your output features skewed anatomy or doubling of features you should experiment with a different model or output size.
  • width - if 'sizePreset' is set to 'custom' you may pass a custom pixel width between 256 and 2048
  • height - if 'sizePreset' is set to 'custom' you may pass a custom pixel height between 256 and 2048
  • steps - number of inference steps between random pixels to final image. Higher steps generally lead to higher quality images and more details but varies by model, prompt, guidance, and desired look. For most Stable Diffusion models 20-40 steps is ideal with 20 being 2x faster to render than 40. For Flux 4 steps is optimal. Lightning, Turbo and LCM models are designed for quality output in as little as 1 step. (More info).
  • guidance - guidance scale. For most Stable Diffusion models, optimal value is 7.5 (More info).
  • network - network type to use, fast or relaxed. This parameter allows to override default network type for this project.
  • disableNSFWFilter - request the alternate content policy for this project; the server remains authoritative.
  • seed - uint32 number to use as seed. If not provided, random seed will be used. If numberOfImages is greater than 1, provided seed will be user only for one of them. (More info).
  • numberOfPreviews - number of preview images to generate. If not provided, no preview images will be generated.
  • sampler - sampler algorithm (More info). For available options, see the "Samplers" section below.
  • scheduler - scheduler to use (More info). For available options, see the "Schedulers" section below.
  • startingImage - guide image in PNG format. Can be File, Blob or Buffer
  • startingImageStrength - strong effect of starting image should be. From 0 to 1, default 0.5.
  • controlNet - Stable Diffusion ControlNet parameters. See ControlNets section below for more info.
  • outputFormat - output image format: png, jpg, or webp. The SDK defaults to png; worker availability determines support for each model and format.
  • embedPromptMetadata - whether worker images include the generation prompt and settings in their metadata. Defaults to true; pass false to omit them.

TypeScript type definitions for project parameters can be found in ProjectParams docs.

GPT Image 2

GPT Image 2 is available through the normal image project API:

const project = await sogni.projects.create({
  type: 'image',
  network: 'fast',
  modelId: 'gpt-image-2',
  positivePrompt: 'A clean product render of translucent headphones on a white background',
  numberOfMedia: 1,
  width: 1024,
  height: 1024,
  gptImageQuality: 'high',
  outputFormat: 'webp',
  tokenType: 'spark'
});

const imageUrls = await project.waitForCompletion();

For GPT Image 2 edits, pass contextImages; the SDK supports up to 16 context images for this model. Cost estimates can include gptImageQuality, outputFormat, and contextImages so external input-image pricing is represented.

Detecting available output presets

You can get a list of available output presets for a specific network and model using sogni.projects.getOutputPresets method.

const presets = await sogni.projects.getSizePresets('fast', 'flux1-schnell-fp8');
console.log('Available output presets:', presets);

Sample response:

[
  {
    "label": "Square",
    "id": "square",
    "width": 512,
    "height": 512,
    "ratio": "1:1",
    "aspect": "1"
  },
  {
    "label": "Square HD",
    "id": "square_hd",
    "width": 1024,
    "height": 1024,
    "ratio": "1:1",
    "aspect": "1"
  },
  {
    "label": "Portrait: Standard",
    "id": "portrait_7_9",
    "width": 896,
    "height": 1152,
    "ratio": "7:9",
    "aspect": "0.78"
  },
  {
    "label": "Portrait: 35mm",
    "id": "portrait_13_19",
    "width": 832,
    "height": 1216,
    "ratio": "13:19",
    "aspect": "0.68"
  },
  {
    "label": "Portrait: Mobile",
    "id": "portrait_4_7",
    "width": 768,
    "height": 1344,
    "ratio": "4:7",
    "aspect": "0.57"
  },
  {
    "label": "Portrait: Extended",
    "id": "portrait_5_12",
    "width": 640,
    "height": 1536,
    "ratio": "5:12",
    "aspect": "0.42"
  },
  {
    "label": "Landscape: Standard",
    "id": "landscape_9_7",
    "width": 1152,
    "height": 896,
    "ratio": "9:7",
    "aspect": "1.28"
  },
  {
    "label": "Landscape: 35mm",
    "id": "landscape_19_13",
    "width": 1216,
    "height": 832,
    "ratio": "19:13",
    "aspect": "1.46"
  },
  {
    "label": "Landscape: Widescreen",
    "id": "landscape_7_4",
    "width": 1344,
    "height": 768,
    "ratio": "7:4",
    "aspect": "1.75"
  },
  {
    "label": "Landscape: Ultrawide",
    "id": "landscape_12_5",
    "width": 1536,
    "height": 640,
    "ratio": "12:5",
    "aspect": "2.4"
  }
]

Samplers

Samplers control the denoising process — the sequence of steps that transforms random noise into your final image.

Avaliable sampler options depend on a model. You can use api to get available samplers for a specific model:

const modelOptions = await sogni.projects.getModelOptions('flux1-schnell-fp8');
console.log(modelOptions.sampler);
/*
 {
   allowed: [ 'euler', 'euler_a', 'dpm_pp_2m', 'dpmpp_2m_sde', 'dpm_pp_sde' ],
   default: 'euler'
 }
 */

See Samplers and Schedulers docs for more info.

For video models, the same call returns the server-advertised dimension ranges, size grid, and optional total-pixel budget:

const options = await sogni.projects.getModelOptions('minimax-h3-fl2va-fp8_t2v');
console.log(options.width); // { min: 544, max: 1344, step: 32, default: 1344 }
console.log(options.height); // { min: 544, max: 1344, step: 32, default: 768 }
console.log(options.maxPixels); // 1032192

Schedulers

Control how steps are distributed. For more info see Schedulers and Samplers docs.

Available scheduler options depend on a model. You can use api to get available schedulers for a specific model:

const modelOptions = await sogni.projects.getModelOptions(modelId);
console.log(modelOptions.scheduler);
/*
 {
   allowed: [
     'simple',
     'karras',
     'linear',
     'sgm_uniform',
     'beta',
     'normal',
     'ddim',
     'kl_optimal'
   ],
   default: 'simple'
 }
 */

LoRAs

Some models accept LoRAs — small adapters that steer style, lighting, detail, or character traits. The Krea 2 family carries the largest set. Discover which LoRAs a model accepts, and the strength contract of each, with projects.availableLoras. The catalog is public, so this works without credentials, and results are cached for five minutes.

const { loras, models, constraints } = await sogni.projects.availableLoras({
  modelId: 'krea2_turbo_fp8_scaled'
});

for (const lora of loras) {
  console.log(lora.loraId, lora.name, lora.ui.min, lora.ui.max, lora.ui.default);
}

// `models` lists every LoRA-capable model and `constraints` the shared limits,
// both unaffected by the `modelId` filter. Use them instead of hard-coding a
// model list or a stacking cap that goes stale.
console.log(models); // ['dark_beast_krea2_fp8', 'krea2_turbo_fp8_scaled', ...]
console.log(constraints.maxPerRequest); // 8

// Or look up a single LoRA
const warmLight = await sogni.projects.getLora('krea2-warm-light');
console.log(warmLight?.ui.rangeLabels);
// { min: 'Cooler & Darker', max: 'Warmer & Golden' }

// Decide whether to offer a LoRA control at all
if (await sogni.projects.supportsLoras(modelId)) {
  const { maxPerRequest } = await sogni.projects.loraConstraints();
  console.log(`Attach up to ${maxPerRequest} LoRAs`);
}

Apply them with the positionally-matched loras and loraStrengths project parameters:

const project = await sogni.projects.create({
  modelId: 'krea2_turbo_fp8_scaled',
  positivePrompt: 'candid editorial street portrait at dusk',
  steps: 8,
  guidance: 1,
  loras: ['krea2-detail-enhancer', 'krea2-amateur'],
  loraStrengths: [3, -2]
});
  • Up to constraints.maxPerRequest LoRAs per render (8 today). Order is significant — the adapters are applied in sequence and do not commute, so the same set in a different order produces a different image. The render pipeline rejects a request over the cap at submit.
  • loraStrengths[i] applies to loras[i]. Omit the array to use 1.0 for every LoRA, which is not the same as each LoRA's own ui.default.
  • Do not clamp strengths to 0-1. Most Krea 2 LoRAs are bipolar sliders: ui.min is negative, a negative strength applies the inverse effect, and 0 disables it. Bound your input with each entry's ui.min/ui.max, and prefer ui.recommendedMin/ui.recommendedMax for the band its author calls usable.
  • ui.nsfw and ui.sexual mark LoRAs that require the artist to have the Sensitive Content Filter off.
  • Workers download a LoRA on first use, so the first render with an uncached one takes longer to start.

Reusable subscriber uploads

projects.create() keeps preparation tied to the initiating sign-in session. If the SDK observes an account change or sign-out while the call is pending, it rejects with guidance for that stage. Routine token refresh does not interrupt preparation or reconnect recovery. A session change clears locally tracked projects and ignores recovery data from the previous session. Pending project completion waits and socket chat streams reject when the account session ends or the client is disposed. This ends local tracking; it does not cancel work already submitted to the server.

In browser multi-tab mode, this SDK can share an unchanged account session with older open tabs without interrupting their work. Older tabs cannot identify which account started an in-flight request. After an observed sign-out or account replacement, reload those older tabs before submitting more work; the error message identifies this case. Tabs using the current SDK exchange session markers and can continue after signing in again.

This check cannot cancel an upload already sent to its original presigned URL, or observe a cookie change before the browser reports it to the SDK. A request already submitted may still run under its original account; rejecting the pending call does not cancel that work.

On servers that support saved uploads, eligible subscribers can reuse the same image, video or audio file across projects. Pass files to projects.create() as usual: the SDK checks for a previously saved copy before transferring bytes. Uploads remain private to the signed-in account. Older servers and accounts without this feature continue using ordinary project uploads.

const saved = await sogni.projects.assets.upload(file, file.type, 'Product reference');
const { assets, limits } = await sogni.projects.assets.list();
console.log(saved.id, assets, limits);
// Reusing the same file in later projects needs no repeat upload.
// Removal does not remove inputs already copied into an existing project.
await sogni.projects.assets.remove(saved.id);

Use the returned limits and expiresAt to display remaining storage and expiry. An expired entry can be removed even after the subscription ends. Explicit assets.upload() calls report errors; automatic project uploads fall back only when saved storage cannot be prepared. Transfer or verification failures stop project submission. Saved-upload IDs are not accepted in place of files in projects.create(); assets.bind(id, { projectId, type, id? }) is available for clients that manage project IDs and input slots directly.

Project history may include byolUsed, personalLoras public-source snapshots, and reusedAssetCount. Missing fields on older projects mean unknown, not zero.

ControlNets

EXPERIMENTAL FEATURE: This feature is still in development and may not work as expected. Use at your own risk.

ControlNet is a neural network that controls image generation in Stable Diffusion by adding extra conditions. See more info and usage samples in ControlNets docs for Sogni Studio.

To use ControlNet in your project, you need to provide controlNet object with the following properties:

  • name - name of the ControlNet to use. Currently supported:
    • canny
    • depth
    • inpaint
    • instrp2p
    • lineart
    • lineartanime
    • mlsd
    • normalbae
    • openpose
    • scribble
    • segmentation
    • shuffle
    • softedge
    • tile
    • instantid
  • image - input image. Image size should match the size of the generated image. Can be File, Blob or Buffer
  • strength - ControlNet strength 0 to 1. 0 full control to prompt, 1 full control to ControlNet
  • mode - How control and prompt should be weighted. Can be:
    • balanced - (default) balanced, no preference between prompt and control model
    • prompt_priority - the prompt has more impact than the model
    • cn_priority - the controlnet model has more impact than the prompt
  • guidanceStart - step when ControlNet first applied, 0 means first step, 1 means last step. Must be less than guidanceEnd
  • guidanceEnd - step when ControlNet last applied, 0 means first step, 1 means last step. Must be greater than guidanceStart

Example:

const cnImage = fs.readFileSync('./cn.jpg');
const project = await sogni.projects.create({
  type: 'image',
  network: 'fast',
  modelId: 'coreml-cyberrealistic_v70_768',
  numberOfMedia: 1,
  positivePrompt: 'make men look older',
  steps: 20,
  guidance: 7.5,
  controlNet: {
    name: 'instrp2p',
    image: cnImage
  }
});

Full ControlNet type definition:

export type ControlNetName =
  | 'canny'
  | 'depth'
  | 'inpaint'
  | 'instrp2p'
  | 'lineart'
  | 'lineartanime'
  | 'mlsd'
  | 'normalbae'
  | 'openpose'
  | 'scribble'
  | 'segmentation'
  | 'shuffle'
  | 'softedge'
  | 'tile'
  | 'instantid';

export type ControlNetMode = 'balanced' | 'prompt_priority' | 'cn_priority';
export interface ControlNetParams {
  name: ControlNetName;
  image?: File | Buffer | Blob;
  strength?: number;
  mode?: ControlNetMode;
  guidanceStart?: number;
  guidanceEnd?: number;
}

Personal LoRA library

Use the same account/API key as Sogni Web. Importing and generating require an active Unlimited subscription; listing and removing owned entries remain available after expiry. The server checks ownership, readiness, content-filter requirements, compatible models, and quotas on every request.

const library = await sogni.projects.personalLoras.list();
// Choose modelId from library.models; obtain the user's permission to use the file.
const imported = await sogni.projects.personalLoras.import({
  url: 'https://huggingface.co/author/repository/resolve/main/style.safetensors',
  name: 'My style',
  modelId: 'krea2_turbo_fp8_scaled',
  rightsConfirmed: true,
});
const current = await sogni.projects.personalLoras.get(imported.id);
// Importing is asynchronous. Poll get() until ready, rejected, or revoked;
// queued, validating, and review are not usable yet. Surface reason/failureCode.
const { loras } = await sogni.projects.availableLoras({
  modelId: 'krea2_turbo_fp8_scaled', includePersonal: true,
});
// Pass a ready row.loraId in project.loras and row.ui.default in loraStrengths.
// Respect its modelIds, requirements, and ui.nsfw content-filter requirement.
// Removal is explicit:
// await sogni.projects.personalLoras.remove(imported.id);

personalLoras.catalog({modelId}) returns ready private catalog rows. getLora('personal-…') also reads the authenticated catalog. Personal catalog responses are never placed in the shared public cache. forceRefresh controls the public catalog; personal entries are always fetched again. Standard and non-audio FastH3 Two-Stage modes expose their compatible adapters through modelIds; audio-guided H3 modes do not support LoRAs.

Hosted tools include SogniTools.imageTo3d, SogniTools.removeBackground, and SogniTools.segmentImage. Use image_to_3d with a front image and optional named leftViewImageIndex, backViewImageIndex, and rightViewImageIndex; its result has mediaType: 'model' and is a binary GLB. generate_speech supports creativity (0.1–2), outputFormat (wav, mp3, flac), and seed, alongside studio voices, reference-audio cloning, and voice design.

Video Generation (WAN 2.2, Wan 3, LTX-2.3, Seedance & Happy Horse)

The Sogni SDK supports advanced video generation workflows powered by Wan 2.2 14B FP8 models. These models are available on the fast network and support various video generation workflows.

Available Wan 2.2 Workflows

The Wan 2.2 model family supports five distinct video generation workflows:

  1. Text-to-Video (t2v) - Generate videos from text prompts
  2. Image-to-Video (i2v) - Animate a static image into a video (First and Last Frame supported)
  3. Sound-to-Video (s2v) - Bring a character in an image to life with video and audio synchronization including lip syncing
  4. Animate-Move - Transfer character motion and emotion from a reference video to a subject from an image into a new video
  5. Animate-Replace - Replace a subject in a video while preserving motion

Model Variants

WAN workflows have two model variants optimized for different use cases:

  • Speed variant (with _lightx2v suffix) - Faster inference (4-step), good quality
  • Quality variant (without _lightx2v) - Slower inference, best quality

LTX 2.5 is the recommended LTX family; LTX 2.3 remains available for rollback and for its ID-LoRA, transition, and 10Eros-only paths. LTX 2.5 distilled models use the official direct distilled checkpoint at a fixed 8 steps. Dev models use the official 2.5 Dev checkpoint plus the required distilled Speed LoRA refinement. Both generate native audio. Seedance models use the external API path and are available through three canonical multimodal model IDs: seedance-2-0, seedance-2-0-mini, and seedance-2-5.

Example model IDs:

  • wan_v2.2-14b-fp8_t2v_lightx2v (Text-to-Video, speed)
  • wan_v2.2-14b-fp8_t2v (Text-to-Video, quality)
  • wan_v2.2-14b-fp8_i2v_lightx2v (Image-to-Video, speed)
  • wan_v2.2-14b-fp8_i2v (Image-to-Video, quality)
  • wan_v2.2-14b-fp8_s2v_lightx2v (Sound-to-Video, speed)
  • wan_v2.2-14b-fp8_s2v (Sound-to-Video, quality)
  • wan_v2.2-14b-fp8_animate-move_lightx2v (Animate-Move, speed)
  • wan_v2.2-14b-fp8_animate-replace_lightx2v (Animate-Replace, speed)
  • ltx25-22b-int8_t2v_distilled (LTX 2.5 Text-to-Video, fixed 8-step)
  • ltx25-22b-int8_i2v_distilled (LTX 2.5 Image/First+Last-Frame-to-Video, fixed 8-step)
  • ltx25-22b-int8_a2v_distilled / ltx25-22b-int8_ia2v_distilled (LTX 2.5 audio-driven video)
  • ltx25-22b-int8_v2v_distilled (LTX 2.5 controls plus inpaint/outpaint)
  • ltx25-22b-int8_v2v_dev (LTX 2.5 Dev + Speed LoRA controls; no inpaint/outpaint)
  • ltx23-22b-fp8_t2v_distilled (LTX-2.3 Text-to-Video, fast)
  • ltx23-22b-fp8_i2v_distilled (LTX-2.3 Image-to-Video, fast)
  • ltx23-22b-10eros-v1.4-fp8mixed_i2v (LTX-2.3 10Eros v1.4 Image-to-Video, 30GB+ workers, subject to server authorization)
  • ltx23-22b-fp8_v2v_distilled (LTX-2.3 Video-to-Video ControlNet, fast)
  • seedance-2-0 (Seedance 2.0 multimodal video, external API, 4K capable)
  • seedance-2-0-mini (Seedance 2.0 Mini multimodal video, external API, 720p cap)
  • seedance-2-5 (Seedance 2.5 multimodal video, external API, 480p/720p/1080p, 4-30s, first+last frame)
  • happyhorse-1.1-t2v (Happy Horse 1.1 Text-to-Video, external API, image-only references)
  • happyhorse-1.1-i2v (Happy Horse 1.1 Image-to-Video, external API, one first-frame image)
  • happyhorse-1.1-r2v (Happy Horse 1.1 Reference-to-Video, external API, 1-9 reference images)
  • wan3.0-video (Wan 3 unified multimodal video, external API, 2-30s, 480P/720P/1080P, fixed 30fps)
  • minimax-h3-fastvideo-int8_ia2v_turbo / minimax-h3-fastvideo-int8_flfa2v_turbo / minimax-h3-fastvideo-int8_a2v_turbo (MiniMax H3 FastH3 audio guide: an uploaded referenceAudio drives the video and is kept in the output, with a first frame, first and last frames, or no image; 124-362 frames, size with getMinimaxH3FramesForAudioDuration(); catalog and Personal H3 LoRAs are accepted as on the frame modes; no generateAudio: false or audioDuration)
  • minimax-h3-fastvideo-int8_t2v_turbo_2stage / minimax-h3-fastvideo-int8_i2v_turbo_2stage / minimax-h3-fastvideo-int8_flf2v_turbo_2stage / minimax-h3-fastvideo-int8_ia2v_turbo_2stage / minimax-h3-fastvideo-int8_flfa2v_turbo_2stage / minimax-h3-fastvideo-int8_a2v_turbo_2stage (MiniMax H3 FastH3 Two-Stage: the FastH3 Turbo request, delivered at twice the canvas with the same length and audio. 720p: the chosen aspect at a 384 px short edge, 672×384 → 1344×768. 1080p: a 544 px short edge, 960×544 → 1920×1088. 2K: the 768p canvas, 1344×768 → 2688×1536)
  • minimax-h3-ref2va-fp8_r2v_2stage / minimax-h3-ref2va-fp8_r2v_balanced_2stage (MiniMax H3 Two-Stage Reference-to-Video: the Standard 20-step or Balanced 8-step Ref2VA request, delivered at twice the canvas with the same length, audio and references; the same 384/544/768 px canvas choices as the FastH3 Two-Stage ids)
  • flashvsr_v1.1_tiny_long_bf16 (FlashVSR v1.1 promptless 1080p/1440p video upscaling of one finished video)

The repository does not bundle sample prompts or input media for the 10Eros model. Creators who choose to use it must provide their own prompt and image to examples/workflow_image_to_video.mjs. Optional test prompts and images are available from the model author's sample gallery.

Video Parameters

When creating video projects, you can specify:

  • duration - Duration in seconds. WAN 2.2 supports 1-10s, Wan 3 supports 2-30s, LTX 2.5 supports 2-20s, LTX 2.3 supports 4-20s, Seedance 2.0 supports 4-15s, and Seedance 2.5 supports 4-30s.
  • fps - Frames per second. WAN 2.2 supports 16/32 output, Wan 3 is fixed at 30fps, LTX 2.x supports 1-60 native FPS, and Seedance is fixed at 24fps.
  • frames - Number of frames. Prefer duration; the SDK calculates model-correct frame counts, and calculateVideoFrames(modelId, seconds, fps) returns the count a duration resolves to. Pass frames when positions inside the clip must be exact, as with MiniMax H3 keyframes (H3 takes 124, 141, 158, … 362)
  • width - Video width in pixels
  • height - Video height in pixels
  • steps - Increase inference steps to increase quality
  • seed - Random seed for reproducibility
  • referenceImage - Reference image for workflows that require it (i2v, s2v, animate-move, animate-replace)
  • referenceVideo - Reference video for animate and v2v workflows, and the source video for FlashVSR upscaling
  • upscaleResolution - FlashVSR only: output short edge, 1080 or 1440
  • referenceVideoDurations - Optional MiniMax H3 r2v duration hints in [referenceVideo, ...referenceVideos] order for early client-side validation; Socket probes the uploaded files and uses measured durations for pricing and admission
  • referenceAudio - Reference audio for sound-to-video workflows (s2v, ia2v, flfa2v, a2v)
  • referenceImageEnd - Last frame for i2v, flf2v and the MiniMax H3 FastH3 flfa2v audio-guide workflow
  • keyframes - Every MiniMax H3 workflow except t2v (21 ids, isMinimaxH3KeyframeModel(): i2v and flf2v on every tier, Sound to Video ia2v/flfa2v/a2v, and Ref2VA r2v): up to MINIMAX_H3_MAX_KEYFRAMES (8) { image, frameIndex } stills pinned between the first and last frame. frameIndex is the 0-based frame at 24 fps (Math.round(seconds * 24)), an integer from 1 to frames - 2, each frame used once. Pass frames from the H3 grid (124, 141, 158, … 362) so the count is exact: duration snaps to the grid (duration: 6 renders 141 frames, not 144). Frame 0 and the last frame are never keyframes: i2v, flf2v and flfa2v set them with referenceImage / referenceImageEnd, ia2v sets frame 0 with referenceImage, and a2v and r2v cannot pin them. The prompt names each keyframe <Picture N> (MiniMax's keyframe format), numbered in time order after the workflow's own pictures: after the first frame on i2v and ia2v (after the last frame on a last-frame-only i2v job), after the first and last frames on flf2v and flfa2v, from <Picture 1> on a2v, and after the reference images on r2v; keyframes still never count as references. i2v, flf2v and Sound to Video prompts open with one alignment line listing every picture at its mark (How the reference pictures align with the target video — Picture 1 (from Shot 1) aligns with the 0.00-second mark of the target video; Picture 2 (from Shot 2) aligns with the 2.88-second mark of the target video.), the shot where a keyframe lands says "the shot's keyframe corresponds to <Picture N>", and r2v adds a <Picture N> is the keyframe of [Shot M], showing ... entry, keyframe completion in the summary tasks and a <Picture N> ([Shot M] keyframe): fully_preserved - ... retention entry. H3's text encoder never sees the keyframe images, so the prompt must still describe what each keyframe shows at its time. On Sound to Video the audio drives the performance and keyframes pin how it looks at their times. When a keyframe changes the framing, camera angle, location or light, start a new shot (a hard cut) in the prompt at its time: two differently framed or lit stills inside one continuous shot cross-fade, and a shot described differently from its still can flash the still for a single frame. Images upload to their own keyframeImage1..N slots in array order, so r2v sends its references (referenceImage, contextImages) and keyframes together. If no worker serving the model can pin keyframes yet, the job is refused with error code 4100 MiniMax H3 keyframe pricing: the first two keyframes are included; each extra keyframe adds output time at the job's per-second rate, 0.75 s on FastH3 and 0.3 s on every other tier (an 8 s FastH3 clip with 8 keyframes: 32 + 18 = 50 Spark). Pass keyframeCount (or the job's keyframes) to estimateVideoCost to quote it.
  • referenceImageUrls - Loose image context URLs for Seedance, Happy Horse, and Wan 3; Wan 3 accepts up to 10
  • referenceVideoUrls - Loose video context URLs for Seedance and Wan 3; Wan 3 accepts up to 5
  • referenceAudioUrls - Loose audio context URLs for Seedance and Wan 3; Wan 3 accepts up to 5
  • seedanceTaskType - Seedance 2.5 loose-reference operation: reference, edit, or extend. Edit and extend require a reference video.
  • hasVideoInput - Estimate-only flag for estimateVideoCost; set this when estimating a canonical Seedance video-input job without passing referenceVideo/referenceVideoUrls
  • referenceImageCount - Optional estimate-only count of image references the video job will submit; models whose pricing does not use it ignore it
  • keyframeCount / keyframes - Estimate-only count of MiniMax H3 intermediate keyframes (0-8). The first two are included; each extra keyframe adds output time at the job's per-second rate: 0.75 s on FastH3, 0.3 s on every other tier
  • referenceVideoCount / referenceVideoDurationSeconds - Estimate-only MiniMax H3 r2v input metadata; reference-video seconds use the full resolution-tier input rate ($0.05/s at 480p or $0.08/s at 544/768p), even with Turbo output. On the two-stage Reference ids, reference video with 2K output is $0.13/s
  • MiniMax H3 two-stage quotes (FastH3 and Ref2VA alike): call estimateVideoCost with the _2stage model id and the canvas the job renders (672×384 for 720p, 960×544 for 1080p, 1344×768 for 2K). Two-stage output is a model id, not a request option; passing the retired outputScale throws before any request

Seedance 2.0 can combine image, video, and audio reference assets in one external API request. Reference limits are up to 9 image assets, 3 video assets, 3 audio assets, and 12 asset files total. Text+audio without at least one image or video reference is not supported by Seedance. URL-array references must be HTTPS URLs that the vendor can fetch; local multi-reference files should be uploaded first, as shown in examples/workflow_partner_seedance_video.mjs. In prompts and creative briefs, refer to attachments by Seedance-style tags: @Image1, @Video1, and @Audio1, counted independently by modality in attachment order. Assign each useful reference a role, such as product identity, motion timing, camera path, edit rhythm, background music, or speech reference. Prefer positive preservation language like "maintain the same product silhouette and logo placement from @Image1"; exact readable text, logos, lip-sync, voice cloning, and real-human-reference behavior still need review. Seedance dispatch omits negative prompts; Wan 2.2 and LTX 2.3 video models can still use negativePrompt. Seedance jobs are Spark-only and should not use SOGNI token fallback.

Seedance 2.5 raises the reference limits to 30 images, 10 videos, 10 audios, and 50 total files, and it permits audio-only loose-reference generation. Its frame-conditioned, edit, and extend operations inherit source aspect ratio. Send seedanceTaskType: 'edit' for source-video edits and seedanceTaskType: 'extend' for continuation; the Sogni vendor adapter applies Seedance 2.5's required adaptive ratio and provider-selected edit duration. Use reference when media supplies creative guidance for a newly generated video. Loose-reference requests require an explicit task type.

Wan 3 uses the single exact model ID wan3.0-video for text, first-frame, first+last-frame, loose-reference, and audio-driven generation. It accepts fixed or smart 2-30 second output at 480P/720P/1080P and fixed 30fps, with adaptive, 16:9, 4:3, 1:1, 3:4, or 9:16 ratio. Native audio defaults on and watermarking is optional. It accepts up to 10 loose images, 5 videos, and 5 audios, plus either one public document or one webpage. Native first/last frames cannot be mixed with loose media or document/web context. In English prompts, identify loose assets as Image 1, Video 1, and Audio 1, numbered independently by type. A video reference conditions a new generation; Wan 3 does not expose source-video edit or extend task modes. Wan 3 has no negativePrompt. Sogni prompt expansion and Alibaba prompt_extend are coordinated so an expanded prompt is not rewritten twice. Wan 3 is Premium Spark-only.

Text-to-Video Example

const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_t2v_lightx2v',
  positivePrompt: 'A serene ocean wave crashing on a beach at sunset',
  fps: 16,
  frames: 81,
  width: 512,
  height: 512
});

const videoUrls = await project.waitForCompletion();
console.log('Video URL:', videoUrls[0]);

Seedance 2.0 example:

const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'seedance-2-0',
  positivePrompt: 'A cinematic neon skyline time lapse, sweeping camera motion',
  duration: 5,
  fps: 24,
  width: 1920,
  height: 1080,
  tokenType: 'spark'
});

const videoUrls = await project.waitForCompletion();

Seedance multimodal context example:

const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'seedance-2-0',
  positivePrompt:
    'Use @Image1 as the product identity, @Image2 for detail inserts, @Video1 for camera movement, and @Audio1 for music rhythm. Create one cohesive launch spot with smooth continuity and crisp product preservation.',
  duration: 8,
  fps: 24,
  width: 1920,
  height: 1080,
  referenceImageUrls: [
    'https://cdn.example.com/product-front.png',
    'https://cdn.example.com/product-detail.png'
  ],
  referenceVideoUrls: ['https://cdn.example.com/motion-reference.mp4'],
  referenceAudioUrls: ['https://cdn.example.com/music-reference.m4a'],
  tokenType: 'spark'
});

const videoUrls = await project.waitForCompletion();

Image-to-Video Example

const referenceImage = fs.readFileSync('./input-image.png');

const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_i2v_lightx2v',
  positivePrompt: 'camera zooms in slowly',
  referenceImage: referenceImage,
  fps: 16,
  frames: 81
});

const videoUrls = await project.waitForCompletion();

Sound-to-Video Example

const referenceImage = fs.readFileSync('./image.jpg');
const referenceAudio = fs.readFileSync('./audio.m4a');

const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_s2v_lightx2v',
  referenceImage: referenceImage,
  referenceAudio: referenceAudio,
  fps: 16,
  frames: 81
});

const videoUrls = await project.waitForCompletion();

Animate-Move Example

Transfer motion from a reference video to a subject in an image:

const referenceImage = fs.readFileSync('./subject.jpg');
const referenceVideo = fs.readFileSync('./motion-source.mp4');

const project = await sogni.projects.create({
  type: 'video',
  network: 'fast',
  modelId: 'wan_v2.2-14b-fp8_animate-move_lightx2v',
  referenceImage: referenceImage,
  referenceVideo: referenceVideo,
  fps: 16,
  frames: 81
});

const videoUrls = await project.waitForCompletion();

Animate-Replace Example

Replace a subject in a v