TypeScript SDK

TypeScript-first SDK for the Runware API. REST or WebSocket transport, JSON-Schema-validated requests, typed errors, LLM streaming, and a content namespace for browsing the curated model catalog. Runs on Node, Bun, Deno, and edge runtimes.

Introduction

The Runware TypeScript SDK gives you a single client for the whole inference surface: image, video, audio, text, and 3D generation, plus utility endpoints like model search and account management. It's TypeScript-first and ships precise types for every architecture and curated model. It can also validate a request against the model's JSON Schema before sending.

You pick the transport at construction. REST is the right choice for serverless and edge runtimes. WebSocket keeps a persistent connection and is faster when you're issuing many calls per process or want push-based delivery on long-running tasks. Both transports share the same run() method and produce the same result shape, so switching is a single config change.

The SDK also exposes a content namespace (client.content.*) that hits the public model catalog without burning credits. List curated models, fetch a model's metadata or pricing, pull curated example payloads, or browse collections and creators. Useful for building model pickers or for an in-app agent to discover what's available.

Installation

The SDK runs on Node 18+, Bun, Deno, and any V8 isolate runtime (Cloudflare Workers, Vercel Edge, etc.).

npm install @runware/sdk
pnpm add @runware/sdk
bun add @runware/sdk
yarn add @runware/sdk

Quick start

The fastest path is REST with sync delivery. The server holds the connection open until the task completes and returns the result in the same response:

import { createClient } from '@runware/sdk'

const client = createClient({
  apiKey: process.env.RUNWARE_API_KEY,
  transport: 'rest',
})

const images = await client.run({
  taskType: 'imageInference',
  model: 'runware:101@1',
  positivePrompt: 'A serene mountain landscape at sunset',
  width: 1024,
  height: 1024,
  deliveryMethod: 'sync',
})

console.log(images[0].imageURL)

There's no connect() step for REST. The first request opens a connection if needed, the SDK reuses it for follow-ups, and Node's keep-alive handles the underlying socket pool.

Set RUNWARE_API_KEY in your environment and pass it to createClient. The SDK doesn't read from process.env automatically because edge runtimes don't always expose it.

Choosing a transport

The SDK ships two transports behind the same API. They differ in connection model and in how the server delivers the result.

Transport When to use it
rest Serverless functions, edge runtimes, low-volume scripts. No persistent socket.
websocket (default) Many requests per process, lower per-call latency, push-based progress on long-running tasks.

Construct with the transport you want:

const client = createClient({
  apiKey: process.env.RUNWARE_API_KEY,
  transport: 'rest', // or 'websocket'
})

REST has two delivery modes. Sync holds the response open and works well for fast tasks like image inference. Async returns a task UUID immediately and polls for completion, which is what you want for video and other long-running operations.

// Sync (recommended for image inference and other fast tasks)
const images = await client.run({
  taskType: 'imageInference',
  model: 'runware:101@1',
  positivePrompt: 'A coastal town at dusk',
  width: 1024,
  height: 1024,
  deliveryMethod: 'sync',
})

// Async (the SDK polls for you; recommended for video)
const videos = await client.run({
  taskType: 'videoInference',
  model: 'google:3@3',
  positivePrompt: 'Waves crashing on a beach',
  duration: 8,
  // deliveryMethod defaults to 'async'
})

WebSocket uses async delivery only. Each request gets a taskUUID and results stream back over the same socket. The SDK reconciles each frame with its awaiting call. On Node, the SDK imports ws lazily. Browsers and edge runtimes use the platform WebSocket directly.

Typed parameters

When you know the task you're running, import the matching type and TypeScript will validate the request shape at compile time:

import { createClient, type ImageInferenceParams } from '@runware/sdk'

const client = createClient({ apiKey: process.env.RUNWARE_API_KEY })

const params: ImageInferenceParams = {
  taskType: 'imageInference',
  model: 'runware:101@1',
  positivePrompt: 'A professional headshot portrait',
  negativePrompt: 'blurry, distorted',
  width: 1024,
  height: 1024,
  steps: 30,
}

const images = await client.run(params)

The SDK ships one type per supported task. A wrong field name or a missing required field surfaces as red squiggles in your editor before the code runs.

Schema validation

The SDK can validate your request against the model's JSON Schema before it leaves the process. Errors (a missing model, or a width that isn't a valid enum) surface as a typed RunwareError with the offending field name, not as a 400 from the server hundreds of milliseconds later.

Validation is off by default and relies on ajv, an optional peer dependency. Install it, then turn validation on for a call (or globally via validate: true in createClient):

npm install ajv
const result = await client.run(payload, { validate: true })

Concurrent requests

Promise.all is the canonical way to fan out:

const client = createClient({
  apiKey: process.env.RUNWARE_API_KEY,
  transport: 'websocket',
})

await client.connect()

const [images, alt, noBg] = await Promise.all([
  client.run({
    taskType: 'imageInference',
    model: 'runware:101@1',
    positivePrompt: 'Abstract digital art',
    width: 1024,
    height: 1024,
  }),
  client.run({
    taskType: 'imageInference',
    model: 'runware:101@1',
    positivePrompt: 'A neon-lit alley at night',
    width: 1024,
    height: 1024,
  }),
  client.run({
    taskType: 'imageBackgroundRemoval',
    inputImage: 'https://example.com/portrait.jpg',
  }),
])

WebSocket has a real edge here. All three requests share one socket and the responses stream back independently. REST would issue three HTTP requests in parallel and tear them down after each.

LLM streaming

Text inference supports Server-Sent Events streaming for low-latency generation. Call client.stream() and iterate over the resulting TextStream:

const stream = await client.stream({
  taskType: 'textInference',
  model: 'minimax:m2.7@0',
  messages: [
    { role: 'user', content: 'Write a haiku about the ocean.' },
  ],
})

