@molecule/api-ai-video-generation-ltx

Provider bond · ai-video-generation · API (Node) · v1.1.2 · Apache-2.0

LTX video generation provider for molecule.dev — Lightricks' hosted LTX-2.5/LTX-2.3 text/image-to-video-with-audio over the async V2 job API

npm install @molecule/api-ai-video-generation-ltx

npm · Source on GitHub · Implements @molecule/api-ai-video-generation

How it works

@molecule/api-ai-video-generation-ltx is a provider bond on the API (Node) side: it implements the ai-video-generation core interface (@molecule/api-ai-video-generation) with a concrete vendor or library behind it.

Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.

import { requireProvider, setProvider } from '@molecule/api-ai-video-generation'
import { provider } from '@molecule/api-ai-video-generation-ltx'

// Reads LTXV_API_KEY (from console.ltx.video) on each call.
setProvider(provider)

const job = await requireProvider().generate({
  prompt: 'A majestic eagle soaring through clouds at sunset',
  model: 'ltx-2-5-pro',
  durationSeconds: 8,
  resolution: '1920x1080',
})
let status = await requireProvider().getStatus(job.id)
while (status.status === 'pending' || status.status === 'processing') {
  await new Promise((resolve) => setTimeout(resolve, 5000))
  status = await requireProvider().getStatus(job.id)
}
// status.result?.url — it EXPIRES; download and re-host it now.

Works with: @molecule/api-ai-video-generation, @molecule/api-secrets

Secrets: LTXV_API_KEY, LTX_MODEL (optional), LTX_BASE_URL (optional)

Reference

LTX video-generation provider for @molecule/api-ai-video-generation.

Generates short video clips with a synchronized audio track through Lightricks' hosted LTX API (LTX-2.5 / LTX-2.3, ltx-2-5-fast by default) over its asynchronous V2 job flow: submit, poll, download. Raw first-frame bytes are uploaded through the API's own /v1/upload ticket flow.

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Quick Start

import { requireProvider, setProvider } from '@molecule/api-ai-video-generation'
import { provider } from '@molecule/api-ai-video-generation-ltx'

// Reads LTXV_API_KEY (from console.ltx.video) on each call.
setProvider(provider)

const job = await requireProvider().generate({
  prompt: 'A majestic eagle soaring through clouds at sunset',
  model: 'ltx-2-5-pro',
  durationSeconds: 8,
  resolution: '1920x1080',
})
let status = await requireProvider().getStatus(job.id)
while (status.status === 'pending' || status.status === 'processing') {
  await new Promise((resolve) => setTimeout(resolve, 5000))
  status = await requireProvider().getStatus(job.id)
}
// status.result?.url — it EXPIRES; download and re-host it now.

Type

provider

Installation

npm install @molecule/api-ai-video-generation-ltx @molecule/api-ai-video-generation @molecule/api-secrets

API

Interfaces

LtxVideoGenerationConfig

Configuration for the LTX video generation provider. Every field falls back to its env var, read on each call (never at import time).

interface LtxVideoGenerationConfig {
  /** API key from the LTX developer console. Defaults to the `LTXV_API_KEY` env var (required). */
  apiKey?: string
  /** Base URL override. Defaults to `LTX_BASE_URL`, then `https://api.ltx.io`. */
  baseUrl?: string
  /** Default model id. Defaults to `LTX_MODEL`, then `ltx-2-5-fast`. */
  defaultModel?: string
  /** Per-request timeout in milliseconds. Defaults to 120000. */
  timeoutMs?: number
}

Classes

LtxVideoError

Error thrown when the LTX API refuses or fails a request. Carries the API's HTTP status (0 when it could not be reached or timed out) and the error type from its error body (content_filtered_error, insufficient_funds_error, …). Deliberately NOT tagged with statusCode/errorKey, so API middleware answers its generic 500 instead of echoing the vendor's status to the caller.

class LtxVideoError extends Error {
  /** HTTP status from the LTX API; 0 for a network failure or timeout. */
  readonly status: number
  /** Error type from the LTX error body, when present. */
  readonly code: string | undefined

