Runway Free code examples in Python and JavaScript
Text-to-video and image-to-video generation.
WHAT THIS PAGE COVERS
Polling a long-running video job without leaking a connection.
gen4.5
runwayml
Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.
RUNWAYML_API_SECRET, not _API_KEY. The client reads it with no arguments, so RunwayML() is the whole construction.
pip install runwayml
export RUNWAYML_API_SECRET="key_your-secret" # macOS / Linux
setx RUNWAYML_API_SECRET "key_your-secret" # Windows, then open a new shell
prompt_image takes a URL the API fetches itself. The ratio is pixel dimensions, not an aspect string — '1280:720', with a colon, and a mismatch against the source image is a validation error rather than a crop.
import os
from runwayml import RunwayML
client = RunwayML(api_key=os.environ["RUNWAYML_API_SECRET"])
task = client.image_to_video.create(
model="gen4.5",
prompt_image="https://example.com/assets/microphone.jpg",
prompt_text="The camera slowly pushes in on the microphone",
ratio="1280:720",
duration=5,
)
print("submitted:", task.id)
wait_for_task_output() does the polling at the rate Runway asks for. Writing your own loop at one second gets you rate-limited; writing one at thirty wastes half a minute per job.
from runwayml import TaskFailedError
try:
# Chains onto create(): submits, then blocks until SUCCEEDED.
result = client.image_to_video.create(
model="gen4.5",
prompt_image="https://example.com/assets/microphone.jpg",
prompt_text="The camera slowly pushes in on the microphone",
ratio="1280:720",
duration=5,
).wait_for_task_output()
video_url = result.output[0]
print(video_url)
except TaskFailedError as error:
# task_details carries the reason — usually moderation or a
# source image the model could not use.
print("generation failed:", error.task_details)
A five-second clip takes minutes to render. Submit, store the task id, return immediately, and let a worker or a poll from the client pick it up — this is the shape that survives a load balancer.
from fastapi import FastAPI
app = FastAPI()
@app.post("/videos")
def submit(image_url: str, prompt: str):
task = client.image_to_video.create(
model="gen4.5",
prompt_image=image_url,
prompt_text=prompt,
ratio="1280:720",
duration=5,
)
# Return the id now. Nothing waits.
return {"task_id": task.id, "status": "PENDING"}
@app.get("/videos/{task_id}")
def check(task_id: str):
task = client.tasks.retrieve(task_id)
return {
"status": task.status, # PENDING | RUNNING | SUCCEEDED | FAILED
"url": task.output[0] if task.status == "SUCCEEDED" else None,
}
The output URL is signed and temporary, like every other media API here. Fetch the bytes as soon as the task succeeds rather than storing the link.
import requests
result = client.image_to_video.create(
model="gen4.5",
prompt_image="https://example.com/assets/microphone.jpg",
prompt_text="The camera slowly pushes in on the microphone",
ratio="1280:720",
duration=5,
).wait_for_task_output()
video = requests.get(result.output[0], stream=True, timeout=120)
video.raise_for_status()
with open("clip.mp4", "wb") as handle:
for chunk in video.iter_content(chunk_size=1 << 16):
handle.write(chunk)
The package is scoped. The secret lives in RUNWAYML_API_SECRET and the client picks it up on its own.
npm install @runwayml/sdk
export RUNWAYML_API_SECRET="key_your-secret"
promptImage is a URL the API fetches. ratio is pixel dimensions with a colon — '1280:720' — and not an aspect ratio string, which is the mistake the error message does not quite explain.
import RunwayML from "@runwayml/sdk";
const client = new RunwayML();
const task = await client.imageToVideo.create({
model: "gen4.5",
promptImage: "https://example.com/assets/microphone.jpg",
promptText: "The camera slowly pushes in on the microphone",
ratio: "1280:720",
duration: 5,
});
console.log("submitted:", task.id);
waitForTaskOutput polls at the documented rate. Runway asks for no more than one check every five seconds per task, and a hand-written loop usually gets that wrong in one direction or the other.
import { TaskFailedError } from "@runwayml/sdk";
try {
const result = await client.imageToVideo
.create({
model: "gen4.5",
promptImage: "https://example.com/assets/microphone.jpg",
promptText: "The camera slowly pushes in on the microphone",
ratio: "1280:720",
duration: 5,
})
.waitForTaskOutput();
console.log(result.output[0]);
} catch (error) {
if (error instanceof TaskFailedError) {
// Usually moderation, or a source image the model rejected.
console.error("generation failed:", error.taskDetails);
} else {
throw error;
}
}
Rendering takes minutes and a request that waits will be killed by something in between. Submit, return the id, and let the client poll your own endpoint.
import express from "express";
const app = express();
app.use(express.json());
app.post("/videos", async (request, response) => {
const task = await client.imageToVideo.create({
model: "gen4.5",
promptImage: request.body.imageUrl,
promptText: request.body.prompt,
ratio: "1280:720",
duration: 5,
});
response.status(202).json({ taskId: task.id, status: "PENDING" });
});
app.get("/videos/:id", async (request, response) => {
const task = await client.tasks.retrieve(request.params.id);
response.json({
status: task.status, // PENDING | RUNNING | SUCCEEDED | FAILED
url: task.status === "SUCCEEDED" ? task.output[0] : null,
});
});
app.listen(3000);
The output URL is signed and short-lived. Stream it to disk or to object storage the moment the task succeeds — a stored URL is a broken video tomorrow.
import { createWriteStream } from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
const result = await client.imageToVideo
.create({
model: "gen4.5",
promptImage: "https://example.com/assets/microphone.jpg",
promptText: "The camera slowly pushes in on the microphone",
ratio: "1280:720",
duration: 5,
})
.waitForTaskOutput();
const video = await fetch(result.output[0]);
if (!video.ok) throw new Error(`Download failed: ${video.status}`);
// Stream it: a ten-second clip is tens of megabytes.
await pipeline(Readable.fromWeb(video.body), createWriteStream("clip.mp4"));
QUESTIONS
Which languages are the Runway examples in?
Python and JavaScript. Every sample runs as pasted and reads its credentials from an environment variable — no key is ever written into the code.
Which package do I install for Runway?
The samples use runwayml. The install step on each language tab gives the exact command.
Which Runway model do these examples use?
gen4.5, checked against the official documentation on 26 September 2026. Model ids change often; if a call returns a model-not-found error, check the current list in the official docs.
Where is the official Runway documentation?
https://docs.dev.runwayml.com/ — it is linked at the bottom of this page, and it is the authority whenever it disagrees with a sample here.
ALSO IN IMAGE & VIDEO
The authority
Official Runway documentation
Model names, pricing and rate limits change faster than any page here can. When this page and the official docs disagree, the official docs are right.
https://docs.dev.runwayml.com/