---
title: TypeScript SDK | Runware Docs
url: https://runware.ai/docs/platform/typescript
description: 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.
relatedDocuments:
  - https://runware.ai/docs/platform/python
  - https://runware.ai/docs/platform/mcp
  - https://runware.ai/docs/platform/streaming
---
## 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**:

```bash
npm install @runware/sdk
```

**pnpm**:

```bash
pnpm add @runware/sdk
```

**bun**:

```bash
bun add @runware/sdk
```

**yarn**:

```bash
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:

```typescript
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.

> [!NOTE]
> 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:

```typescript
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.

```typescript
// 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:

```typescript
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`):

```bash
npm install ajv
```

```typescript
const result = await client.run(payload, { validate: true })
```

## Concurrent requests

`Promise.all` is the canonical way to fan out:

```typescript
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`:

```typescript
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.

> [!NOTE]
> `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.

```typescript
// 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](https://runware.ai/models) 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()`:

```typescript
// 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](#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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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'`.

> [!WARNING]
> 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:

```typescript
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.

```typescript
// 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](https://github.com/runware/runware-typescript)