  /**
   * Create the error.
   *
   * @param message - Human-readable message.
   * @param status - HTTP status (0 for network failure/timeout).
   * @param code - LTX error type, when it sent one.
   * @param cause - The underlying error, if any.
   */
  constructor(message: string, status: number, code?: string, cause?: unknown) {
    super(message, cause === undefined ? undefined : { cause })
    this.name = 'LtxVideoError'
    this.status = status
    this.code = code
  }
}

Functions

createProvider(config)

Creates an LTX video generation provider.

function createProvider(config?: LtxVideoGenerationConfig): AIVideoGenerationProvider
  • config — Optional overrides (API key, base URL, default model, timeout).

Returns: An AIVideoGenerationProvider backed by the LTX hosted API.

Constants

DEFAULT_BASE_URL

Default API base URL.

const DEFAULT_BASE_URL: 'https://api.ltx.io'

DEFAULT_MODEL

Default model: the fast LTX-2.5 tier.

const DEFAULT_MODEL: 'ltx-2-5-fast'

DEFAULT_RESOLUTION

Default output resolution (720p landscape) when the caller sets none.

const DEFAULT_RESOLUTION: '1280x720'

LTX_MODELS

The model ids the V2 API accepts.

const LTX_MODELS: readonly ['ltx-2-3-fast', 'ltx-2-3-pro', 'ltx-2-5-fast', 'ltx-2-5-pro']

LTX_CAMERA_MOTIONS

The camera motions the V2 API accepts.

const LTX_CAMERA_MOTIONS: readonly [
  'dolly_in',
  'dolly_out',
  'dolly_left',
  'dolly_right',
  'jib_up',
  'jib_down',
  'static',
  'focus_shift',
]

DEFAULT_TIMEOUT_MS

Default per-request timeout.

const DEFAULT_TIMEOUT_MS: 120000

provider

The default provider, configured from env vars on each call (wire with setProvider). Safe to import before secrets are loaded.

const provider: AIVideoGenerationProvider

aiVideoGenerationLtxSecretDefinitions

Secret definitions used by the LTX video generation bond.

const aiVideoGenerationLtxSecretDefinitions: SecretDefinition[]

Core Interface

Implements @molecule/api-ai-video-generation interface.

Bond Wiring

Setup function to register this provider with the core interface:

import { setProvider } from '@molecule/api-ai-video-generation'
import { provider } from '@molecule/api-ai-video-generation-ltx'

export function setupAiVideoGenerationLtx(): void {
  setProvider(provider)
}

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-ai-video-generation >=1.0.0
  • @molecule/api-secrets ^1.0.1

Environment Variables

  • LTXV_API_KEY (required) — LTX API key
    • Setup: Create one in the LTX developer console (console.ltx.video); sent as a Bearer token on every request. This is the env-var name the LTX docs themselves use.
    • Get it here: https://console.ltx.video
    • Example: ltx-...
  • LTX_MODEL (optional) — LTX model id
    • Setup: One of ltx-2-5-fast, ltx-2-5-pro, ltx-2-3-fast, ltx-2-3-pro. Defaults to ltx-2-5-fast.
    • Get it here: https://docs.ltx.io/models
    • Example: ltx-2-5-fast
  • LTX_BASE_URL (optional) — LTX API base URL

