runware-sdk 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Runware
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,665 @@
1
+ Metadata-Version: 2.4
2
+ Name: runware-sdk
3
+ Version: 1.2.0
4
+ Summary: Async Python SDK for the Runware platform — image, video, audio, text, and 3D generation, plus upscaling, background removal, and more, over REST or WebSocket with typed parameters and runtime validation.
5
+ Keywords: runware,ai,sdk,image-generation,video-generation,audio-generation,text-generation,inference,generative-ai,stable-diffusion,flux,websocket,async,asyncio,llm,streaming
6
+ Author: Runware
7
+ Author-email: Runware <support@runware.ai>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Information Technology
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: Implementation :: CPython
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Framework :: AsyncIO
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Multimedia :: Graphics
24
+ Classifier: Topic :: Multimedia :: Video
25
+ Classifier: Topic :: Multimedia :: Sound/Audio
26
+ Classifier: Typing :: Typed
27
+ Requires-Dist: aiohttp>=3.10
28
+ Requires-Dist: websockets>=13
29
+ Requires-Dist: fastjsonschema>=2.20
30
+ Requires-Python: >=3.11
31
+ Project-URL: Homepage, https://runware.ai
32
+ Project-URL: Documentation, https://runware.ai/docs
33
+ Project-URL: Repository, https://github.com/runware/runware-python
34
+ Project-URL: Issues, https://github.com/runware/runware-python/issues
35
+ Project-URL: Changelog, https://github.com/runware/runware-python/releases
36
+ Description-Content-Type: text/markdown
37
+
38
+ # Runware Python SDK
39
+
40
+ Async Python SDK for the Runware platform. A unified API for image, video, audio, text, and 3D generation — powered by the [Runware inference platform](https://runware.ai).
41
+
42
+ [Documentation](https://runware.ai/docs) · [Report a bug](https://github.com/runware/runware-python/issues)
43
+
44
+ - **One method for everything** — `.run()` handles every model type
45
+ - **Schema-driven types** — generated from Runware's canonical JSON schemas
46
+ - **WebSocket and REST transports** — persistent connections or stateless HTTP
47
+ - **LLM streaming via SSE** — token-by-token text generation with `.stream()`
48
+ - **Automatic model resolution** — the SDK resolves task types from AIR identifiers
49
+ - **Python 3.11+, asyncio-native** — single source of truth for all I/O
50
+
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install runware-sdk
56
+ ```
57
+
58
+ ## Quick start
59
+
60
+ Generate an image in five lines:
61
+
62
+ ```python
63
+ import asyncio
64
+ import os
65
+ from runware import Runware
66
+
67
+ async def main() -> None:
68
+ async with Runware(api_key=os.environ["RUNWARE_API_KEY"]) as client:
69
+ results = await client.run({
70
+ "model": "runware:400@1",
71
+ "positivePrompt": "A serene mountain landscape at sunset",
72
+ "width": 1024,
73
+ "height": 1024,
74
+ })
75
+ print(results[0]["imageURL"])
76
+
77
+ asyncio.run(main())
78
+ ```
79
+
80
+ The SDK resolves `runware:400@1` to the right task type automatically. `RUNWARE_API_KEY` is read from the environment if you don't pass `api_key=...` explicitly.
81
+
82
+ More runnable patterns in [`examples/`](./examples/) — curated models, community fine-tunes, streaming, the WebSocket transport.
83
+
84
+ ## Core Concepts
85
+
86
+ ### One method for everything
87
+
88
+ Every inference task goes through `.run()`. The SDK determines the task type from the model's AIR identifier:
89
+
90
+ ```python
91
+ # Image generation
92
+ images = await client.run({
93
+ "model": "runware:400@1",
94
+ "positivePrompt": "Abstract digital art",
95
+ "width": 1024, "height": 1024,
96
+ })
97
+
98
+ # Video generation
99
+ videos = await client.run({
100
+ "model": "google:3@3",
101
+ "positivePrompt": "Ocean waves at sunset",
102
+ "duration": 8,
103
+ })
104
+
105
+ # Text inference (LLM)
106
+ responses = await client.run({
107
+ "model": "google:gemma@4-31b",
108
+ "messages": [{"role": "user", "content": "Explain quantum computing"}],
109
+ })
110
+
111
+ # Audio generation (designs a voice from the prompt, then speaks the text)
112
+ audio = await client.run({
113
+ "model": "alibaba:qwen@3-tts-1.7b-voicedesign",
114
+ "positivePrompt": "A calm, friendly young woman with a soft tone",
115
+ "speech": {"text": "Hello world", "voice": "design"},
116
+ })
117
+ ```
118
+
119
+ ### Typed params per architecture
120
+
121
+ When you know the architecture, import its `Params` TypedDict from `runware.types.task_map` and annotate your call:
122
+
123
+ ```python
124
+ from runware.types.task_map import SdxlArchParams
125
+
126
+ params: SdxlArchParams = {
127
+ "model": "civitai:133005@782002",
128
+ "taskType": "imageInference",
129
+ "positivePrompt": "A professional headshot portrait",
130
+ "negativePrompt": "blurry, distorted",
131
+ "width": 1024,
132
+ "height": 1024,
133
+ "steps": 30,
134
+ "scheduler": "DPMSolverMultistep",
135
+ }
136
+ images = await client.run(params)
137
+ ```
138
+
139
+ The generated module ships params and result TypedDicts for every architecture and operation, plus three lookup dicts:
140
+
141
+ - `architecture_task_types` — `sdxl`, `flux-1-dev`, `pony`, `illustrious`, and more
142
+ - `modality_task_types` — `image`, `video`, `audio`, `text`, `3d`
143
+ - `operation_task_types` — `caption-image`, `upscale-image`, `remove-background-image`, `prompt-enhance`, `vectorize`, and more
144
+
145
+ ### Community and trained models
146
+
147
+ For models not built into the SDK (community uploads, fine-tunes), the registry won't have them yet — pass `taskType` explicitly:
148
+
149
+ ```python
150
+ images = await client.run({
151
+ "model": "runware:exactly-illustrative@my-trained-style",
152
+ "taskType": "imageInference",
153
+ "positivePrompt": "A lighthouse on a rocky cliff at twilight",
154
+ "width": 1024,
155
+ "height": 1024,
156
+ })
157
+ ```
158
+
159
+ Validation (when enabled) automatically picks the right schema for the AIR — no extra option needed.
160
+
161
+ ### Curated-model slugs
162
+
163
+ The registry indexes every curated model under both its AIR (`runware:400@1`) and its slug (`bfl-flux-2-dev`). You can pass either:
164
+
165
+ ```python
166
+ # Both call the same model.
167
+ await client.run({"model": "runware:400@1", "positivePrompt": "..."})
168
+ await client.run({"model": "bfl-flux-2-dev", "positivePrompt": "..."})
169
+ ```
170
+
171
+ The SDK rewrites slugs to canonical AIRs before sending. Non-curated identifiers (custom fine-tunes, unknown strings) pass through unchanged.
172
+
173
+ ## LLM Streaming
174
+
175
+ For text models, `.stream()` delivers tokens as they're generated:
176
+
177
+ ```python
178
+ stream = await client.stream({
179
+ "model": "google:gemma@4-31b",
180
+ "messages": [{"role": "user", "content": "Tell me a story about a robot"}],
181
+ })
182
+
183
+ async for word in stream.text_stream:
184
+ print(word, end="", flush=True)
185
+ ```
186
+
187
+ `stream()` returns a `TextStream` with multiple ways to consume the response:
188
+
189
+ ```python
190
+ stream = await client.stream({
191
+ "model": "google:gemma@4-31b",
192
+ "messages": [{"role": "user", "content": "Explain gravity"}],
193
+ })
194
+
195
+ # Iterate text deltas
196
+ async for word in stream.text_stream:
197
+ print(word, end="", flush=True)
198
+
199
+ # Iterate reasoning content (reasoning models)
200
+ async for thought in stream.reasoning_stream:
201
+ print(f"[thinking] {thought}")
202
+
203
+ # Get the full text at once (awaits the entire stream)
204
+ full_text = await stream.text()
205
+
206
+ # Get the final result with metadata
207
+ result = await stream.result()
208
+ print(result.text)
209
+ print(result.finish_reason) # "stop", "length", etc.
210
+ print(result.usage) # {"promptTokens": ..., "completionTokens": ...}
211
+ print(result.cost) # USD cost
212
+ ```
213
+
214
+ > **Note:** `stream()` only supports `numberResults: 1`. For multiple completions use `run()` — `stream()` raises if you pass `numberResults > 1`.
215
+
216
+ ## Transport Options
217
+
218
+ ### WebSocket (default)
219
+
220
+ Best when you make multiple requests or want real-time feedback:
221
+
222
+ ```python
223
+ async with Runware(transport_type="websocket") as client:
224
+ images = await client.run({"model": "runware:400@1", "positivePrompt": "..."})
225
+ videos = await client.run({"model": "google:3@3", "positivePrompt": "..."})
226
+ ```
227
+
228
+ WebSocket connections are automatically recovered on network interruptions. The SDK re-authenticates with the same session UUID and the server replays pending results.
229
+
230
+ ### REST
231
+
232
+ Best for serverless functions or one-off requests:
233
+
234
+ ```python
235
+ async with Runware(transport_type="rest") as client:
236
+ images = await client.run({
237
+ "model": "runware:400@1",
238
+ "positivePrompt": "A landscape painting",
239
+ "width": 1024, "height": 1024,
240
+ })
241
+ ```
242
+
243
+ ## Concurrent Operations
244
+
245
+ Run multiple tasks in parallel with `asyncio.gather`:
246
+
247
+ ```python
248
+ import asyncio
249
+
250
+ async with Runware() as client:
251
+ images, upscaled, caption = await asyncio.gather(
252
+ client.run({
253
+ "model": "runware:400@1",
254
+ "positivePrompt": "Abstract art",
255
+ "numberResults": 3,
256
+ }),
257
+ client.run({
258
+ "model": "runware:504@1",
259
+ "taskType": "upscale",
260
+ "inputs": {"image": "https://example.com/photo.jpg"},
261
+ }),
262
+ client.run({
263
+ "model": "runware:150@2",
264
+ "taskType": "caption",
265
+ "inputs": {"image": "https://example.com/photo.jpg"},
266
+ }),
267
+ )
268
+ ```
269
+
270
+ ## Cancellation
271
+
272
+ Pass an `asyncio.Event` and `.set()` it to cancel mid-flight. Works for `run()` and `stream()`, on both transports.
273
+
274
+ > **Heads-up:** cancel is client-side only. The server keeps processing the task and **you will be billed for it**. Cancelling just stops the SDK from waiting for the result.
275
+
276
+ ```python
277
+ import asyncio
278
+ from runware import RunOptions, RunwareError
279
+
280
+ async def main() -> None:
281
+ async with Runware() as client:
282
+ cancel = asyncio.Event()
283
+
284
+ async def deadline() -> None:
285
+ await asyncio.sleep(5)
286
+ cancel.set()
287
+
288
+ asyncio.create_task(deadline())
289
+
290
+ try:
291
+ await client.run(
292
+ {"model": "runware:400@1", "positivePrompt": "A detailed scene"},
293
+ RunOptions(cancel_event=cancel),
294
+ )
295
+ except RunwareError as exc:
296
+ if exc.code == "aborted":
297
+ print("Cancelled")
298
+ ```
299
+
300
+ For streams, set the cancel event to end iteration:
301
+
302
+ ```python
303
+ from runware import StreamOptions
304
+
305
+ stream = await client.stream(
306
+ {"model": "google:gemma@4-31b", "messages": [{"role": "user", "content": "..."}]},
307
+ StreamOptions(cancel_event=cancel),
308
+ )
309
+ async for word in stream.text_stream:
310
+ print(word, end="")
311
+ if some_condition:
312
+ cancel.set()
313
+ ```
314
+
315
+ ## Result and progress callbacks
316
+
317
+ Two callbacks let you observe a task as it unfolds:
318
+
319
+ - **`on_result(item)`** — fires once per item the moment it reaches a terminal state (`success` or `error`). For `numberResults > 1`, fires up to N times. Useful for streaming results into a UI as they appear.
320
+ - **`on_progress(item)`** — fires when an item's `progress` field changes (0-100). Currently only a handful of long-running models emit progress (mostly training).
321
+
322
+ ```python
323
+ from runware import RunOptions
324
+
325
+ def watch(item: dict) -> None:
326
+ if item.get("status") == "success":
327
+ print("ready:", item.get("imageURL"))
328
+ else:
329
+ print("failed:", item.get("error"))
330
+
331
+ def progress(item: dict) -> None:
332
+ print(f"{item.get('progress')}%")
333
+
334
+ results = await client.run(
335
+ {"model": "google:3@3", "positivePrompt": "Ocean waves", "numberResults": 3},
336
+ RunOptions(on_result=watch, on_progress=progress),
337
+ )
338
+ ```
339
+
340
+ Error items fire `on_result` **before** the call raises — so when a per-result failure happens (provider hiccup, one of N results moderated, etc.), you still see the successful items via callback before the call raises. Same behavior on both WebSocket and REST. Request-level failures (`validation`, `auth`, `quota`, `rateLimit`) are the exception: they raise at submit time, before any results exist.
341
+
342
+ ## Error Handling
343
+
344
+ All SDK errors are `RunwareError` instances:
345
+
346
+ ```python
347
+ from runware import Runware, RunwareError
348
+
349
+ async with Runware() as client:
350
+ try:
351
+ results = await client.run({
352
+ "model": "runware:400@1",
353
+ "positivePrompt": "A detailed rendering",
354
+ })
355
+ except RunwareError as exc:
356
+ print(exc.code) # 'validation' | 'auth' | 'quota' | ...
357
+ print(exc.retryable) # True for provider/timeout/connection/rateLimit/serverError
358
+ print(exc.message) # Human-readable description
359
+ print(exc.parameter) # Which param caused the error, if any
360
+ print(exc.documentation) # Link to model / utility / errors docs
361
+ print(exc.task_uuid) # Request UUID
362
+ print(exc.status_code) # HTTP status, when applicable
363
+ print(exc.validation_errors) # Per-field errors when validate=True
364
+ ```
365
+
366
+ For cross-realm setups (different asyncio loops, subprocess boundaries) use `is_runware_error(exc)` instead of `isinstance`:
367
+
368
+ ```python
369
+ from runware import is_runware_error
370
+
371
+ if is_runware_error(exc):
372
+ ...
373
+ ```
374
+
375
+ `code` is a small, stable enum — `validation`, `auth`, `quota`, `rateLimit`, `safety`, `provider`, `timeout`, `notFound`, `serverError`, `connection`, `aborted`, `unknown`. Switch on it for high-level handling. The server's raw error identifier (hundreds of unstable values) is intentionally not exposed.
376
+
377
+ ```python
378
+ if exc.code == "validation":
379
+ # Show form error, use exc.parameter to highlight the field
380
+ ...
381
+ elif exc.code == "quota":
382
+ # Redirect to billing
383
+ ...
384
+ elif exc.retryable:
385
+ # Backoff and retry
386
+ ...
387
+ ```
388
+
389
+ ### Raising your own RunwareError
390
+
391
+ If you're wrapping the SDK behind another layer and want to surface errors with the same shape, build one with `create_runware_error`:
392
+
393
+ ```python
394
+ from runware import create_runware_error
395
+
396
+ raise create_runware_error(
397
+ "invalidParameter",
398
+ "Width must be a multiple of 64",
399
+ parameter="width",
400
+ task_type="imageInference",
401
+ )
402
+ ```
403
+
404
+ The constructor derives `code` and `documentation` URL from the raw code + model/parameter context — same logic the SDK uses internally.
405
+
406
+ ## Configuration
407
+
408
+ `Runware(...)` accepts keyword arguments matching the `SDKConfig` dataclass:
409
+
410
+ | Field | Default | Notes |
411
+ |---|---|---|
412
+ | `api_key` | from `RUNWARE_API_KEY` | required |
413
+ | `transport_type` | `"websocket"` | or `"rest"` |
414
+ | `http_base_url` | `https://api.runware.ai/v1` | include the version path |
415
+ | `ws_base_url` | `wss://ws-api.runware.ai/v1` | include the version path |
416
+ | `timeout` | `1_200_000` (ms) | per-HTTP-call (one POST, one `getResponse` poll) |
417
+ | `poll_timeout` | `1_200_000` (ms) | end-to-end polling budget on either transport |
418
+ | `auth_timeout` | `15_000` (ms) | WebSocket auth handshake |
419
+ | `max_retries` | `3` | REST retries |
420
+ | `retry_delay` | `1_000` (ms) | base backoff |
421
+ | `retry_strategy` | `"exponential"` | or `"linear"` |
422
+ | `max_reconnect_attempts` | `inf` | WebSocket reconnect cap |
423
+ | `debug` | `False` | enable structured debug logs |
424
+ | `validate` | `False` | enable client-side schema validation |
425
+ | `dependencies` | `None` | inject a custom `aiohttp.ClientSession` and/or `ws_connect` |
426
+ | `log_sink` | `None` | pluggable destination for log entries |
427
+
428
+ ### Custom log sink
429
+
430
+ By default, logs go through Python's stdlib `logging` under the `runware` logger. To send them elsewhere (Datadog, Sentry, a file, an aggregator), pass a `log_sink`:
431
+
432
+ ```python
433
+ from runware import LogEntry, Runware
434
+
435
+ def sink(entry: LogEntry) -> None:
436
+ # entry: { category, message, data, timestamp }
437
+ print(entry.category, entry.message, entry.data)
438
+
439
+ async with Runware(debug=True, log_sink=sink) as client:
440
+ ...
441
+ ```
442
+
443
+ Categories: `connection`, `auth`, `heartbeat`, `send`, `receive`, `request`, `retry`, `error`, `warn`, `info`. With `debug=False`, the logger is a noop — every call drops, no I/O.
444
+
445
+ ### Picking up newly-launched models
446
+
447
+ New Runware models become usable automatically — no SDK update needed. To force a refresh immediately (instead of waiting for the next 5-minute background cycle):
448
+
449
+ ```python
450
+ await client.refresh_registry()
451
+ results = await client.run({"model": "newprovider:1@1", "positivePrompt": "..."})
452
+ ```
453
+
454
+ The registry caches the model map for 5 minutes. A bundled snapshot ships with the package and is used as a fallback when the network is unreachable.
455
+
456
+ ### Async delivery
457
+
458
+ The SDK sends `deliveryMethod: "async"` by default for all inference tasks. On both transports, the server stores the result and the SDK polls `getResponse` until the task completes — that's why the same `poll_timeout` controls behavior on REST and WebSocket alike (default: 20 minutes).
459
+
460
+ For long tasks (video, training, large upscale), raise `poll_timeout`:
461
+
462
+ ```python
463
+ client = Runware(poll_timeout=1_800_000) # 30 minutes
464
+ ```
465
+
466
+ Or per-call via `RunOptions`:
467
+
468
+ ```python
469
+ videos = await client.run(
470
+ {"model": "google:3@3", "positivePrompt": "Ocean waves"},
471
+ RunOptions(timeout=600_000),
472
+ )
473
+ ```
474
+
475
+ #### Opting into sync delivery
476
+
477
+ For fast tasks (text inference, fast image gen, captioning) you can skip the polling round-trips by setting `deliveryMethod: "sync"`. The server holds the response open and pushes back the result in one round trip:
478
+
479
+ ```python
480
+ responses = await client.run({
481
+ "model": "google:gemma@4-31b",
482
+ "messages": [{"role": "user", "content": "Hello"}],
483
+ "deliveryMethod": "sync",
484
+ })
485
+ ```
486
+
487
+ On **WebSocket** this is where the persistent connection pays off — one frame in, one frame back, no polling.
488
+ On **REST** it's a single HTTP request with the full result in the response body.
489
+
490
+ Pick sync when the task finishes inside the server's connection budget (~120s for WebSocket sync, the HTTP read timeout for REST). For anything longer — video, 3D, large upscale, multi-result batches — stick with the async default.
491
+
492
+ ### Per-call options
493
+
494
+ The second argument to `client.run()` and `client.stream()` is a `RunOptions` / `StreamOptions` instance — per-call overrides that don't belong on the client:
495
+
496
+ ```python
497
+ from runware import RunOptions
498
+
499
+ await client.run(
500
+ {"model": "runware:400@1", "positivePrompt": "A landscape"},
501
+ RunOptions(
502
+ timeout=600_000, # ms — override config.poll_timeout for this call
503
+ cancel_event=cancel, # asyncio.Event — cancel this call
504
+ on_result=watch, # fires per item as it completes
505
+ on_progress=progress, # fires when an item's progress % changes
506
+ validate=True, # override config.validate for this call
507
+ ),
508
+ )
509
+ ```
510
+
511
+ `stream()` accepts `StreamOptions` with `timeout`, `cancel_event`, and `validate` (no polling means no per-item callbacks).
512
+
513
+ ## Validation
514
+
515
+ Enable client-side validation to catch invalid parameters before they reach the API:
516
+
517
+ ```python
518
+ async with Runware(validate=True) as client:
519
+ await client.run({"model": "...", ...}) # raises RunwareError(code='validation') on bad params
520
+ ```
521
+
522
+ The schema for each model is fetched on first use and cached per-process. Works the same for curated models and community fine-tunes — pass nothing beyond `validate=True`.
523
+
524
+ If the schema can't be fetched (network failure, model unknown to the registry), validation is silently skipped and the server still validates as the source of truth.
525
+
526
+ Validation errors come back as a `RunwareError` with `code="validation"` and structured details on `validation_errors`:
527
+
528
+ ```python
529
+ from runware import RunwareError
530
+
531
+ try:
532
+ await client.run({...})
533
+ except RunwareError as exc:
534
+ if exc.code == "validation":
535
+ print(exc.task_type) # "imageInference"
536
+ print(exc.validation_errors) # [{message, path, rule, rule_definition}]
537
+ ```
538
+
539
+ Validation can also be toggled **per call** via `RunOptions.validate`, which overrides `config.validate`:
540
+
541
+ ```python
542
+ # Force on for one call even if config.validate is False
543
+ await client.run({...}, RunOptions(validate=True))
544
+
545
+ # Skip for one call even if config.validate is True
546
+ await client.run({...}, RunOptions(validate=False))
547
+ ```
548
+
549
+ To clear the in-process validator cache (e.g., after a server-side schema change without restarting):
550
+
551
+ ```python
552
+ from runware import clear_validator_cache
553
+ clear_validator_cache()
554
+ ```
555
+
556
+ ## Utility Methods
557
+
558
+ ```python
559
+ # Search for available models
560
+ models = await client.model_search({
561
+ "search": "portrait",
562
+ "category": "checkpoint",
563
+ "architecture": "sdxl",
564
+ "limit": 10,
565
+ })
566
+
567
+ # Upload an image for use as input
568
+ uploaded = await client.image_upload({
569
+ "image": "https://example.com/photo.jpg", # URL, Data URI, or Base64
570
+ })
571
+
572
+ # Get account details
573
+ account = await client.account_management({"operation": "getDetails"})
574
+
575
+ # Retrieve a previously executed task
576
+ archived = await client.get_task_details({"taskUUID": "abc-123"})
577
+
578
+ # Poll for an async task result (used internally by run() — rarely needed directly)
579
+ result = await client.get_response({"taskUUID": "abc-123"})
580
+
581
+ # Upload a custom model
582
+ await client.model_upload({
583
+ "category": "checkpoint",
584
+ "architecture": "sdxl",
585
+ "format": "safetensors",
586
+ # ... plus model file details
587
+ })
588
+ ```
589
+
590
+ `get_task_details` vs `get_response`: use `get_task_details` for "look up something I ran before" — it queries the task archive. `get_response` is the polling mechanism the SDK uses internally during async `.run()`; you generally don't need to call it directly.
591
+
592
+ ## Content metadata
593
+
594
+ `client.content.*` exposes Runware's curated model catalog as read-only metadata — names, AIRs, headlines, capabilities, pricing, examples. Public information, no extra cost.
595
+
596
+ ```python
597
+ # List curated models, optionally filtered
598
+ models = await client.content.list_models({
599
+ "capability": "io:text-to-image",
600
+ "category": "image",
601
+ "creator": "black-forest-labs",
602
+ "search": "flux",
603
+ })
604
+
605
+ # Single curated model by id
606
+ model = await client.content.get_model("alibaba-z-image-turbo")
607
+
608
+ # Sample input/output pairs the model can produce
609
+ examples = await client.content.get_model_examples("flux-1-dev")
610
+
611
+ # Pricing summary and per-configuration examples
612
+ pricing = await client.content.get_model_pricing("flux-1-dev")
613
+
614
+ # Discover the capability taxonomy (io:*, op:*, form:*)
615
+ capabilities = await client.content.list_capabilities()
616
+
617
+ # Collections (Runware-defined model groupings) with full model objects inlined
618
+ collections = await client.content.list_collections({"category": "image"})
619
+
620
+ # Creators with their curated models inlined
621
+ creators = await client.content.list_creators()
622
+ google = await client.content.get_creator("google")
623
+
624
+ # Pagination — pass paginate=True to get {"total", "limit", "offset", "items"}
625
+ page = await client.content.list_models({"paginate": True, "limit": 25, "offset": 0})
626
+ ```
627
+
628
+ `creator`, `capabilities`, and `architecture` on each model are returned as id strings — resolve them against `list_creators`, `list_capabilities`, and the architecture id respectively when you need the human-readable label. Collections and creators are the only endpoints that resolve their inner `models` array to full objects.
629
+
630
+ ## File helpers
631
+
632
+ `file_to_data_uri` encodes a local file as a `data:` URI for passing as input:
633
+
634
+ ```python
635
+ from pathlib import Path
636
+ from runware import file_to_data_uri
637
+
638
+ data_uri = file_to_data_uri(Path("photo.jpg"))
639
+ await client.image_upload({"image": data_uri})
640
+ ```
641
+
642
+ Accepts both `Path` and `bytes` — `bytes` is useful when the file lives in memory (e.g. a freshly downloaded blob).
643
+
644
+ ## Custom dependencies
645
+
646
+ For testing, proxies, or custom auth flows, pass a `RuntimeDependencies`:
647
+
648
+ ```python
649
+ import aiohttp
650
+ from runware import Runware, RuntimeDependencies
651
+
652
+ session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60))
653
+
654
+ async with Runware(
655
+ api_key="...",
656
+ dependencies=RuntimeDependencies(session=session),
657
+ ) as client:
658
+ ...
659
+ ```
660
+
661
+ Pass `ws_connect=...` similarly to override the WebSocket connect path (defaults to `websockets.connect`). Injected sessions are not closed on `client.close()` — you own the lifecycle.
662
+
663
+ ## License
664
+
665
+ MIT