for await (const delta of stream.textStream) {
  process.stdout.write(delta)
}

const result = await stream.result()
console.log(`\nFinish reason: ${result.finishReason}`)

The stream exposes two async iterators (textStream and reasoningStream) plus a result() promise that resolves to the final accumulated text, finish reason, and usage stats. Iteration errors surface as typed exceptions so a half-truncated stream never silently ends.

stream() handles a single completion only. Pass numberResults greater than 1 and it throws, use run() for batch text generation.

Content namespace

client.content reaches Runware's public model catalog and does not consume credits. Use it to list curated models, fetch pricing, pull example payloads, or browse the catalog by collection or creator.

// Search the curated catalog
const models = await client.content.listModels({
  category: 'image',
  creator: 'black-forest-labs',
  search: 'flux dev',
})

// Inspect one
const flux = await client.content.getModel('flux-1-dev')
console.log(flux?.headline)

// Pull curated examples to seed prompts
const examples = await client.content.getModelExamples('flux-1-dev')

// Pricing for budget-driven decisions
const pricing = await client.content.getModelPricing('flux-1-dev')

// Browse the capability taxonomy, collections, and creators
const capabilities = await client.content.listCapabilities()
const collections = await client.content.listCollections({ category: 'image' })
const creators = await client.content.listCreators()

This is the same data the model picker and pricing pages render from. List endpoints accept paginate: true if you want a paginated envelope instead of a flat array.

Utility methods

Beyond inference, the client exposes the platform's utility endpoints. Each is a typed method that takes the same RunOptions second argument as run():

// Search the full live model catalog
const models = await client.modelSearch({ search: 'portrait', architecture: 'sdxl', limit: 10 })

// Upload an image for use as input (URL, data URI, or Base64)
const uploaded = await client.imageUpload({ image: 'https://example.com/photo.jpg' })

// Account details (credits, limits)
const account = await client.accountManagement({ operation: 'getDetails' })

// Look up a task you ran earlier, by UUID
const archived = await client.getTaskDetails({ taskUUID: 'abc-123' })

// Upload a custom model
await client.modelUpload({ category: 'checkpoint', architecture: 'sdxl', format: 'safetensors' })

modelSearch queries the full live catalog, including non-curated models. To browse the curated set as metadata without spending credits, use the content namespace above.

File helpers

fileToDataURI encodes a local file or in-memory buffer as a data: URI you can pass anywhere an image input is accepted:

import { fileToDataURI } from '@runware/sdk'
import { readFile } from 'node:fs/promises'

const dataUri = await fileToDataURI(await readFile('photo.jpg'))
await client.imageUpload({ image: dataUri })

Errors

Every failure throws a typed RunwareError with a stable code enum and the offending parameter when applicable:

import { isRunwareError } from '@runware/sdk'

try {
  await client.run(payload)
} catch (err) {
  if (!isRunwareError(err)) { throw err }

  switch (err.code) {
    case 'quota':
      // Insufficient credits; prompt the user to top up
      break
    case 'rateLimit':
      // Back off and retry
      break
    case 'safety':
      // Prompt or image triggered a safety filter
      break
    default:
      throw err
  }
}

The code value is one of validation, auth, quota, rateLimit, safety, provider, timeout, notFound, serverError, connection, aborted, unknown. Raw provider-specific codes are mapped onto this stable set so your error-handling code doesn't have to track upstream changes.

The same enum is used by the Python SDK, so cross-language services can react to the same code values.

Configuration

Most apps only need apiKey. The full createClient config accepts:

const client = createClient({
  apiKey: process.env.RUNWARE_API_KEY,
  transport: 'websocket',
  // Timeouts (ms)
  timeout: 120_000,           // Per-request cap
  pollTimeout: 600_000,       // Async-delivery polling cap
  // Validation behavior
  validate: true,             // Toggle JSON-Schema validation (off by default)
  // Logging
  debug: true,                // Console logs; pass logSink for custom routing
})

Per-call overrides accept the same keys plus an AbortSignal for cancellation:

const controller = new AbortController()

const result = await client.run(payload, {
  timeout: 30_000,
  signal: controller.signal,
  validate: false,
})

Cancellation and progress

Every call accepts an AbortSignal. Aborting it causes the in-flight call (REST poll, WebSocket subscription, or LLM stream) to terminate cleanly and throw a RunwareError with code === 'aborted'.

Aborting is client-side only. The server keeps processing the task and you are still billed for it. Aborting just stops the SDK from waiting for the result.

For long-running tasks, two callbacks let you observe a task as it unfolds. onResult fires once per item as it reaches a terminal state, and onProgress fires when an item's progress field (0-100) changes. Only a few long-running models (mostly training) emit progress:

const result = await client.run(payload, {
  onResult: (item) => {
    console.log('Got partial:', item.imageUUID ?? item.videoUUID)
  },
  onProgress: (item) => {
    console.log(`${item.progress}%`)
  },
})

Edge runtimes

The SDK runs unmodified on Cloudflare Workers, Vercel Edge, and Deno Deploy. REST is the right transport here. WebSocket is supported, but the per-request lifetime of edge invocations means you usually don't get the WS performance benefit.

// Cloudflare Worker
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const client = createClient({
      apiKey: env.RUNWARE_API_KEY,
      transport: 'rest',
    })

    const images = await client.run({
      taskType: 'imageInference',
      model: 'runware:101@1',
      positivePrompt: 'A coastal town at dusk',
      width: 1024,
      height: 1024,
      deliveryMethod: 'sync',
    })

    return Response.json({ url: images[0].imageURL })
  },
}

Source

The SDK is open source. Issues and pull requests are welcome.

Repository: github.com/runware/runware-typescript