Runtime Dependencies

  • @molecule/api-ai-video-generation

  • @molecule/api-secrets

  • Only the async V2 job API is used. The synchronous V1 endpoints (POST /v1/text-to-video returning the MP4 body directly) are deprecated and stop working after October 26, 2026 — this bond never touches them. Submit is POST /v2/text-to-video or /v2/image-to-video (202 + {id, created_at}); poll GET /v2/{endpoint}/{id} — the docs recommend waiting at least 5 seconds between polls (vary 5–6 s).

  • duration is required but nullable — omitting durationSeconds sends null, which ONLY ltx-2-5 tiers accept (automatic duration: the model picks the length from the prompt). With no durationSeconds on an ltx-2-3-* model this bond throws before any request. On a prepaid account, duration: null HOLDS credits for the maximum (20 s on fast, 10 s on pro) even if the clip comes out shorter.

  • resolution is a "WxH" string (landscape "1920x1080", portrait "1080x1920"), never an enum, and model ids (ltx-2-5-fast, ltx-2-5-pro, ltx-2-3-fast, ltx-2-3-pro) ride in the body, not the URL. With no resolution the bond sends "1280x720" (720p landscape). Supported resolutions are per model tier — 720p/1080p/1440p/4K on 2.5.

  • Output URLs expire on their own, independently of job status (which itself is only kept ≤24 h after completion): download the finished MP4 and re-host it (e.g. via the uploads bond) immediately — never store the result.url as if it were permanent.

  • Job ids this bond returns are prefixed — ltx/<endpoint>/<model>/<api id> — because the poll path embeds the submit endpoint. Pass the id exactly as generate() returned it; a bare API id is rejected with a clear error. An API id this bond could never poll back (dot segments, empty segments, ?, #) is refused at submit time instead of minted into a handle getStatus() would reject.

  • Auth is Authorization: Bearer with LTXV_API_KEY (the env-var name the LTX docs themselves use; create the key at console.ltx.video). A missing key throws the tagged config.notConfigured error. LTX_BASE_URL overrides https://api.ltx.io for brokers/tests.

  • First frames: URIs pass through, bytes upload. An image that is an http(s) or ltx: URI is sent as image_uri unchanged; a Buffer, base64 string or data URL is uploaded via POST /v1/upload (pre-signed PUT; uploads expire after 1 h and stored files after 24 h) and the returned storage_uri is used.

  • Diffusion controls do not exist here. negativePrompt, seed, steps and guidanceScale are silently NOT forwarded — the V2 API has no such fields (they are Diffusers-pipeline knobs, not hosted-API ones). fps and generateAudio (→ generate_audio) forward only when the caller sets them — this bond sends no default of its own — as does cameraMotion (→ camera_motion, validated against the eight documented motions).

  • Errors are LtxVideoError carrying the HTTP status (400 invalid, 401 auth, 402 insufficient credits, 422 content filtered, 429 concurrency, 500/503/504 server; 0 = unreachable/timeout) and the API's error type as code (content_filtered_error, …). A failed JOB surfaces through getStatus() as status: 'failed' with the same error shape, not as a throw. Nothing is retried; the default timeout is 120 s.

  • License asymmetry vs the sibling bond: LTX-2.5 model weights are NOT open source — the LTX-2.x Community License excludes ≥$10M-revenue entities and bans building competing products; you are responsible for your own compliance when reselling access (outputs themselves are unencumbered — "Licensor claims no rights in the Output"). Kandinsky 6.0 (the -kandinsky bond) is MIT with no such limits.

E2E Tests

Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:

  • Entering a prompt in the UI and submitting produces a REAL playable video in the live preview once the job completes — not a broken video element, a spinner that never ends, or an error toast. Confirm the video element actually loaded (its readyState/duration are set) and the clip visibly reflects the prompt (a "waves on a beach" prompt shows waves, not a placeholder).
  • Two different prompts produce two visibly different clips — a fixed stub, or a cached first result that never changes, is a broken integration.
  • The video is STORED AND SERVED FROM THE APP'S OWN ORIGIN: the played source/src (and any saved record) points at the app's uploads/storage, not the provider's temporary URL. Provider url results expire — reload the page (or revisit the record later) and the video must still play. Download the provider result server-side and persist it (uploads bond) once the job completes; never hotlink the provider URL. (data results must likewise be saved, not held only in the response.)
  • Job progress is visible while polling: the UI shows the job is pending/processing (not frozen), and a failed job surfaces the provider's error message clearly so the user can recover and try another prompt.
  • Any exposed generation options take effect: changing durationSeconds/resolution yields a different-length/-sized clip. If the UI exposes no such options, this box is n/a — say so.
  • Generation is server-side and authorized: the provider API key never reaches the browser (check the network tab / built client bundle — no key, no direct provider call from the page), the generate endpoint requires auth, and a caller cannot run unbounded costly generations through an open or unrate-limited route. Every clip is billed or GPU-expensive.