# Documentation This file contains the complete documentation in markdown format for LLM consumption. --- ## One API for image, video, audio, 3D, and text **URL:** https://runware.ai/docs/platform/introduction **Description:** Hundreds of models on Runware are addressable by their model identifier and reachable through a single call. Runware exposes a **single endpoint** for image, video, audio, 3D, and text generation. Models are addressed by their **model identifier**, and the same request shape works across every modality. ## One shape, every modality ImageVideoAudio3DText [Create API key](https://runware.ai/signup) → Request ```json { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "xai:grok-imagine@image-quality", "positivePrompt": "A golden retriever on a park bench, cinematic lighting", "width": 1024, "height": 1024 } ``` ```json { "taskType": "videoInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "klingai:kling-video@3-4k", "positivePrompt": "Ocean waves crashing on a beach at sunset", "width": 3840, "height": 2160, "duration": 8 } ``` ```json { "taskType": "audioInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "fishaudio:s2.1@pro", "speech": { "text": "Welcome to Runware." } } ``` ```json { "taskType": "3dInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "tripo:v3.1@0", "positivePrompt": "A vintage Polaroid camera, detailed mesh" } ``` ```json { "taskType": "textInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "anthropic:claude@opus-4.8", "messages": [ { "role": "user", "content": "Write a haiku about the ocean." } ] } ``` ← Response ```json { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "imageUUID": "77da2d99-a6d3-44d9-b8c0-ae9fb06b6200", "imageURL": "https://im.runware.ai/image/os/a14d18/ws/2/ii/77da2d99-a6d3-44d9-b8c0-ae9fb06b6200.jpg", "cost": 0.05 } ``` ```json { "taskType": "videoInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "videoUUID": "b7db282d-2943-4f12-992f-77df3ad3ec71", "videoURL": "https://vm.runware.ai/video/os/a14d18/ws/2/vi/b7db282d-2943-4f12-992f-77df3ad3ec71.mp4", "cost": 3.36 } ``` ```json { "taskType": "audioInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "audioUUID": "f1e2d3c4-b5a6-7890-1234-567890abcdef", "audioURL": "https://am.runware.ai/audio/os/a14d18/ws/2/ai/f1e2d3c4-b5a6-7890-1234-567890abcdef.mp3", "cost": 0.0003 } ``` ```json { "taskType": "3dInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "outputs": { "files": [ { "uuid": "8c2e6d99-7404-43e6-a8f0-7a3b8d8d9f0c", "url": "https://im.runware.ai/image/os/a14d18/ws/5/ii/8c2e6d99-7404-43e6-a8f0-7a3b8d8d9f0c.glb" } ] }, "cost": 0.3 } ``` ```json { "taskType": "textInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "text": "Ocean breathes the dawn\nWaves whisper their endless songs\nMoon hangs silent watch", "finishReason": "stop", "cost": 0.0005 } ``` bytedance:seedream@5.0-proalibaba:happyhorse@1.1klingai:kling-video@3.0-turboanthropic:claude@fable-5prunaai:p-video@replaceideogram:4@remixideogram:4@0runway:aleph@2.0minimax:m3@0fishaudio:s2.1@proexactly:illustrative@trainingxai:grok-imagine@video-1.5bfl:flux@vtoprunaai:p-video@animateanthropic:claude@opus-4.8krea:krea@2-largegoogle:gemini@3.5-flashrecraft:v4.1-pro@0recraft:v4.1-utility-pro@0xai:grok-imagine@image-qualityxai:grok@4.3inworld:tts@2luma:uni@1luma:uni@1-maxheygen:avatar@5prunaai:p-video@avataralibaba:happyhorse@1.0deepseek:v4@flashdeepseek:v4@proskywork:skyreels@v4openai:gpt@5.5klingai:kling-video@o3-4kklingai:kling-video@3-4kopenai:gpt-image@2moonshotai:kimi@k2.6anthropic:claude@opus-4.7google:gemini@3.1-flash-ttsimagineart:2.0@0minimax:music@2.6minimax:music@coverbytedance:seedream@5.0-proalibaba:happyhorse@1.1klingai:kling-video@3.0-turboanthropic:claude@fable-5prunaai:p-video@replaceideogram:4@remixideogram:4@0runway:aleph@2.0minimax:m3@0fishaudio:s2.1@proexactly:illustrative@trainingxai:grok-imagine@video-1.5bfl:flux@vtoprunaai:p-video@animateanthropic:claude@opus-4.8krea:krea@2-largegoogle:gemini@3.5-flashrecraft:v4.1-pro@0recraft:v4.1-utility-pro@0xai:grok-imagine@image-qualityxai:grok@4.3inworld:tts@2luma:uni@1luma:uni@1-maxheygen:avatar@5prunaai:p-video@avataralibaba:happyhorse@1.0deepseek:v4@flashdeepseek:v4@proskywork:skyreels@v4openai:gpt@5.5klingai:kling-video@o3-4kklingai:kling-video@3-4kopenai:gpt-image@2moonshotai:kimi@k2.6anthropic:claude@opus-4.7google:gemini@3.1-flash-ttsimagineart:2.0@0minimax:music@2.6minimax:music@cover **406 hosted models**. Plus thousands of community and user-uploaded models that run on any of our supported architectures. [Browse all models](https://runware.ai/docs/models) ## Getting started 1. [Sign up](https://runware.ai/signup) for a Runware account. 2. Generate an API key from your dashboard. 3. Read [Authentication](https://runware.ai/docs/platform/authentication) to learn how to send the key with your requests. 4. Browse [Models](https://runware.ai/docs/models) to find the right one for your use case. > [!NOTE] > The fastest path is the [TypeScript SDK](https://runware.ai/docs/platform/typescript) or [Python SDK](https://runware.ai/docs/platform/python). Both wrap REST and WebSocket transports and validate requests against the model's JSON Schema before sending. For AI agents, see the [MCP integration](https://runware.ai/docs/platform/mcp) and [Skills](https://runware.ai/docs/platform/skills). ## Your first request The same call in cURL, TypeScript, or Python. TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const [result] = await client.run({ model: 'xai:grok-imagine@image-quality', positivePrompt: 'A golden retriever on a park bench, cinematic lighting', width: 1024, height: 1024 }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: results = await client.run({ "model": "xai:grok-imagine@image-quality", "positivePrompt": "A golden retriever on a park bench, cinematic lighting", "width": 1024, "height": 1024 }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "xai:grok-imagine@image-quality", "positivePrompt": "A golden retriever on a park bench, cinematic lighting", "width": 1024, "height": 1024 } ]' ``` ```bash runware run xai:grok-imagine@image-quality \ positivePrompt="A golden retriever on a park bench, cinematic lighting" \ width=1024 \ height=1024 ``` ```json { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "xai:grok-imagine@image-quality", "positivePrompt": "A golden retriever on a park bench, cinematic lighting", "width": 1024, "height": 1024 } ``` ## Core API concepts ### Task-based architecture Every Runware request is a **task**. Each task is processed independently, so you can send one task or batch many in a single call. Long-running tasks (video, large images) run asynchronously and deliver results as they complete. ### Anatomy of a request Hover any field below to see what it does. The same shape applies to every task type on Runware. ```json [ { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "xai:grok-imagine@image-quality", "positivePrompt": "A golden retriever on a park bench", "width": 1024, "height": 1024 } ] ``` 1 Array of tasks Every request is an array. Send one task or batch many in a single call. 2 Task object Each item in the array is one task. The same object shape works for image, video, audio, 3D, and text. 3 Task type Picks the operation. `imageInference`, `videoInference`, `audioInference`, `3dInference`, `textInference`, plus utilities. 4 Task UUID A UUID v4 you generate. Used to match the response and to trace the task in your dashboard. 5 Model Address any model on the platform by its model identifier: `creator:family@version`. ### Common response structure Every response echoes `taskType` and `taskUUID` from the request, and adds the modality-specific output: `imageURL`, `videoURL`, `audioURL`, mesh files for 3D, or `text` for LLMs. Set **`includeCost: true`** on the request to get a `cost` field back. When a task produces multiple results, each completes independently and arrives in its own message. Output URLs are retained for **7 days** by default. Set `ttl` on the request to change the retention. ```json { "data": [ { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "imageUUID": "77da2d99-a6d3-44d9-b8c0-ae9fb06b6200", "imageURL": "https://im.runware.ai/image/os/a14d18/ws/2/ii/a770f077-f413-47de-9dac-be0b26a35da6.jpg", "cost": 0.0013 }, { "taskType": "videoInference", "taskUUID": "b880f077-e514-58ef-0ebd-ce1c37b46eb7", "videoUUID": "b7db282d-2943-4f12-992f-77df3ad3ec71", "videoURL": "https://vm.runware.ai/video/os/a14d18/ws/2/vi/b7db282d-2943-4f12-992f-77df3ad3ec71.mp4", "cost": 0.18 } ] } ``` --- ## Connection & Authentication **URL:** https://runware.ai/docs/platform/authentication **Description:** Learn how to connect and authenticate with the Runware API using HTTP REST or WebSockets. ## Authentication To interact with the Runware API, you need to authenticate your requests using an API key. This key is unique to your account and is used to identify you when making requests. You can create multiple keys for different projects or environments (development, production, staging), add descriptions to them, and revoke them at any time. With the teams feature, you can also share keys with your team members. To create an API key, simply sign up on [Runware](https://runware.ai/signup) and visit the "API Keys" page. Then, click "Create Key" and fill the details for your new key. ## HTTP (REST) We recommend using [WebSockets](#websockets) for a more efficient and faster connection. However, if you prefer to use a **simpler connection** and don't need to keep it open, you can use HTTP REST API. The URL for the API is **`https://api.runware.ai/v1`**. All requests must be made using the `POST` method and the `Content-Type` header must be set to `application/json`. The payload for each request is a **JSON array with one or more objects**. Each object represents a task to be executed by the API. Authentication can be done by including the authentication object as the first element in the array, or by using the `Authorization` header with the value `Bearer `. **Payload Auth**: ```bash curl --location 'https://api.runware.ai/v1' \ --header 'Content-Type: application/json' \ --data-raw '[ { "taskType": "authentication", "apiKey": "" }, { "taskType": "imageInference", "taskUUID": "39d7207a-87ef-4c93-8082-1431f9c1dc97", "positivePrompt": "a cat", "width": 512, "height": 512, "model": "civitai:102438@133677", "numberResults": 1 } ]' ``` **Header Auth**: ```bash curl --location 'https://api.runware.ai/v1' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data-raw '[ { "taskType": "imageInference", "taskUUID": "39d7207a-87ef-4c93-8082-1431f9c1dc97", "positivePrompt": "a cat", "width": 512, "height": 512, "model": "civitai:102438@133677", "numberResults": 1 } ]' ``` The API will return a JSON object with the `data` property. This property is an array containing all the response objects. Each object will contain the `taskType` and the `taskUUID` of the request it's responding to, as well as other properties related to the task. ```json { "data": [ { "taskType": "imageInference", "taskUUID": "39d7207a-87ef-4c93-8082-1431f9c1dc97", "imageUUID": "b7db282d-2943-4f12-992f-77df3ad3ec71", "imageURL": "https://im.runware.ai/image/os/a14d18/ws/2/ii/b7db282d-2943-4f12-992f-77df3ad3ec71.jpg" } ] } ``` If there's an error, the API will not return the `data` property. Instead, it will return the `error` property, containing the error message. ## WebSockets We support WebSocket connections as they are **more efficient, faster, and less resource intensive**. We have made our WebSocket connections easy to work with, as each response contains the request ID. So it's possible to easily match **request → response**. The API uses a bidirectional protocol that encodes all messages as **JSON objects**. To connect you can use one of the SDKs we provide ([TypeScript](https://runware.ai/docs/platform/typescript), [Python](https://runware.ai/docs/platform/python)) or manually. If you prefer to connect manually (to use another language/technology), the endpoint URL is **`wss://ws-api.runware.ai/v1`**. ### New connections #### Request WebSocket connections are point-to-point, so **there's no need for each request to contain an authentication header**. Instead, the first request **must always** be an authentication request that includes the API key. This way we can identify which subsequent requests are arriving from the same user. TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const [result] = await client.run({ taskType: 'authentication', apiKey: '' }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: results = await client.run({ "taskType": "authentication", "apiKey": "" }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "authentication", "apiKey": "" } ]' ``` ```bash runware run undefined apiKey="" ``` ```json { "taskType": "authentication", "apiKey": "" } ``` #### Response After you've made the authentication request the API will return an object with the `connectionSessionUUID` parameter. This string is unique to your connection and is used to resume connections in case of disconnection (more on this later). ```json { "data": [ { "taskType": "authentication", "connectionSessionUUID": "f40c2aeb-f8a7-4af7-a1ab-7594c9bf778f" } ] } ``` In case of error you will receive an object with the error message. ```json { "errors": [ { "code": "invalidApiKey", "message": "Invalid API key. Get one at https://runware.ai/signup", "parameter": "apiKey", "type": "string", "taskType": "authentication" } ] } ``` ### Keeping connection alive The WebSocket connection is kept open for 120 seconds **from the last message exchanged**. If you **don't send any messages for 120 seconds**, the connection will be closed automatically. To keep the connection going, you can send a `ping` message when needed, to which we will reply with a `pong`. #### Request Send a ping task to signal that the connection is still active: TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const [result] = await client.run({ taskType: 'ping', ping: true }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: results = await client.run({ "taskType": "ping", "ping": True }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "ping", "ping": true } ]' ``` ```bash runware run undefined ping=true ``` ```json { "taskType": "ping", "ping": true } ``` #### Response The server will respond confirming the connection is alive: ```json { "data": [ { "taskType": "ping", "pong": true } ] } ``` ### Resuming connections If any service, server or network is unresponsive (for instance due to a restart), all the results that could not be delivered **are kept in a buffer memory for 120 seconds**. You can reconnect and have these messages delivered by including the `connectionSessionUUID` parameter in the initial authentication connection request. ```json [ { "taskType": "authentication", "apiKey": "", "connectionSessionUUID": "f40c2aeb-f8a7-4af7-a1ab-7594c9bf778f" } ] ``` This means there is no need to make the same request again, the initial one will be delivered when reconnecting. SDK libraries reconnect **automatically**. --- ## Task Polling **URL:** https://runware.ai/docs/platform/task-polling **Description:** Retrieve results from async operations using the getResponse task. Learn how to poll for status updates and fetch completed results. ## Introduction Some operations like video generation require extended processing time. Instead of holding the connection open, you can set `"deliveryMethod": "async"` on your request to **queue the task for asynchronous processing**. You receive an acknowledgment immediately, then use the `getResponse` task to poll for status updates and retrieve the final result when ready. Alternatively, you can use [Webhooks](https://runware.ai/docs/platform/webhooks) to receive results via HTTP POST as soon as they are ready, without polling. > [!NOTE] > If you are using the [TypeScript](https://runware.ai/docs/platform/typescript) or [Python](https://runware.ai/docs/platform/python) SDK, async polling is handled automatically. You do not need to call `getResponse` directly. This page is relevant if you are integrating with the raw API. ### How it works When you call `getResponse` with a task UUID from an async operation, the system: 1. **Checks active operations** and finds the running async task and all its generations. 2. **Returns the current status** of each generation: `processing`, `success`, or `error`. 3. **Provides results as they become available.** Completed generations include their final outputs, so you can access partial results without waiting for everything to finish. > [!NOTE] > Polling best practices > > Implement **exponential backoff** when polling to avoid overwhelming the API. Start with 1-2 second intervals and gradually increase the delay between requests. > > For tasks with predictable durations (like video generation), consider adding an **initial delay** before your first poll to reduce unnecessary requests. ## Request The Runware API always accepts an array of objects as input, where each object represents a **specific task to be performed**. The structure varies depending on the workflow and features used. The following example shows the structure of a request object. TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const [result] = await client.run({ taskType: 'getResponse', taskUUID: '50836053-a0ee-4cf5-b9d6-ae7c5d140ada' }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: results = await client.run({ "taskType": "getResponse", "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada" }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "getResponse", "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada" } ]' ``` ```bash runware result 50836053-a0ee-4cf5-b9d6-ae7c5d140ada ``` ```json { "taskType": "getResponse", "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada" } ``` --- ### [taskType](#request-tasktype) - **Type**: `string` - **Required**: true - **Value**: `getResponse` Identifier for the type of task being performed ### [taskUUID](#request-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier for tracking tasks and matching async responses. Must be unique per task. ## Response All requests return the same response format. Processing and successful generations appear in the `data` array, while failed generations are moved to the `errors` array. ```json { "data": [ { "taskType": "videoInference", "taskUUID": "24cd5dff-cb81-4db5-8506-b72a9425f9d1", "status": "processing", "progress": 47 }, { "taskType": "videoInference", "taskUUID": "24cd5dff-cb81-4db5-8506-b72a9425f9d1", "status": "success", "videoUUID": "b7db282d-2943-4f12-992f-77df3ad3ec71", "videoURL": "https://vm.runware.ai/video/os/a14d18/ws/2/vi/b7db282d-2943-4f12-992f-77df3ad3ec71.mp4", "cost": 0.18 } ], "errors": [ { "code": "timeoutProvider", "status": "error", "message": "The external provider did not respond within the timeout window. The request was automatically terminated.", "documentation": "https://runware.ai/docs/platform/errors", "taskUUID": "24cd5dff-cb81-4db5-8506-b72a9425f9d1" } ] } ``` --- ### [taskType](#response-tasktype) - **Type**: `string` - **Required**: true Identifier for the type of task this response belongs to. **Possible values**: `authentication` `imageInference` `videoInference` `audioInference` `textInference` `modelSearch` `modelUpload` `accountManagement` `imageUpload` `mediaStorage` `getResponse` `caption` `controlNetPreprocess` `imageMasking` `promptEnhance` `removeBackground` `upscale` `vectorize` `training` `ping` ### [taskUUID](#response-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier echoed from the original request, used to match async responses to their tasks. ### [status](#response-status) - **Type**: `string` - **Required**: true Current status of the task. **Possible values**: `processing` `success` `error` ### [progress](#response-progress) - **Type**: `integer` - **Min**: `0` - **Max**: `100` Task progress as a percentage from 0 to 100. Returned while `status` is `processing`, and only for tasks that support progress reporting. ### [error](#response-error) - **Path**: `error.code` - **Type**: `object (2 properties)` Error details if the task failed. #### [code](#response-error-code) - **Path**: `error.code` - **Type**: `string` Error code. #### [message](#response-error-message) - **Path**: `error.message` - **Type**: `string` Error message description. --- ## Streaming (SSE) **URL:** https://runware.ai/docs/platform/streaming **Description:** Stream text inference results token-by-token using Server-Sent Events. Learn how to enable streaming and parse the SSE response format. ## Overview Streaming lets you receive text inference responses **token-by-token as they are generated**, rather than waiting for the entire response to complete. Results are delivered via [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) (SSE), a lightweight HTTP-based protocol designed for real-time, server-to-client data delivery. This is particularly useful for chat interfaces and any application where **perceived latency matters**. Instead of a multi-second wait followed by a wall of text, your users see the response appear progressively. Streaming is available for all text inference models and works alongside the existing `sync` and `async` delivery methods. ## Enabling streaming Add `"deliveryMethod": "stream"` to your request. Everything else stays the same: TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const [result] = await client.run({ model: 'minimax:m2.7@0', deliveryMethod: 'stream', messages: [ { role: 'user', content: 'Hello' } ], settings: { maxTokens: 4096, temperature: 1 } }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: results = await client.run({ "model": "minimax:m2.7@0", "deliveryMethod": "stream", "messages": [ { "role": "user", "content": "Hello" } ], "settings": { "maxTokens": 4096, "temperature": 1 } }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "textInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "minimax:m2.7@0", "deliveryMethod": "stream", "messages": [ { "role": "user", "content": "Hello" } ], "settings": { "maxTokens": 4096, "temperature": 1 } } ]' ``` ```bash runware run minimax:m2.7@0 \ deliveryMethod=stream \ messages.0.role=user \ messages.0.content=Hello \ settings.maxTokens=4096 \ settings.temperature=1 ``` ```json { "taskType": "textInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "minimax:m2.7@0", "deliveryMethod": "stream", "messages": [ { "role": "user", "content": "Hello" } ], "settings": { "maxTokens": 4096, "temperature": 1 } } ``` The response will be an SSE stream instead of a single JSON object. Each event contains a chunk of the generated text that you can display immediately. ## Delivery methods compared The `deliveryMethod` parameter controls how results are returned. Streaming is one of three options available for text inference tasks: | Value | Behavior | Best for | | --- | --- | --- | | `sync` | Waits for the full response, returns it as a single JSON object. This is the default. | Simple integrations, short responses | | `stream` | Streams tokens as SSE events as they are generated. | Chat UIs, long-form generation | | `async` | Returns immediately with a task acknowledgment. Poll for results using [Task Polling](https://runware.ai/docs/platform/task-polling). | Background processing, long-running tasks | ## SSE response format The response is a standard SSE stream. Each event is a line prefixed with `data:`, followed by a JSON object, and terminated by a blank line. The stream ends with a `data: [DONE]` sentinel. The server may send `: ping` comments as keepalives, which should be ignored. ```text : ping data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","delta":{"text":"Hello"},"finishReason":null} data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","delta":{"text":" there"},"finishReason":null} data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","delta":{},"finishReason":"stop"} data: [DONE] ``` ### Parsing rules Follow these steps to parse the SSE stream: 1. **Skip blank lines** and comment lines (lines starting with `:`). 2. **Strip the `data:` prefix** from each event line. 3. **Stop when you see `data: [DONE]`**, which signals the end of the stream. 4. **Parse each remaining line as JSON**. 5. **Read the text** from `delta.text`. 6. **Check for errors** by looking for an `errors` array in the parsed object. ### Content chunks During generation, each event contains a small piece of the response text in `delta.text`. Concatenate these chunks to build the full response: ```text data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","delta":{"text":"The"},"finishReason":null} data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","delta":{"text":" answer"},"finishReason":null} data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","delta":{"text":" is"},"finishReason":null} data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","delta":{"text":" 42."},"finishReason":null} ``` ### Reasoning chunks Some models perform internal reasoning before generating the final response. For these models, reasoning tokens arrive **first** in `delta.reasoningContent`, followed by the actual response in `delta.text`. ```text // Reasoning chunks - delta.reasoningContent data: {"taskUUID":"6e879837-4b2a-4c1d-ae5f-8f3c21b07a92","taskType":"textInference","delta":{"reasoningContent":"The user asks: \"What is 2+2? Be brief.\" They want a short answer. It's a simple arithmetic: 4. Provide"}} data: {"taskUUID":"6e879837-4b2a-4c1d-ae5f-8f3c21b07a92","taskType":"textInference","delta":{"reasoningContent":" short answer."}} // Actual response - switches to delta.text data: {"taskUUID":"6e879837-4b2a-4c1d-ae5f-8f3c21b07a92","taskType":"textInference","delta":{"text":"4"},"finishReason":null} ``` You can display reasoning content in a collapsible section or debug panel, while streaming the final response directly to the user. ### Multiple results When you set `numberResults` greater than 1, multiple completions stream on the same connection. Each chunk includes a `resultIndex` field so you can tell which result it belongs to, since all results share the same `taskUUID`: ```text data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","resultIndex":0,"delta":{"text":"Paris"},"finishReason":null} data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","resultIndex":1,"delta":{"text":"The capital"},"finishReason":null} data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","resultIndex":0,"delta":{},"finishReason":"stop"} data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","resultIndex":1,"delta":{"text":" is Paris."},"finishReason":null} data: {"taskUUID":"a770f077-f413-47de-9dac-be0b26a35da6","taskType":"textInference","resultIndex":1,"delta":{},"finishReason":"stop"} data: [DONE] ``` Group chunks by `resultIndex` to reconstruct each result independently. Results may finish at different times. ### Final chunk and finish reason The last content-bearing event includes a `finishReason` value that tells you why the model stopped generating: | Finish reason | Meaning | | --- | --- | | `stop` | The model completed its response naturally. | | `length` | The response hit the `maxTokens` limit. | | `content_filter` | Content was filtered by the safety system. | | `tool_calls` | The model is requesting a tool call. | | `tool_use` | The model is requesting a tool use. | | `unknown` | The model stopped for an unrecognized reason. | ```text data: {"taskUUID":"6e879837-4b2a-4c1d-ae5f-8f3c21b07a92","taskType":"textInference","delta":{},"finishReason":"stop"} data: [DONE] ``` ### Cost and usage Cost and token usage are reported in the **final chunk** of the stream, but only when explicitly requested: - Set **`includeCost: true`** to receive the `cost` field with the total price of the request in USD. Useful for tracking spend and billing. - Set **`includeUsage: true`** to receive the `usage` object with detailed token counts and processing metadata. Useful for monitoring context window usage and optimizing prompts. TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const [result] = await client.run({ model: 'minimax:m2.7@0', deliveryMethod: 'stream', messages: [ { role: 'user', content: 'What is 2+2? Be brief.' } ], settings: { maxTokens: 4096, temperature: 1 }, includeCost: true, includeUsage: true }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: results = await client.run({ "model": "minimax:m2.7@0", "deliveryMethod": "stream", "messages": [ { "role": "user", "content": "What is 2+2? Be brief." } ], "settings": { "maxTokens": 4096, "temperature": 1 }, "includeCost": True, "includeUsage": True }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "textInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "minimax:m2.7@0", "deliveryMethod": "stream", "messages": [ { "role": "user", "content": "What is 2+2? Be brief." } ], "settings": { "maxTokens": 4096, "temperature": 1 }, "includeCost": true, "includeUsage": true } ]' ``` ```bash runware run minimax:m2.7@0 \ deliveryMethod=stream \ messages.0.role=user \ messages.0.content="What is 2+2? Be brief." \ settings.maxTokens=4096 \ settings.temperature=1 \ includeCost=true \ includeUsage=true ``` ```json { "taskType": "textInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "minimax:m2.7@0", "deliveryMethod": "stream", "messages": [ { "role": "user", "content": "What is 2+2? Be brief." } ], "settings": { "maxTokens": 4096, "temperature": 1 }, "includeCost": true, "includeUsage": true } ``` The final chunk before `[DONE]` will include both fields: ```text data: {"taskUUID":"6e879837-4b2a-4c1d-ae5f-8f3c21b07a92","taskType":"textInference","delta":{},"finishReason":"stop","usage":{"promptTokens":51,"completionTokens":38,"totalTokens":89},"cost":0.000061} data: [DONE] ``` ### Error handling If an error occurs during streaming, the event will contain an `errors` array instead of a `delta` object. ```json { "errors": [ { "code": "timeoutProvider", "message": "The provider timed out while generating the response.", "taskType": "textInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6" } ] } ``` Check for the presence of `errors` in your parsing logic and handle them accordingly. Error fields follow the same structure as [standard API errors](https://runware.ai/docs/platform/errors). ## Code examples **curl**: ```bash # The -N flag disables curl's output buffering so chunks print as they arrive. curl -N -X POST https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "textInference", "taskUUID": "550e8400-e29b-41d4-a716-446655440000", "model": "minimax:m2.7@0", "deliveryMethod": "stream", "messages": [{"role": "user", "content": "Tell me a joke"}], "settings": {"maxTokens": 512, "temperature": 1.0}, "includeCost": true } ]' ``` **JavaScript**: ```javascript const response = await fetch('https://api.runware.ai/v1', { method: 'POST', headers: { 'Authorization': 'Bearer ' + RUNWARE_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify([{ taskType: 'textInference', taskUUID: crypto.randomUUID(), model: 'minimax:m2.7@0', deliveryMethod: 'stream', messages: [{ role: 'user', content: 'Tell me a joke' }], settings: { maxTokens: 512, temperature: 1.0 }, }]), }); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop(); for (const line of lines) { if (!line.trim() || line.startsWith(':')) continue; if (line === 'data: [DONE]') return; const json = JSON.parse(line.replace('data: ', '')); if (json.errors) { console.error(json.errors[0].message); return; } const text = json.delta?.text; if (text) process.stdout.write(text); } } ``` **Python**: ```python import json import uuid import httpx response = httpx.post( 'https://api.runware.ai/v1', headers={ 'Authorization': f'Bearer {RUNWARE_API_KEY}', 'Content-Type': 'application/json', }, json=[{ 'taskType': 'textInference', 'taskUUID': str(uuid.uuid4()), 'model': 'minimax:m2.7@0', 'deliveryMethod': 'stream', 'messages': [{'role': 'user', 'content': 'Tell me a joke'}], 'settings': {'maxTokens': 512, 'temperature': 1.0}, }], timeout=None, ) for line in response.iter_lines(): if not line or line.startswith(':'): continue if line == 'data: [DONE]': break data = json.loads(line.removeprefix('data: ')) if 'errors' in data: raise Exception(data['errors'][0]['message']) text = data.get('delta', {}).get('text', '') if text: print(text, end='', flush=True) ``` ## Best practices - **Buffer by line, not by byte**. Network chunks may split a JSON event across multiple reads. Accumulate data in a buffer and process complete lines only. - **Handle `[DONE]` explicitly**. Always check for the `data: [DONE]` sentinel before attempting to parse JSON. Treating it as JSON will cause a parse error. - **Separate reasoning from content**. If you're working with reasoning models, track whether the stream is currently delivering `delta.reasoningContent` or `delta.text` and route them accordingly. - **Implement timeouts**. Set a reasonable timeout for the overall stream connection. If no events arrive within your timeout window, close the connection and retry. - **Use the Fetch API for browser clients**. The browser's native `EventSource` API only supports GET requests. Since text inference uses POST, use the Fetch API with `ReadableStream` instead. --- ## Webhooks **URL:** https://runware.ai/docs/platform/webhooks **Description:** Receive real-time notifications when your tasks complete using webhooks. Learn how to configure, secure, and handle webhook deliveries from Runware. ## Overview Webhooks allow you to receive real-time HTTP POST notifications when your generation tasks complete, eliminating the need to poll for results. When a task finishes processing, Runware sends the complete response to your specified URL automatically. This is especially useful for long-running operations like video generation, batch processing where tasks complete at different times, or integrations with external systems that need immediate notifications. For an alternative approach using client-side polling, see [Task Polling](https://runware.ai/docs/platform/task-polling). ## How it works You provide a webhook URL when submitting a task. Runware processes your request asynchronously, and when the task completes, sends an HTTP POST request to your URL with the complete task response as JSON. Your endpoint must respond with a 2xx status code to confirm receipt. If it doesn't, Runware will retry delivery automatically (see [Retry behavior](#retry-behavior)). For batch requests with multiple results, each completed item triggers a separate webhook call as results become available. ## Configuration Include the `webhookURL` parameter in your API request: TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const [result] = await client.run({ model: 'runware:100@1', positivePrompt: 'a serene landscape', width: 1024, height: 1024, webhookURL: 'https://api.example.com/webhooks/runware' }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: results = await client.run({ "model": "runware:100@1", "positivePrompt": "a serene landscape", "width": 1024, "height": 1024, "webhookURL": "https://api.example.com/webhooks/runware" }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "runware:100@1", "positivePrompt": "a serene landscape", "width": 1024, "height": 1024, "webhookURL": "https://api.example.com/webhooks/runware" } ]' ``` ```bash runware run runware:100@1 \ positivePrompt="a serene landscape" \ width=1024 \ height=1024 \ webhookURL=https://api.example.com/webhooks/runware ``` ```json { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "model": "runware:100@1", "positivePrompt": "a serene landscape", "width": 1024, "height": 1024, "webhookURL": "https://api.example.com/webhooks/runware" } ``` ### Authentication Webhooks support authentication through URL parameters. You can include tokens, API keys, or custom tracking parameters directly in the webhook URL: **Authentication token**: ```text https://api.example.com/webhooks/runware?token=your_auth_token ``` **API key**: ```text https://api.example.com/webhooks/runware?apiKey=sk_live_abc123 ``` **Custom parameters**: ```text https://api.example.com/webhooks/runware?projectId=proj_789&userId=12345 ```> [!WARNING] > Always use HTTPS for webhook URLs to ensure data transmission is encrypted. Validate authentication parameters in your endpoint before processing webhook data. ## Receiving notifications The webhook POST body contains the complete JSON response for your task. The structure matches the standard API response for the corresponding task type: TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const [result] = await client.run({ imageUUID: '550e8400-e29b-41d4-a716-446655440000', imageURL: 'https://im.runware.ai/image/os/a14d18/ws/2/ii/550e8400-e29b-41d4-a716-446655440000.jpg', model: 'runware:100@1', width: 1024, height: 1024, cost: 0.004, createdAt: '2026-01-16T10:30:45Z' }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: results = await client.run({ "imageUUID": "550e8400-e29b-41d4-a716-446655440000", "imageURL": "https://im.runware.ai/image/os/a14d18/ws/2/ii/550e8400-e29b-41d4-a716-446655440000.jpg", "model": "runware:100@1", "width": 1024, "height": 1024, "cost": 0.004, "createdAt": "2026-01-16T10:30:45Z" }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "imageUUID": "550e8400-e29b-41d4-a716-446655440000", "imageURL": "https://im.runware.ai/image/os/a14d18/ws/2/ii/550e8400-e29b-41d4-a716-446655440000.jpg", "model": "runware:100@1", "width": 1024, "height": 1024, "cost": 0.004, "createdAt": "2026-01-16T10:30:45Z" } ]' ``` ```bash runware run runware:100@1 \ imageUUID=550e8400-e29b-41d4-a716-446655440000 \ imageURL=https://im.runware.ai/image/os/a14d18/ws/2/ii/550e8400-e29b-41d4-a716-446655440000.jpg \ width=1024 \ height=1024 \ cost=0.004 \ createdAt=2026-01-16T10:30:45Z ``` ```json { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "imageUUID": "550e8400-e29b-41d4-a716-446655440000", "imageURL": "https://im.runware.ai/image/os/a14d18/ws/2/ii/550e8400-e29b-41d4-a716-446655440000.jpg", "model": "runware:100@1", "width": 1024, "height": 1024, "cost": 0.004, "createdAt": "2026-01-16T10:30:45Z" } ``` ## Building your endpoint Your webhook endpoint must respond with an HTTP status code between 200-299 to confirm receipt. The response should be returned quickly, ideally within 5 seconds. If you need to perform heavy processing, respond immediately with a 200 status and handle the work asynchronously. **Node.js (Express)**: ```javascript const express = require('express'); const app = express(); app.use(express.json()); app.post('/webhooks/runware', (req, res) => { const webhookData = req.body; // Verify authentication if (req.query.token !== process.env.WEBHOOK_TOKEN) { return res.status(401).send('Unauthorized'); } // Process asynchronously processWebhook(webhookData).catch(console.error); // Respond immediately res.status(200).send('OK'); }); async function processWebhook(data) { console.log('Task completed:', data.taskUUID); // Handle the completed task... } app.listen(3000); ``` **Python (Flask)**: ```python from flask import Flask, request import os app = Flask(__name__) @app.route('/webhooks/runware', methods=['POST']) def webhook(): if request.args.get('token') != os.environ.get('WEBHOOK_TOKEN'): return 'Unauthorized', 401 webhook_data = request.json process_webhook(webhook_data) return 'OK', 200 def process_webhook(data): print(f"Task completed: {data['taskUUID']}") # Handle the completed task... if __name__ == '__main__': app.run(port=3000) ``` **PHP**: ```php { const { taskUUID } = req.body; if (processed.has(taskUUID)) { return res.status(200).send('Already processed'); } processed.add(taskUUID); processWebhook(req.body); res.status(200).send('OK'); }); ``` ## Troubleshooting If webhooks aren't being received, verify your endpoint is publicly accessible and responding with 2xx status codes. Check server logs for incoming requests and ensure SSL certificates are valid for HTTPS endpoints. For local development, use ngrok to test connectivity. If your endpoint times out, make sure you're responding immediately with a 200 status and moving heavy processing to background jobs. Webhook delivery is considered successful only when your endpoint returns a 2xx response within the timeout window. --- ## Errors **URL:** https://runware.ai/docs/platform/errors **Description:** Understanding error responses from the Runware API and how to handle them in your integration. ## Overview When a request fails, the Runware API returns a JSON response containing an `errors` array. This format is consistent across both **HTTP REST** and **WebSocket** connections. Error Response ```json { "errors": [ { "code": "invalidApiKey", "message": "Invalid API key. Get one at https://runware.ai/signup", "parameter": "apiKey", "taskType": "authentication" } ] } ``` Each object in the `errors` array represents a single failed operation. When sending multiple tasks in one request errors are scoped to the specific task that failed, while other tasks in the same request may still succeed. ### Error fields | Field | Type | Description | | --- | --- | --- | | `code` | string | A short identifier for the error (e.g., `invalidApiKey`, `timeoutProvider`). | | `message` | string | A human-readable explanation with details about what went wrong. | | `parameter` | string | The request parameter related to the error, if applicable. | | `taskType` | string | The task type of the request that failed. | | `taskUUID` | string | The unique identifier of the request that failed, useful for debugging and support. | | `documentation` | string | A link to relevant documentation for the parameter. | > [!NOTE] > We are actively standardizing our error codes and response formats. During this transition, you may encounter error codes or message formats that vary between models and providers. We recommend logging the full `message` and `taskUUID` fields to debug issues effectively. ## HTTP status codes When using the HTTP REST API, the response also includes a standard HTTP status code alongside the `errors` array. These codes indicate the general category of the failure: | Code | Description | | --- | --- | | `400` | **Bad Request** - The request was unacceptable, often due to missing a required parameter. | | `401` | **Unauthorized** - No valid API key provided. | | `402` | **Payment Required** - Your account balance is insufficient. | | `403` | **Forbidden** - The API key doesn't have permissions to perform the request. | | `404` | **Not Found** - The requested resource doesn't exist. | | `429` | **Too Many Requests** - Too many requests hit the API too quickly. See [Rate Limits](https://runware.ai/docs/platform/rate-limits). | | `500` | **Server Error** - Something went wrong on Runware's end. | | `503` | **Service Unavailable** - The service is temporarily unavailable (e.g., maintenance or capacity). | WebSocket connections do not have HTTP status codes. For WebSocket integrations, use the `code` field in the error object to determine the error category. ## Handling errors For programmatic retry decisions, use HTTP status codes when on REST, or the error `code` field when on WebSocket. For debugging, always log the full `message` and `taskUUID`. The task UUID is the fastest way to get help from our support team. ### Retries For transient errors, it is often safe to retry the request with **exponential backoff**. These include: - HTTP `429` (Too Many Requests) and `5xx` (Server Error) status codes. - Error codes indicating capacity or provider issues (e.g., `timeoutProvider`, `providerRateLimitExceeded`). For client errors such as invalid parameters or authentication failures, you should **not** retry without modifying the request, as it will fail again. See [Rate Limits](https://runware.ai/docs/platform/rate-limits) for detailed retry implementation patterns. --- ## Rate Limits **URL:** https://runware.ai/docs/platform/rate-limits **Description:** Understanding platform rate limits and best practices for building resilient integrations with Runware. ## How we handle capacity Runware operates on a shared infrastructure model with finite processing capacity. Rather than enforcing hard rate limits that reject requests arbitrarily, we use dynamic queue-based systems that balance load across all users while prioritizing active workloads. This approach maximizes platform accessibility while maintaining service quality. You won't hit artificial walls, but you will experience graceful degradation under high load rather than immediate rejection. **Current approach:** - **No hard rate limits** - We don't reject requests based on arbitrary thresholds. - **Shared queues** - Requests are processed through model-specific queues with finite capacity. - **Fair allocation** - Processing capacity is distributed fairly across active users. - **Graceful degradation** - Under load, you'll experience increased latency rather than immediate failures. ## What to expect Under normal conditions, requests are processed quickly and reliably. When platform capacity is reached, here's what happens: **1\. Queueing** - Requests enter a queue and wait for available processing capacity. **2\. Increased latency** - Generation time increases as queue depth grows. This is normal and expected. **3\. Timeout potential** - Extremely long queues may result in timeout errors after waiting too long. **4\. Transient failures** - During peak demand, some requests may fail with retryable errors. ### HTTP status codes You may encounter these status codes when capacity is constrained: - **`429 Too Many Requests`** - Queue capacity exceeded, implement retry with exponential backoff. - **`503 Service Unavailable`** - Temporary capacity constraint, retry recommended. - **`504 Gateway Timeout`** - Request exceeded maximum queue wait time. All of these are transient and should be handled with retry logic. ## Third-party provider limits We integrate with external AI providers like OpenAI, Black Forest Labs, ByteDance, and others. **Their rate limits and capacity constraints affect our service**, even when our infrastructure has available capacity. > [!NOTE] > Models from third-party providers are subject to the provider's own rate limits and capacity constraints. When a provider experiences high demand or enforces their limits, you may see reduced throughput or temporary unavailability for those specific models. **What this means:** - **Provider throttling appears as our errors** - You'll see 429, 503, or increased latency when upstream providers hit their limits. - **We can't control their capacity** - Some providers enforce strict concurrency or rate limits that cascade to our service. - **Model-specific constraints** - Check individual model documentation for provider-specific limitations. This is an inherent part of working with third-party AI services and why we recommend implementing robust retry logic. ## Best practices ### Implement retry logic Always implement exponential backoff for failed requests. This is critical for resilient integrations. **TypeScript**: ```typescript async function generateWithRetry(params: any, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await runware.imageInference(params); } catch (error: any) { // Retry on capacity errors if (error.status === 429 || error.status === 503) { const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, delay)); continue; } // Don't retry other errors throw error; } } throw new Error('Max retries exceeded'); } ``` **Python**: ```python import time async def generate_with_retry(params, max_retries=3): for i in range(max_retries): try: return await runware.image_inference(params) except Exception as error: # Retry on capacity errors if hasattr(error, 'status') and error.status in [429, 503]: delay = (2 ** i) * 1000 # 1s, 2s, 4s time.sleep(delay / 1000) continue # Don't retry other errors raise error raise Exception('Max retries exceeded') ``` ### Manage concurrency While we don't enforce hard concurrency limits, we recommend the following for optimal performance: **Standard usage** - 2-4 concurrent requests provides optimal throughput for most use cases. **High-volume deployments** - Contact our team to discuss capacity planning and dedicated infrastructure options. **Burst workloads** - Implement request throttling on your end to avoid saturating queues during sudden spikes. > [!WARNING] > Sending hundreds of concurrent requests may saturate queues and result in timeouts. Implement concurrency limits in your application for better reliability. ### Monitor response times Track generation latency as a proxy for platform capacity: - **Consistent sub-30s responses** - Healthy capacity, system operating normally. - **Increasing latency trends** - Approaching capacity limits, consider reducing concurrency. - **Frequent timeouts** - Queue saturation, implement backoff or reduce request volume. Use latency monitoring to dynamically adjust your request patterns and avoid overwhelming the platform. ## Model-specific capacity Some models have limited infrastructure due to hardware requirements, demand patterns, or provider constraints: **High-demand models** - Popular models may experience longer queues during peak hours. **Resource-intensive models** - Video generation and large models require significant GPU resources, limiting concurrent capacity. Check individual model documentation for specific constraints. **Third-party provider models** - Models from external providers are subject to their capacity constraints and rate limits. > [!NOTE] > For production deployments requiring guaranteed capacity or SLAs on specific models, contact our sales team to discuss dedicated infrastructure options. ## What's coming We're actively developing improvements to provide more predictable performance: **Tiered concurrency limits** - Predictable limits based on usage tier, with clear thresholds and automatic scaling. **Capacity reservations** - Guaranteed throughput for enterprise users with dedicated infrastructure. **Real-time capacity metrics** - API endpoints showing current queue depth and estimated processing times. **Serverless scaling** - Dynamic model loading for improved availability and reduced cold starts. These improvements will provide enterprise-grade reliability while maintaining platform accessibility for all users. ## Summary **Current state:** - No hard rate limits, but shared infrastructure with finite capacity - Graceful degradation through queueing under load - Third-party provider limits affect service availability **Your responsibilities:** - Implement exponential backoff retry logic - Manage concurrency (2-4 concurrent requests recommended) - Monitor latency and adjust request patterns **Need guarantees?** - Contact sales for dedicated capacity, SLAs, and custom infrastructure This approach balances accessibility with reliability. By following these best practices, you'll build integrations that gracefully handle capacity constraints and maintain high uptime. --- ## Pricing **URL:** https://runware.ai/docs/platform/pricing **Description:** How Runware pricing works, from compute-based billing to understanding costs in your integration. ## Pay as you go Our pricing philosophy is simple: **we optimize models to run faster, and we pass those savings directly to you**. Unlike platforms that charge a flat fee per generation regardless of inference time, our architecture is based on **optimized compute time**, so fewer GPU seconds means lower cost. Pay only for what you use, with no subscriptions or commitments. ## Pricing models We operate with two primary pricing structures depending on the model type: ### Serverless (Optimized Compute) For most open-source models (like Stable Diffusion, Flux, etc.) that we host and optimize, pricing is based on **compute time**. - **Granular billing**: You are charged for the exact resources used to generate your output. - **Speed discounts**: As we optimize our inference engine to be faster, the cost per generation drops automatically. - **No idle costs**: You don't pay for cold starts or idle GPU time. > [!NOTE] > **Example**: If we optimize a model to run 2x faster, your cost for that generation effectively drops by ~50%. ### Fixed Price For closed-source or partner models where we do not control the underlying infrastructure optimization, we may offer **fixed per-request pricing**. - **Predictable costs**: You know exactly how much each request costs upfront. - **Standardized**: Prices are set based on the provider's rates or license agreements. Our aggregate request volume across the platform allows us to negotiate competitive rates with providers, often resulting in lower per-request pricing than integrating with them directly. For high-volume deployments, contact our [sales team](https://runware.ai/contact) to discuss custom pricing. ## What affects cost The cost of a generation depends on several factors: | Factor | Impact | | --- | --- | | **Model** | Different models have different compute requirements and provider rates. | | **Resolution** | Higher output resolution requires more processing time. | | **Duration** | Longer video or audio outputs cost more. | | **Steps** | More inference steps increase compute time (serverless models). | | **Batch size** | Cost scales linearly with the number of outputs requested. | For serverless models, anything that increases GPU time increases cost. For fixed-price models, costs are determined per request based on the provider's pricing structure. ## Understanding costs All costs are denominated in **USD**. Your account balance is deducted in real-time as you generate content, and you can top up or configure auto-reload in the [Dashboard](https://runware.ai/dashboard). Your balance does not expire. To avoid service interruptions, you can configure **auto-reload** to automatically top up your balance when it falls below a threshold. The Dashboard also lets you set up **low-balance alerts** and **backup payment methods**. Failed requests are not charged. You only pay for successful generations. To see the exact cost of any request, include the `includeCost` parameter in your API call. The response will contain a `cost` field showing the amount in USD deducted for that specific task. > [!NOTE] > To see the pricing for a specific model, check its page in the [Models](https://runware.ai/docs/models) section. You can also generate a test request in the [Playground](https://runware.ai/playground) to see the exact cost before integrating. --- ## TypeScript SDK **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. ## 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' }) ``` Either transport supports two delivery modes, set with `deliveryMethod`. **Sync** waits for the result and returns it in one round trip, which works well for fast tasks like image inference. **Async** returns a task UUID immediately and the SDK 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', width: 1280, height: 720, duration: 8, // deliveryMethod defaults to 'async' }) ``` Over WebSocket, requests and results travel on the same persistent socket. With **async** delivery the SDK gets a `taskUUID` and polls for completion over that connection. With **sync** the result comes straight back. Either way, 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() ``` The per-model methods (`getModel`, `getModelExamples`, `getModelPricing`) accept either the model's AIR or its catalog slug (the `model` field returned by `listModels`). 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 }) // Store media for reuse as input (URL, data URI, or Base64) const uploaded = await client.mediaStorage({ operation: 'upload', media: '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.mediaStorage({ operation: 'upload', media: 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 { 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) --- ## Python SDK **URL:** https://runware.ai/docs/platform/python **Description:** Async Python 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. ## Introduction The Runware Python SDK gives you a single `Runware` client for the **whole inference surface**: image, video, audio, text, and 3D generation, plus utility endpoints like model search and account management. It runs on `asyncio` and `aiohttp`, and ships typed `TypedDict` parameter shapes 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 one-off requests and serverless functions. **WebSocket** keeps a persistent connection and is faster when you're issuing many calls or want streaming progress 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. You can 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 the agent in your application to discover what's available. ## Installation The SDK requires Python 3.11 or higher. **pip**: ```bash pip install runware-sdk ``` **uv**: ```bash uv 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: ```python import asyncio from runware import Runware async def main(): async with Runware(api_key="your-api-key", transport="rest") as client: images = await client.run({ "taskType": "imageInference", "model": "runware:101@1", "positivePrompt": "A serene mountain landscape at sunset", "width": 1024, "height": 1024, "deliveryMethod": "sync", }) print(images[0]["imageURL"]) asyncio.run(main()) ``` The `async with` block manages the underlying HTTP session. You can also call `await client.connect()` and `await client.disconnect()` explicitly if you need finer control over the lifecycle. > [!NOTE] > Set `RUNWARE_API_KEY` in your environment and the SDK picks it up automatically. The `api_key=` argument is only needed when you want to override it for a specific client instance. ## 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` | One-off requests, serverless functions, short-lived processes. 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: ```python client = Runware(api_key="your-api-key", transport="rest") # or client = Runware(api_key="your-api-key", transport="websocket") ``` Either transport supports two delivery modes, set with `deliveryMethod`. **Sync** waits for the result and returns it in one round trip, which works well for fast tasks like image inference. **Async** returns a task UUID immediately and the SDK polls for completion, which is what you want for video and other long-running operations. ```python # Sync (recommended for image inference and other fast tasks) 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) videos = await client.run({ "taskType": "videoInference", "model": "google:3@3", "positivePrompt": "Waves crashing on a beach", "width": 1280, "height": 720, "duration": 8, # deliveryMethod defaults to "async" }) ``` Over WebSocket, requests and results travel on the same persistent socket. With **async** delivery the SDK gets a `taskUUID` and polls for completion over that connection. With **sync** the result comes straight back. Either way, the SDK reconciles each frame with its awaiting call. ## Typed parameters When you know the architecture you're targeting, import the matching `TypedDict` and the SDK gives you compile-time validation in your editor: ```python from runware import Runware from runware.types.task_map import SdxlArchParams params: SdxlArchParams = { "model": "civitai:133005@782002", "taskType": "imageInference", "positivePrompt": "A professional headshot portrait", "negativePrompt": "blurry, distorted", "width": 1024, "height": 1024, "steps": 30, } async with Runware() as client: images = await client.run(params) ``` The `task_map` module ships one `Params` (or `ArchParams`) `TypedDict` per supported architecture and curated model. Pyright and mypy will flag wrong field names and wrong value types before you run the code. ## Schema validation The SDK can validate your request against the model's **JSON Schema** before it leaves the process. Mistakes (a missing `model`, or an `inputImage` passed as `bytes` instead of a URL) surface as a typed exception with the offending field name, not as a 400 from the server hundreds of milliseconds later. Validation is off by default. Turn it on for a call (or globally via `validate=True` in the `Runware` constructor) when you want the SDK to check a payload against the model's schema before sending: ```python from runware import Runware, RunOptions async with Runware() as client: result = await client.run( payload, RunOptions(validate=True), ) ``` ## Concurrent requests The SDK is async all the way down. `asyncio.gather` is the canonical way to fan out: ```python import asyncio from runware import Runware async def main(): async with Runware(transport="websocket") as client: await client.connect() results = await asyncio.gather( 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", }), ) asyncio.run(main()) ``` 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`: ```python from runware import Runware async def main(): async with Runware() as client: stream = await client.stream({ "taskType": "textInference", "model": "minimax:m2.7@0", "messages": [ {"role": "user", "content": "Write a haiku about the ocean."}, ], }) async for delta in stream.text_stream: print(delta, end="", flush=True) result = await stream.result() print(f"\nFinish reason: {result.finish_reason}") import asyncio asyncio.run(main()) ``` The stream exposes two iterators (`text_stream` and `reasoning_stream`) plus a `result()` coroutine that yields 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 raises, 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. ```python async with Runware() as client: # Search the curated catalog models = await client.content.list_models({ "category": "image", "creator": "black-forest-labs", "search": "flux dev", }) # Inspect one flux = await client.content.get_model("flux-1-dev") print(flux["headline"]) # Pull curated examples to seed prompts examples = await client.content.get_model_examples("flux-1-dev") # Pricing for budget-driven decisions pricing = await client.content.get_model_pricing("flux-1-dev") # Browse the capability taxonomy, collections, and creators capabilities = await client.content.list_capabilities() collections = await client.content.list_collections({"category": "image"}) creators = await client.content.list_creators() ``` The per-model methods (`get_model`, `get_model_examples`, `get_model_pricing`) accept either the model's AIR or its catalog slug (the `model` field returned by `list_models`). This is the same data the [model picker](https://runware.ai/models) and pricing pages render from. Listing endpoints accept `paginate=True` if you want a paginated envelope instead of a flat list. ## 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()`: ```python async with Runware() as client: # Search the full live model catalog models = await client.model_search({"search": "portrait", "architecture": "sdxl", "limit": 10}) # Store media for reuse as input (URL, data URI, or Base64) uploaded = await client.media_storage({"operation": "upload", "media": "https://example.com/photo.jpg"}) # Account details (credits, limits) account = await client.account_management({"operation": "getDetails"}) # Look up a task you ran earlier, by UUID archived = await client.get_task_details({"taskUUID": "abc-123"}) # Upload a custom model await client.model_upload({"category": "checkpoint", "architecture": "sdxl", "format": "safetensors"}) ``` `model_search` 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 `file_to_data_uri` encodes a local file as a `data:` URI you can pass anywhere an image input is accepted. It accepts a `Path` or raw `bytes`: ```python from pathlib import Path from runware import file_to_data_uri data_uri = file_to_data_uri(Path("photo.jpg")) await client.media_storage({"operation": "upload", "media": data_uri}) ``` ## Errors Every failure raises a typed `RunwareError` with a stable `code` enum and the offending parameter when applicable: ```python from runware import Runware, RunwareError async with Runware() as client: try: await client.run(payload) except RunwareError as err: if err.code == "quota": # Insufficient credits; prompt the user to top up ... elif err.code == "rateLimit": # Back off and retry ... elif err.code == "safety": # Prompt or image triggered a safety filter ... else: raise ``` 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 TypeScript SDK, so cross-language services can react to the same code values. ## Configuration Most apps only need to set `api_key` (or rely on `RUNWARE_API_KEY` in the env). The full `Runware` constructor accepts: ```python from runware import Runware client = Runware( api_key="your-api-key", transport="websocket", # Timeouts timeout=120_000, # Per-request ms cap poll_timeout=600_000, # Async-delivery polling cap # Validation behavior validate=True, # Toggle JSON-Schema validation (off by default) # Logging debug=True, # Stdout JSON logs; pass log_sink for a custom sink ) ``` Per-call overrides live on `RunOptions`. Use them to tighten the timeout for a fast task or to provide an `asyncio.Event` for cancellation: ```python from runware import Runware, RunOptions import asyncio cancel = asyncio.Event() options = RunOptions( timeout=30_000, cancel_event=cancel, validate=False, ) result = await client.run(payload, options) ``` ## Cancellation and progress `RunOptions.cancel_event` accepts an `asyncio.Event`. Set the event from anywhere and the in-flight call (REST poll, WebSocket subscription, or LLM stream) aborts cleanly and raises a `RunwareError` with `code="aborted"`. > [!WARNING] > Cancelling is client-side only. The server keeps processing the task and **you are still billed for it**. Cancelling just stops the SDK from waiting for the result. For long-running tasks, two callbacks let you observe a task as it unfolds. `on_result` fires once per item as it reaches a terminal state, and `on_progress` fires when an item's `progress` field (0-100) changes. Only a few long-running models (mostly training) emit progress: ```python def on_done(item): print("Got partial:", item.get("imageUUID") or item.get("videoUUID")) def on_progress(item): print(f"{item.get('progress')}%") result = await client.run( payload, RunOptions(on_result=on_done, on_progress=on_progress), ) ``` ## Source The SDK is open source. Issues and pull requests are welcome. Repository: [github.com/runware/runware-python](https://github.com/runware/runware-python) --- ## CLI **URL:** https://runware.ai/docs/platform/cli **Description:** Run Runware inference from the terminal. A single static binary for image, video, audio, text, and 3D generation, plus model search, presets, uploads, and account management. ## Introduction The Runware CLI is a **single static binary** that drives the inference API from your terminal: image, video, audio, text, and 3D generation, plus model search, presets, uploads, and account management. It's written in Go and ships for macOS, Linux, and Windows with no runtime to install. Inference runs through one command. `runware run ` takes a model's **AIR identifier** and `key=value` parameters, fetches the model's schema to detect the task type and coerce each value, then downloads the result. Output is a table by default, or **JSON** for piping into `jq` and scripts. runware · zsh $ runware run runware:400@1 ⇥ positivePrompt=Text prompt describing the output width=Width of the generated media in pixels height=Height of the generated media in pixels steps=Number of denoising steps CFGScale=How closely the result follows the prompt $ runware run runware:400@1 positivePrompt="A lighthouse on a rocky cliff at dusk" width=1344 height=768 INFO Task submitted taskUUID=85eb8a3e-7a14-4043-b42f-c335780a515c INFO saved path=outputs/3ceeb9cf-c3bf-4266-af69-d97f5d978c62.jpg ![A lighthouse on a rocky cliff at dusk with storm clouds and crashing waves](https://runware.ai/docs/assets/result.BjNDIjKx_1wEBqd.webp) ## Installation Install with your platform's package manager, or build from source. **Homebrew**: ```bash brew install --cask runware/tap/runware ``` **Scoop**: ```bash scoop bucket add runware https://github.com/Runware/scoop-bucket.git scoop install runware ``` **Linux**: ```bash curl -fsSL https://cli.runware.ai/install.sh | sh ``` **From source**: ```bash git clone https://github.com/runware/runware-cli.git cd runware-cli make build ``` ## Authentication `runware auth login` prompts for your API key and stores it at `~/.runware/config.yaml`. Pass `--key` to skip the prompt in scripts: ```bash runware auth login # interactive prompt runware auth login --key # non-interactive runware auth status # check the stored key ``` > [!NOTE] > The `RUNWARE_API_KEY` environment variable overrides the stored key, which is the right fit for CI and ephemeral shells. ## Quick start From an authenticated shell, generate an image in one command. The result downloads to `./outputs` by default: ```bash # Check connectivity runware ping # Generate an image runware run runware:400@1 positivePrompt="A serene mountain landscape" width=1024 height=1024 # Inspect your account runware account details ``` ## Running inference `runware run` is the entry point for every model. You give it an **AIR identifier** and parameters as `key=value` pairs: ```bash runware run [key=value ...] [flags] ``` The CLI fetches the model's **JSON Schema** to coerce each value to the right type and detect the task type. Parameter names are the API's own field names, so `positivePrompt`, `width`, and `steps` are exactly what you'd find in the model's reference. ### Nested and array parameters Object and array fields use **dot notation**. Index into arrays with a number, and the CLI builds the nested structure for you: ```bash # Chat messages (array of objects) runware run google:gemma@4-31b \ messages.0.role=user messages.0.content="Explain quantum computing" # Speech settings (nested object) runware run alibaba:qwen@3-tts-1.7b-voicedesign \ positivePrompt="A calm, friendly young woman with a soft tone" \ speech.text="Hello there" speech.voice=design # Image inputs (array of strings) runware run tencent:hunyuan-3d@3.1-pro inputs.images.0="https://example.com/product.jpg" ``` ### More examples ```bash # Video runware run google:3@3 positivePrompt="Ocean waves at sunset" width=1280 height=720 duration=8 # Music runware run minimax:music@2.6 positivePrompt="Upbeat synthwave with driving bass" settings.instrumental=true # Text to 3D runware run tencent:hunyuan-3d@3.1-pro positivePrompt="A red vintage sports car" ``` ### Community and custom models When the CLI can't resolve a task type from the schema (community uploads, custom fine-tunes), name it explicitly with `--task-type`: ```bash runware run civitai:305149@392545 --task-type imageInference \ positivePrompt="A portrait" width=1024 height=1024 ``` ### Flags | Flag | Description | | --- | --- | | `--task-type` | Override the detected task type (e.g. `imageInference`, `videoInference`, `3dInference`) | | `--output-dir` | Directory for downloaded files (default `./outputs`) | | `--no-download` | Print result URLs without downloading them | | `--delivery-method` | `sync` or `async` (default `async`) | | `--poll-interval` | How often to poll async tasks (default `2s`) | | `--preset` | Load parameters from a saved preset | | `--validate` | Check parameters against the schema before sending | ### Delivery and downloads By default the CLI submits with **async delivery**, polls until the task finishes, and downloads any media to the output directory. Use `--no-download` to print the URLs instead, which pairs well with `--format json`: ```bash runware run runware:400@1 positivePrompt="Abstract art" width=1024 height=1024 \ --format json --no-download ``` Each result prints to stdout as a JSON object, ready to pipe into `jq`: Output ```json { "imageURL": "https://im.runware.ai/image/os/w01d10/ws/3/ii/1d0c0c3a-3d58-4efc-9ddd-112f23dd4113.jpg", "imageUUID": "1d0c0c3a-3d58-4efc-9ddd-112f23dd4113", "seed": 1951942294, "status": "success", "taskType": "imageInference", "taskUUID": "60cb0ed5-3fdc-4b6f-ba78-2f724441520b" } ``` For tasks that finish quickly, `--delivery-method sync` skips polling and returns the result in a single round trip. ### Validation Client-side validation is **off by default**, since the API validates every request. Turn it on with `--validate` to check parameters against the model's schema before the request leaves your machine. Missing required fields and out-of-range values both fail locally, with no round trip: ```bash runware run runware:400@1 positivePrompt="A landscape" width=-1 height=1024 --validate ``` The bad width is caught before the task is submitted: Output ```text ERRO invalid value for "width": -1 is below the minimum of 512 ``` ## Models `runware model` searches and inspects the catalog. Each subcommand takes a model's AIR (or its catalog id): - **`search`** finds models by keyword, creator, architecture, or capability, for when you don't yet know a model's AIR. - **`show`** prints a model's full details: name, AIR, category, architecture, base model, and visibility. - **`schema`** lists the request parameters with their type, whether they're required, defaults, and descriptions, indenting nested objects. Add `--response` for the response shape, or `--format json` for the raw schema envelope. - **`pricing`** breaks the model's cost down as a configuration and price table. - **`examples`** prints a ready-to-run `runware run` command for each curated example, so you can copy one, tweak the prompt, and run it. ```bash runware model search -q "flux" --architecture sdxl # search runware model show civitai:305149@392545 # full details runware model schema google:3@2 # request parameters runware model schema google:3@2 --response # response shape runware model pricing google:gemini@3.1-pro # pricing breakdown runware model examples google:gemini@3.1-pro # example requests ``` `model pricing` and `model examples` read from the public catalog and accept either an AIR or the model id. Add `--format json` to any `model` command for the full payload. Upload a custom model with `runware model upload` (WebSocket transport only). Run `runware model upload --help` for the full flag set: ```bash runware model upload \ --air "myorg:my-model@1.0" --name "My Model" \ --category checkpoint --architecture sdxl \ --download-url "https://example.com/model.safetensors" ``` ## Presets A preset stores a model and a set of parameters under a name. Pass `--preset` to `run` and override individual values on the command line: ```bash runware preset save quick-flux runware:100@1 width=512 height=512 steps=4 runware preset list runware run --preset quick-flux positivePrompt="A neon city street" ``` ## Uploading inputs `runware media upload` sends a local file, URL, or data URI and returns a `mediaUUID` you can feed into other tasks. `runware media delete ` removes stored media when you no longer need it: ```bash runware media upload ./photo.jpg # Use the result as a seed image runware run runware:400@1 positivePrompt="Same scene at night" \ inputs.seedImage=$(runware media upload ./photo.jpg -F json | jq -r '.mediaUUID') ``` ## Resuming a task Long-running tasks print a `taskUUID` when they're submitted. If `run` is interrupted, resume waiting with `runware result`: ```bash runware result 7fbf4fc9-5b61-461c-84a4-1e496da4debb ``` ## Configuration The CLI keeps its settings in `~/.runware/config.yaml`. The `runware config` subcommands read and edit it: ```bash runware config show # print current settings runware config set output_dir ~/runware-outputs # set a default runware config set format json runware config set transport http runware config path # print the config file path runware config reset # restore defaults ``` The editable keys are `output_dir`, `format`, and `transport`. `config reset` restores those defaults while keeping your stored API key and saved presets, and `config path` prints the file location. The `RUNWARE_API_KEY` environment variable always overrides the stored key. ### Transports The CLI talks to the API over **WebSocket** (`ws`) by default, or **REST** (`http`). WebSocket streams progress for long-running tasks over one connection. REST sends a stateless request per invocation. Switch per command with `--transport`, or set a default in config: ```bash runware run runware:400@1 positivePrompt="A landscape" --transport http ``` ### Global flags | Flag | Description | | --- | --- | | `-F, --format` | `table` (default), `json`, or `yaml` | | `--transport` | `ws` (default) or `http` | | `-v, --verbose` | Show request and response details | | `--debug` | Full debug output | ## Output and scripting Every command prints a table by default. Switch to **JSON** or **YAML** for machine-readable output. Progress spinners and logs are written to stderr, so `--format json` keeps stdout clean for piping: ```bash runware account details --format json | jq '.balance' ``` Output ```text 48.2034 ``` ## Shell completions `runware completion` generates completion scripts for bash, zsh, fish, and powershell. Completions are **schema-driven**: after a model's AIR, pressing Tab suggests that model's parameter names (`positivePrompt=`, `width=`, `messages.0.role=`). The [repository](https://github.com/runware/runware-cli) has per-shell install steps. ## Source The CLI is open source. Issues and pull requests are welcome. Repository: [github.com/runware/runware-cli](https://github.com/runware/runware-cli) --- ## MCP integration **URL:** https://runware.ai/docs/platform/mcp **Description:** Connect your AI agent to Runware over the Model Context Protocol. Run any Runware task from inside Claude, Cursor, Codex, and other MCP clients. ## Introduction Runware exposes a **hosted MCP server** at `https://mcp.runware.ai`. Connect any MCP-compatible agent (Claude, Cursor, Codex, Windsurf, VS Code, custom clients) and the agent gets a small, well-typed set of tools that cover image and video generation, model search, pricing lookup, and task inspection. Models are addressed by their **model identifier** (`xai:grok-imagine@image-quality`, `klingai:kling-video@3-4k`, `anthropic:claude@opus-4.8`), so any Runware-hosted model is one tool call away. Auth runs on **OAuth 2.1 with an API-key form** on first connect. The MCP client speaks standard OAuth, but the credential you provide is your Runware API key, entered once into a server-rendered page. The key stays server-side, encrypted in the OAuth session, and the client only ever holds the OAuth token. For environments that cannot do remote OAuth, a `mcp-remote` stdio bridge and a local `@runware/mcp` npx package are both supported. ## Quick connect Pick your client and follow the three steps: **Claude**: 1 Open Claude settings Launch Claude Desktop or open **claude.ai** and go to Customize → Connectors. [Open Claude connectors](https://claude.ai/customize/connectors) 2 Add a custom connector Click **Add custom connector**, set the name to `Runware`, and paste the server URL: ```text https://mcp.runware.ai ``` 3 Sign in Click **Connect**, sign in with your Runware account, and approve the OAuth scope. The tools appear in the Claude tool tray. **Claude Code**: 1 Add the MCP From a project directory, register the hosted server: ```bash claude mcp add --transport http runware https://mcp.runware.ai ``` [Claude Code MCP guide](https://code.claude.com/docs/en/mcp) 2 Sign in Start a session. Claude Code opens the OAuth flow in your browser on first tool call and caches the refresh token. 3 Use it Ask Claude to generate, search, or inspect anything Runware supports. The tool calls are visible in the conversation log. **Cursor**: 1 Open MCP settings Cursor → Settings → **MCP** → Add server. [Cursor MCP guide](https://cursor.com/docs/context/mcp) 2 Paste the URL Choose the **HTTP** transport and enter: ```text https://mcp.runware.ai ``` 3 Sign in Cursor opens the OAuth flow on first use. After approval the Runware tools become available to the agent in chat and Composer. **VS Code**: 1 Enable Copilot Agent VS Code → Settings → enable **Copilot: Chat → MCP**. [VS Code MCP guide](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) 2 Add the server Open the MCP settings JSON and add: ```json { "mcp": { "servers": { "runware": { "url": "https://mcp.runware.ai" } } } } ``` 3 Sign in Run the **MCP: List Servers** command, pick `runware`, and complete the OAuth flow in your browser. **Codex**: 1 Edit ~/.codex/config.toml Add a server entry that goes through the `mcp-remote` bridge (Codex speaks stdio, not remote OAuth directly): ```toml [mcp_servers.runware] command = "npx" args = ["-y", "mcp-remote", "https://mcp.runware.ai"] ``` [Codex MCP guide](https://developers.openai.com/codex/mcp) 2 Sign in Start Codex. The bridge opens the OAuth flow once and caches the token under `~/.mcp-auth`. 3 Use it The Runware tools show up the same way as any other MCP server. **Other clients**: 1 Remote-capable clients If the client supports remote MCP with OAuth, point it at: ```text https://mcp.runware.ai ``` [MCP spec](https://modelcontextprotocol.io) 2 Stdio-only clients For clients that only speak stdio, use the `mcp-remote` bridge: ```json { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.runware.ai"] } ``` 3 Self-hosted Run the CLI locally instead. See [Local install](#local-install) below. ## How it works The hosted server runs at the edge and proxies tool calls into the Runware API on your behalf. **Your API key carries the account identity**, so the credit charge is recorded against your Runware balance, not against an anonymous service account. The first time a client connects, the server shows a branded page where you paste your Runware API key. The key is wrapped inside an OAuth 2.1 token and kept server-side. The MCP client only holds the token, and subsequent tool calls are silent. The agent selects the tool and model from your natural-language request and fills in the parameters. You don't have to memorize task type names or schema fields, but you can override either if you want a specific behavior. ## Tools The agent gets a small set of tools, grouped by intent. ### Generate `run` Run any Runware task. Image inference, video inference, audio inference, upscale, background removal, training, and anything else exposed by the API are all reachable through this one tool. Parameters are validated against the model's JSON Schema. ### Browse `list_models` List curated models, filtered by capability (`io:text-to-image`, `io:image-to-video`, etc.), category (`image`, `video`, `audio`, `text`, `3d`), creator, or free-text search. Returns model identifiers the agent can hand straight to `run`. `model_details` Full metadata for a single model: name, capabilities, description, creator, and cover image. `model_examples` Curated prompt and output samples for the model. Use this to inspect what a model can produce without spending credits on a generation. `model_pricing` Dedicated pricing lookup. Returns the model's pricing overview and example configurations. `list_capabilities` The full capability taxonomy (`io:*`, `op:*`, `form:*`). Useful for the agent to discover what kinds of tasks it can drive. ### Inspect `model_schema` Fetch the JSON Schema for a model. Returned as an MCP resource so the agent can validate parameters before calling `run`. `get_task_details` Look up a previously submitted task by `taskUUID`. Returns the same payload the agent would have received synchronously. ## Example flows Here's what an agent does when you give it a single sentence and the right tools. claude · runware Generate a cyberpunk street scene at 2K. Use xAI's image model. On it. Let me find the right xAI image model and check the cost. list\_modelscreator=xai · capability=io:text-to-image 2 models matched. Highest-weight is `xai:grok-imagine@image-quality`. model\_pricingair=xai:grok-imagine@image-quality $0.05 at 1K, $0.07 at 2K. **xai:grok-imagine@image-quality** at 2K. Running now. runtaskType=imageInference · 2752 × 1504 Generated in 6.4s. Cost: $0.07. Done. Here's your cyberpunk street scene.![Cyberpunk street scene generated by Grok Imagine](https://runware.ai/docs/assets/result.TCdXX-qR_1i0i3A.webp) A few other patterns come up often. **Mimic a curated example.** > Make me an image like the third example on Flux 1 Dev SRPO. The agent calls `model_examples` for the slug, copies the prompt and parameters from the matching example, and feeds them to `run`. **Compare pricing.** > What does each Veo model cost for an 8-second 1080p clip? The agent calls `list_models` filtered by `category=video`, then `model_pricing` for each model, and compiles a side-by-side table. You don't have to script any of this. The agent decides the order on its own. ## Local install If you can't reach the hosted server (corporate egress block, air-gapped environment), run the CLI locally. It exposes the same tools but ships an API key in env rather than OAuth. Pin the version if you need reproducibility. ```bash npx -y @runware/mcp ``` Most clients launch the CLI per session, so the more common shape is a config block. Example for Claude Desktop: ```json { "mcpServers": { "runware": { "command": "npx", "args": ["-y", "@runware/mcp"], "env": { "RUNWARE_API_KEY": "your-api-key" } } } } ``` The hosted server is the default path. Reach for the CLI when OAuth or remote MCP isn't an option. ## Auth The hosted server uses **OAuth 2.1 with PKCE** as the protocol, but the credential is your Runware API key (entered into a server-rendered page, not collected by the client). The flow is: 1. Client opens `https://mcp.runware.ai/.well-known/oauth-authorization-server` and discovers the authorization endpoint. 2. Browser opens the MCP server's `/authorize` page. You paste your Runware API key and submit. 3. The server wraps the key inside an OAuth access + refresh token and redirects back to the client. 4. Subsequent tool calls send the access token in the `Authorization` header. The server unwraps it server-side to recover your API key and calls Runware on your behalf. The API key is stored encrypted in the OAuth session and never reaches the client. Your spend lands on your Runware balance because every call uses your key. To revoke access, **rotate or delete the API key** in your Runware dashboard. The OAuth token continues to exist, but every Runware call it tries to make starts failing, so the MCP effectively stops working until you reconnect with a fresh key. The local CLI skips OAuth entirely and reads the key from `RUNWARE_API_KEY` instead. Same charge path. > [!NOTE] > The legitimate flow asks you to paste your Runware API key into a Runware-branded page served by `mcp.runware.ai`. Any client prompting for an API key in its own UI before the browser redirect is bypassing OAuth. This is common for stdio-only clients connecting through a bridge. ## Troubleshooting The OAuth popup closes immediately and the connector shows as failed.▸ The browser blocked the popup, or the popup was opened but never received the callback. Unblock popups for the client's domain (`claude.ai`, `cursor.com`, etc.) and try connecting again. If the popup opens but closes without completing, it's typically a cross-origin restriction on the popup window. Disconnect and reconnect the server. I see 'Token exchange failed' or an Authentication Error after submitting my key.▸ Locally cached OAuth state is stale. Clear it and reconnect: - **Clients using `mcp-remote`**: `rm -rf ~/.mcp-auth` and restart the client. - **Claude Code**: open `/mcp`, choose **Clear authentication** on the Runware entry, then re-add the server. - **Claude Desktop / claude.ai**: Customize → Connectors → disconnect Runware and reconnect. The tools don't appear after I connect.▸ Verify the server is actually registered in the client. The connector can show "connected" without the tool list having been fetched: - **Claude Desktop / claude.ai**: Customize → Connectors → Runware should read "Connected." If not, remove and re-add. - **Claude Code**: run `claude mcp list` and confirm the entry, then restart the session. - **Codex CLI**: run `codex mcp list` and start a new session. Then ask the agent a trivial Runware question (`check my account` or `list a few image models`). If it calls a tool, the connection is live. I rotated my API key and the connector is still billing the old one.▸ The OAuth session stored the key at connect time, so rotating the key in your dashboard doesn't update the session. Disconnect the connector in your client and reconnect, then paste the new key on the OAuth page. A run failed with 'insufficient credits.'▸ Your account balance was below the model's cost. Top up your balance and have the agent retry. The agent doesn't see your balance directly so it can't pre-check. Call `model_pricing` first if you want it to budget before running. ## Pricing MCP tool calls are billed at standard Runware pricing for whatever underlying task runs. **There is no MCP-specific surcharge.** If a `run` call generates an image that costs $0.05, MCP charges $0.05. Every other tool is free. See the [pricing page](https://runware.ai/docs/platform/pricing) for how Runware bills (compute-based, no subscriptions). For per-model rates, call `model_pricing` from inside the agent or check the model's page directly. --- ## Skills **URL:** https://runware.ai/docs/platform/skills **Description:** Give your AI agent packaged, outcome-oriented recipes for Runware. Ask for a result in plain language, the right skill routes to the right model, and the agent runs it through the SDKs, MCP, or CLI. ## Introduction Runware Skills are packaged recipes that let an AI agent get good results from Runware without knowing the catalog. Each skill is a small folder of Markdown an agent loads on demand. You ask for an outcome in plain language, the matching skill picks up the request and chooses the right model, and the agent runs it to produce the result. The point is the routing. You ask for "a product video" or "the same character in a new scene" the same way today and a year from now. The skill decides which model fits, and that choice updates as better models ship. The skill is the stable part. The model underneath is free to change. You ask Make a launch video from this product photo A skill picks up the intent `product-ad-video` Named by the outcome, not the model. This is the stable part. It routes to the best model available today bytedance:seedance@2.0google:3@2klingai:kling-video@3.0-turbo Swappable underneath. When a better model ships, the skill's routing updates and every user gets the upgrade without changing a thing. Runs and returns `videoURL` with your finished clip Skills run on top of what you already have. A skill is instructions an agent loads, not a new endpoint, so there is nothing to host. Your agent reads the skill and carries out the work through the Runware access it already has: the [MCP server](https://runware.ai/docs/platform/mcp), the [CLI](https://runware.ai/docs/platform/cli), or the SDKs. ## How skills work A skill is a folder with a `SKILL.md` file. The frontmatter `description` is the trigger. It lists the phrases that should activate the skill, and the agent matches your request against it. When a request matches, the agent reads the skill body and follows it. Skills load on demand, so the cost of having many installed is small. Until a request matches, only a skill's name and description sit in the agent's context. Then the body loads. Deeper material such as worked examples loads only if the task calls for it. You can install the whole catalog and pay almost nothing until one applies. The set has two layers: - **Foundation skills** hold the shared machinery. How to call the API, how to pick a model, how to prompt. - **Outcome skills** are the customer recipes. Each one composes the foundation and adds the craft for its specific job. > [!NOTE] > A skill never hardcodes a single model as the only answer. It names a sensible default, then confirms the model is live and reads its current schema before running. That is what keeps a skill correct as the catalog changes underneath it. ## Add skills to your agent Skills are files. Add the ones you want wherever your agent loads skills from. **Claude Code**: 1 Add the marketplace Register the Runware marketplace, then install the skills plugin: ```bash /plugin marketplace add runware/runware-skills /plugin install skills@runware ``` You stay current as new commits land. 2 Or copy by hand To cherry-pick individual skills instead, clone the repo and drop the folders you want into `.claude/skills/` (or `~/.claude/skills/` for every project): ```bash git clone https://github.com/runware/runware-skills.git mkdir -p .claude/skills cp -r runware-skills/skills/product-ad-video .claude/skills/ ``` 3 Ask for the outcome Start a session and ask in plain language. The matching skill activates from its description. **Claude.ai**: 1 Open Skills On a plan with Skills, go to **Customize → Skills**. [Open Claude skills](https://claude.ai/customize/skills) 2 Upload the skill folders Upload the skills you want. Each becomes available to Claude in chat and activates when your request matches its description. **Cursor**: 1 Clone the repository ```bash git clone https://github.com/runware/runware-skills.git ``` 2 Copy the skills into Cursor Drop the skill folders into `.cursor/skills/` in your project, or `~/.cursor/skills/` for every project. Cursor reads the standard `SKILL.md` and discovers them recursively: ```bash mkdir -p .cursor/skills cp -r runware-skills/skills/* .cursor/skills/ ``` 3 Use a skill Ask in plain language and Cursor applies the matching skill, or type `/` and pick it by name. **OpenClaw**: 1 Install from the repo OpenClaw installs the skills straight from the repository: ```bash openclaw skills install git:runware/runware-skills ``` This adds them to the workspace `skills/` directory. Add `--global` for `~/.openclaw/skills`. 2 Ask for the outcome Start a session and ask in plain language. The matching skill activates from its description. **Any agent**: 1 Get the skills ```bash git clone https://github.com/runware/runware-skills.git ``` 2 Point your agent at them The skills follow the open Agent Skills standard, so any agent that supports skills can read them. Drop the folders wherever that agent looks, commonly `.agents/skills/` (or `~/.agents/skills/` globally), and it picks them up by their `name` and `description`. Pair skills with the [MCP server](https://runware.ai/docs/platform/mcp) or the [CLI](https://runware.ai/docs/platform/cli) so the agent has a way to actually run the tasks the skill describes. ## The catalog Three foundation skills carry the shared machinery. Every outcome skill leans on them. `runware-run` The execution contract. Inspect the model's schema, send only valid fields, run synchronously for images or asynchronously for video, audio, and 3D, then read the result. `runware-models` Pick the right model for a task and keep that choice current. Discovers what is live, routes by capability, and respects deprecations. `runware-prompting` Per-model-family prompt craft. In-image text, negation, and cinematic grammar, matched to how each model reads a prompt. The outcome skills are grouped by what they make. **Image** - `product-photography` turns a packshot into studio, lifestyle, and hero shots. - `character-consistency` keeps a character or product identical across scenes. - `text-in-image` renders posters, packaging, and UI with exact copy. - `edit-image` adds, removes, replaces, recolors, relights, inpaints, and outpaints. - `restore-and-upscale` deblurs, denoises, upscales, and restores old photos. - `photoreal-stills` produces realistic, non-AI-looking imagery. - `composite-scene` merges a product, a subject, and a backdrop without masking. - `logos-and-vectors` makes true SVG logos, icons, and scalable art. - `controlled-generation` drives generation from a pose, edge, or depth guide. - `game-assets-2d` makes sprites, stickers, and consistent asset sets. **Video** - `product-ad-video` turns a product image into an ad clip. - `ugc-ad` makes creator-style demos, testimonials, and talking-head ads. - `animate-image` turns a still into motion. - `talking-avatar` drives a talking head from a script or audio. - `multi-shot-video` tells a multi-shot story in one call. - `cinematic-video` directs shot language and color grade. - `edit-video` transforms, restyles, or surgically edits footage. - `replace-in-video` swaps a character, product, or wardrobe in a clip. - `reframe-video` changes aspect ratio without cropping. - `add-audio-to-video` adds sound effects, narration, or a music bed. - `video-upscale` increases resolution toward 4K. **3D, audio, and text** - `image-to-3d-asset` turns a photo or prompt into a textured GLB mesh. - `voiceover` generates TTS narration with emotion and voice control. - `dialogue-audio` produces a multi-speaker conversation in one file. - `voice-cloning` clones a voice from a sample or designs one. - `music` makes songs, instrumental beds, and jingles. - `llm-agent` builds chat and tool-calling over your own functions. - `vision-understanding` reads images, video, and documents. **Custom models** - `train-style-model` fine-tunes a reusable brand or style LoRA. - `bring-your-own-model` uploads and uses your own LoRA or checkpoint. ## Anatomy of a skill Every skill follows the same shape. The frontmatter is the trigger, and the body is the recipe. ```markdown --- name: product-ad-video description: > Make an ad or promo video from a product image. Use for "turn this product photo into a video", "launch clip", "product ad". --- # Product ad video ## Inputs to collect ## Models ## Workflow ## Technique ## Parameters that matter ## Quality bar ## Related skills ``` `Models` names a default plus alternatives and tells the agent to confirm the model is live. `Technique` carries the craft, drawn from the model's own guide. `Parameters that matter` lists only the load-bearing fields and points at the live schema for the rest, so the skill does not go stale when a model updates. Flagship skills add a `references/` folder with worked examples that load only when needed. ## What an agent does with one Give an agent the skills and a single sentence. > Restore this old family photo and make it print-ready. The agent matches the request to `restore-and-upscale`, reads it, picks an upscaler that is live, runs the task, and returns the result. The skill also tells it the honest limit: there is no dedicated face-restoration model yet, so it sets that expectation instead of overpromising. > Make a launch video from this product shot, vertical, with a voiceover. Two skills compose. `product-ad-video` animates the product and `voiceover` generates the narration. The agent runs the video asynchronously, polls for the result, and hands back the clip. You did not name a model or a parameter. ## Write your own A skill is just a folder: a `SKILL.md` with a name and a description in the frontmatter, then the instructions, plus an optional `references/` for depth. The fastest start is to copy a skill from the [`runware-skills`](https://github.com/runware/runware-skills) repo and adapt it to your outcome. Two things carry most of the weight. Name the skill by the **outcome**, not the model, so the model underneath can change without breaking the skill. And treat the **description as the trigger**: lead with the phrases a user would actually say, because that line is what the agent matches a request against. Point the technique at a sensible model, but have the skill confirm the model is live before relying on it, so it stays correct as the catalog moves. ## How skills relate to the rest of the platform Skills are the know-how. They decide what to do and how to do it well. The agent runs the work through the [SDKs](https://runware.ai/docs/platform/typescript), the [MCP server](https://runware.ai/docs/platform/mcp), or the [CLI](https://runware.ai/docs/platform/cli). A skill points the agent at a model and a set of parameters, and one of those surfaces makes the call. Use skills when you want the agent to reach a result without you scripting the steps. Use the SDKs, MCP, or CLI directly when you want to drive the API yourself. --- ## ComfyUI integration **URL:** https://runware.ai/docs/platform/comfyui **Description:** Every Runware model as a ComfyUI node. Image, video, audio, 3D, and text run in the cloud, with builder nodes for LoRAs and ControlNets. ## Every model is a node Runware's ComfyUI integration turns every model on the platform into its own node. Each node's widgets are the model's real parameters, so the catalog stays current as new models launch. Generation runs on Runware, so you compose graphs the usual way without a local GPU. Runware LoRA lora modelcivitai:993... weight0.80 Runware: FLUX.2 \[dev\] loraIMAGE positivePrompta neon koi in fog width768 height512 seed834102 Preview Image images ![A red and white koi fish in dark blue water](https://runware.ai/docs/assets/preview.CDOK-2gx.jpg) Every model is its own node, generated from its live schema. A feature builder wires into the model's matching typed socket, and the IMAGE output flows on like any native node. ComfyUI gives you a node graph for building generation workflows. This integration adds Runware's full catalog to that graph as typed nodes for image, video, audio, 3D, and text, plus builder nodes for LoRAs, ControlNets, IP-Adapters, and more. You wire them together like any native node, queue the prompt, and the work happens on Runware. ## Install Install [ComfyUI](https://docs.comfy.org/get_started) first, then add the Runware nodes. **ComfyUI Manager**: 1 Find the nodes Open **ComfyUI Manager**, go to Custom Nodes Manager, and search for **Runware**. 2 Install and restart Click install, then restart ComfyUI. The Runware nodes appear in the node menu. **Manual**: ```bash cd ComfyUI/custom_nodes git clone https://github.com/Runware/ComfyUI-Runware pip install -r ComfyUI-Runware/requirements.txt ``` Restart ComfyUI to load the nodes. ## API key Provide your key, which you create in the [dashboard](https://runware.ai/api-keys), in any of these ways: - **ComfyUI Settings.** Open Settings and paste it into the **Runware API key** field. Easiest for local use, no terminal needed. - **`RUNWARE_API_KEY`** in the environment ComfyUI runs in. It overrides the Settings field, so a server deployment can pin its own key. - **Runware CLI.** Run `runware auth login` once and the nodes reuse the stored key. ## Finding nodes Search the node menu for **Runware**. You get one node per model, grouped by modality and creator as `Runware//`. A FLUX image model lives under `Runware/Image/runware`, a Kling video model under `Runware/Video/klingai`. A node's widgets are the model's parameters with the right types, ranges, and defaults, and enums become dropdowns. When Runware ships a new model or changes a parameter, the next release of the pack picks it up, so the nodes never drift from the API. Update through ComfyUI Manager or `git pull` to bring new models and parameter changes in one step. ## Your first image 1 Add a model node Double-click the canvas and search for a model by name, like `FLUX.2 [dev]`, or browse to `Runware/Image`. Drop the node onto the graph. 2 Set the prompt Type into `positivePrompt` and set the dimensions. 3 Wire the output Connect the node's **IMAGE** output to a **Preview Image** or **Save Image** node. 4 Queue Queue the prompt. The request runs on Runware and the result comes back as an `IMAGE`, ready for any downstream node. ## Outputs match the modality Each node returns the native ComfyUI type for its modality, so results flow straight into the nodes you already use. | Modality | Output | Notes | | --- | --- | --- | | Image, upscale, background removal | `IMAGE` | Into Preview, Save, or any image node | | Audio | `AUDIO` | Into native audio preview and save nodes | | Video | `VIDEO` | Into native video nodes | | 3D, vector, other files | file path | Downloaded to your output folder, path returned as a string | | Text, caption | text | A string, into text nodes | Audio and video use ComfyUI's native types when the runtime supports them and fall back to a saved file path otherwise, so a node never breaks on an older ComfyUI build. ## Inputs Inputs map to ComfyUI's native types. Reference and seed images are **IMAGE** inputs, and inpainting masks are **MASK** inputs, so a mask editor wires straight into a `maskImage` socket. Audio, video, and document inputs take a URL, file path, or UUID, one per line for the list inputs. Two utility nodes help here: **Runware Upload Image** uploads once and returns a reusable UUID, and **Runware Load Image (URL)** pulls any URL, including a Runware result, into the graph as an IMAGE. ## Parameters and defaults A node's widgets are grouped into sections, so even a model with many parameters reads top to bottom: inputs, the core generation settings, model settings, and output options. Some models tune parameters on their own. Where a model has no fixed default for a setting, the node leaves it to the model instead of guessing a value. A numeric setting like `steps` or `CFGScale` shows a **set** toggle that reveals its field only when you opt in, and a dropdown like `scheduler` carries a `(default)` option. A cohesive settings group reveals behind a single toggle as well: `safety`, `toolChoice`, and `advancedFeatures` each keep their fields hidden, and send nothing, until you enable the group. Leave these as they are to use the model's own default, or set them to take control. Dimensions and `seed` always show a value, since a generation needs a concrete canvas and a seed. ## Builder nodes Stackable features like LoRAs and ControlNets live in their own nodes instead of crowding every model node with fields you rarely touch. A model that supports a feature exposes a **typed socket** for it, such as `lora` or `controlNet`. Drop the matching builder from `Runware/Params`, wire its output into that socket, and the feature joins the request. Each socket has its own type, so a ControlNet only fits the ControlNet socket. To stack LoRAs, chain several `Runware LoRA` builders into the `lora` socket, where the chain order is the apply order. This keeps model nodes compact. A node carries its own parameters as widgets, and a feature attaches only when you add its builder, so the graph shows exactly what each generation uses and nothing more. Runware LoRA lora modelcivitai:993... weight0.80 Runware LoRA loralora modelcivitai:512... weight0.65 Runware: FLUX.2 \[dev\] lora positivePrompta neon koi in fog seed834102 Stack a feature by chaining its builders into the model’s typed socket. Two LoRA nodes feed the FLUX node’s lora input, and the chain order is the apply order. Every builder is below. The **Chainable** ones stack: wire several of the same kind into one socket and they apply in order. The rest attach once. `Runware LoRA`Chainable Apply a LoRA adapter at a chosen weight. `Runware ControlNet`Chainable Guide generation with a control image, weight, and step range. `Runware IP-Adapter`Chainable Condition on reference images for style and content transfer. `Runware Embeddings`Chainable Apply textual-inversion embeddings to steer the prompt. `Runware Reference Images`Chainable Pass reference images to models that take them. `Runware Reference Videos`Chainable Pass reference videos to video models. `Runware Reference Voices`Chainable Pass reference voices for cloning and speech models. `Runware Messages`Chainable Build the chat message list for text and LLM models. `Runware Refiner` Hand off to a refiner model for a second pass. `Runware PuLID` Preserve a subject's identity from reference images. `Runware PhotoMaker` Identity-preserving generation from reference photos. `Runware ACE++` Subject-driven editing and conditioning with ACE++. `Runware Watermark` Stamp text or an image onto the result. `Runware Outpaint` Extend the canvas with outpainting margins. `Runware Ultralytics` Detect or segment regions with an Ultralytics model. `Runware Speech` Voice and speech settings for text-to-speech models. `Runware Audio Settings` Fine-tune output settings for audio models. `Runware Import Model` Reference an external model to import for the request. `Runware Accelerator Options` Tune caching and speed settings as one reusable block. The LoRA, Embeddings, and Refiner builders have an editable **model** field with a **search catalog** button beneath it: click it, type a name, and pick from the live catalog results to fill the AIR, so you never have to memorize identifiers. The ControlNet and IP-Adapter builders instead scope themselves to the base model you wire them into, since both the compatible models and the supported parameters depend on the architecture. Connect the builder (chaining through any other ControlNet or IP-Adapter builders) to a model or architecture node, and its **model** field becomes a dropdown of exactly the models that node accepts, while parameters the model does not support (some architectures add advanced IP-Adapter controls, others do not) hide themselves. Until it reaches a node, or when the node doesn't restrict things, it stays a free AIR field with all parameters shown. The Speech, reference, Accelerator Options, and Outpaint builders scope their fields the same way, since what each supports varies by model. Wired into a model, a builder shows only the fields that model accepts and hides the rest: the Speech builder's **voice** and **language** become that model's own lists (or a free text field when the model takes an open voice ID), the Accelerator Options builder shows only the caching strategies the model offers, and the Outpaint builder drops a field like `blur` on models that lack it. Unwired, a builder shows its full set. Settings that belong to a single model show up as **dotted fields right on the model node**, like `refiner.model` or `providerSettings.google.webSearch`. Only the provider that matches the model appears, so a Google model shows `providerSettings.google.*` and nothing from the other providers. Anything not covered by a widget or a builder goes into the node's `advanced_json` input as raw JSON. ## Custom models The catalog covers the models Runware hosts, but you can also run your own community checkpoint, like an SDXL or FLUX fine-tune from a model site. Under `Runware/Custom models` there is one node per model architecture: `Stable Diffusion XL`, `SD 1.5`, `FLUX.1 [dev]`, `Pony Diffusion XL`, and more. Pick the node that matches your checkpoint's architecture, then set its **model** field to the checkpoint's AIR. Its **search catalog** button is scoped to the architecture, so you can browse compatible checkpoints and fill the AIR without leaving the graph. Everything else works like a model node: the widgets are that architecture's parameters, and LoRA, ControlNet, and the other builders attach the same way. There is a node per architecture rather than one generic node because each family exposes different parameters. SDXL has a refiner, SD 1.5 has clip skip, and FLUX has its own guidance control, so the node you pick shows exactly the settings that apply to your checkpoint. ## The generic node `Runware (custom)` targets any model AIR and task type. Reach for it when a model shipped after your installed version of the pack, before the next release adds its typed node. It makes no assumption about modality. You give it the request body as JSON in `request_json`, and it returns the raw response as `result_json`. That single pair works for an image model, a video model, a 3D model, or anything Runware adds later, because the node only moves JSON in and out. To feed an image, video, or audio input, upload it once with `Runware Upload Image` for a reusable UUID and reference that UUID inside `request_json`, the same way you would in a direct API call. To use a result, pass `result_json` to `Runware Get`, which pulls a field out by dot-path. Set the path to match the model's response, such as `0.imageURL`, `0.videoURL`, or `0.outputs.files.0.url`, or leave it empty to grab the first URL it finds. The `Get` node outputs a string, so wire it to `Runware Load Image (URL)` to preview an image, or to a Save or Preview node for any other result. > [!NOTE] > A new model works through the generic node the moment it is live on Runware. Pass its AIR and `taskType`, and the request runs the same way a typed node would. ## Run info: cost and safety After a run, every model, architecture, and custom node shows a short line along its title bar with the cost of that run and, when the model ran a content check, whether it flagged the result, like `$0.00078 · NSFW: no`. You can watch spend and safety as you iterate without leaving the graph. Each part appears only when the response carried it, so a model that returns no cost or runs no content check simply shows less. ## Seeds The `seed` widget behaves like the sampler's seed, with the fixed, increment, decrement, and randomize control, so re-queuing produces a new image instead of handing back the cached one. ## Troubleshooting A node errors with 'No Runware API key.'▸ Set your key in ComfyUI Settings under **Runware API key**, or export `RUNWARE_API_KEY` in the environment ComfyUI runs in, or run `runware auth login`. The Settings value reaches the backend when a browser tab is open, so headless runs should use the environment variable. A model I want isn't in the node menu.▸ It shipped after your installed version. Update the pack through ComfyUI Manager or `git pull`, or use the **Runware (custom)** node with the model's AIR right away. How do I run my own community checkpoint?▸ Add the architecture node under `Runware/Custom models` that matches your checkpoint's family, such as `Stable Diffusion XL` or `SD 1.5`, then set its **model** field to the checkpoint's AIR or use its **search catalog** button to find it. An audio or video model returns a file path instead of a preview.▸ The runtime is missing the library for that native type. The path still points to the file saved in your output folder, so the result is there to use or chain. Model search returns nothing.▸ Check that your API key is set and that the search term matches a model name or family. The search queries the live catalog, so a typo or an empty key returns no results. ## Source The integration is open source and maintained by Runware. Browse the nodes, file issues, or contribute on [GitHub](https://github.com/Runware/ComfyUI-Runware). Join the [Discord community](https://discord.gg/aJ4UzvBqNU) to share workflows and get help. --- ## Vercel AI SDK integration **URL:** https://runware.ai/docs/platform/vercel-ai **Description:** Use Runware's image generation capabilities through the Vercel AI SDK with our community provider. Complete setup guide and usage examples. ## Introduction The Runware provider for Vercel AI SDK allows you to integrate Runware's image generation capabilities directly into applications built with the Vercel AI SDK. This integration provides a **standardized interface** that follows Vercel's conventions while maintaining **full access to Runware's advanced features** and flexible API. Source code: [https://github.com/Runware/ai-sdk-provider](https://github.com/Runware/ai-sdk-provider) ## Installation Install the provider package alongside the Vercel AI SDK: ```bash npm install @runware/ai-sdk-provider ai@^4.3.16 ``` ## Basic setup Before you start generating images, you'll need to configure your API key. The simplest approach is to set it as an environment variable, which **keeps your credentials secure** and makes deployment easier: ```bash export RUNWARE_API_KEY="your-api-key-here" ``` For advanced configuration options like setting the API key in code or custom connection settings, see the [custom provider configuration](#custom-provider-configuration) section below. Once your API key is configured, you can generate your first image with just a few lines of code: ```javascript import { runware } from '@runware/ai-sdk-provider'; import { experimental_generateImage as generateImage } from 'ai'; const { image } = await generateImage({ model: runware.image('runware:101@1'), prompt: 'A serene mountain landscape at sunset', size: '1024x1024', }); console.log('Generated image:', image.url); ``` That's it! Your first AI-generated image is ready. The provider **handles all the API communication, authentication, and response formatting automatically**. ## Key integration concepts ### Model selection with AIR IDs Runware uses the AIR ID system to identify models across different sources. When working with the Vercel AI SDK provider, you'll specify models using this standardized format: ```javascript // High-quality generation with FLUX.1 Dev const fluxDev = runware.image('runware:101@1'); // Ultra-fast generation with FLUX.1 Schnell const fluxSchnell = runware.image('runware:100@1'); // Community models from Civitai const civitaiModel = runware.image('civitai:133005@782002'); // Your own fine-tuned models const customModel = runware.image('custom:your-model@1'); ``` Each model offers different strengths and characteristics. You can explore the full catalog and find the perfect model for your use case in the [models directory](https://runware.ai/models). ### Accessing advanced features While the Vercel AI SDK provides a clean, standardized interface, Runware's API offers extensive customization options. You can access these advanced features through the `providerOptions.runware` object: ```javascript const { image } = await generateImage({ model: runware.image('runware:101@1'), prompt: 'A cyberpunk cityscape with neon lights', size: '1024x1024', providerOptions: { runware: { steps: 30, // Higher steps for better quality CFGScale: 7.5, // How closely to follow the prompt scheduler: 'DPM++ 2M', // Sampling algorithm seed: 42, // For reproducible results }, }, }); ``` This approach gives you the best of both worlds: the simplicity and consistency of the Vercel AI SDK with **full access to Runware's powerful features**. For a complete list of available parameters and their effects, see our [model documentation](https://runware.ai/docs/models). ## Common usage patterns ### Image transformation Transform existing images while preserving their basic structure. This technique is perfect for style transfer or enhancement: ```javascript const { image } = await generateImage({ model: runware.image('runware:97@2'), // HiDream Dev prompt: 'vibrant cyberpunk style with neon lighting', size: '1024x1024', providerOptions: { runware: { seedImage: 'image-uuid-or-url', // Accepts UUID, URL, or Base64 strength: 0.7, // Controls transformation intensity steps: 20, }, }, }); ``` The `strength` parameter is crucial here. Lower values (0.1-0.4) preserve more of the original image structure, while higher values (0.7-1.0) allow more dramatic transformations. Start with 0.6 to 0.8 for most use cases. ### Batch generation Generate multiple variations of the same concept in a single request. This is particularly useful for giving users choices or A/B testing different approaches: ```javascript // Configure the model to allow multiple images // Note: This model configuration pattern is specific to the Vercel AI SDK const model = runware.image('runware:100@1', { maxImagesPerCall: 4, outputFormat: 'webp', }); const { images } = await generateImage({ model, prompt: 'A friendly AI assistant robot in a modern office', n: 4, // Generate 4 variations size: '1024x1024', providerOptions: { runware: { steps: 4, // Schnell model works well with fewer steps }, }, }); // Process each generated variation images.forEach((img, index) => { console.log(`Variation ${index + 1}:`, img.url); }); ``` Runware excels at batch generation, supporting **up to 20 images in a single request without any speed penalties**. ### Precise editing with inpainting Inpainting allows you to selectively replace parts of an image while keeping the rest intact. It's like intelligent content-aware fill, but guided by AI and your specific prompts: ```javascript const { image } = await generateImage({ model: runware.image('runware:102@1'), // FLUX.1 Fill - specialized for inpainting prompt: 'A beautiful zen garden with cherry blossoms and stone lanterns', size: '1024x1024', providerOptions: { runware: { seedImage: 'base-image-uuid', // Your source image maskImage: 'mask-image-uuid', // Black/white mask defining edit areas steps: 25, }, }, }); ``` To use inpainting effectively, create masks using any image editor where **white pixels indicate areas to regenerate and black pixels are preserved**. This technique is powerful for removing unwanted objects, changing backgrounds, or adding new elements to existing images. ## Configuration options ### Default provider The simplest setup uses environment variables for configuration, which works great for most applications: ```javascript import { runware } from '@runware/ai-sdk-provider'; // Automatically uses RUNWARE_API_KEY from environment ``` This default provider handles authentication automatically and connects to Runware's standard API endpoint. ### Custom provider configuration For more complex applications, you might need custom configuration. The provider supports additional options for flexibility, such as API proxying or custom authentication flows: ```javascript import { createRunware } from '@runware/ai-sdk-provider'; const runware = createRunware({ apiKey: 'your-specific-api-key', // Override environment variable baseURL: 'https://your-proxy.com/v1', // Custom endpoint for proxying headers: { 'X-Proxy-Auth': 'token', // Additional headers as needed } }); ``` ### Model configuration The Vercel AI SDK allows you to configure models with default parameters, which is different from Runware's native SDKs but provides a convenient way to avoid repeating common settings. These defaults apply to every generation with that model instance: ```javascript const highQualityModel = runware.image('runware:101@1', { outputFormat: 'png', // Always use PNG for this model outputQuality: 95, // High quality checkNSFW: true, // Enable content filtering steps: 30, // Default to high quality scheduler: 'DPM++ 2M', // Preferred scheduler }); // Now every generation with this model uses these defaults const { image } = await generateImage({ model: highQualityModel, prompt: 'A stunning landscape photography', size: '1024x1024', // Configuration is already applied }); ``` This pattern is especially useful when you have different quality tiers or specific use cases in your application. Note that this model configuration approach is specific to the Vercel AI SDK integration. ## Error handling Robust error handling is essential for production applications. The provider includes descriptive error messages to help you debug issues quickly and provide meaningful feedback to users: ```javascript try { const { image } = await generateImage({ model: runware.image('runware:101@1'), prompt: 'A detailed architectural rendering', size: '1024x1024', }); // Success - use the generated image console.log('Generated successfully:', image.url); } catch (error) { if (error.name === 'RunwareAPIError') { // API-specific errors with detailed information console.error('Runware API Error:', error.message); console.error('Status Code:', error.status); } else { // Network errors, timeout, or other issues console.error('Request failed:', error.message); } } ``` Common error scenarios include insufficient credits, invalid model IDs, unsupported parameter combinations, or network connectivity issues. The provider surfaces these clearly so your application can respond appropriately and provide good user experience. ## TypeScript support The provider includes comprehensive TypeScript definitions for all APIs, model configurations, and response types. Your IDE will provide autocomplete and type checking for the entire feature set. --- ## OpenAI Compatibility **URL:** https://runware.ai/docs/platform/openai **Description:** Use Runware's text inference through any OpenAI-compatible client. Drop-in replacement with the same endpoint format, streaming, and SDK support. ## Introduction Runware exposes a fully **OpenAI-compatible chat completions endpoint** at `https://api.runware.ai/v1/chat/completions`. If you already use the OpenAI SDK or any tool that speaks the OpenAI protocol, you can point it at Runware by changing two values: the **base URL** and the **API key**. This is the fastest way to get started with Runware's text inference. There is **no Runware-specific parsing required**, so any OpenAI-compatible client or framework will work out of the box. > [!NOTE] > If you need access to Runware-specific features like `taskUUID` tracking, `includeCost`, or the `async` delivery method, use the [native API](https://runware.ai/docs/platform/streaming) instead. The OpenAI-compatible endpoint focuses on broad compatibility with the standard OpenAI request and response format. ## Quick start Send a standard chat completion request to `https://api.runware.ai/v1/chat/completions` using your Runware API key and a Runware model ID: ```json { "model": "minimax:m2.7@0", "messages": [ { "role": "user", "content": "What is the capital of France?" } ], "max_completion_tokens": 256 } ``` The request format is identical to the [OpenAI Chat Completions API](https://platform.openai.com/docs/api-reference/chat/create). The only difference is that `model` uses a **Runware AIR ID** (e.g., `minimax:m2.7@0`, `google:gemini@3.1-pro`) instead of an OpenAI model name. **curl**: ```bash curl -X POST https://api.runware.ai/v1/chat/completions \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "minimax:m2.7@0", "messages": [{"role": "user", "content": "What is the capital of France?"}], "max_completion_tokens": 256 }' ``` **Python**: ```python from openai import OpenAI client = OpenAI( api_key="your_runware_api_key", base_url="https://api.runware.ai/v1", ) response = client.chat.completions.create( model="minimax:m2.7@0", messages=[{"role": "user", "content": "What is the capital of France?"}], max_completion_tokens=256, ) print(response.choices[0].message.content) ``` **TypeScript**: ```typescript import OpenAI from 'openai'; const client = new OpenAI({ apiKey: 'your_runware_api_key', baseURL: 'https://api.runware.ai/v1', }); const response = await client.chat.completions.create({ model: 'minimax:m2.7@0', messages: [{ role: 'user', content: 'What is the capital of France?' }], max_completion_tokens: 256, }); console.log(response.choices[0].message.content); ``` ## Streaming Set `"stream": true` to receive tokens as they are generated, exactly like you would with the OpenAI API. To include token counts at the end of the stream, add `stream_options`: ```json { "model": "minimax:m2.7@0", "stream": true, "stream_options": { "include_usage": true }, "messages": [ { "role": "user", "content": "Tell me a joke" } ], "max_completion_tokens": 512 } ``` The SSE response format is **identical to OpenAI's**. Content arrives in `choices[0].delta.content`, and the stream ends with `data: [DONE]`. ### Streaming with OpenAI SDKs The OpenAI Python and TypeScript SDKs handle all SSE parsing for you: **Python**: ```python from openai import OpenAI client = OpenAI( api_key="your_runware_api_key", base_url="https://api.runware.ai/v1", ) with client.chat.completions.stream( model="minimax:m2.7@0", messages=[{"role": "user", "content": "Tell me a joke"}], max_tokens=512, ) as stream: for text in stream.text_stream: print(text, end="", flush=True) ``` **TypeScript**: ```typescript import OpenAI from 'openai'; const client = new OpenAI({ apiKey: 'your_runware_api_key', baseURL: 'https://api.runware.ai/v1', }); const stream = await client.chat.completions.create({ model: 'minimax:m2.7@0', messages: [{ role: 'user', content: 'Tell me a joke' }], max_tokens: 512, stream: true, }); for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content; if (content) process.stdout.write(content); } ``` **curl**: ```bash curl -N -X POST https://api.runware.ai/v1/chat/completions \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "minimax:m2.7@0", "stream": true, "stream_options": {"include_usage": true}, "messages": [{"role": "user", "content": "Tell me a joke"}], "max_completion_tokens": 512 }' ``` ### Reasoning models Models that support internal reasoning (like MiniMax M2.7) stream reasoning tokens in `choices[0].delta.reasoning_content` before the final response appears in `choices[0].delta.content`. This follows the same pattern as OpenAI's reasoning models. ```text // First chunk - role assignment data: {"id":"chatcmpl-e36b09e6-7a1c-4d8f-b5e2-9c4a3f6d8e1b","object":"chat.completion.chunk","model":"minimax:m2.7@0","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]} // Reasoning chunks - choices[0].delta.reasoning_content data: {"id":"chatcmpl-e36b09e6-7a1c-4d8f-b5e2-9c4a3f6d8e1b","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"reasoning_content":"The user asks 2+2. Simple arithmetic."},"finish_reason":null}]} // Actual response - switches to choices[0].delta.content data: {"id":"chatcmpl-e36b09e6-7a1c-4d8f-b5e2-9c4a3f6d8e1b","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"4"},"finish_reason":null}]} // Usage chunk (when stream_options.include_usage is set) data: {"id":"chatcmpl-e36b09e6-7a1c-4d8f-b5e2-9c4a3f6d8e1b","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":51,"completion_tokens":38,"total_tokens":89,"cost":0.000134}} // Final chunk - finish reason data: {"id":"chatcmpl-e36b09e6-7a1c-4d8f-b5e2-9c4a3f6d8e1b","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} data: [DONE] ``` ## Key differences from OpenAI While the endpoint is fully compatible with the OpenAI protocol, there are a few things to be aware of: | Aspect | OpenAI | Runware | | --- | --- | --- | | **Model IDs** | `gpt-4o`, `gpt-4o-mini` | AIR format: `minimax:m2.7@0`, `google:gemini@3.1-pro` | | **Authentication** | OpenAI API key | Runware API key (same `Authorization: Bearer` header) | | **Base URL** | `https://api.openai.com/v1` | `https://api.runware.ai/v1` | | **Available models** | OpenAI models only | Multiple providers: MiniMax, Google Gemini, and more | | **Naming convention** | snake\_case (`finish_reason`, `reasoning_content`) | snake\_case (same as OpenAI on this endpoint) | > [!NOTE] > This endpoint uses **snake\_case** field names (`finish_reason`, `max_tokens`) to match the OpenAI convention. The [native API](https://runware.ai/docs/platform/streaming) uses **camelCase** (`finishReason`, `maxTokens`). ## Key differences from the native API If you are choosing between the OpenAI-compatible endpoint and the native Runware API, here is how they compare: | | Native API | OpenAI-compatible | | --- | --- | --- | | **Endpoint** | `https://api.runware.ai/v1` | `https://api.runware.ai/v1/chat/completions` | | **Request format** | Array of task objects with `taskType` and `taskUUID` | Single object (standard OpenAI format) | | **Streaming trigger** | `"deliveryMethod": "stream"` | `"stream": true` | | **Async support** | Yes (`"deliveryMethod": "async"` with webhooks/polling) | No | | **Cost tracking** | `includeCost: true` on the request | Included in the `usage` chunk when `stream_options.include_usage` is set | | **Usage tracking** | `includeUsage: true` on the request | `stream_options: { "include_usage": true }` | | **Task tracking** | `taskUUID` for request/response correlation | Standard `id` field on response chunks | | **Naming convention** | camelCase | snake\_case | Use the **OpenAI-compatible endpoint** when you want the fastest integration path or are already working with the OpenAI SDK. Use the **native API** when you want a consistent request format across all Runware modalities (images, video, audio, text, 3d) and access to features like async delivery and task-level tracking. --- ## Model Search API **URL:** https://runware.ai/docs/platform/model-search **Description:** Search and discover AI models available in the Runware platform. Filter and find the right model for your generation tasks. ## Introduction The Model Search API enables discovery of available models on the Runware platform, providing search and filtering capabilities across **public models from the community** and **private models within your organization**. Models discovered through this API can be used immediately in generation tasks **by referencing their AIR identifiers**. This enables dynamic model selection in applications and helps discover new models for specific use cases. The search works across model names, versions, tags, and other metadata. Multiple filters can be combined to narrow results by category, type, architecture, and visibility. Results are returned in a **paginated format** with a default of 20 models per page, controlled by the `limit` and `offset` parameters. ## Request The Runware API always accepts an array of objects as input, where each object represents a **specific task to be performed**. The structure of the object varies depending on the type of the task. For this section, we will focus on the parameters related to the **model search task**. The following JSON snippet shows the basic structure of a request object. TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const result = await client.modelSearch({ search: 'realistic', tags: 'photorealistic', category: 'checkpoint', type: 'base', architecture: 'sdxl', visibility: 'all', offset: 0, limit: 20 }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: result = await client.model_search({ "search": "realistic", "tags": "photorealistic", "category": "checkpoint", "type": "base", "architecture": "sdxl", "visibility": "all", "offset": 0, "limit": 20 }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "modelSearch", "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada", "search": "realistic", "tags": "photorealistic", "category": "checkpoint", "type": "base", "architecture": "sdxl", "visibility": "all", "offset": 0, "limit": 20 } ]' ``` ```bash runware model search \ -q realistic \ --tags photorealistic \ --category checkpoint \ --type base \ --architecture sdxl \ --visibility all \ --offset 0 \ --limit 20 ``` ```json { "taskType": "modelSearch", "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada", "search": "realistic", "tags": "photorealistic", "category": "checkpoint", "type": "base", "architecture": "sdxl", "visibility": "all", "offset": 0, "limit": 20 } ``` --- ### [taskType](#request-tasktype) - **Type**: `string` - **Required**: true - **Value**: `modelSearch` Identifier for the type of task being performed ### [taskUUID](#request-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier for tracking tasks and matching async responses. Must be unique per task. ### [search](#request-search) - **Type**: `string` - **Required**: true Search query to find models by name, description or AIR ID. ### [source](#request-source) - **Type**: `string` Filter by model source. **Allowed values**: `featured` `community` ### [category](#request-category) - **Type**: `string` Filter models by category. **Allowed values**: `checkpoint` `lora` `lycoris` `vae` `embeddings` ### [architecture](#request-architecture) - **Type**: `string` Filter by model architecture. ### [capabilities](#request-capabilities) - **Type**: `array of strings` Filter by model capabilities. ### [visibility](#request-visibility) - **Type**: `string` Filter by visibility status. **Allowed values**: `public` `private` `favorite` `owned` ### [limit](#request-limit) - **Type**: `integer` - **Min**: `1` - **Max**: `100` - **Default**: `20` Maximum number of results to return. ### [offset](#request-offset) - **Type**: `integer` - **Min**: `0` - **Default**: `0` Number of results to skip for pagination. ### [sort](#request-sort) - **Type**: `string` - **Default**: `popularity` Sort order for results. A bare field name sorts ascending and a `-` prefix sorts descending, except `popularity`, whose bare value is already descending (most popular first). **Allowed values**: `popularity` `-popularity` `name` `-name` `addedUnixTimestamp` `-addedUnixTimestamp` `updatedDateUnixTimestamp` `-updatedDateUnixTimestamp` ## Response Results will be delivered in the format below. ```json { "data": [ { "results": [ { "name": "Promissing_Realistic_XL", "air": "civitai:305149@392545", "tags": [ "photorealistic", "base model", "sci-fi", "photo", "woman", "fantasy", "photorealism", "rpg", "general use", "close up", "close up shot", "promissing_realistic_xl" ], "heroImage": "https://mim.runware.ai/r/66a70a0bb7c38-450x450.jpg", "category": "checkpoint", "private": false, "comment": "", "version": "v22", "architecture": "sdxl", "type": "base", "defaultWidth": 1024, "defaultHeight": 1024, "defaultSteps": 20, "defaultScheduler": "Default", "defaultCFG": 7.5 } ], "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada", "taskType": "modelSearch", "totalResults": 2 } ] } ``` --- ### [taskType](#response-tasktype) - **Type**: `string` - **Required**: true - **Value**: `modelSearch` Identifier for the type of task this response belongs to. ### [taskUUID](#response-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier echoed from the original request, used to match async responses to their tasks. ### [totalResults](#response-totalresults) - **Type**: `integer` - **Required**: true - **Min**: `0` Total number of models matching the search criteria. ### [results](#response-results) - **Path**: `results.air` - **Type**: `array of objects (13 properties)` - **Required**: true List of models found. #### [air](#response-results-air) - **Path**: `results.air` - **Type**: `string` - **Required**: true Artificial Intelligence Resource identifier. #### [name](#response-results-name) - **Path**: `results.name` - **Type**: `string` - **Required**: true Model name. #### [category](#response-results-category) - **Path**: `results.category` - **Type**: `string` - **Required**: true Model category. **Possible values**: `checkpoint` `lora` `lycoris` `vae` `embeddings` #### [architecture](#response-results-architecture) - **Path**: `results.architecture` - **Type**: `string | null` - **Required**: true Model architecture. #### [capabilities](#response-results-capabilities) - **Path**: `results.capabilities` - **Type**: `array of strings` - **Required**: true Model capabilities. #### [source](#response-results-source) - **Path**: `results.source` - **Type**: `string` - **Required**: true Model source. **Possible values**: `featured` `community` #### [heroImage](#response-results-heroimage) - **Path**: `results.heroImage` - **Type**: `string | null` - **Required**: true - **Format**: `uri` Representative image URL. #### [private](#response-results-private) - **Path**: `results.private` - **Type**: `boolean` - **Required**: true Whether the model is private. #### [isFavorite](#response-results-isfavorite) - **Path**: `results.isFavorite` - **Type**: `boolean` Whether the model is favorited. #### [provider](#response-results-provider) - **Path**: `results.provider` - **Type**: `string` Model provider. #### [shortDescription](#response-results-shortdescription) - **Path**: `results.shortDescription` - **Type**: `string` Short description. #### [positiveTriggerWords](#response-results-positivetriggerwords) - **Path**: `results.positiveTriggerWords` - **Type**: `string` Positive trigger words. #### [negativeTriggerWords](#response-results-negativetriggerwords) - **Path**: `results.negativeTriggerWords` - **Type**: `string` Negative trigger words. --- ## Model Upload **URL:** https://runware.ai/docs/platform/model-upload **Description:** Upload and manage your custom AI models on the Runware platform. Integrate your own checkpoints, LoRAs, and other model types into your workflow. ## Introduction The Model Upload API lets you integrate **custom models** into the Runware platform. Upload your own checkpoints, LoRAs, or other supported model types and use them in generation tasks just like any other model on the platform. Uploaded models are automatically **optimized for the Sonic Inference Engine®**, enhancing speed and efficiency without compromising output quality. Once processed, models are distributed across our infrastructure and can be referenced by their assigned identifiers in any API request. ### Supported model categories - **Checkpoints**: Base models across multiple architectures including Stable Diffusion (1.x, 2.x, XL, 3.x) and FLUX. - **LoRAs**: Low-Rank Adaptation models for style and concept fine-tuning. - **LyCORIS**: An advanced alternative to LoRA for model fine-tuning. - **VAE**: Variational Autoencoder models for image encoding and decoding. - **Embeddings**: Textual Inversion embeddings for custom concepts and styles. ### Visibility and versioning You have full control over who can access your models. Set them as **public** (accessible to all platform users) or **private** (restricted to your organization). The API also supports **multiple versions** of the same model, allowing you to iterate on weights or configurations while maintaining a clean version history. ### Storage pricing Model upload is currently in **beta and free of charge**. There are no costs for uploading or storing models during this period. After the beta period, storage will be charged at **$0.05 per GB per month**, rounded up to the nearest GB and calculated daily. We will not retroactively charge for storage used during the beta. A positive account balance is required to upload new models. ## Request The Runware API always accepts an array of objects as input, where each object represents a **specific task to be performed**. The structure of the object varies depending on the type of the task. For this section, we will focus on the parameters related to the **model upload task**. The following JSON snippet shows the basic structure of a request object. TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const result = await client.modelUpload({ category: 'checkpoint', architecture: 'flux', format: 'safetensors', air: 'myorg:42@1', uniqueIdentifier: 'abc123def456', name: 'My Custom Model', version: 'v1', downloadURL: 'https://example.com/models/my-model.safetensors', private: true }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: result = await client.model_upload({ "category": "checkpoint", "architecture": "flux", "format": "safetensors", "air": "myorg:42@1", "uniqueIdentifier": "abc123def456", "name": "My Custom Model", "version": "v1", "downloadURL": "https://example.com/models/my-model.safetensors", "private": True }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "modelUpload", "taskUUID": "b92ea202-349f-4560-adae-abb55a8146ee", "category": "checkpoint", "architecture": "flux", "format": "safetensors", "air": "myorg:42@1", "uniqueIdentifier": "abc123def456", "name": "My Custom Model", "version": "v1", "downloadURL": "https://example.com/models/my-model.safetensors", "private": true } ]' ``` ```bash runware model upload \ --category checkpoint \ --architecture flux \ --format safetensors \ --air myorg:42@1 \ --unique-identifier abc123def456 \ --name "My Custom Model" \ --version v1 \ --download-url https://example.com/models/my-model.safetensors \ --private true ``` ```json { "taskType": "modelUpload", "taskUUID": "b92ea202-349f-4560-adae-abb55a8146ee", "category": "checkpoint", "architecture": "flux", "format": "safetensors", "air": "myorg:42@1", "uniqueIdentifier": "abc123def456", "name": "My Custom Model", "version": "v1", "downloadURL": "https://example.com/models/my-model.safetensors", "private": true } ``` --- ### [taskType](#request-tasktype) - **Type**: `string` - **Required**: true - **Value**: `modelUpload` Identifier for the type of task being performed ### [taskUUID](#request-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier for tracking tasks and matching async responses. Must be unique per task. ### [category](#request-category) - **Type**: `string` - **Required**: true Category of the model. **Allowed values**: `checkpoint` `lora` `lycoris` `vae` `embeddings` ### [format](#request-format) - **Type**: `string` - **Required**: true Format of the model file. **Allowed values**: `safetensors` ### [air](#request-air) - **Type**: `string` Artificial Intelligence Resource identifier. Format: `provider:model@version`. ### [uniqueIdentifier](#request-uniqueidentifier) - **Type**: `string` Unique identifier for the model. ### [name](#request-name) - **Type**: `string` - **Required**: true Name of the model. ### [version](#request-version) - **Type**: `string` - **Required**: true Version of the model. ### [downloadURL](#request-downloadurl) - **Type**: `string` - **Required**: true - **Format**: `uri` URL where the model file is hosted. ### [private](#request-private) - **Type**: `boolean` - **Default**: `true` Whether the model should be private. ### [heroImageURL](#request-heroimageurl) - **Type**: `string` - **Format**: `uri` URL of the hero image. ### [tags](#request-tags) - **Type**: `array of strings` Tags associated with the model. ### [shortDescription](#request-shortdescription) - **Type**: `string` Short description of the model. ### [comment](#request-comment) - **Type**: `string` Additional comments or notes. ### Checkpoints When `category` is set to `checkpoint`, the following additional parameters are available: ### [type](#request-type) - **Type**: `string` Type of the model (specific to category). **Allowed values**: `base` `inpainting` ### [architecture](#request-architecture) - **Type**: `string` - **Required**: true Architecture of the model. **Allowed values**: `exactlyai_image` `flux1d` `flux1s` `fluxkontextdev` `illustrious` `noobai` `pony` `sd1x` `sdxl` `sdxllightning` `sdxlturbo` `z_image` `z_image_turbo` ### [defaultScheduler](#request-defaultscheduler) - **Type**: `string` Default scheduler. ### [defaultSteps](#request-defaultsteps) - **Type**: `integer` - **Min**: `1` Default number of inference steps. ### [defaultCFG](#request-defaultcfg) - **Type**: `float` - **Min**: `0` Default classifier-free guidance scale. ### [defaultStrength](#request-defaultstrength) - **Type**: `float` - **Min**: `0` - **Max**: `1` Default strength for img2img. ### LoRAs When `category` is set to `lora`, the following additional parameters are available: ### [type](#request-type) - **Type**: `string` Type of the model (specific to category). **Allowed values**: `positive` `negative` ### [architecture](#request-architecture) - **Type**: `string` - **Required**: true Architecture of the model. **Allowed values**: `flux1d` `flux1s` `flux2klein_4b` `flux2klein_9b` `flux2klein_base_4b` `flux2klein_base_9b` `flux_2_dev` `fluxkontextdev` `hidreamdev` `hidreamfast` `hidreamfull` `illustrious` `ltx_2_3_video` `noobai` `pony` `qwen_image` `qwen_image_edit` `qwen_image_edit_plus` `qwen_image_layered` `sd1x` `sd3` `sdxl` `sdxllightning` `sdxlturbo` `z_image` `z_image_turbo` ### [defaultWeight](#request-defaultweight) - **Type**: `float` Default weight for the model. ### [positiveTriggerWords](#request-positivetriggerwords) - **Type**: `string` List of positive trigger words. ### LyCORIS When `category` is set to `lycoris`, the following additional parameters are available: ### [type](#request-type) - **Type**: `string` Type of the model (specific to category). **Allowed values**: `positive` `negative` ### [architecture](#request-architecture) - **Type**: `string` - **Required**: true Architecture of the model. **Allowed values**: `flux1d` `flux1s` `flux2klein_4b` `flux2klein_9b` `flux2klein_base_4b` `flux2klein_base_9b` `flux_2_dev` `fluxkontextdev` `hidreamdev` `hidreamfast` `hidreamfull` `illustrious` `noobai` `pony` `qwen_image` `qwen_image_edit` `qwen_image_edit_plus` `qwen_image_layered` `sd1x` `sd3` `sdxl` `sdxllightning` `sdxlturbo` `z_image` `z_image_turbo` ### [defaultWeight](#request-defaultweight) - **Type**: `float` Default weight for the model. ### [positiveTriggerWords](#request-positivetriggerwords) - **Type**: `string` List of positive trigger words. ### VAE When `category` is set to `vae`, the following additional parameters are available: ### [architecture](#request-architecture) - **Type**: `string` - **Required**: true Architecture of the model. **Allowed values**: `illustrious` `noobai` `pony` `sd1x` `sdxl` `sdxllightning` `sdxlturbo` ### Embeddings When `category` is set to `embeddings`, the following additional parameters are available: ### [type](#request-type) - **Type**: `string` Type of the model (specific to category). **Allowed values**: `positive` `negative` ### [architecture](#request-architecture) - **Type**: `string` - **Required**: true Architecture of the model. **Allowed values**: `illustrious` `noobai` `pony` `sd1x` `sdxl` `sdxllightning` `sdxlturbo` ## Response The API **streams a sequence of status messages** as your model progresses through the upload pipeline. Each message uses the same structure but indicates the current processing phase: 1. **Validated**: Model parameters and configuration have been verified. 2. **Downloaded**: Model file has been retrieved from the provided URL. 3. **Optimized**: Model has been optimized for the Sonic Inference Engine. 4. **Stored**: Model has been uploaded to distributed storage. 5. **Ready**: Model is deployed and available for use in generation tasks. ```json { "data": [ { "taskType": "modelUpload", "taskUUID": "b92ea202-349f-4560-adae-abb55a8146ee", "status": "ready", "message": "Model successfully deployed and ready for use.", "air": "myorg:42@1" } ] } ``` --- ### [taskType](#response-tasktype) - **Type**: `string` - **Value**: `modelUpload` Identifier for the type of task this response belongs to. ### [taskUUID](#response-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier echoed from the original request, used to match async responses to their tasks. ### [status](#response-status) - **Type**: `string` - **Required**: true Status of the upload operation phase. **Possible values**: `validated` `downloaded` `optimized` `stored` `ready` `failed` ### [message](#response-message) - **Type**: `string` - **Required**: true Status message or error details. ### [air](#response-air) - **Type**: `string` The AIR identifier of the uploaded model. --- ## Account Management **URL:** https://runware.ai/docs/platform/account-management **Description:** Retrieve account details, team members, API keys, and usage stats (spend, performance, errors) for your organization with the account management API. ## Introduction The `accountManagement` task **reads your organization's account data over the API**: the `team` and their roles, your API keys, the current `balance`, and usage statistics. Reach for it whenever a script needs account state that would otherwise mean opening the web console, like billing automation or a usage alert. Everything runs through the `operation` parameter: it selects the dataset and **shapes both the request and the response**. Each operation is documented in its own section below. | Operation | Returns | | --- | --- | | `getDetails` | Organization info, team, API keys, and rolling usage totals. | | `getUsageActivity` | Requests and spend over a date range, broken down by day, model, and/or API key. | | `getUsagePerformance` | Per-model inference-time percentiles over a date range. | | `getUsageErrors` | Client (`4xx`) and server (`5xx`) error counts over a date range. | The three usage operations share the same request parameters (a date window plus optional filters) and the same `usage` response envelope, **differing only in the metrics each row reports**. ## getDetails Everything about the account in one call, with **no date range or filters**. The response carries the organization identity, the current `balance`, the full `team` roster with roles, and every API key with its lifetime request count. The `usage` object here holds **rolling totals** rather than a time breakdown: credits and requests for `today`, `last7Days`, `last30Days`, and lifetime `total`. Use `getDetails` for a point-in-time picture of the account, and the operations below when you need consumption sliced across a date range. ### Request TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const result = await client.accountManagement({ operation: 'getDetails' }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: result = await client.account_management({ "operation": "getDetails" }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "accountManagement", "taskUUID": "f4dd3dfe-955f-49d5-a785-7e3b633d6e7a", "operation": "getDetails" } ]' ``` ```bash runware account details ``` ```json { "taskType": "accountManagement", "taskUUID": "f4dd3dfe-955f-49d5-a785-7e3b633d6e7a", "operation": "getDetails" } ``` --- ### [taskType](#request-tasktype) - **Type**: `string` - **Required**: true - **Value**: `accountManagement` Identifier for the type of task being performed ### [taskUUID](#request-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier for tracking tasks and matching async responses. Must be unique per task. ### [operation](#request-operation) - **Type**: `string` - **Required**: true - **Value**: `getDetails` The specific account management operation to perform. ### Response ```json { "data": [ { "taskType": "accountManagement", "taskUUID": "f4dd3dfe-955f-49d5-a785-7e3b633d6e7a", "operation": "getDetails", "organizationUUID": "a6379343-9ff2-46a0-996b-e4a7b3057c88", "organizationName": "Acme Corporation", "balance": { "amount": 2450.75, "freeBalance": 120.00, "currency": "USD" }, "team": [ { "name": "John Smith", "email": "john.smith@acme.com", "roles": ["Owner"], "joinedAt": "2024-01-15T10:30:00Z" } ], "apiKeys": [ { "name": "Production API Key", "apiKey": "YHluz4gk5KU4ZZWr****************", "enabled": true, "createdAt": "2024-01-20T11:00:00Z", "requests": 15420, "lastUsedAt": "2025-10-12T08:45:30Z" } ], "usage": { "today": { "credits": 35.80, "requests": 1850 }, "last7Days": { "credits": 412.25, "requests": 21400 }, "last30Days": { "credits": 1685.90, "requests": 87560 }, "total": { "credits": 48920.50, "requests": 2540318 } } } ] } ``` --- ### [taskType](#response-tasktype) - **Type**: `string` - **Required**: true - **Value**: `accountManagement` Identifier for the type of task this response belongs to. ### [taskUUID](#response-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier echoed from the original request, used to match async responses to their tasks. ### [operation](#response-operation) - **Type**: `string` - **Required**: true - **Value**: `getDetails` The account management operation that produced this response. ### [organizationName](#response-organizationname) - **Type**: `string` The name of the organization. ### [organizationUUID](#response-organizationuuid) - **Type**: `string` - **Format**: `UUID v4` Unique identifier for the organization. ### [balance](#response-balance) - **Path**: `balance.amount` - **Type**: `object (3 properties)` Current account balance and currency. #### [amount](#response-balance-amount) - **Path**: `balance.amount` - **Type**: `float` - **Required**: true Current balance amount. #### [freeBalance](#response-balance-freebalance) - **Path**: `balance.freeBalance` - **Type**: `float` Available free credit balance. #### [currency](#response-balance-currency) - **Path**: `balance.currency` - **Type**: `string` - **Required**: true Currency code. ### [team](#response-team) - **Path**: `team.name` - **Type**: `array of objects (4 properties)` List of team members. #### [name](#response-team-name) - **Path**: `team.name` - **Type**: `string` - **Required**: true Full name of the team member. #### [email](#response-team-email) - **Path**: `team.email` - **Type**: `string` - **Required**: true - **Format**: `email` Email address of the team member. #### [roles](#response-team-roles) - **Path**: `team.roles` - **Type**: `array of strings` - **Required**: true Each team member is assigned a role that determines their level of access within the organization. | Capability | Owner | Admin | Developer | | --- | --- | --- | --- | | API generation and Playground | ✓ | ✓ | ✓ | | Manage Playground workflows | ✓ | ✓ | ✓ | | Create and manage API keys | ✓ | ✓ | | | View API logs and usage analytics | ✓ | ✓ | | | Manage billing and payment methods | ✓ | ✓ | | | Invite and remove team members | ✓ | ✓ | | | Assign Admin and Developer roles | ✓ | ✓ | | | Assign Owner role | ✓ | | | | Delete organization | ✓ | | | #### [joinedAt](#response-team-joinedat) - **Path**: `team.joinedAt` - **Type**: `string` - **Format**: `date-time` Date and time when the member joined. ### [apiKeys](#response-apikeys) - **Path**: `apiKeys.apiKey` - **Type**: `array of objects (7 properties)` List of API keys associated with the account. #### [apiKey](#response-apikeys-apikey) - **Path**: `apiKeys.apiKey` - **Type**: `string` - **Required**: true The API key string (partially masked). #### [name](#response-apikeys-name) - **Path**: `apiKeys.name` - **Type**: `string` - **Required**: true Name or label for the API key. #### [description](#response-apikeys-description) - **Path**: `apiKeys.description` - **Type**: `string` Description of the API key. #### [enabled](#response-apikeys-enabled) - **Path**: `apiKeys.enabled` - **Type**: `boolean` - **Required**: true Whether the API key is active. #### [createdAt](#response-apikeys-createdat) - **Path**: `apiKeys.createdAt` - **Type**: `string` - **Required**: true - **Format**: `date-time` Date and time when the key was created. #### [lastUsedAt](#response-apikeys-lastusedat) - **Path**: `apiKeys.lastUsedAt` - **Type**: `string` - **Format**: `date-time` Date and time when the key was last used. #### [requests](#response-apikeys-requests) - **Path**: `apiKeys.requests` - **Type**: `integer` Total number of requests made with this key. ### [usage](#response-usage) - **Path**: `usage.today` - **Type**: `object (12 properties)` Account usage statistics. #### [today](#response-usage-today) - **Path**: `usage.today` - **Type**: `object (2 properties)` Usage stats for today. ##### [credits](#response-usage-today-credits) - **Path**: `usage.today.credits` - **Type**: `float` - **Required**: true Total credits consumed. ##### [requests](#response-usage-today-requests) - **Path**: `usage.today.requests` - **Type**: `integer` - **Required**: true Total API requests made. #### [last7Days](#response-usage-last7days) - **Path**: `usage.last7Days` - **Type**: `object (2 properties)` Usage stats for the last 7 days. ##### [credits](#response-usage-last7days-credits) - **Path**: `usage.last7Days.credits` - **Type**: `float` - **Required**: true Total credits consumed. ##### [requests](#response-usage-last7days-requests) - **Path**: `usage.last7Days.requests` - **Type**: `integer` - **Required**: true Total API requests made. #### [last30Days](#response-usage-last30days) - **Path**: `usage.last30Days` - **Type**: `object (2 properties)` Usage stats for the last 30 days. ##### [credits](#response-usage-last30days-credits) - **Path**: `usage.last30Days.credits` - **Type**: `float` - **Required**: true Total credits consumed. ##### [requests](#response-usage-last30days-requests) - **Path**: `usage.last30Days.requests` - **Type**: `integer` - **Required**: true Total API requests made. #### [total](#response-usage-total) - **Path**: `usage.total` - **Type**: `object (2 properties)` Total lifetime usage stats. ##### [credits](#response-usage-total-credits) - **Path**: `usage.total.credits` - **Type**: `float` - **Required**: true Total credits consumed. ##### [requests](#response-usage-total-requests) - **Path**: `usage.total.requests` - **Type**: `integer` - **Required**: true Total API requests made. ## getUsageActivity How much the account spent and how many requests it ran, over the window you set with `startDate` and `endDate`. This is the call behind a **usage dashboard** or a **monthly billing report**. `groupBy` controls how the totals are sliced. Ask for `date` and you get a `timeseries` of per-day rows. Ask for `model` or `apiKey` and the same totals come back **split by that dimension**, and you can request several at once. Each slice lands under `usage` as its own breakdown: a `data` array with one row per bucket (`count` and `spend`), plus a `meta` roll-up that totals the window and projects a 30-day spend. ### Request TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const result = await client.accountManagement({ operation: 'getUsageActivity', startDate: '2026-07-01', endDate: '2026-07-06', groupBy: [ 'date', 'model' ], timezone: 'America/New_York' }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: result = await client.account_management({ "operation": "getUsageActivity", "startDate": "2026-07-01", "endDate": "2026-07-06", "groupBy": [ "date", "model" ], "timezone": "America/New_York" }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "accountManagement", "taskUUID": "b7e0a3f2-3c1a-4d9e-8f2b-1a2c3d4e5f60", "operation": "getUsageActivity", "startDate": "2026-07-01", "endDate": "2026-07-06", "groupBy": [ "date", "model" ], "timezone": "America/New_York" } ]' ``` ```bash runware account getUsageActivity ``` ```json { "taskType": "accountManagement", "taskUUID": "b7e0a3f2-3c1a-4d9e-8f2b-1a2c3d4e5f60", "operation": "getUsageActivity", "startDate": "2026-07-01", "endDate": "2026-07-06", "groupBy": [ "date", "model" ], "timezone": "America/New_York" } ``` --- ### [taskType](#request-tasktype) - **Type**: `string` - **Required**: true - **Value**: `accountManagement` Identifier for the type of task being performed ### [taskUUID](#request-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier for tracking tasks and matching async responses. Must be unique per task. ### [operation](#request-operation) - **Type**: `string` - **Required**: true - **Value**: `getUsageActivity` The specific account management operation to perform. ### [startDate](#request-startdate) - **Type**: `string` - **Format**: `date` Start of the usage window (inclusive). ### [endDate](#request-enddate) - **Type**: `string` - **Format**: `date` End of the usage window (inclusive). Must be on or after startDate, and the span must not exceed 30 days. ### [models](#request-models) - **Type**: `array of strings` Restrict usage to these model AIRs. Defaults to all models. ### [apiKeys](#request-apikeys) - **Type**: `array of strings` Restrict usage to these API key UUIDs. Defaults to all keys. ### [groupBy](#request-groupby) - **Type**: `array of strings` - **Default**: `date,model` Breakdowns to return. ### [timezone](#request-timezone) - **Type**: `string` - **Default**: `UTC` IANA timezone name used for day-bucketing. ### Response ```json { "data": [ { "taskType": "accountManagement", "taskUUID": "b7e0a3f2-3c1a-4d9e-8f2b-1a2c3d4e5f60", "operation": "getUsageActivity", "startDate": "2026-07-01", "endDate": "2026-07-06", "usage": { "timeseries": { "data": [{ "date": "2026-07-01", "count": 340, "spend": 120.05154 }], "meta": { "totalRequests": 1197, "totalResults": 1197, "totalSpend": 493.7819, "avgDailySpend": 87.37, "projectedSpend": 2708.55 } }, "model": { "data": [{ "date": "2026-07-01", "model": "google:gemini@omni-flash", "modelName": "Gemini Omni Flash", "count": 207, "spend": 101.457393 }], "meta": { "totalRequests": 1197, "totalResults": 1197, "totalSpend": 493.7819, "avgDailySpend": 87.37, "projectedSpend": 2708.55 } } } } ] } ``` --- ### [taskType](#response-tasktype) - **Type**: `string` - **Required**: true - **Value**: `accountManagement` Identifier for the type of task this response belongs to. ### [taskUUID](#response-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier echoed from the original request, used to match async responses to their tasks. ### [operation](#response-operation) - **Type**: `string` - **Required**: true - **Value**: `getUsageActivity` The account management operation that produced this response. ### [startDate](#response-startdate) - **Type**: `string` - **Format**: `date` Start of the returned window (inclusive). ### [endDate](#response-enddate) - **Type**: `string` - **Format**: `date` End of the returned window (inclusive). ### [usage](#response-usage) - **Path**: `usage.today` - **Type**: `object (46 properties)` Account usage statistics. #### [today](#response-usage-today) - **Path**: `usage.today` - **Type**: `object` Usage stats for today. #### [last7Days](#response-usage-last7days) - **Path**: `usage.last7Days` - **Type**: `object` Usage stats for the last 7 days. #### [last30Days](#response-usage-last30days) - **Path**: `usage.last30Days` - **Type**: `object` Usage stats for the last 30 days. #### [total](#response-usage-total) - **Path**: `usage.total` - **Type**: `object` Total lifetime usage stats. #### [timeseries](#response-usage-timeseries) - **Path**: `usage.timeseries` - **Type**: `object (13 properties)` Per-day breakdown (from groupBy date). ##### [data](#response-usage-timeseries-data) - **Path**: `usage.timeseries.data` - **Type**: `array of objects (6 properties)` - **Required**: true Rows for this breakdown. ##### [date](#response-usage-timeseries-data-date) - **Path**: `usage.timeseries.data.date` - **Type**: `string` - **Format**: `date` Day bucket. ##### [model](#response-usage-timeseries-data-model) - **Path**: `usage.timeseries.data.model` - **Type**: `string` Model AIR. ##### [modelName](#response-usage-timeseries-data-modelname) - **Path**: `usage.timeseries.data.modelName` - **Type**: `string` Human-friendly model name. Falls back to the AIR when none exists. ##### [apiKey](#response-usage-timeseries-data-apikey) - **Path**: `usage.timeseries.data.apiKey` - **Type**: `string` API key UUID. ##### [count](#response-usage-timeseries-data-count) - **Path**: `usage.timeseries.data.count` - **Type**: `integer` Number of requests in this bucket. ##### [spend](#response-usage-timeseries-data-spend) - **Path**: `usage.timeseries.data.spend` - **Type**: `float` Amount spent in this bucket. ##### [meta](#response-usage-timeseries-meta) - **Path**: `usage.timeseries.meta` - **Type**: `object (5 properties)` - **Required**: true Roll-up totals for a breakdown. Which fields are present depends on the operation. ##### [totalRequests](#response-usage-timeseries-meta-totalrequests) - **Path**: `usage.timeseries.meta.totalRequests` - **Type**: `integer` Total requests across the window. ##### [totalResults](#response-usage-timeseries-meta-totalresults) - **Path**: `usage.timeseries.meta.totalResults` - **Type**: `integer` Total results produced across the window. ##### [totalSpend](#response-usage-timeseries-meta-totalspend) - **Path**: `usage.timeseries.meta.totalSpend` - **Type**: `float` Total spend across the window. ##### [avgDailySpend](#response-usage-timeseries-meta-avgdailyspend) - **Path**: `usage.timeseries.meta.avgDailySpend` - **Type**: `float` Average spend per day. An estimate that drifts between calls. ##### [projectedSpend](#response-usage-timeseries-meta-projectedspend) - **Path**: `usage.timeseries.meta.projectedSpend` - **Type**: `float` Projected 30-day spend extrapolated from the window. An estimate that drifts between calls. #### [model](#response-usage-model) - **Path**: `usage.model` - **Type**: `object (13 properties)` Per-model breakdown (from groupBy model). ##### [data](#response-usage-model-data) - **Path**: `usage.model.data` - **Type**: `array of objects (6 properties)` - **Required**: true Rows for this breakdown. ##### [date](#response-usage-model-data-date) - **Path**: `usage.model.data.date` - **Type**: `string` - **Format**: `date` Day bucket. ##### [model](#response-usage-model-data-model) - **Path**: `usage.model.data.model` - **Type**: `string` Model AIR. ##### [modelName](#response-usage-model-data-modelname) - **Path**: `usage.model.data.modelName` - **Type**: `string` Human-friendly model name. Falls back to the AIR when none exists. ##### [apiKey](#response-usage-model-data-apikey) - **Path**: `usage.model.data.apiKey` - **Type**: `string` API key UUID. ##### [count](#response-usage-model-data-count) - **Path**: `usage.model.data.count` - **Type**: `integer` Number of requests in this bucket. ##### [spend](#response-usage-model-data-spend) - **Path**: `usage.model.data.spend` - **Type**: `float` Amount spent in this bucket. ##### [meta](#response-usage-model-meta) - **Path**: `usage.model.meta` - **Type**: `object (5 properties)` - **Required**: true Roll-up totals for a breakdown. Which fields are present depends on the operation. ##### [totalRequests](#response-usage-model-meta-totalrequests) - **Path**: `usage.model.meta.totalRequests` - **Type**: `integer` Total requests across the window. ##### [totalResults](#response-usage-model-meta-totalresults) - **Path**: `usage.model.meta.totalResults` - **Type**: `integer` Total results produced across the window. ##### [totalSpend](#response-usage-model-meta-totalspend) - **Path**: `usage.model.meta.totalSpend` - **Type**: `float` Total spend across the window. ##### [avgDailySpend](#response-usage-model-meta-avgdailyspend) - **Path**: `usage.model.meta.avgDailySpend` - **Type**: `float` Average spend per day. An estimate that drifts between calls. ##### [projectedSpend](#response-usage-model-meta-projectedspend) - **Path**: `usage.model.meta.projectedSpend` - **Type**: `float` Projected 30-day spend extrapolated from the window. An estimate that drifts between calls. #### [apiKey](#response-usage-apikey) - **Path**: `usage.apiKey` - **Type**: `object (13 properties)` Per-key breakdown (from groupBy apiKey). ##### [data](#response-usage-apikey-data) - **Path**: `usage.apiKey.data` - **Type**: `array of objects (6 properties)` - **Required**: true Rows for this breakdown. ##### [date](#response-usage-apikey-data-date) - **Path**: `usage.apiKey.data.date` - **Type**: `string` - **Format**: `date` Day bucket. ##### [model](#response-usage-apikey-data-model) - **Path**: `usage.apiKey.data.model` - **Type**: `string` Model AIR. ##### [modelName](#response-usage-apikey-data-modelname) - **Path**: `usage.apiKey.data.modelName` - **Type**: `string` Human-friendly model name. Falls back to the AIR when none exists. ##### [apiKey](#response-usage-apikey-data-apikey) - **Path**: `usage.apiKey.data.apiKey` - **Type**: `string` API key UUID. ##### [count](#response-usage-apikey-data-count) - **Path**: `usage.apiKey.data.count` - **Type**: `integer` Number of requests in this bucket. ##### [spend](#response-usage-apikey-data-spend) - **Path**: `usage.apiKey.data.spend` - **Type**: `float` Amount spent in this bucket. ##### [meta](#response-usage-apikey-meta) - **Path**: `usage.apiKey.meta` - **Type**: `object (5 properties)` - **Required**: true Roll-up totals for a breakdown. Which fields are present depends on the operation. ##### [totalRequests](#response-usage-apikey-meta-totalrequests) - **Path**: `usage.apiKey.meta.totalRequests` - **Type**: `integer` Total requests across the window. ##### [totalResults](#response-usage-apikey-meta-totalresults) - **Path**: `usage.apiKey.meta.totalResults` - **Type**: `integer` Total results produced across the window. ##### [totalSpend](#response-usage-apikey-meta-totalspend) - **Path**: `usage.apiKey.meta.totalSpend` - **Type**: `float` Total spend across the window. ##### [avgDailySpend](#response-usage-apikey-meta-avgdailyspend) - **Path**: `usage.apiKey.meta.avgDailySpend` - **Type**: `float` Average spend per day. An estimate that drifts between calls. ##### [projectedSpend](#response-usage-apikey-meta-projectedspend) - **Path**: `usage.apiKey.meta.projectedSpend` - **Type**: `float` Projected 30-day spend extrapolated from the window. An estimate that drifts between calls. ## getUsagePerformance How fast the account's models run. For a date window, each `model` row reports the **average, p90, and p99 inference time** in seconds, so you can track tail latency per model. Percentiles are only meaningful within a single model, so the data lives in the `model` breakdown and the `timeseries` breakdown **comes back empty**. ### Request TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const result = await client.accountManagement({ operation: 'getUsagePerformance', startDate: '2026-07-01', endDate: '2026-07-06', groupBy: [ 'date', 'model' ] }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: result = await client.account_management({ "operation": "getUsagePerformance", "startDate": "2026-07-01", "endDate": "2026-07-06", "groupBy": [ "date", "model" ] }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "accountManagement", "taskUUID": "ed936635-488f-48c2-8c4d-dbf117c8a7b1", "operation": "getUsagePerformance", "startDate": "2026-07-01", "endDate": "2026-07-06", "groupBy": [ "date", "model" ] } ]' ``` ```bash runware account getUsagePerformance ``` ```json { "taskType": "accountManagement", "taskUUID": "ed936635-488f-48c2-8c4d-dbf117c8a7b1", "operation": "getUsagePerformance", "startDate": "2026-07-01", "endDate": "2026-07-06", "groupBy": [ "date", "model" ] } ``` --- ### [taskType](#request-tasktype) - **Type**: `string` - **Required**: true - **Value**: `accountManagement` Identifier for the type of task being performed ### [taskUUID](#request-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier for tracking tasks and matching async responses. Must be unique per task. ### [operation](#request-operation) - **Type**: `string` - **Required**: true - **Value**: `getUsagePerformance` The specific account management operation to perform. ### [startDate](#request-startdate) - **Type**: `string` - **Format**: `date` Start of the usage window (inclusive). ### [endDate](#request-enddate) - **Type**: `string` - **Format**: `date` End of the usage window (inclusive). Must be on or after startDate, and the span must not exceed 30 days. ### [models](#request-models) - **Type**: `array of strings` Restrict usage to these model AIRs. Defaults to all models. ### [apiKeys](#request-apikeys) - **Type**: `array of strings` Restrict usage to these API key UUIDs. Defaults to all keys. ### [groupBy](#request-groupby) - **Type**: `array of strings` - **Default**: `date,model` Breakdowns to return. ### [timezone](#request-timezone) - **Type**: `string` - **Default**: `UTC` IANA timezone name used for day-bucketing. ### Response ```json { "data": [ { "taskType": "accountManagement", "taskUUID": "ed936635-488f-48c2-8c4d-dbf117c8a7b1", "operation": "getUsagePerformance", "startDate": "2026-07-01", "endDate": "2026-07-06", "usage": { "model": { "data": [{ "date": "2026-07-03", "model": "bytedance:video-upscaler@standard", "modelName": "Bytedance Video Upscaler", "avgInferenceTime": 134.8486, "p90InferenceTime": 226.245, "p99InferenceTime": 291.6665 }], "meta": { "totalRequests": 164, "totalResults": 164, "totalSpend": 1.6226, "avgDailySpend": 0.2704, "projectedSpend": 8.38, "avgInferenceTime": 134.8486, "p50InferenceTime": 144.49, "p90InferenceTime": 226.245, "p99InferenceTime": 291.6665 } } } } ] } ``` --- ### [taskType](#response-tasktype) - **Type**: `string` - **Required**: true - **Value**: `accountManagement` Identifier for the type of task this response belongs to. ### [taskUUID](#response-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier echoed from the original request, used to match async responses to their tasks. ### [operation](#response-operation) - **Type**: `string` - **Required**: true - **Value**: `getUsagePerformance` The account management operation that produced this response. ### [startDate](#response-startdate) - **Type**: `string` - **Format**: `date` Start of the returned window (inclusive). ### [endDate](#response-enddate) - **Type**: `string` - **Format**: `date` End of the returned window (inclusive). ### [usage](#response-usage) - **Path**: `usage.today` - **Type**: `object (61 properties)` Account usage statistics. #### [today](#response-usage-today) - **Path**: `usage.today` - **Type**: `object` Usage stats for today. #### [last7Days](#response-usage-last7days) - **Path**: `usage.last7Days` - **Type**: `object` Usage stats for the last 7 days. #### [last30Days](#response-usage-last30days) - **Path**: `usage.last30Days` - **Type**: `object` Usage stats for the last 30 days. #### [total](#response-usage-total) - **Path**: `usage.total` - **Type**: `object` Total lifetime usage stats. #### [timeseries](#response-usage-timeseries) - **Path**: `usage.timeseries` - **Type**: `object (18 properties)` Per-day breakdown (from groupBy date). ##### [data](#response-usage-timeseries-data) - **Path**: `usage.timeseries.data` - **Type**: `array of objects (7 properties)` - **Required**: true Rows for this breakdown. ##### [date](#response-usage-timeseries-data-date) - **Path**: `usage.timeseries.data.date` - **Type**: `string` - **Format**: `date` Day bucket. ##### [model](#response-usage-timeseries-data-model) - **Path**: `usage.timeseries.data.model` - **Type**: `string` Model AIR. ##### [modelName](#response-usage-timeseries-data-modelname) - **Path**: `usage.timeseries.data.modelName` - **Type**: `string` Human-friendly model name. Falls back to the AIR when none exists. ##### [apiKey](#response-usage-timeseries-data-apikey) - **Path**: `usage.timeseries.data.apiKey` - **Type**: `string` API key UUID. ##### [avgInferenceTime](#response-usage-timeseries-data-avginferencetime) - **Path**: `usage.timeseries.data.avgInferenceTime` - **Type**: `float | null` Average inference time in seconds, or null when there were no inferences. ##### [p90InferenceTime](#response-usage-timeseries-data-p90inferencetime) - **Path**: `usage.timeseries.data.p90InferenceTime` - **Type**: `float | null` 90th-percentile inference time in seconds, or null when there were no inferences. ##### [p99InferenceTime](#response-usage-timeseries-data-p99inferencetime) - **Path**: `usage.timeseries.data.p99InferenceTime` - **Type**: `float | null` 99th-percentile inference time in seconds, or null when there were no inferences. ##### [meta](#response-usage-timeseries-meta) - **Path**: `usage.timeseries.meta` - **Type**: `object (9 properties)` - **Required**: true Roll-up totals for a breakdown. Which fields are present depends on the operation. ##### [totalRequests](#response-usage-timeseries-meta-totalrequests) - **Path**: `usage.timeseries.meta.totalRequests` - **Type**: `integer` Total requests across the window. ##### [totalResults](#response-usage-timeseries-meta-totalresults) - **Path**: `usage.timeseries.meta.totalResults` - **Type**: `integer` Total results produced across the window. ##### [totalSpend](#response-usage-timeseries-meta-totalspend) - **Path**: `usage.timeseries.meta.totalSpend` - **Type**: `float` Total spend across the window. ##### [avgDailySpend](#response-usage-timeseries-meta-avgdailyspend) - **Path**: `usage.timeseries.meta.avgDailySpend` - **Type**: `float` Average spend per day. An estimate that drifts between calls. ##### [projectedSpend](#response-usage-timeseries-meta-projectedspend) - **Path**: `usage.timeseries.meta.projectedSpend` - **Type**: `float` Projected 30-day spend extrapolated from the window. An estimate that drifts between calls. ##### [avgInferenceTime](#response-usage-timeseries-meta-avginferencetime) - **Path**: `usage.timeseries.meta.avgInferenceTime` - **Type**: `float` Average inference time in seconds. ##### [p50InferenceTime](#response-usage-timeseries-meta-p50inferencetime) - **Path**: `usage.timeseries.meta.p50InferenceTime` - **Type**: `float` Median inference time in seconds. ##### [p90InferenceTime](#response-usage-timeseries-meta-p90inferencetime) - **Path**: `usage.timeseries.meta.p90InferenceTime` - **Type**: `float` 90th-percentile inference time in seconds. ##### [p99InferenceTime](#response-usage-timeseries-meta-p99inferencetime) - **Path**: `usage.timeseries.meta.p99InferenceTime` - **Type**: `float` 99th-percentile inference time in seconds. #### [model](#response-usage-model) - **Path**: `usage.model` - **Type**: `object (18 properties)` Per-model breakdown (from groupBy model). ##### [data](#response-usage-model-data) - **Path**: `usage.model.data` - **Type**: `array of objects (7 properties)` - **Required**: true Rows for this breakdown. ##### [date](#response-usage-model-data-date) - **Path**: `usage.model.data.date` - **Type**: `string` - **Format**: `date` Day bucket. ##### [model](#response-usage-model-data-model) - **Path**: `usage.model.data.model` - **Type**: `string` Model AIR. ##### [modelName](#response-usage-model-data-modelname) - **Path**: `usage.model.data.modelName` - **Type**: `string` Human-friendly model name. Falls back to the AIR when none exists. ##### [apiKey](#response-usage-model-data-apikey) - **Path**: `usage.model.data.apiKey` - **Type**: `string` API key UUID. ##### [avgInferenceTime](#response-usage-model-data-avginferencetime) - **Path**: `usage.model.data.avgInferenceTime` - **Type**: `float | null` Average inference time in seconds, or null when there were no inferences. ##### [p90InferenceTime](#response-usage-model-data-p90inferencetime) - **Path**: `usage.model.data.p90InferenceTime` - **Type**: `float | null` 90th-percentile inference time in seconds, or null when there were no inferences. ##### [p99InferenceTime](#response-usage-model-data-p99inferencetime) - **Path**: `usage.model.data.p99InferenceTime` - **Type**: `float | null` 99th-percentile inference time in seconds, or null when there were no inferences. ##### [meta](#response-usage-model-meta) - **Path**: `usage.model.meta` - **Type**: `object (9 properties)` - **Required**: true Roll-up totals for a breakdown. Which fields are present depends on the operation. ##### [totalRequests](#response-usage-model-meta-totalrequests) - **Path**: `usage.model.meta.totalRequests` - **Type**: `integer` Total requests across the window. ##### [totalResults](#response-usage-model-meta-totalresults) - **Path**: `usage.model.meta.totalResults` - **Type**: `integer` Total results produced across the window. ##### [totalSpend](#response-usage-model-meta-totalspend) - **Path**: `usage.model.meta.totalSpend` - **Type**: `float` Total spend across the window. ##### [avgDailySpend](#response-usage-model-meta-avgdailyspend) - **Path**: `usage.model.meta.avgDailySpend` - **Type**: `float` Average spend per day. An estimate that drifts between calls. ##### [projectedSpend](#response-usage-model-meta-projectedspend) - **Path**: `usage.model.meta.projectedSpend` - **Type**: `float` Projected 30-day spend extrapolated from the window. An estimate that drifts between calls. ##### [avgInferenceTime](#response-usage-model-meta-avginferencetime) - **Path**: `usage.model.meta.avgInferenceTime` - **Type**: `float` Average inference time in seconds. ##### [p50InferenceTime](#response-usage-model-meta-p50inferencetime) - **Path**: `usage.model.meta.p50InferenceTime` - **Type**: `float` Median inference time in seconds. ##### [p90InferenceTime](#response-usage-model-meta-p90inferencetime) - **Path**: `usage.model.meta.p90InferenceTime` - **Type**: `float` 90th-percentile inference time in seconds. ##### [p99InferenceTime](#response-usage-model-meta-p99inferencetime) - **Path**: `usage.model.meta.p99InferenceTime` - **Type**: `float` 99th-percentile inference time in seconds. #### [apiKey](#response-usage-apikey) - **Path**: `usage.apiKey` - **Type**: `object (18 properties)` Per-key breakdown (from groupBy apiKey). ##### [data](#response-usage-apikey-data) - **Path**: `usage.apiKey.data` - **Type**: `array of objects (7 properties)` - **Required**: true Rows for this breakdown. ##### [date](#response-usage-apikey-data-date) - **Path**: `usage.apiKey.data.date` - **Type**: `string` - **Format**: `date` Day bucket. ##### [model](#response-usage-apikey-data-model) - **Path**: `usage.apiKey.data.model` - **Type**: `string` Model AIR. ##### [modelName](#response-usage-apikey-data-modelname) - **Path**: `usage.apiKey.data.modelName` - **Type**: `string` Human-friendly model name. Falls back to the AIR when none exists. ##### [apiKey](#response-usage-apikey-data-apikey) - **Path**: `usage.apiKey.data.apiKey` - **Type**: `string` API key UUID. ##### [avgInferenceTime](#response-usage-apikey-data-avginferencetime) - **Path**: `usage.apiKey.data.avgInferenceTime` - **Type**: `float | null` Average inference time in seconds, or null when there were no inferences. ##### [p90InferenceTime](#response-usage-apikey-data-p90inferencetime) - **Path**: `usage.apiKey.data.p90InferenceTime` - **Type**: `float | null` 90th-percentile inference time in seconds, or null when there were no inferences. ##### [p99InferenceTime](#response-usage-apikey-data-p99inferencetime) - **Path**: `usage.apiKey.data.p99InferenceTime` - **Type**: `float | null` 99th-percentile inference time in seconds, or null when there were no inferences. ##### [meta](#response-usage-apikey-meta) - **Path**: `usage.apiKey.meta` - **Type**: `object (9 properties)` - **Required**: true Roll-up totals for a breakdown. Which fields are present depends on the operation. ##### [totalRequests](#response-usage-apikey-meta-totalrequests) - **Path**: `usage.apiKey.meta.totalRequests` - **Type**: `integer` Total requests across the window. ##### [totalResults](#response-usage-apikey-meta-totalresults) - **Path**: `usage.apiKey.meta.totalResults` - **Type**: `integer` Total results produced across the window. ##### [totalSpend](#response-usage-apikey-meta-totalspend) - **Path**: `usage.apiKey.meta.totalSpend` - **Type**: `float` Total spend across the window. ##### [avgDailySpend](#response-usage-apikey-meta-avgdailyspend) - **Path**: `usage.apiKey.meta.avgDailySpend` - **Type**: `float` Average spend per day. An estimate that drifts between calls. ##### [projectedSpend](#response-usage-apikey-meta-projectedspend) - **Path**: `usage.apiKey.meta.projectedSpend` - **Type**: `float` Projected 30-day spend extrapolated from the window. An estimate that drifts between calls. ##### [avgInferenceTime](#response-usage-apikey-meta-avginferencetime) - **Path**: `usage.apiKey.meta.avgInferenceTime` - **Type**: `float` Average inference time in seconds. ##### [p50InferenceTime](#response-usage-apikey-meta-p50inferencetime) - **Path**: `usage.apiKey.meta.p50InferenceTime` - **Type**: `float` Median inference time in seconds. ##### [p90InferenceTime](#response-usage-apikey-meta-p90inferencetime) - **Path**: `usage.apiKey.meta.p90InferenceTime` - **Type**: `float` 90th-percentile inference time in seconds. ##### [p99InferenceTime](#response-usage-apikey-meta-p99inferencetime) - **Path**: `usage.apiKey.meta.p99InferenceTime` - **Type**: `float` 99th-percentile inference time in seconds. ## getUsageErrors Reliability, sliced the same way `getUsageActivity` slices spend. Each row splits failures into **client errors** (`4xx`) and **server errors** (`5xx`), and the `meta` roll-up carries the window's `totalErrors` and an `errorRate` as a percentage. Group by `model` or `apiKey` to see **which one is failing**. ### Request TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const result = await client.accountManagement({ operation: 'getUsageErrors', startDate: '2026-07-01', endDate: '2026-07-06', groupBy: [ 'date', 'model' ] }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: result = await client.account_management({ "operation": "getUsageErrors", "startDate": "2026-07-01", "endDate": "2026-07-06", "groupBy": [ "date", "model" ] }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "accountManagement", "taskUUID": "cb460b4c-0d63-4fe2-9694-2115b2ce2161", "operation": "getUsageErrors", "startDate": "2026-07-01", "endDate": "2026-07-06", "groupBy": [ "date", "model" ] } ]' ``` ```bash runware account getUsageErrors ``` ```json { "taskType": "accountManagement", "taskUUID": "cb460b4c-0d63-4fe2-9694-2115b2ce2161", "operation": "getUsageErrors", "startDate": "2026-07-01", "endDate": "2026-07-06", "groupBy": [ "date", "model" ] } ``` --- ### [taskType](#request-tasktype) - **Type**: `string` - **Required**: true - **Value**: `accountManagement` Identifier for the type of task being performed ### [taskUUID](#request-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier for tracking tasks and matching async responses. Must be unique per task. ### [operation](#request-operation) - **Type**: `string` - **Required**: true - **Value**: `getUsageErrors` The specific account management operation to perform. ### [startDate](#request-startdate) - **Type**: `string` - **Format**: `date` Start of the usage window (inclusive). ### [endDate](#request-enddate) - **Type**: `string` - **Format**: `date` End of the usage window (inclusive). Must be on or after startDate, and the span must not exceed 30 days. ### [models](#request-models) - **Type**: `array of strings` Restrict usage to these model AIRs. Defaults to all models. ### [apiKeys](#request-apikeys) - **Type**: `array of strings` Restrict usage to these API key UUIDs. Defaults to all keys. ### [groupBy](#request-groupby) - **Type**: `array of strings` - **Default**: `date,model` Breakdowns to return. ### [timezone](#request-timezone) - **Type**: `string` - **Default**: `UTC` IANA timezone name used for day-bucketing. ### Response ```json { "data": [ { "taskType": "accountManagement", "taskUUID": "cb460b4c-0d63-4fe2-9694-2115b2ce2161", "operation": "getUsageErrors", "startDate": "2026-07-01", "endDate": "2026-07-06", "usage": { "timeseries": { "data": [{ "date": "2026-07-03", "clientErrors": 126, "serverErrors": 0 }], "meta": { "totalErrors": 128, "errorRate": 78.05 } }, "model": { "data": [{ "date": "2026-07-03", "model": "bytedance:video-upscaler@standard", "modelName": "Bytedance Video Upscaler", "clientErrors": 126, "serverErrors": 0 }], "meta": { "totalErrors": 128, "errorRate": 78.05 } } } } ] } ``` --- ### [taskType](#response-tasktype) - **Type**: `string` - **Required**: true - **Value**: `accountManagement` Identifier for the type of task this response belongs to. ### [taskUUID](#response-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier echoed from the original request, used to match async responses to their tasks. ### [operation](#response-operation) - **Type**: `string` - **Required**: true - **Value**: `getUsageErrors` The account management operation that produced this response. ### [startDate](#response-startdate) - **Type**: `string` - **Format**: `date` Start of the returned window (inclusive). ### [endDate](#response-enddate) - **Type**: `string` - **Format**: `date` End of the returned window (inclusive). ### [usage](#response-usage) - **Path**: `usage.today` - **Type**: `object (37 properties)` Account usage statistics. #### [today](#response-usage-today) - **Path**: `usage.today` - **Type**: `object` Usage stats for today. #### [last7Days](#response-usage-last7days) - **Path**: `usage.last7Days` - **Type**: `object` Usage stats for the last 7 days. #### [last30Days](#response-usage-last30days) - **Path**: `usage.last30Days` - **Type**: `object` Usage stats for the last 30 days. #### [total](#response-usage-total) - **Path**: `usage.total` - **Type**: `object` Total lifetime usage stats. #### [timeseries](#response-usage-timeseries) - **Path**: `usage.timeseries` - **Type**: `object (10 properties)` Per-day breakdown (from groupBy date). ##### [data](#response-usage-timeseries-data) - **Path**: `usage.timeseries.data` - **Type**: `array of objects (6 properties)` - **Required**: true Rows for this breakdown. ##### [date](#response-usage-timeseries-data-date) - **Path**: `usage.timeseries.data.date` - **Type**: `string` - **Format**: `date` Day bucket. ##### [model](#response-usage-timeseries-data-model) - **Path**: `usage.timeseries.data.model` - **Type**: `string` Model AIR. ##### [modelName](#response-usage-timeseries-data-modelname) - **Path**: `usage.timeseries.data.modelName` - **Type**: `string` Human-friendly model name. Falls back to the AIR when none exists. ##### [apiKey](#response-usage-timeseries-data-apikey) - **Path**: `usage.timeseries.data.apiKey` - **Type**: `string` API key UUID. ##### [clientErrors](#response-usage-timeseries-data-clienterrors) - **Path**: `usage.timeseries.data.clientErrors` - **Type**: `integer` Number of 4xx (client) errors in this bucket. ##### [serverErrors](#response-usage-timeseries-data-servererrors) - **Path**: `usage.timeseries.data.serverErrors` - **Type**: `integer` Number of 5xx (server) errors in this bucket. ##### [meta](#response-usage-timeseries-meta) - **Path**: `usage.timeseries.meta` - **Type**: `object (2 properties)` - **Required**: true Roll-up totals for a breakdown. Which fields are present depends on the operation. ##### [totalErrors](#response-usage-timeseries-meta-totalerrors) - **Path**: `usage.timeseries.meta.totalErrors` - **Type**: `integer` Total errors across the window. ##### [errorRate](#response-usage-timeseries-meta-errorrate) - **Path**: `usage.timeseries.meta.errorRate` - **Type**: `float` Error rate across the window, as a percentage. #### [model](#response-usage-model) - **Path**: `usage.model` - **Type**: `object (10 properties)` Per-model breakdown (from groupBy model). ##### [data](#response-usage-model-data) - **Path**: `usage.model.data` - **Type**: `array of objects (6 properties)` - **Required**: true Rows for this breakdown. ##### [date](#response-usage-model-data-date) - **Path**: `usage.model.data.date` - **Type**: `string` - **Format**: `date` Day bucket. ##### [model](#response-usage-model-data-model) - **Path**: `usage.model.data.model` - **Type**: `string` Model AIR. ##### [modelName](#response-usage-model-data-modelname) - **Path**: `usage.model.data.modelName` - **Type**: `string` Human-friendly model name. Falls back to the AIR when none exists. ##### [apiKey](#response-usage-model-data-apikey) - **Path**: `usage.model.data.apiKey` - **Type**: `string` API key UUID. ##### [clientErrors](#response-usage-model-data-clienterrors) - **Path**: `usage.model.data.clientErrors` - **Type**: `integer` Number of 4xx (client) errors in this bucket. ##### [serverErrors](#response-usage-model-data-servererrors) - **Path**: `usage.model.data.serverErrors` - **Type**: `integer` Number of 5xx (server) errors in this bucket. ##### [meta](#response-usage-model-meta) - **Path**: `usage.model.meta` - **Type**: `object (2 properties)` - **Required**: true Roll-up totals for a breakdown. Which fields are present depends on the operation. ##### [totalErrors](#response-usage-model-meta-totalerrors) - **Path**: `usage.model.meta.totalErrors` - **Type**: `integer` Total errors across the window. ##### [errorRate](#response-usage-model-meta-errorrate) - **Path**: `usage.model.meta.errorRate` - **Type**: `float` Error rate across the window, as a percentage. #### [apiKey](#response-usage-apikey) - **Path**: `usage.apiKey` - **Type**: `object (10 properties)` Per-key breakdown (from groupBy apiKey). ##### [data](#response-usage-apikey-data) - **Path**: `usage.apiKey.data` - **Type**: `array of objects (6 properties)` - **Required**: true Rows for this breakdown. ##### [date](#response-usage-apikey-data-date) - **Path**: `usage.apiKey.data.date` - **Type**: `string` - **Format**: `date` Day bucket. ##### [model](#response-usage-apikey-data-model) - **Path**: `usage.apiKey.data.model` - **Type**: `string` Model AIR. ##### [modelName](#response-usage-apikey-data-modelname) - **Path**: `usage.apiKey.data.modelName` - **Type**: `string` Human-friendly model name. Falls back to the AIR when none exists. ##### [apiKey](#response-usage-apikey-data-apikey) - **Path**: `usage.apiKey.data.apiKey` - **Type**: `string` API key UUID. ##### [clientErrors](#response-usage-apikey-data-clienterrors) - **Path**: `usage.apiKey.data.clientErrors` - **Type**: `integer` Number of 4xx (client) errors in this bucket. ##### [serverErrors](#response-usage-apikey-data-servererrors) - **Path**: `usage.apiKey.data.serverErrors` - **Type**: `integer` Number of 5xx (server) errors in this bucket. ##### [meta](#response-usage-apikey-meta) - **Path**: `usage.apiKey.meta` - **Type**: `object (2 properties)` - **Required**: true Roll-up totals for a breakdown. Which fields are present depends on the operation. ##### [totalErrors](#response-usage-apikey-meta-totalerrors) - **Path**: `usage.apiKey.meta.totalErrors` - **Type**: `integer` Total errors across the window. ##### [errorRate](#response-usage-apikey-meta-errorrate) - **Path**: `usage.apiKey.meta.errorRate` - **Type**: `float` Error rate across the window, as a percentage. --- ## Media Storage **URL:** https://runware.ai/docs/platform/media-storage **Description:** Upload media to your Runware storage or delete it by UUID, for any image, video, audio, or 3D model input reused across the API. ## Introduction The `mediaStorage` task **stores media in your Runware account and returns a reusable UUID**, so you can hand an image, video, audio, or 3D model to any task that accepts media inputs without re-uploading it each time. It also **deletes stored media** when you no longer need it. Every call is a single `operation`, either an `upload` or a `delete`, sent as one task object in the usual request array. ## Upload An `upload` takes the media as a **publicly accessible URL, a data URI, or a base64 string**, stores it, and returns a `mediaUUID` and `mediaURL` you can reuse as input anywhere in the API. ### Request TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const result = await client.mediaStorage({ operation: 'upload', media: 'data:image/png;base64,iVBORw0KGgo...' }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: result = await client.media_storage({ "operation": "upload", "media": "data:image/png;base64,iVBORw0KGgo..." }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "mediaStorage", "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada", "operation": "upload", "media": "data:image/png;base64,iVBORw0KGgo..." } ]' ``` ```bash runware media upload "data:image/png;base64,iVBORw0KGgo..." ``` ```json { "taskType": "mediaStorage", "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada", "operation": "upload", "media": "data:image/png;base64,iVBORw0KGgo..." } ``` --- ### [taskType](#request-tasktype) - **Type**: `string` - **Required**: true - **Value**: `mediaStorage` Identifier for the type of task being performed ### [taskUUID](#request-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier for tracking tasks and matching async responses. Must be unique per task. ### [operation](#request-operation) - **Type**: `string` - **Required**: true - **Value**: `upload` Store new media and return its UUID and URL. ### [media](#request-media) - **Type**: `string` - **Required**: true For upload, the media as a publicly accessible URL, data URI, or base64 string. For delete, the mediaUUID of the media to remove. ### Response The response returns the stored media's **UUID and URL**. ```json { "data": [ { "taskType": "mediaStorage", "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada", "operation": "upload", "mediaUUID": "989ba605-1449-4e1e-b462-cd83ec9c1a67", "mediaURL": "https://mm.runware.ai/media-storage/ws/2/id/989ba605-1449-4e1e-b462-cd83ec9c1a67.png" } ] } ``` --- ### [taskType](#response-tasktype) - **Type**: `string` - **Required**: true - **Value**: `mediaStorage` Identifier for the type of task this response belongs to. ### [taskUUID](#response-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier echoed from the original request, used to match async responses to their tasks. ### [operation](#response-operation) - **Type**: `string` - **Required**: true - **Value**: `upload` The media storage operation that produced this response. ### [mediaUUID](#response-mediauuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID of the stored media. ### [mediaURL](#response-mediaurl) - **Type**: `string` - **Required**: true - **Format**: `uri` URL where the stored media is accessible. ## Delete A `delete` removes media you stored earlier. Pass its `mediaUUID` as the `media` value. ### Request TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const result = await client.mediaStorage({ operation: 'delete', media: '989ba605-1449-4e1e-b462-cd83ec9c1a67' }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: result = await client.media_storage({ "operation": "delete", "media": "989ba605-1449-4e1e-b462-cd83ec9c1a67" }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "mediaStorage", "taskUUID": "b7e0a3f2-3c1a-4d9e-8f2b-1a2c3d4e5f60", "operation": "delete", "media": "989ba605-1449-4e1e-b462-cd83ec9c1a67" } ]' ``` ```bash runware media delete 989ba605-1449-4e1e-b462-cd83ec9c1a67 ``` ```json { "taskType": "mediaStorage", "taskUUID": "b7e0a3f2-3c1a-4d9e-8f2b-1a2c3d4e5f60", "operation": "delete", "media": "989ba605-1449-4e1e-b462-cd83ec9c1a67" } ``` --- ### [taskType](#request-tasktype) - **Type**: `string` - **Required**: true - **Value**: `mediaStorage` Identifier for the type of task being performed ### [taskUUID](#request-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier for tracking tasks and matching async responses. Must be unique per task. ### [operation](#request-operation) - **Type**: `string` - **Required**: true - **Value**: `delete` Permanently remove previously stored media by its UUID. ### [media](#request-media) - **Type**: `string` - **Required**: true For upload, the media as a publicly accessible URL, data URI, or base64 string. For delete, the mediaUUID of the media to remove. ### Response The response returns the **deleted media's UUID**, so you can confirm what was removed. ```json { "data": [ { "taskType": "mediaStorage", "taskUUID": "b7e0a3f2-3c1a-4d9e-8f2b-1a2c3d4e5f60", "operation": "delete", "mediaUUID": "989ba605-1449-4e1e-b462-cd83ec9c1a67" } ] } ``` --- ### [taskType](#response-tasktype) - **Type**: `string` - **Required**: true - **Value**: `mediaStorage` Identifier for the type of task this response belongs to. ### [taskUUID](#response-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier echoed from the original request, used to match async responses to their tasks. ### [operation](#response-operation) - **Type**: `string` - **Required**: true - **Value**: `delete` The media storage operation that produced this response. ### [mediaUUID](#response-mediauuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID of the stored media. --- ## Task Details **URL:** https://runware.ai/docs/platform/task-details **Description:** Retrieve the original request and response for any previously executed task. Useful for debugging, recovering past results, and auditing API interactions. ## Introduction The `getTaskDetails` task retrieves the **complete request and response objects** for any previously executed task. Pass the `taskUUID` of a past task to recover its original request payload and the API response it produced. This is useful for **debugging** failed requests by inspecting the exact payload that was sent, **recovering** responses that your application didn't store, and **auditing** historical API interactions. The returned `request` and `response` objects are untyped because their structure depends on the original task type. They are the whole original objects, exactly as they were sent and received. > [!NOTE] > The `taskUUID` in this request refers to the UUID of the task you want to inspect. The response echoes it back along with the original request and response objects. ## Request The Runware API always accepts an array of objects as input, where each object represents a **specific task to be performed**. The structure of the object varies depending on the type of the task. For this section, we will focus on the parameters related to the **task details task**. The following JSON snippet shows the basic structure of a request object. TypeScriptPythoncURLCLIJSON ```typescript import { createClient } from '@runware/sdk' const client = await createClient({ apiKey: process.env.RUNWARE_API_KEY }) await client.connect() const result = await client.getTaskDetails({ taskUUID: 'a770f077-f413-47de-9dac-be0b26a35da6' }) ``` ```python import asyncio import os from runware import Runware async def main(): async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client: result = await client.get_task_details({ "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6" }) asyncio.run(main()) ``` ```bash curl https://api.runware.ai/v1 \ -H "Authorization: Bearer $RUNWARE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "taskType": "getTaskDetails", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6" } ]' ``` ```bash runware result a770f077-f413-47de-9dac-be0b26a35da6 ``` ```json { "taskType": "getTaskDetails", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6" } ``` --- ### [taskType](#request-tasktype) - **Type**: `string` - **Required**: true - **Value**: `getTaskDetails` Identifier for the type of task being performed ### [taskUUID](#request-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier for tracking tasks and matching async responses. Must be unique per task. ## Response The response always includes the original `request` array and a `response` object. The `request` is the original array of task objects you sent to the API. The `response` is the full API response envelope, containing either a `data` array (if the task completed successfully) or an `errors` array (if the task failed). Both objects are untyped because their structure depends on the original task type. ### Successful task When the original task completed successfully, the `response` object contains a `data` array with the results. ```json { "data": [ { "taskType": "getTaskDetails", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "request": [ { "taskType": "imageInference", "model": "runware:101@1", "positivePrompt": "a cat", "width": 1024, "height": 1024, "numberResults": 1, "includeCost": true, "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6" } ], "response": { "data": [ { "taskType": "imageInference", "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", "imageUUID": "77da2d99-a6d3-44d9-b8c0-ae9fb06b6200", "imageURL": "https://im.runware.ai/image/os/a14d18/ws/2/ii/77da2d99-a6d3-44d9-b8c0-ae9fb06b6200.jpg", "cost": 0.0013 } ] } } ] } ``` ### Failed task When the original task failed, the `response` object contains an `errors` array with the error details from the original failure. ```json { "data": [ { "taskType": "getTaskDetails", "taskUUID": "b880f077-e514-58ef-0ebd-ce1c37b46eb7", "request": [ { "taskType": "imageInference", "model": "runware:400@1", "positivePrompt": "a landscape", "width": 1024, "height": 1024, "numberResults": 1, "inputs": { "referenceImages": [ "https://im.runware.ai/image/ws/5/bucket/media-storage/ii/4e7a91c3-28d5-4f6b-a0e2-7c3d8f1b5a94", "https://im.runware.ai/image/ws/5/bucket/media-storage/ii/d2f08b47-9c31-4a85-be6f-5e9a12c7d403", "https://im.runware.ai/image/ws/5/bucket/media-storage/ii/8b5c3e19-f7a2-4d60-91c4-a6e8d0f23b71", "https://im.runware.ai/image/ws/5/bucket/media-storage/ii/c1d94f6a-3b82-47e5-a5c0-9f2e71d08b36", "https://im.runware.ai/image/ws/5/bucket/media-storage/ii/7a0e5d93-6f14-4c28-b9d7-e3c1a84f2650" ] }, "taskUUID": "b880f077-e514-58ef-0ebd-ce1c37b46eb7" } ], "response": { "errors": [ { "code": "invalidReferenceImagesCount", "message": "Invalid number of elements for 'referenceImages' parameter. Reference images must contain between 0 and 4.", "parameter": "inputs.referenceImages", "type": "string[]", "documentation": "https://runware.ai/docs", "taskUUID": "b880f077-e514-58ef-0ebd-ce1c37b46eb7" } ] } } ] } ``` ### Task not found If the provided `taskUUID` does not exist or belongs to a different organization, the API returns a standard error response. ```json { "data": [], "errors": [ { "code": "taskNotFound", "message": "No task found for the provided 'taskUUID'. The taskUUID may not exist or may belong to a different organization.", "parameter": "taskUUID", "type": "string", "documentation": "https://runware.ai/docs", "taskUUID": "abcdc144-7364-4a7c-b6e4-fadb3dbf2f67" } ] } ``` --- ### [taskType](#response-tasktype) - **Type**: `string` - **Required**: true - **Value**: `getTaskDetails` Identifier for the type of task this response belongs to. ### [taskUUID](#response-taskuuid) - **Type**: `string` - **Required**: true - **Format**: `UUID v4` UUID v4 identifier echoed from the original request, used to match async responses to their tasks. ### [request](#response-request) - **Type**: `array of objects` - **Required**: true The original request array sent for this task. The structure of each object depends on the task type. ### [response](#response-response) - **Type**: `object` - **Required**: true The original API response for this task. Contains a `data` array when the task completed successfully, or an `errors` array when the task failed. --- ## ComfyUI integration **URL:** https://runware.ai/docs/platform/comfyui-legacy **Description:** Get started with Runware's official ComfyUI integration. Use cloud-based image generation capabilities with the flexibility of ComfyUI's node-based workflow. > [!WARNING] > **Legacy integration.** This documents the original hand-built ComfyUI nodes and still works, but new projects should use the [ComfyUI integration](https://runware.ai/docs/platform/comfyui). It exposes every Runware model as its own node, generated from the live schemas, and gets all new features going forward. ## Introduction Runware's ComfyUI integration brings our powerful cloud infrastructure directly into ComfyUI's node-based workflow environment. This integration **eliminates the need for specific hardware** while maintaining all the flexibility and control that makes ComfyUI special. ![ComfyUI interface showing Runware Image Inference with model settings, prompt, and preview of a woman with vibrant hair and dark makeup](https://runware.ai/docs/assets/image-screenshot.D-94JGV5_Z17zqK6.jpg) ComfyUI is a powerful tool that offers a node-based interface, allowing users to visually construct complex image generation workflows by connecting different components. However, running ComfyUI locally typically requires significant computational resources, particularly a high-end GPU with substantial VRAM. Our integration solves this challenge by handling **all resource-intensive operations in the cloud**. The Runware integration provides a comprehensive set of nodes that connect to different Runware API features, including image generation, editing, enhancement, and more. These nodes enable you to create advanced image generation workflows without worrying about hardware limitations or performance issues, while maintaining the ability to run multiple complex workflows simultaneously and with consistent performance. ## Installation ### Step 1: Install ComfyUI First, ensure you have ComfyUI installed. You can follow the [pre-built package guide](https://docs.comfy.org/get_started/pre_package) or the [manual installation guide](https://docs.comfy.org/get_started/manual_install). ### Step 2: Install ComfyUI-Runware Make sure your system have Python 3.10 or higher installed. You can install the Runware nodes for ComfyUI using two different methods: #### Option 1: Using ComfyUI Manager (Recommended) If you already have the `ComfyUI-Manager` custom node installed, this is the simplest method: 1. Open ComfyUI Manager. 2. Click on "Custom Nodes Manager". 3. Search for "Runware" or "Runware.ai". 4. Click install or update. 5. Restart ComfyUI to apply the changes. If you don't have ComfyUI Manager installed yet or are using the beta ComfyUI desktop version, follow the instructions on the [ComfyUI-Manager GitHub Repository](https://github.com/ltdrdata/ComfyUI-Manager?tab=readme-ov-file#installation). #### Option 2: Manual installation If you prefer manual installation or don't have ComfyUI Manager, follow these steps: ```bash cd custom_nodes git clone https://github.com/Runware/ComfyUI-Runware.git cd ComfyUI-Runware pip install -r requirements.txt ``` To start ComfyUI with our nodes: ```bash python main.py --front-end-version Comfy-Org/ComfyUI_frontend@latest ``` If you're running without a GPU, add the `--cpu` flag: ```bash python main.py --cpu --front-end-version Comfy-Org/ComfyUI_frontend@latest ``` ### Step 3: Explore workflows Inside the `ComfyUI-Runware` custom node folder, you'll find a `workflows` folder with pre-made workflows to get you started. These examples demonstrate different features and capabilities of our nodes. You can load these workflows through the ComfyUI interface to explore and modify them to create your custom workflows. They are also available in our [GitHub repository](https://github.com/Runware/ComfyUI-Runware/tree/main/workflows). ## API key setup When you first run any workflow using Runware nodes, you'll be prompted to enter your API key. This one-time setup connects your workflow to our cloud infrastructure. > [!NOTE] > You can also manage your API key later through the Runware API Manager node, which allows you to update or change your credentials at any time. ## Available nodes Our integration provides a comprehensive set of nodes for ComfyUI that connect to different Runware API features: ### Configuration nodes - **Runware API Manager**: Set or change your API keys and adjust the max connection timeout directly in ComfyUI, no need to edit config files manually. ### Image generation nodes - **Runware Image Inference**: Perform advanced tasks like text-to-image, inpainting, outpainting, and more. - **Runware PhotoMaker v2**: Create consistent character identities and styles with our photomaker pipeline. - **Runware Model**: Choose specific models to connect with image inference. - **Runware Refiner**: Polish and enhance generated images with advanced tools. - **Runware VAE**: Search and connect a VAE to Image inference. ### Enhancement nodes - **Runware LoRA**: Search and select LoRAs to enhance your workflow. - **Runware LoRA Combine**: Combine up to three LoRAs together for complex effects. - **Runware ControlNet Preprocessor**: Prepare images before using them as guide images in ControlNet. - **Runware ControlNet**: Guide your image generation with ControlNet and guide images. - **Runware ControlNet Combine**: Create complex workflows using multiple ControlNets. - **Runware Embedding**: Search and connect embeddings to image inference. - **Runware Embedding Combine**: Combine multiple embeddings together. - **Runware IP-Adapter**: Use reference images to guide the style and content of generated images. - **Runware IP-Adapter Combine**: Merge multiple IP-Adapter inputs for sophisticated image conditioning. ### Image tools nodes - **Runware Image Upscale**: Enhance image resolution up to 4x. - **Runware Background Removal**: Effortlessly separate subjects from backgrounds. - **Runware Image Masking**: Automatically detect and mask specific elements like faces, hands, and more. - **Runware Image Caption**: Generate descriptive text from images for further workflow integration. ## Support & community This is the official Runware integration, maintained by Runware Inc. We're here to help you every step of the way! Join our [Discord community](https://discord.gg/aJ4UzvBqNU) to share your creations, get help with node configurations, connect with other creators, receive updates about new features... and much more! ### Troubleshooting If you encounter issues while using the Runware nodes in ComfyUI, here's a checklist of things to verify: - Your API key is correctly entered in the API Manager node. - Your internet connection is stable and working. - You've installed the latest version of the ComfyUI-Runware nodes. - ComfyUI has been restarted after installation. - All Python dependencies were successfully installed. - All nodes are properly connected in your workflow. - Required input parameters have values provided. - Model IDs and other references are correct. - Image dimensions are within supported ranges. - API timeout settings are appropriate for your connection speed. - You have sufficient credits in your Runware account. - The ComfyUI console shows no Python errors. When troubleshooting, always check the ComfyUI console for specific error messages that can help identify the exact issue. The console will display a task UUID for any API requests. You can submit this UUID to our support team on Discord or via support page, and we will help you resolve the issue. You can also submit an issue on our [GitHub repository](https://github.com/Runware/ComfyUI-Runware). --- ## JavaScript library **URL:** https://runware.ai/docs/platform/javascript-legacy **Description:** Integrate Runware's API with our JavaScript library. Get started with code examples and comprehensive usage instructions. > [!WARNING] > **Legacy SDK.** This is the previous JavaScript SDK and still works, but new projects should use the [TypeScript SDK](https://runware.ai/docs/platform/typescript). It covers the full inference and utility surface, supports REST or WebSocket transports, and gets all new features going forward. ## Introduction The Runware JavaScript SDK provides a **high-performance WebSocket-based interface** built for modern web applications requiring AI-powered media processing. Unlike traditional REST APIs that require establishing new connections for each request, the SDK maintains **persistent connections** that improve performance for applications requiring multiple operations or real-time feedback. The SDK handles all the complexity of **connection management, authentication, and error recovery** while exposing Runware's complete feature set through an intuitive Promise-based API. **Asynchronous operations** for tasks that require longer processing times, such as video generation, are handled automatically. You don't need to manage polling or status checking manually, all the complexity is managed behind the scenes. ## Key SDK benefits ### Performance advantages **Reduced latency** is achieved by eliminating the connection establishment overhead that occurs with each HTTP request. In applications performing multiple operations, this can significantly reduce total processing time compared to REST-based approaches. **Progressive result delivery** allows your application to display completed results immediately as they finish, rather than waiting for entire batches. This creates a more responsive user experience, particularly valuable when generating multiple variations or conducting iterative workflows. ### Reliability features **Automatic resilience** ensures your application remains functional even when network conditions change. The SDK detects connection issues and re-establishes connectivity transparently, queuing operations during brief disconnections and resuming when connectivity returns. **Concurrent operation efficiency** leverages the persistent connection to handle multiple simultaneous requests without connection overhead. This makes the SDK particularly effective for applications that need to perform different types of operations simultaneously. ### When to choose the JavaScript SDK The JavaScript SDK is optimal for applications that prioritize **performance and real-time feedback**. Consider this SDK when your application needs to perform multiple operations frequently, provide immediate user feedback during processing, or integrate AI capabilities as a core interactive feature rather than an occasional utility. Source: [https://github.com/runware/sdk-js](https://github.com/runware/sdk-js) ## Installation Install the SDK using your preferred package manager: ```bash npm install @runware/sdk-js ``` Or using Yarn: ```bash yarn add @runware/sdk-js ``` ## Basic setup Get your API key from the [Runware dashboard](https://runware.ai) and initialize the SDK directly: ```javascript import { Runware } from "@runware/sdk-js"; const runware = new Runware({ apiKey: "your-api-key-here" }); const images = await runware.requestImages({ positivePrompt: "A serene mountain landscape at sunset", model: "runware:101@1", width: 1024, height: 1024, }); console.log('Generated image:', images[0].imageURL); ``` The SDK automatically handles **connection establishment, authentication, and response formatting**, allowing you to focus on your application logic rather than infrastructure concerns. ## Initialization patterns ### Synchronous initialization The standard pattern creates the SDK instance immediately and establishes connections on-demand: ```javascript import { Runware } from "@runware/sdk-js"; const runware = new Runware({ apiKey: "your-api-key-here", shouldReconnect: true, globalMaxRetries: 3, }); // Connection established automatically on first request const images = await runware.requestImages({ positivePrompt: "A bustling city street at night", model: "runware:101@1", width: 1024, height: 1024, }); ``` This approach is **ideal for most applications** because it's simple and the connection is established when needed. ### Asynchronous initialization For applications requiring **guaranteed connection readiness** before proceeding: ```javascript const runware = await Runware.initialize({ apiKey: "your-api-key-here", timeoutDuration: 60000, }); // Connection is established and ready const images = await runware.requestImages({ positivePrompt: "Professional headshot portrait", model: "runware:101@1", width: 1024, height: 1024, }); // Clean shutdown when application terminates await runware.disconnect(); ``` This pattern is useful when you need to **ensure connection establishment** before performing operations, such as in server applications or when you want to handle connection errors upfront. ### Manual connection control For applications requiring **explicit connection lifecycle management**: ```javascript const runware = new Runware({ apiKey: "your-api-key-here" }); // Explicitly ensure connection is ready await runware.ensureConnection(); // Perform operations with guaranteed connection const images = await runware.requestImages({ positivePrompt: "Professional headshot portrait", model: "runware:101@1", width: 1024, height: 1024, }); // Clean shutdown when application terminates await runware.disconnect(); ``` The `ensureConnection()` method guarantees that the WebSocket connection is established before proceeding, while `disconnect()` provides clean connection termination. ## Progressive result delivery One of the SDK's most powerful features is **progressive result delivery**, which allows you to receive completed images immediately as they finish generating rather than waiting for entire batches: ```javascript const images = await runware.requestImages({ positivePrompt: "A collection of architectural sketches", model: "runware:101@1", width: 1024, height: 1024, numberResults: 5, onPartialImages: (partialImages, error) => { if (error) { console.error('Generation error:', error); return; } // Update UI immediately as each image completes partialImages.forEach((image, index) => { displayImage(image.imageURL); updateProgress(partialImages.length, 5); }); }, }); console.log('All images completed'); ``` The progressive delivery pattern enables user experiences where users **can see completed results immediately** rather than waiting for entire batches to finish. The `onPartialImages` callback receives arrays of completed images as they become available. ## Asynchronous operations Some operations like video generation require **extended processing time** and use asynchronous workflows. The SDK handles all the complexity automatically, including polling for status updates and retrieving final results when ready. ### Understanding async processing When you call methods for long-running operations, the SDK: 1. **Submits your request** and receives an immediate task acknowledgment. 2. **Polls for status updates** automatically. 3. **Returns the final results** once processing completes. This happens transparently, you use the same async/await patterns regardless of operation duration. ### Video generation example Video operations demonstrate the async processing workflow: ```javascript import { Runware } from '@runware/sdk-js'; async function generateVideo() { const runware = new Runware({ apiKey: process.env.RUNWARE_API_KEY }); await runware.connect(); const payload = { positivePrompt: "A serene mountain landscape at sunset", model: "klingai:5@3", duration: 10, width: 1920, height: 1080 }; // This call handles all async complexity internally const response = await runware.videoInference(payload); console.log(`Generated video: ${response[0].videoURL}`); } generateVideo(); ``` The `videoInference` method appears to work like any other async function, but internally manages the **submission, polling, and result retrieval** workflow automatically. ### Handling long operations For operations that may take several minutes, configure appropriate timeouts and retry behavior. See [Configuration options](#configuration-options) for detailed setup: ```javascript // Configure timeouts for long-running operations const runware = new Runware({ apiKey: process.env.RUNWARE_API_KEY, timeout: 600000, // 10 minutes for complex video generation maxRetries: 3, retryDelay: 2000 }); await runware.connect(); // SDK handles extended processing time automatically const payload = { positivePrompt: "Complex cinematic sequence with multiple scenes", model: "google:3@2", duration: 8, width: 1280, height: 720 }; const response = await runware.videoInference(payload); ``` **Timeout configuration** is particularly important for video operations, which can take significantly longer than image generation depending on duration and complexity. ## Concurrent operations The SDK's WebSocket architecture excels at **handling multiple simultaneous operations** without the connection overhead typical of HTTP-based approaches: ```javascript const runware = new Runware({ apiKey: "your-api-key-here" }); // Execute completely different operations simultaneously const [ generatedImages, upscaledImage, backgroundRemoved, imageCaption, ] = await Promise.all([ runware.requestImages({ positivePrompt: "Abstract digital art", numberResults: 3, width: 1024, height: 1024, }), runware.upscaleGan({ inputImage: "some-uuid", upscaleFactor: 4, }), runware.removeImageBackground({ inputImage: "some-uuid", }), runware.requestImageToText({ inputImage: "some-uuid", }), ]); ``` This concurrent execution capability is particularly powerful for **workflow automation** or **batch processing**. ### Long-running concurrent operations The same concurrent patterns work efficiently for **multiple long-running operations** like video generation: ```javascript async function generateMultipleVideos() { const runware = new Runware({ apiKey: process.env.RUNWARE_API_KEY }); await runware.connect(); // Start multiple video generations concurrently const videoTasks = [ runware.videoInference({ positivePrompt: "Ocean waves at sunset", model: "klingai:5@3", duration: 10 }), runware.videoInference({ positivePrompt: "City traffic time-lapse", model: "klingai:5@3", duration: 10 }), runware.videoInference({ positivePrompt: "Forest in autumn", model: "klingai:5@3", duration: 10 }) ]; // All operations process simultaneously const results = await Promise.all(videoTasks); results.forEach((videos, index) => { console.log(`Video ${index + 1}: ${videos[0].videoURL}`); }); } ``` Each operation polls independently, maximizing **throughput for batch processing** scenarios. ## Configuration options The SDK accepts several configuration options that control connection behavior and default settings: ```javascript const runware = new Runware({ apiKey: "your-api-key-here", // Connection management shouldReconnect: true, // Enable automatic reconnection (default: true) globalMaxRetries: 3, // Default retry attempts for all requests (default: 2) timeoutDuration: 90000, // Timeout in milliseconds (default: 60000) // Custom WebSocket endpoint (optional) url: "wss://custom-endpoint.com/v1", }); ``` The **shouldReconnect** option enables automatic reconnection when WebSocket connections are lost, which is essential for maintaining reliability in web applications where network conditions can vary. **globalMaxRetries** sets the default number of retry attempts for all requests. Individual requests can override this setting using their own `retry` parameter. **timeoutDuration** controls how long the SDK waits for responses before timing out operations. This is particularly important for complex generations that may take longer to complete. ## Error handling The SDK provides error information to help you handle failures appropriately: ```javascript try { const images = await runware.requestImages({ positivePrompt: "A detailed architectural rendering", model: "runware:101@1", width: 1024, height: 1024, steps: 50, }); // Process successful results console.log('Generated images:', images); } catch (error) { // Error information available for debugging and user feedback console.error('Generation failed:', { message: error.message, taskUUID: error.taskUUID }); // Handle the error appropriately for your application showErrorMessage(error.message); } ``` When using progressive result delivery, errors can occur during the generation process: ```javascript const images = await runware.requestImages({ positivePrompt: "A series of landscape photographs", numberResults: 8, width: 1024, height: 1024, onPartialImages: (partialImages, error) => { if (error) { console.error('Generation error:', error); // Some images may have succeeded before the error if (partialImages.length) { console.log(`Partial success: ${partialImages.length} images completed`); processPartialResults(partialImages); } return; } // Normal progress updateProgressBar(partialImages.length, 8); displayResults(partialImages); } }); ``` The `onPartialImages` callback receives both successful partial results and any errors that occur, allowing you to handle partial successes gracefully. ## Per-request configuration Individual requests can override global SDK settings for specific needs: ```javascript const images = await runware.requestImages({ positivePrompt: "Ultra-detailed fantasy artwork", model: "runware:101@1", width: 1024, height: 1024, // Override global settings for this specific request retry: 5, // More retries for important operations includeCost: true, // Include cost information in response onPartialImages: (partial) => { // Custom progress handling for this specific request updateSpecializedUI(partial); }, }); ``` The `retry` parameter allows you to specify different retry behavior for individual requests, while `includeCost` adds cost information to the response when needed. Each request can also have its own `onPartialImages` callback for customized progress handling. ## TypeScript support The SDK includes **TypeScript definitions** that provide compile-time type checking and IntelliSense support: ```typescript import { Runware, IImageInference, IImage, IError, ETaskType, } from "@runware/sdk-js"; const runware = new Runware({ apiKey: "your-api-key-here" }); // Type-safe request parameters const requestParams: IImageInference = { positivePrompt: "A professional product photograph", model: "runware:101@1", width: 1024, height: 1024, steps: 30, }; // Fully typed responses const images: IImage[] = await runware.requestImages(requestParams); // Type-safe error handling const handleStreamingError = ( partialImages: IImage[], error: IError | null ) => { if (error) { console.error(`Task ${error.taskUUID} failed: ${error.message}`); } }; ``` TypeScript support extends to **all SDK methods, configuration options, and response types**, enabling better development experience and reduced runtime errors in production applications. --- ## Python library **URL:** https://runware.ai/docs/platform/python-legacy **Description:** Integrate Runware's API with our Python library. Get started with code examples and comprehensive usage instructions. > [!WARNING] > **Legacy SDK.** This is the previous Python SDK and still works, but new projects should use the [Python SDK](https://runware.ai/docs/platform/python). It covers the full inference and utility surface, supports REST or WebSocket transports, and gets all new features going forward. ## Introduction The Runware Python SDK provides a **WebSocket-based interface** built for Python applications that need AI-powered media processing. Using Python's async/await patterns, the SDK handles connection management, authentication, and error recovery while exposing Runware's capabilities through a clean, Pythonic API. The SDK is designed for server-side applications where you need reliable AI integration. You can use it to build web APIs or handle batch processing jobs, with support for both image and video workflows. The setup is straightforward and handles the complexity for you. The SDK automatically handles **asynchronous operations** for tasks that require longer processing times, such as video generation. You don't need to manage polling or status checking manually, the SDK handles all the complexity behind the scenes. ## Key SDK benefits ### Built for Python The SDK uses **async/await patterns** that integrate naturally with modern Python frameworks like FastAPI and asyncio-based applications. Your applications stay responsive even during intensive AI operations thanks to the asynchronous architecture. **Comprehensive type hints** provide better IDE support, enable static analysis with tools like mypy, and help catch errors before they reach production. The API follows **Python conventions** for method naming and error handling, so it feels natural if you're already comfortable with Python development. ### Performance advantages **Persistent WebSocket connections** eliminate the connection overhead that occurs with traditional HTTP requests. This provides measurable performance improvements when you're doing multiple operations or batch processing. **Concurrent operations** work seamlessly with Python's asyncio, letting you handle multiple requests simultaneously without blocking your application. ### When to use the Python SDK Choose the Python SDK for **server-side applications** that need reliable AI integration. It's especially effective for web APIs, batch processing systems, and applications where you need robust error handling and automatic retry logic. Source: [https://github.com/runware/sdk-python](https://github.com/runware/sdk-python) ## Installation Install the SDK using pip: ```bash pip install runware ``` ## Basic setup The Python SDK supports both environment variable and direct API key configuration. For security best practices, use environment variables: ```bash export RUNWARE_API_KEY="your-api-key-here" ``` Then initialize and use the SDK: ```python import asyncio from runware import Runware, IImageInference async def main(): # SDK reads RUNWARE_API_KEY automatically runware = Runware() await runware.connect() request = IImageInference( positivePrompt="A serene mountain landscape at sunset", model="runware:101@1", width=1024, height=1024 ) images = await runware.imageInference(requestImage=request) print(f"Generated image: {images[0].imageURL}") if __name__ == "__main__": asyncio.run(main()) ``` The SDK automatically handles **connection establishment, authentication, and response parsing**, allowing you to focus on your application logic. ## Connection management ### Automatic connection handling The Python SDK requires explicit connection establishment but handles reconnection automatically: ```python from runware import Runware async def main(): runware = Runware(api_key="your-api-key-here") # Establish connection before making requests await runware.connect() # Perform operations - connection maintained automatically request = IImageInference( positivePrompt="A bustling city street at night", model="runware:101@1", width=1024, height=1024 ) images = await runware.imageInference(requestImage=request) # Connection cleanup (optional - handled automatically) await runware.disconnect() ``` ### Connection configuration For applications with specific requirements, customize connection behavior: ```python runware = Runware( api_key="your-api-key-here", timeout=120, # Custom timeout for operations max_retries=5, # Retry attempts for failed requests retry_delay=2.0 # Delay between retries in seconds ) ``` **Connection lifecycle** is managed explicitly in Python, giving you control over when connections are established and terminated. This is particularly useful in **server applications** where you want to maintain connections across multiple requests. ## Asynchronous operations Some operations like video generation require **extended processing time** and use asynchronous workflows. The SDK handles all the complexity automatically, including polling for status updates and retrieving final results when ready. ### Understanding async processing When you call methods for long-running operations, the SDK: 1. **Submits your request** and receives an immediate task acknowledgment. 2. **Polls for status updates** automatically. 3. **Returns the final results** once processing completes. This happens transparently, you use the same async/await patterns regardless of operation duration. ### Video generation example Video operations demonstrate the async processing workflow: ```python import asyncio from runware import Runware, IVideoInference async def main(): runware = Runware() await runware.connect() request = IVideoInference( positivePrompt="A serene mountain landscape at sunset", model="klingai:5@3", duration=10 ) # This call handles all async complexity internally videos = await runware.videoInference(requestVideo=request) print(f"Generated video: {videos[0].videoURL}") if __name__ == "__main__": asyncio.run(main()) ``` The `videoInference` method appears to work like any other async function, but internally manages the **submission, polling, and result retrieval** workflow automatically. ### Handling long operations For operations that may take several minutes, configure appropriate timeouts and retry behavior. See [Configuration options](#configuration-options) for detailed setup: ```python # Configure timeouts for long-running operations runware = Runware( timeout=600, # 10 minutes for complex video generation max_retries=3, retry_delay=2.0 ) await runware.connect() # SDK handles extended processing time automatically request = IVideoInference( positivePrompt="Complex cinematic sequence with multiple scenes", model="google:3@2", duration=8, width=1280, height=720 ) videos = await runware.videoInference(requestVideo=request) ``` **Timeout configuration** is particularly important for video operations, which can take significantly longer than image generation depending on duration and complexity. ## Concurrent operations The SDK's async design excels at handling **multiple simultaneous operations**: ```python import asyncio from runware import Runware, IImageInference, IImageUpscale, IImageBackgroundRemoval async def main(): runware = Runware() await runware.connect() # Execute multiple operations concurrently results = await asyncio.gather( runware.imageInference(requestImage=IImageInference( positivePrompt="Abstract digital art", model="runware:101@1", width=1024, height=1024 )), runware.imageUpscale(upscaleGanPayload=IImageUpscale( inputImage="existing-image-uuid", upscaleFactor=4 )), runware.imageBackgroundRemoval(removeImageBackgroundPayload=IImageBackgroundRemoval( image_initiator="portrait-path.jpg" )) ) generated_images, upscaled_image, background_removed = results ``` This concurrent execution pattern is particularly powerful for **batch processing**, **workflow automation**, and **applications that need to perform multiple operations** on the same or different inputs simultaneously. ### Long-running concurrent operations The same concurrent patterns work efficiently for **multiple long-running operations** like video generation: ```python import asyncio async def generate_multiple_videos(): runware = Runware() await runware.connect() # Start multiple video generations concurrently video_tasks = [ runware.videoInference(requestVideo=IVideoInference( positivePrompt="Ocean waves at sunset", model="klingai:5@3", duration=10 )), runware.videoInference(requestVideo=IVideoInference( positivePrompt="City traffic time-lapse", model="klingai:5@3", duration=10 )), runware.videoInference(requestVideo=IVideoInference( positivePrompt="Forest in autumn", model="klingai:5@3", duration=10 )) ] # All operations process simultaneously results = await asyncio.gather(*video_tasks) for i, videos in enumerate(results): print(f"Video {i+1}: {videos[0].videoURL}") ``` Each operation polls independently, maximizing **throughput for batch processing** scenarios. ## Error handling The SDK provides comprehensive error handling with detailed information for debugging and user feedback: ```python from runware import Runware async def main(): runware = Runware() await runware.connect() try: request = IImageInference( positivePrompt="A detailed architectural rendering", model="runware:101@1", width=1024, height=1024 ) images = await runware.imageInference(requestImage=request) print(f"Success: {len(images)} images generated") except Exception as e: # Error information available for debugging and user feedback print(f"Generation failed: {e}") # Handle the error appropriately for your application ``` ### Batch operation error handling When processing multiple operations, handle partial failures gracefully: ```python async def process_batch(image_requests): runware = Runware() await runware.connect() results = [] for i, request in enumerate(image_requests): try: images = await runware.imageInference(requestImage=request) results.append({"index": i, "success": True, "images": images}) except Exception as e: results.append({"index": i, "success": False, "error": str(e)}) return results ``` ## Integration patterns ### FastAPI integration The SDK integrates seamlessly with FastAPI for building AI-powered web APIs: ```python from fastapi import FastAPI, HTTPException from runware import Runware, IImageInference import asyncio app = FastAPI() runware = Runware() @app.on_event("startup") async def startup(): await runware.connect() @app.on_event("shutdown") async def shutdown(): await runware.disconnect() @app.post("/generate-image") async def generate_image(prompt: str): try: request = IImageInference( positivePrompt=prompt, model="runware:101@1", width=1024, height=1024 ) images = await runware.imageInference(requestImage=request) return {"image_url": images[0].imageURL} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) ``` ### Batch processing workflows For processing large datasets or multiple files: ```python import asyncio from pathlib import Path from runware import Runware, IImageBackgroundRemoval async def process_images_batch(image_folder: Path, batch_size: int = 10): runware = Runware() await runware.connect() image_files = list(image_folder.glob("*.jpg")) for i in range(0, len(image_files), batch_size): batch = image_files[i:i + batch_size] # Process batch concurrently tasks = [ runware.imageBackgroundRemoval( removeImageBackgroundPayload=IImageBackgroundRemoval( image_initiator=str(img) ) ) for img in batch ] results = await asyncio.gather(*tasks, return_exceptions=True) # Handle results and errors for j, result in enumerate(results): if isinstance(result, Exception): print(f"Failed to process {batch[j]}: {result}") else: print(f"Processed {batch[j]}: {result[0].imageURL}") ``` ## Configuration options ### Environment-based configuration The recommended approach for production applications: ```python import os from runware import Runware # Reads from RUNWARE_API_KEY environment variable runware = Runware() # Or explicitly specify environment variable name runware = Runware(api_key=os.getenv("CUSTOM_API_KEY_NAME")) ``` ### Programmatic configuration For applications requiring dynamic configuration: ```python runware = Runware( api_key="your-api-key-here", timeout=180, # 3 minutes timeout for complex operations max_retries=3, # Retry attempts for failed operations retry_delay=1.5, # Delay between retries base_url="wss://custom-endpoint.com/v1" # Custom endpoint ) ``` **Timeout configuration** is particularly important for applications processing high-resolution images or using complex models that may require extended processing time. **Retry configuration** allows you to balance between reliability and responsiveness, with higher retry counts improving success rates in unstable network conditions. ## Type safety and IDE support The SDK provides comprehensive type hints for better development experience: ```python from runware import ( Runware, IImageInference, IImageUpscale ) from typing import List async def generate_and_upscale(prompt: str) -> List[str]: runware = Runware() await runware.connect() # Type-safe request construction request: IImageInference = IImageInference( positivePrompt=prompt, model="runware:101@1", width=512, height=512 ) images = await runware.imageInference(requestImage=request) # Upscale the first image upscale_request: IImageUpscale = IImageUpscale( inputImage=images[0].imageURL, upscaleFactor=2 ) upscaled = await runware.imageUpscale(upscaleGanPayload=upscale_request) return [upscaled[0].imageURL] ``` Type hints enable **static analysis with mypy**, **better IDE autocompletion**, and **reduced runtime errors** in production applications. ## Best practices ### Connection lifecycle management **Establish connections early** in your application lifecycle, particularly in web applications where you'll handle multiple requests. Reuse connections across requests rather than establishing new ones for each operation. **Handle connection cleanup** properly in long-running applications to prevent resource leaks, especially important in server environments. ### Error resilience **Implement comprehensive error handling** that distinguishes between recoverable and non-recoverable errors. Use the SDK's built-in retry mechanisms for transient failures. **Log errors with context** including task UUIDs for debugging and monitoring in production environments. ### Performance optimization **Use concurrent operations** with `asyncio.gather()` when processing multiple independent requests to maximize throughput. **Configure appropriate timeouts** based on your operation types and infrastructure requirements. **Batch operations** when possible to reduce connection overhead and improve overall system efficiency. ---