Google shut down the Veo 3.1 preview models on the Gemini API on October 22, 2026, according to useapi.net’s Veo pricing notes, and named Gemini Omni 1.1 Flash as the replacement. If your Gemini AI video generator script still calls the old preview IDs, it stopped working this week. The newer model is also cheaper on Runway Dev’s price list, at $0.10 per second at 720p against $0.40 per second for Veo 3.1 with audio. Cheap per second still adds up fast when every retry, reference image and input clip carries its own charge.
This tutorial builds the tool that keeps that bill honest. It is a small Python project called videoledger with a price table, a cost estimator, a SQLite ledger, a budget guard and a matrix runner. It sends the same prompt to Gemini Omni Flash 1.1, Veo 3.1, Veo 3.1 Fast, Seedance 2.5, Kling O3 and Wan 3.0 through one API key, then reports what each model really cost per usable second. Plan on 12 steps and about 50 minutes. Everything except the live generations runs for free, and the full eight-second matrix costs $10.56 at list prices.
Think of it as the money-side companion to two pieces we already run. The earlier Veo 3.1 API walkthrough covers the Google route that just closed, and our ranked AI video generator test judges quality. Neither tells you what a usable second costs once failed takes and retries are counted. That number is what this ledger produces.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What Changed: Veo 3.1 Previews Out, Gemini Omni Flash 1.1 In
Runway Dev’s model guide lists Gemini Omni Flash 1.1, a multimodal video model that reads text, images and video together. It returns clips of 3 to 10 seconds with audio generated in the same pass, including dialogue written into the prompt and the sound of the scene. Output comes at 360p, 720p, 1080p and 4K, and the same model extends or edits clips you already have. A prompt can run to 4,000 characters, and a text request can carry up to 5 reference images and 3 reference videos.
The route matters as much as the model. Per useapi.net, Google’s own Veo API now sits on Agent Platform in Google Cloud (formerly Vertex AI) at the same prices and needs a Google Cloud project with billing attached. Runway Dev sells the official Gemini Omni Flash 1.1 model behind a plain API key and one credit balance, with no Google Cloud project, billing account or region to set up. One credit equals $0.01, which makes the arithmetic easy to audit. That single balance across labs is the reason this tutorial uses Runway Dev. A ledger that spans five vendors’ invoices would be a project of its own.
Two newer names deserve a mention and stay out of the matrix on purpose. Kuaishou launched Kling 4.0 on October 15, 2026, with native clips up to 30 seconds, but it described API access for developers as forthcoming and published no prices, a gap that Wireflow’s Kling 4.0 pricing page also flags. Luma lists Ray 3.2 as its current video model, released in June 2026, and bills its API through credits that vary by model and output spec, so there is no clean per-second table to load. Both can join the ledger later by adding one entry to the price table. Runway’s own Gen-4.5 stays out as well, because I found no per-second API rate for it, though our Runway Gen-4.5 API setup guide shows how to call it.
| Model line | Status on October 24, 2026 | In the matrix? |
|---|---|---|
| Gemini Omni Flash 1.1 | Released August 27, 2026, 3 to 10 second clips with native audio | Yes |
| Veo 3.1 and Veo 3.1 Fast | Sold on Runway Dev at $0.40 and $0.15 per second with audio | Yes |
| Seedance 2.5 | ByteDance announced it July 31, 2026, Runway Dev API live August 7 | Yes |
| Kling O3 (Standard, Pro, 4K) | Listed on Runway Dev at $0.13, $0.17 and $0.43 per second | Standard in the matrix |
| Wan 3.0 and Wan 3.0 Prime | Listed on Runway Dev, 2 to 30 second clips | Yes |
| Kling 4.0 | Launched October 15, 2026, developer API not yet priced | No |
| Luma Ray 3.2 | Current Luma video model, no per-second price table | No |
Prerequisites and Versions
A Gemini AI video generator pipeline needs a laptop, a terminal and a small credit balance. Nothing here depends on a GPU, because the models run on the vendors’ servers and your machine only sends requests and saves files. The code uses the plain REST endpoints instead of the official SDKs. The SDKs poll for you, which suits quick scripts, but a ledger wants the task ID on disk before the first poll, so the project calls the API with the requests library and writes the row first.
| Requirement | Version or value | Notes |
|---|---|---|
| Python | 3.12 (tested on 3.12.3) | Standard library only, apart from requests |
| requests | 2.32 or newer (tested on 2.34.2) | pip install requests |
| SQLite | Bundled with Python’s sqlite3 module | Nothing to install |
| Runway Dev account | API key plus a credit balance | Key goes in RUNWAYML_API_SECRET |
| X-Runway-Version header | 2024-11-06 | Value used in Runway’s own request examples |
| ffmpeg (optional) | Any recent build | Only for checking clip lengths by hand |
| Budget | $0 for estimates and the fake server, $10.56 for the full 8-second matrix | Computed from list prices on October 24, 2026 |
Prices in this article are the ones Runway Dev’s model guides listed as current on October 24, 2026. They change without notice, so treat every number as a snapshot and let the project’s staleness warning nag you about it.
Price Table: What an 8-Second Clip Costs on 17 Model Tiers
Before writing code, look at the spread. The table below applies each model’s published per-second rate to an eight-second clip with audio on. Every figure comes from the estimator you will build in Step 4, fed with the per-second rates in Runway Dev’s own model guides (current as of October 24, 2026), including the Veo 3.1 guide for the Google models. Eight seconds is the longest clip all five model families accept.
| Model | Tier | Credits per second | 8-second clip (credits) | 8-second clip (USD) |
|---|---|---|---|---|
| Gemini Omni Flash 1.1 | 360p | 3.4 | 28 | $0.28 |
| Gemini Omni Flash 1.1 | 720p | 10 | 80 | $0.80 |
| Gemini Omni Flash 1.1 | 1080p | 15 | 120 | $1.20 |
| Gemini Omni Flash 1.1 | 4k | 30 | 240 | $2.40 |
| Veo 3.1 Fast | 720p | 15 | 120 | $1.20 |
| Veo 3.1 | 720p | 40 | 320 | $3.20 |
| Seedance 2.5 | 480p | 20 | 160 | $1.60 |
| Seedance 2.5 | 720p | 30 | 240 | $2.40 |
| Seedance 2.5 | 1080p | 68 | 544 | $5.44 |
| Kling O3 Standard | 720p | 13 | 104 | Not listed |
| Kling O3 Pro | 1080p | 17 | 136 | $1.36 |
| Kling O3 4K | 4k | 43 | 344 | Not listed |
| Wan 3.0 | 480p | 5 | 40 | $0.40 |
| Wan 3.0 | 720p | 10 | 80 | $0.80 |
| Wan 3.0 | 1080p | 20 | 160 | $1.60 |
| Wan 3.0 Prime | 720p | 14 | 112 | $1.12 |
| Wan 3.0 Prime | 1080p | 28 | 224 | Not listed |
Two patterns jump out. Gemini Omni Flash 1.1 at 720p costs the same as Wan 3.0 at 720p, $0.80 for eight seconds, and a quarter of Veo 3.1 with audio at $3.20. At 1080p, Omni’s $1.20 matches Veo 3.1 Fast and undercuts Seedance 2.5, which charges $5.44 for the same eight seconds. Price alone proves little, though. A model that needs three takes to produce one keeper is not cheap, which is exactly why the ledger tracks usable seconds and not just billed ones.
The second table lists the billing rules that move an invoice away from the simple per-second math. Each row comes from the model’s Runway Dev guide.
| Model | Clip length | Extra charges and quirks |
|---|---|---|
| Gemini Omni Flash 1.1 | 3 to 10 seconds, or auto | Audio always on at the same rate. +1 credit per reference image or keyframe. +1 credit per input video second. Auto bills 10 seconds up front and refunds the unused part. Whole-credit rounding per generation. |
| Veo 3.1 and Veo 3.1 Fast | 4, 6 or 8 seconds | Audio on by default. Muting halves the Veo 3.1 rate to $0.20 and drops Fast to $0.10 per second. Input images are free. A request rejected with a 400 costs nothing. |
| Seedance 2.5 | 4 to 30 seconds, or auto | Minimum charge of 80 credits per generation. Auto bills 30 seconds up front. Draft mode bills twice. Video references add $0.10, $0.15 or $0.34 per input second by resolution. |
| Kling O3 | 3 to 15 seconds | Audio does not change the price. An edit bills the length of the source clip. |
| Wan 3.0 and Prime | 2 to 30 seconds | Reference media and generated sound add nothing. You pay for the duration you send. |
One rule applies to every model on the list. A task that fails input moderation (the SAFETY.INPUT family of failure codes) is still charged. Your ledger needs to treat a failed task as money spent until proven otherwise. The same discipline applies on the image side of a pipeline, where our AI image generator API cost breakdown runs the same kind of math.
Steps 1 to 3: Account, Project Skeleton and Price Table
Step 1: Create the Runway Dev Account, API Key and Credit Balance
Open the Runway Dev site at dev.runway.com and create an account. Make an API key in your project’s dashboard, add credits to the project’s balance, and store the key in your shell as RUNWAYML_API_SECRET. Start with a small top-up. The eight-second matrix needs $10.56, and a first test run needs under a dollar. Write down the balance shown in the dashboard before you start, because Step 12 compares it with your ledger.
export RUNWAYML_API_SECRET="paste-your-key-here"
echo "${RUNWAYML_API_SECRET:0:4}..." # prints the first four characters, nothing more
Step 2: Create the Project Skeleton
Make a folder, a virtual environment and the empty package files. The layout stays flat on purpose, since six short modules are easier to read than one clever one.
mkdir videoledger-project && cd videoledger-project
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "requests>=2.32"
mkdir -p videoledger tests clips
touch videoledger/__init__.py
printf 'from .cli import main\n\nmain()\n' > videoledger/__main__.py
echo 'requests>=2.32' > requirements.txt
Step 3: Write the Price Table (prices.py)
The price table is the one file you will edit most. It stores the exact API model ID for each product, the credits per output second for each resolution tier, the 16:9 ratio string the API accepts at that tier, the allowed clip lengths, and the extras from the billing table above. It uses Decimal rather than floats, so 3.4 credits per second times 5 seconds lands on exactly 17 credits and not on 17.000000000000004. Seedance 2.5 deserves a look when you edit it, and our Seedance 2.5 API setup guide covers its draft mode and reference inputs in depth.
from dataclasses import dataclass, field
from decimal import Decimal as D
PRICES_AS_OF = "2026-10-24"
CREDIT_USD = D("0.01")
@dataclass(frozen=True)
class ModelSpec:
key: str
label: str
tiers: dict
ratios: dict
min_s: int
max_s: int
allowed_s: tuple = ()
auto_s: int = 0
muted: dict = field(default_factory=dict)
takes_audio: bool = True
image_credits: D = D(0)
video_in: dict = field(default_factory=dict)
min_credits: D = D(0)
round_up: bool = False
_SPECS = [
ModelSpec(
key="gemini_omni_flash_1.1", label="Gemini Omni Flash 1.1",
tiers={"360p": D("3.4"), "720p": D(10), "1080p": D(15), "4k": D(30)},
ratios={"360p": "640:360", "720p": "1280:720", "1080p": "1920:1080", "4k": "3840:2160"},
min_s=3, max_s=10, auto_s=10, takes_audio=False, image_credits=D(1),
video_in={"360p": D(1), "720p": D(1), "1080p": D(1), "4k": D(1)}, round_up=True,
),
ModelSpec(
key="veo3.1", label="Veo 3.1",
tiers={"720p": D(40), "1080p": D(40)}, muted={"720p": D(20), "1080p": D(20)},
ratios={"720p": "1280:720", "1080p": "1920:1080"},
min_s=4, max_s=8, allowed_s=(4, 6, 8),
),
ModelSpec(
key="veo3.1_fast", label="Veo 3.1 Fast",
tiers={"720p": D(15), "1080p": D(15)}, muted={"720p": D(10), "1080p": D(10)},
ratios={"720p": "1280:720", "1080p": "1920:1080"},
min_s=4, max_s=8, allowed_s=(4, 6, 8),
),
ModelSpec(
key="seedance2_5", label="Seedance 2.5",
tiers={"480p": D(20), "720p": D(30), "1080p": D(68)},
ratios={"480p": "854:480", "720p": "1280:720", "1080p": "1920:1080"},
min_s=4, max_s=30, auto_s=30, min_credits=D(80),
video_in={"480p": D(10), "720p": D(15), "1080p": D(34)},
),
ModelSpec(
key="klingO3_standard", label="Kling O3 Standard",
tiers={"720p": D(13)}, ratios={"720p": "1280:720"}, min_s=3, max_s=15,
),
ModelSpec(
key="klingO3_pro", label="Kling O3 Pro",
tiers={"1080p": D(17)}, ratios={"1080p": "1920:1080"}, min_s=3, max_s=15,
),
ModelSpec(
key="klingO3_4k", label="Kling O3 4K",
tiers={"4k": D(43)}, ratios={"4k": "3840:2160"}, min_s=3, max_s=15,
),
ModelSpec(
key="wan3", label="Wan 3.0",
tiers={"480p": D(5), "720p": D(10), "1080p": D(20)},
ratios={"480p": "832:480", "720p": "1280:720", "1080p": "1920:1080"},
min_s=2, max_s=30,
),
ModelSpec(
key="wan3_prime", label="Wan 3.0 Prime",
tiers={"480p": D("6.8"), "720p": D(14), "1080p": D(28)},
ratios={"480p": "832:480", "720p": "1280:720", "1080p": "1920:1080"},
min_s=2, max_s=30,
),
]
MODELS = {m.key: m for m in _SPECS}
Note the date constant at the top. The code compares it with today’s date and prints a warning to stderr once it is more than 14 days old. That warning is cheap insurance, because stale rates are the most common way a cost tracker lies to you.
Steps 4 to 5: The Cost Estimator and the SQLite Ledger
Step 4: Build the Estimator (estimator.py)
The estimator turns a request into credits and dollars before you spend anything. It validates the clip length against the model’s rules, refuses an audio switch the model does not accept, adds per-image and per-input-second charges, applies the minimum charge and the whole-credit rounding where they exist, and returns notes the CLI prints. Failing loudly here saves money, since a request the API would reject never gets built.
import math
from dataclasses import dataclass
from decimal import Decimal as D
from .prices import CREDIT_USD, MODELS
@dataclass(frozen=True)
class Estimate:
model: str
tier: str
billed_seconds: int
credits: D
usd: D
notes: tuple
def estimate(model, tier, seconds, *, audio=True, images=0, video_in_seconds=0, auto=False):
if model not in MODELS:
raise ValueError(f"unknown model {model!r}, pick one of {sorted(MODELS)}")
spec = MODELS[model]
if tier not in spec.tiers:
raise ValueError(f"{spec.label} has no tier {tier!r}, pick one of {sorted(spec.tiers)}")
notes = []
if auto:
if not spec.auto_s:
raise ValueError(f"{spec.label} has no auto duration")
billed = spec.auto_s
notes.append(f"auto bills {billed}s up front, unused seconds come back as a refund")
else:
if spec.allowed_s and seconds not in spec.allowed_s:
raise ValueError(f"{spec.label} takes only {spec.allowed_s} seconds")
if not spec.allowed_s and not spec.min_s <= seconds <= spec.max_s:
raise ValueError(f"{spec.label} takes {spec.min_s} to {spec.max_s} seconds")
billed = seconds
if not audio and not spec.takes_audio:
raise ValueError(f"{spec.label} always returns sound, drop audio=False")
rate = spec.muted[tier] if (not audio and spec.muted) else spec.tiers[tier]
credits = rate * billed
credits += spec.image_credits * images
credits += spec.video_in.get(tier, D(0)) * D(video_in_seconds)
if spec.min_credits and credits < spec.min_credits:
notes.append(f"minimum charge of {spec.min_credits} credits applies")
credits = spec.min_credits
if spec.round_up:
credits = D(math.ceil(credits))
return Estimate(model, tier, billed, credits, credits * CREDIT_USD, tuple(notes))
Runway Dev prints worked examples for Gemini Omni Flash 1.1. A five-second clip costs 17 credits at 360p, 50 at 720p, 75 at 1080p and 150 at 4K. Once the CLI exists in Step 8, run the commands below. If your numbers differ from the sample output, stop and fix the table before going further.
python3 -m videoledger estimate gemini_omni_flash_1.1 360p 5
python3 -m videoledger estimate gemini_omni_flash_1.1 720p 5
python3 -m videoledger estimate gemini_omni_flash_1.1 4k 5
python3 -m videoledger estimate gemini_omni_flash_1.1 720p 5 --images 1
Gemini Omni Flash 1.1 360p: 5s = 17 credits = $0.17 (prices as of 2026-10-24)
Gemini Omni Flash 1.1 720p: 5s = 50 credits = $0.50 (prices as of 2026-10-24)
Gemini Omni Flash 1.1 4k: 5s = 150 credits = $1.50 (prices as of 2026-10-24)
Gemini Omni Flash 1.1 720p: 5s = 51 credits = $0.51 (prices as of 2026-10-24)
Step 5: Build the Ledger (ledger.py)
The ledger is one SQLite table. The design choice that matters is the order of events. The row is written as PENDING before the request goes out, gets its task ID the moment the API answers, and reaches a final status only after polling ends. If your laptop sleeps mid-run or the script crashes, the row still exists and the money still shows up in your totals. The charged column defaults to 1 for the same reason. A failed task stays on the books until you forgive it, which makes the ledger err on the high side instead of hiding spend.
import sqlite3
from datetime import datetime, timezone
SCHEMA = """
CREATE TABLE IF NOT EXISTS jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT UNIQUE,
label TEXT,
model TEXT NOT NULL,
tier TEXT NOT NULL,
endpoint TEXT NOT NULL,
prompt TEXT,
seconds INTEGER NOT NULL,
audio INTEGER NOT NULL,
est_credits REAL NOT NULL,
est_usd REAL NOT NULL,
charged INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'PENDING',
failure_code TEXT,
latency_s REAL,
output_path TEXT,
usable INTEGER,
created_at TEXT NOT NULL
);
"""
def now_iso():
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
def open_db(path="ledger.db"):
db = sqlite3.connect(path)
db.row_factory = sqlite3.Row
db.executescript(SCHEMA)
return db
def add_job(db, *, label, model, tier, endpoint, prompt, seconds, audio, est):
cur = db.execute(
"INSERT INTO jobs (label, model, tier, endpoint, prompt, seconds, audio,"
" est_credits, est_usd, created_at) VALUES (?,?,?,?,?,?,?,?,?,?)",
(label, model, tier, endpoint, prompt, seconds, int(audio),
float(est.credits), float(est.usd), now_iso()),
)
db.commit()
return cur.lastrowid
def attach_task(db, job_id, task_id):
db.execute("UPDATE jobs SET task_id=?, status='SUBMITTED' WHERE id=?", (task_id, job_id))
db.commit()
def finish_job(db, job_id, *, status, latency_s=None, failure_code=None,
output_path=None, charged=1):
db.execute(
"UPDATE jobs SET status=?, latency_s=?, failure_code=?, output_path=?, charged=?"
" WHERE id=?",
(status, latency_s, failure_code, output_path, charged, job_id),
)
db.commit()
def set_usable(db, job_id, usable):
db.execute("UPDATE jobs SET usable=? WHERE id=?", (int(usable), job_id))
db.commit()
def forgive(db, job_id):
db.execute("UPDATE jobs SET charged=0 WHERE id=?", (job_id,))
db.commit()
def spent_since(db, since_iso):
row = db.execute(
"SELECT COALESCE(SUM(est_usd), 0) AS usd FROM jobs WHERE charged=1 AND created_at>=?",
(since_iso,),
).fetchone()
return row["usd"]
def spent_today(db):
return spent_since(db, datetime.now(timezone.utc).strftime("%Y-%m-%dT00:00:00"))
def report_rows(db):
return db.execute(
"""
SELECT model, tier,
COUNT(*) AS jobs,
SUM(CASE WHEN status='SUCCEEDED' THEN 1 ELSE 0 END) AS ok,
SUM(charged * est_usd) AS usd,
SUM(CASE WHEN status='SUCCEEDED' THEN seconds ELSE 0 END) AS made_s,
SUM(CASE WHEN usable=1 THEN seconds ELSE 0 END) AS usable_s,
AVG(latency_s) AS avg_latency
FROM jobs GROUP BY model, tier ORDER BY usd DESC
"""
).fetchall()
The usable column starts empty. You fill it in by hand after watching each clip, and the report divides spend by usable seconds. That column turns the project from an accountant into a decision tool.
Steps 6 to 7: The API Client and the Budget Guard
Step 6: Write the API Client (runway.py)
Four functions cover the whole API surface. The first builds the JSON body. Note that Gemini Omni Flash 1.1 gets no audio field at all, because the Runway Dev guide says a request carrying audio, aspect_ratio or resolution fails with a 400, and that every clip has sound. Size and shape travel in the single ratio field. The second function submits and retries politely on HTTP 429. The third polls with backoff until the task reaches SUCCEEDED, FAILED or CANCELLED, treating THROTTLED as a queue and not a rejection. The fourth streams the finished file to disk. Teams that prefer Kling’s own endpoints can compare notes with our Kling API setup guide, but the ledger logic stays identical.
import os
import time
import requests
BASE = os.environ.get("RUNWAY_BASE_URL", "https://api.dev.runwayml.com")
API_VERSION = "2024-11-06"
TERMINAL = {"SUCCEEDED", "FAILED", "CANCELLED"}
class ApiError(RuntimeError):
pass
def _headers():
key = os.environ.get("RUNWAYML_API_SECRET")
if not key:
raise SystemExit("Set RUNWAYML_API_SECRET before calling the API")
return {
"Authorization": f"Bearer {key}",
"X-Runway-Version": API_VERSION,
"Content-Type": "application/json",
}
def build_body(spec, tier, prompt, seconds, *, audio=True, auto=False, frames=None):
body = {
"model": spec.key,
"promptText": prompt,
"ratio": spec.ratios[tier],
"duration": "auto" if auto else seconds,
}
if spec.takes_audio:
body["audio"] = audio
if frames:
body["promptImage"] = [{"uri": uri, "position": pos} for uri, pos in frames]
return body
def submit(endpoint, body, retries=4):
delay = 2
for attempt in range(retries + 1):
r = requests.post(f"{BASE}/v1/{endpoint}", json=body, headers=_headers(), timeout=60)
if r.status_code == 429 and attempt < retries:
time.sleep(float(r.headers.get("Retry-After", delay)))
delay *= 2
continue
if r.status_code == 400:
raise ApiError(f"400, request rejected before a task existed: {r.text}")
r.raise_for_status()
return r.json()["id"]
def poll(task_id, timeout_s=1200):
delay = float(os.environ.get("VL_POLL_START", "5"))
deadline = time.monotonic() + timeout_s
while time.monotonic() < deadline:
r = requests.get(f"{BASE}/v1/tasks/{task_id}", headers=_headers(), timeout=60)
r.raise_for_status()
data = r.json()
if data.get("status") in TERMINAL:
return data
time.sleep(delay)
delay = min(delay * 1.5, 30)
raise TimeoutError(f"task {task_id} still running after {timeout_s}s, keep polling it later")
def download(url, path):
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(path, "wb") as fh:
for chunk in r.iter_content(1 << 20):
fh.write(chunk)
return path
Two details are easy to miss. The polling loop raises a TimeoutError after 20 minutes, and the task keeps running on the server, so the ledger row stays SUBMITTED and you can poll the same task ID later. The download step is not optional either. Runway Dev’s guide warns that output URLs are temporary, so a clip you do not save promptly is a clip you paid for and lost.
Step 7: Add the Budget Guard (budget.py)
Cost trackers that only report after the fact are autopsies. The guard runs before every submission and refuses two things: a single job above a per-job cap, and any job that would push today’s logged total past a daily cap. The defaults are $4.00 per job and $20.00 per day, both overridable with environment variables. The per-job default sits just above the $3.20 price of an eight-second Veo 3.1 clip, so the standard matrix runs without a prompt, while a runaway 30-second Seedance 2.5 request at 720p ($9.00) gets stopped cold.
import os
from decimal import Decimal as D
from .ledger import spent_today
MAX_JOB_USD = D(os.environ.get("VL_MAX_JOB_USD", "4.00"))
MAX_DAY_USD = D(os.environ.get("VL_MAX_DAY_USD", "20.00"))
class BudgetError(RuntimeError):
pass
def check(db, est):
if est.usd > MAX_JOB_USD:
raise BudgetError(
f"{est.model} at {est.tier} for {est.billed_seconds}s costs ${est.usd:.2f}, "
f"over the ${MAX_JOB_USD} per-job cap (VL_MAX_JOB_USD)"
)
spent = D(str(spent_today(db)))
if spent + est.usd > MAX_DAY_USD:
raise BudgetError(
f"${spent:.2f} already logged today, adding ${est.usd:.2f} breaks the "
f"${MAX_DAY_USD} daily cap (VL_MAX_DAY_USD)"
)
Steps 8 to 9: Your First Gemini Omni Flash Clip and an Image-to-Video Run
Step 8: Add the CLI and Generate a Clip (cli.py)
The CLI ties the modules together. The key function is run_one, and its order is the whole point: estimate, check the budget, write the PENDING row, submit, attach the task ID, poll, download, close the row. Rejected requests (HTTP 400, nothing billed) are marked REJECTED with charged set to 0. Everything else stays charged until you say otherwise. The file also holds the matrix definition, the report formatter and the reconcile command you will use in Steps 10 to 12.
import argparse
import sys
import time
from datetime import date
from decimal import Decimal as D
from pathlib import Path
from . import budget, ledger, runway
from .estimator import estimate
from .prices import MODELS, PRICES_AS_OF
MATRIX = [
("gemini_omni_flash_1.1", "720p"),
("veo3.1_fast", "720p"),
("veo3.1", "720p"),
("seedance2_5", "720p"),
("klingO3_standard", "720p"),
("wan3", "720p"),
("wan3_prime", "720p"),
]
def run_one(db, model, tier, prompt, seconds, *, audio=True, auto=False,
frames=None, label="", out_dir="clips"):
spec = MODELS[model]
est = estimate(model, tier, seconds, audio=audio, auto=auto, images=len(frames or []))
budget.check(db, est)
endpoint = "image_to_video" if frames else "text_to_video"
body = runway.build_body(spec, tier, prompt, seconds, audio=audio, auto=auto, frames=frames)
job_id = ledger.add_job(db, label=label, model=model, tier=tier, endpoint=endpoint,
prompt=prompt, seconds=est.billed_seconds, audio=audio, est=est)
started = time.monotonic()
try:
task_id = runway.submit(endpoint, body)
except runway.ApiError as err:
ledger.finish_job(db, job_id, status="REJECTED", failure_code=str(err)[:200], charged=0)
raise
ledger.attach_task(db, job_id, task_id)
data = runway.poll(task_id)
latency = round(time.monotonic() - started, 1)
if data["status"] == "SUCCEEDED":
Path(out_dir).mkdir(exist_ok=True)
path = runway.download(data["output"][0], f"{out_dir}/{job_id:04d}-{model}-{tier}.mp4")
ledger.finish_job(db, job_id, status="SUCCEEDED", latency_s=latency, output_path=path)
print(f"job {job_id}: {spec.label} {tier} {est.billed_seconds}s ${est.usd:.2f} -> {path}")
else:
code = data.get("failureCode") or data.get("failure") or "UNKNOWN"
ledger.finish_job(db, job_id, status=data["status"], latency_s=latency, failure_code=code)
print(f"job {job_id}: {data['status']} {code} (kept as charged until you forgive it)")
return job_id
def cmd_estimate(args):
est = estimate(args.model, args.tier, args.seconds, audio=not args.mute,
images=args.images, video_in_seconds=args.video_in, auto=args.auto)
print(f"{MODELS[args.model].label} {args.tier}: {est.billed_seconds}s = "
f"{est.credits} credits = ${est.usd:.2f} (prices as of {PRICES_AS_OF})")
for note in est.notes:
print(" note:", note)
def cmd_run(args):
db = ledger.open_db(args.db)
frames = [(args.image, "first")] if args.image else None
run_one(db, args.model, args.tier, args.prompt, args.seconds, audio=not args.mute,
auto=args.auto, frames=frames, label=args.label)
def cmd_matrix(args):
db = ledger.open_db(args.db)
total = D(0)
for model, tier in MATRIX:
est = estimate(model, tier, args.seconds)
total += est.usd
print(f"{MODELS[model].label:24} {tier:6} {est.credits:>6} credits ${est.usd:>5.2f}")
print(f"{'matrix total':31} {'':>14} ${total:>5.2f}")
if args.dry_run:
return
for model, tier in MATRIX:
try:
run_one(db, model, tier, args.prompt, args.seconds, label=args.label)
except (runway.ApiError, budget.BudgetError) as err:
print(f"skipped {model}: {err}")
def cmd_rate(args):
db = ledger.open_db(args.db)
ledger.set_usable(db, args.job, args.verdict == "keep")
print(f"job {args.job} marked {args.verdict}")
def cmd_forgive(args):
ledger.forgive(ledger.open_db(args.db), args.job)
print(f"job {args.job} no longer counts toward spend")
def cmd_report(args):
db = ledger.open_db(args.db)
print(f"{'model':24} {'tier':6} {'jobs':>4} {'ok':>3} {'usd':>7} {'$/made-s':>9} "
f"{'$/usable-s':>10} {'avg s':>6}")
for r in ledger.report_rows(db):
per_made = f"{r['usd'] / r['made_s']:.3f}" if r["made_s"] else "-"
per_use = f"{r['usd'] / r['usable_s']:.3f}" if r["usable_s"] else "-"
lat = f"{r['avg_latency']:.0f}" if r["avg_latency"] else "-"
print(f"{MODELS[r['model']].label:24} {r['tier']:6} {r['jobs']:>4} {r['ok']:>3} "
f"{r['usd']:>7.2f} {per_made:>9} {per_use:>10} {lat:>6}")
def cmd_reconcile(args):
db = ledger.open_db(args.db)
logged = D(str(ledger.spent_since(db, args.since)))
dashboard = D(args.before) - D(args.after)
print(f"ledger since {args.since}: ${logged:.2f}")
print(f"dashboard balance drop: ${dashboard:.2f}")
print(f"gap (dashboard - ledger): ${dashboard - logged:.2f}")
def main():
p = argparse.ArgumentParser(prog="videoledger")
p.add_argument("--db", default="ledger.db")
sub = p.add_subparsers(dest="cmd", required=True)
e = sub.add_parser("estimate")
e.add_argument("model", choices=sorted(MODELS))
e.add_argument("tier")
e.add_argument("seconds", type=int)
e.add_argument("--mute", action="store_true")
e.add_argument("--auto", action="store_true")
e.add_argument("--images", type=int, default=0)
e.add_argument("--video-in", type=int, default=0)
e.set_defaults(fn=cmd_estimate)
r = sub.add_parser("run")
r.add_argument("model", choices=sorted(MODELS))
r.add_argument("tier")
r.add_argument("seconds", type=int)
r.add_argument("prompt")
r.add_argument("--image")
r.add_argument("--mute", action="store_true")
r.add_argument("--auto", action="store_true")
r.add_argument("--label", default="")
r.set_defaults(fn=cmd_run)
m = sub.add_parser("matrix")
m.add_argument("prompt")
m.add_argument("--seconds", type=int, default=8)
m.add_argument("--label", default="matrix")
m.add_argument("--dry-run", action="store_true")
m.set_defaults(fn=cmd_matrix)
k = sub.add_parser("rate")
k.add_argument("job", type=int)
k.add_argument("verdict", choices=["keep", "toss"])
k.set_defaults(fn=cmd_rate)
f = sub.add_parser("forgive")
f.add_argument("job", type=int)
f.set_defaults(fn=cmd_forgive)
sub.add_parser("report").set_defaults(fn=cmd_report)
c = sub.add_parser("reconcile")
c.add_argument("--since", required=True)
c.add_argument("--before", required=True)
c.add_argument("--after", required=True)
c.set_defaults(fn=cmd_reconcile)
args = p.parse_args()
age = (date.today() - date.fromisoformat(PRICES_AS_OF)).days
if age > 14:
print(f"warning: prices.py is {age} days old, recheck the model pages", file=sys.stderr)
try:
args.fn(args)
except (ValueError, budget.BudgetError, runway.ApiError) as err:
raise SystemExit(f"error: {err}")
if __name__ == "__main__":
main()
Now spend your first fifty cents. The prompt describes both picture and sound, since Gemini Omni Flash 1.1 generates them together.
python3 -m videoledger estimate gemini_omni_flash_1.1 720p 5
python3 -m videoledger run gemini_omni_flash_1.1 720p 5 \
"Locked-off wide shot of fireworks over a calm lake at night. Rockets streak up from the far shore and burst into gold and red, their light reflected in the water. Audio: the whistle of the rockets, deep booms echoing across the lake." \
--label first-clip
The call blocks while the task runs, then prints one line with the job number, the estimated cost and the saved file path. Open the file, check that the audio matches the prompt, and move on.
Step 9: Run Image-to-Video With a First Frame
Image-to-video starts from a picture you supply, which makes results far more repeatable than text alone. If you generate those pictures with an API too, our AI image generator API build produces stills you can feed straight in. Pass a public HTTPS URL with the image flag. On Gemini Omni Flash 1.1 the first frame counts as one reference image, so a five-second 720p clip costs 51 credits and not 50. On the Veo models input images add nothing. The ledger models both rules, so the estimate printed before the run already includes them.
python3 -m videoledger run gemini_omni_flash_1.1 720p 5 \
"Slow push-in as the lantern flickers and rain starts to fall. Audio: soft rain, distant thunder." \
--image https://example.com/lantern.jpg --label i2v-lantern
The body builder also supports a closing frame, which Omni uses to generate the motion between the two images. A last frame on its own is rejected, so always send the first one. Wan 3.0 adds a twist for keyframe requests, where the ratio must start with auto_. The Troubleshooting section lists it.
Step 10: Run the Same Prompt Across Seven Model Tiers
This is the experiment the project exists for. The matrix command sends one prompt to seven tiers at 720p and eight seconds, from Gemini Omni Flash 1.1 down to Wan 3.0 Prime. Always dry-run it first. The dry run prints each estimate and the total, spends nothing, and doubles as a pre-flight check on your price table.
python3 -m videoledger matrix "Handheld shot of a street food cook flipping noodles in a wok, flames rising, steam in the neon light. Audio: sizzling, a metal spatula scraping, chatter." --dry-run
Gemini Omni Flash 1.1 720p 80 credits $ 0.80
Veo 3.1 Fast 720p 120 credits $ 1.20
Veo 3.1 720p 320 credits $ 3.20
Seedance 2.5 720p 240 credits $ 2.40
Kling O3 Standard 720p 104 credits $ 1.04
Wan 3.0 720p 80 credits $ 0.80
Wan 3.0 Prime 720p 112 credits $ 1.12
matrix total $10.56
Remove the dry-run flag when the totals look right. The runner works through the tiers one at a time and keeps going if a model is skipped for a budget or validation reason. Expect it to take a while, because each model queues and renders on its own schedule. Run it in a terminal multiplexer or send it to the background and read the ledger later.
Keep the experiment fair. Same prompt, same length, same ratio, 720p everywhere. If you vary any of those, you are measuring your prompt and not the model.
Pay attention to two things when the clips land. First, whether each model followed the audio instruction, since a silent or mismatched clip is a failed take for any use that needs sound. Second, whether the motion holds together past the fourth second, where cheaper tiers tend to drift. Those observations feed the next step.
Steps 11 to 12: Rate the Takes and Reconcile With the Dashboard
Step 11: Mark Every Clip Keep or Toss
Watch each clip and record a verdict. If you want a structured rubric for picture quality, our quality benchmark harness defines one, and it shares prompt files with this project without conflict. The ledger stores it, and the report divides total spend by the seconds you kept. The distinction matters because the cheapest model per generated second is rarely the cheapest per usable second. A hypothetical shows the mechanics. If a $0.10 per second model delivers a keeper one time in two, each usable second costs $0.20. A $0.40 per second model with the same keep rate costs $0.80 per usable second, and a $0.10 model that only works one time in four lands at $0.40. The assumed keep rates below are illustrations, not measurements, so replace them with your own.
| Scenario (assumed keep rate) | List price per second | Cost per usable second |
|---|---|---|
| $0.10 per second model, 1 keeper in 2 takes | $0.10 | $0.20 |
| $0.40 per second model, 1 keeper in 2 takes | $0.40 | $0.80 |
| $0.10 per second model, 1 keeper in 4 takes | $0.10 | $0.40 |
| $0.13 per second model, 3 keepers in 4 takes | $0.13 | About $0.17 |
python3 -m videoledger rate 1 keep
python3 -m videoledger rate 2 toss
python3 -m videoledger forgive 3 # only after the dashboard shows no charge for job 3
python3 -m videoledger report
A word on forgive. Failed tasks stay charged in your ledger because the safe assumption for a budget tool is that money left your balance. Run forgive only after the dashboard confirms the credits came back. Never forgive a SAFETY.INPUT failure, since Runway Dev’s guides say those are billed.
Step 12: Reconcile the Ledger With the Dashboard Balance
The last step closes the loop. Take the balance you wrote down in Step 1, take the current one, and let the tool compare the drop with what the ledger says you spent over the same window. A gap of a few cents is rounding. A gap of a few dollars means a job is missing from the ledger, a failed task was billed that you forgave, or someone else is using the same key.
python3 -m videoledger reconcile --since 2026-10-24T09:00:00 --before 50.00 --after 39.44
The since timestamp is UTC, matching how the ledger stamps rows. Run this weekly, and treat any gap above 5 percent of spend as a bug in your price table until proven otherwise.
Output Examples: What the Terminal Should Print
To test the whole loop without spending credits, the project ships a fake API server (listed in the next section). Point the tool at it with RUNWAY_BASE_URL and run three jobs, one of which is built to fail input moderation. The lines below are real output from that run. The dollar figures are estimates, not spend, since no real model ran.
job 1: Gemini Omni Flash 1.1 720p 5s $0.50 -> clips/0001-gemini_omni_flash_1.1-720p.mp4
job 2: FAILED SAFETY.INPUT.TEXT (kept as charged until you forgive it)
job 3: Wan 3.0 720p 6s $0.60 -> clips/0003-wan3-720p.mp4
After rating job 1 as keep, job 3 as toss, and forgiving job 2, the report reads like this. Job 2 shows zero dollars because it was forgiven. The Wan row shows a per-made-second price and no per-usable-second price because its only clip was tossed.
model tier jobs ok usd $/made-s $/usable-s avg s
Wan 3.0 720p 1 1 0.60 0.100 - 0
Gemini Omni Flash 1.1 720p 1 1 0.50 0.100 0.100 0
Veo 3.1 Fast 720p 1 0 0.00 - - 0
Guard failures and validation errors print a single line and exit with status 1, with nothing written to the ledger. The first line below came from running with VL_MAX_JOB_USD set to 0.5.
error: veo3.1 at 720p for 8s costs $3.20, over the $0.5 per-job cap (VL_MAX_JOB_USD)
error: Gemini Omni Flash 1.1 always returns sound, drop audio=False
The Complete Project: File Tree, Fake API and Smoke Test
You have now seen every file. Here is the finished tree, so you can check that nothing is missing.
videoledger-project/
requirements.txt
clips/ # downloaded videos land here
ledger.db # created on first run
tests/
fake_runway.py # local stand-in for the API
videoledger/
__init__.py
__main__.py
prices.py
estimator.py
ledger.py
runway.py
budget.py
cli.py
The fake server answers the two endpoints the tool uses. It returns a task ID on submit, reports RUNNING on the first poll, then SUCCEEDED with a download URL, or FAILED with a moderation code when the prompt contains the word FAILME. It also rejects an audio field sent to Gemini Omni Flash 1.1 with a 400, mirroring the real behavior the Runway Dev guide describes. Save it as tests/fake_runway.py.
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
TASKS = {}
class Handler(BaseHTTPRequestHandler):
def _send(self, code, payload, ctype="application/json"):
raw = payload if isinstance(payload, bytes) else json.dumps(payload).encode()
self.send_response(code)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def do_POST(self):
body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
if body["model"] == "gemini_omni_flash_1.1" and "audio" in body:
return self._send(400, {"error": "audio is not a field on this model"})
task_id = f"task-{len(TASKS) + 1}"
TASKS[task_id] = {"polls": 0, "fail": "FAILME" in body["promptText"]}
self._send(200, {"id": task_id})
def do_GET(self):
if self.path.startswith("/v1/tasks/"):
task = TASKS[self.path.rsplit("/", 1)[1]]
task["polls"] += 1
if task["polls"] < 2:
return self._send(200, {"status": "RUNNING"})
if task["fail"]:
return self._send(200, {"status": "FAILED", "failureCode": "SAFETY.INPUT.TEXT"})
return self._send(200, {"status": "SUCCEEDED",
"output": ["http://127.0.0.1:8099/clip.mp4"]})
self._send(200, b"fake-mp4-bytes", "video/mp4")
HTTPServer(("127.0.0.1", 8099), Handler).serve_forever()
Start it in one terminal, then run the smoke test in another. Setting VL_POLL_START to a fraction of a second keeps the polling loop quick. Delete ledger.db afterwards, since the test rows are not real spend.
# terminal 1
python3 tests/fake_runway.py
# terminal 2
export RUNWAYML_API_SECRET=test
export RUNWAY_BASE_URL=http://127.0.0.1:8099
export VL_POLL_START=0.2
python3 -m videoledger run gemini_omni_flash_1.1 720p 5 "fireworks over a lake" --label smoke
python3 -m videoledger run veo3.1_fast 720p 8 "FAILME please"
python3 -m videoledger run wan3 720p 6 "mountain lake"
python3 -m videoledger report
rm ledger.db clips/*.mp4
If all three jobs finish, the failure is recorded as charged, and the report prints three rows, your install is sound. Unset the three variables before pointing the tool at the real API.
Common Pitfalls That Quietly Inflate the Bill
- Calling retired preview IDs. The Veo 3.1 preview models on the Gemini API went dark on October 22, 2026, per useapi.net. Use the Runway Dev IDs in prices.py, or Google’s Cloud route, and do not keep an old key in a cron job that retries forever.
- Trusting a frozen price table. Rates change without notice. The 14-day staleness warning exists because a six-week-old table can be wrong by a lot on a 30-second Seedance 2.5 job.
- Forgetting that auto durations bill the maximum first. Gemini Omni Flash 1.1 bills 10 seconds up front when duration is auto, Seedance 2.5 bills 30. The unused part comes back as a refund after the clip finishes, which can briefly dent your balance and confuse a reconcile.
- Comparing unlike settings. A 720p Omni clip against a 1080p Veo clip, or a muted Veo clip against a clip with sound, tells you nothing. Pin the resolution, length and audio setting before you compare.
- Counting only the successes. Moderation failures are charged, and so are retries you fire after a bad take. The ledger counts all of them, and the report divides by the seconds you kept.
- Losing the file. Output URLs are temporary. A clip you did not download is a clip you paid for twice when you regenerate it.
- Using Seedance draft mode as a free preview. Draft mode bills twice, once for the 480p preview at $0.20 per second and again for the enhanced 1080p video at $0.68 per second, and the preview charge stays.
A last habit worth building is to keep a short prompt file under version control, next to the ledger. When a result looks great, you can rerun it six months from now and compare cost on the same input. Our overview of the best text-to-video generator tools shows what else is on the market if a model in your matrix drops out of the running.
Troubleshooting: 12 Errors and Their Fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Set RUNWAYML_API_SECRET before calling the API | Environment variable missing in this shell | Export the key in the same terminal, or add it to your shell profile |
| 400 naming audio, aspect_ratio or resolution on Omni | Request carries a field Omni does not take on Runway Dev | Drop audio, since every clip has sound, and set size and shape with ratio only |
| 400 saying the input video is too long for its mode | Reference, extend and edit each cap the input clip | Trim the clip to the cap listed for that mode in the model guide |
| 400 on an image-to-video call with only a last frame | A first frame is required | Add a first image with position first |
| 400 on ratio or image references in extend mode | An extension keeps the input’s orientation and takes neither | Remove ratio and the image references from the extend request |
| 400 on a duration number in edit mode | An edit returns a clip as long as its source | Send auto for duration |
| Task FAILED with SAFETY.INPUT | Moderation rejected an input | Change the prompt or images before retrying, the task was still charged |
| Task FAILED with SAFETY.OUTPUT | Moderation rejected the generated video | Change the prompt or references, then try again |
| THIRD_PARTY.UNAVAILABLE or THIRD_PARTY.RATE_LIMITED | The model provider had an outage or heavy load | Wait, then retry, and keep the failed job charged until the dashboard says otherwise |
| THIRD_PARTY.INPUT_VALIDATION on an edit that changes speech | Omni does not process speech edits yet | Edit the picture only, or use another model for the speech |
| Download link stops working | Output URLs expire | Download as soon as the task succeeds, as the CLI does |
| Positive gap in the reconcile output | Missing job, a billed failure you forgave, or a shared key | List jobs with status PENDING or SUBMITTED, then check the dashboard task history |
Three more cases deserve a sentence each. A task stuck in THROTTLED is waiting in a queue and has not been rejected, so keep polling. A TimeoutError from the polling loop means your patience ran out and not the task, which keeps running, so poll the stored task ID again later. A BudgetError is the guard doing its job, and you can raise the caps with VL_MAX_JOB_USD and VL_MAX_DAY_USD once you have checked the estimate.
Two validation errors come from the estimator itself. Veo models take only 4, 6 or 8 seconds, so a request for 5 seconds raises a ValueError before any money moves. Wan 3.0 keyframe requests, meaning two images with first and last positions, need a ratio that starts with auto_. The default build_body sends fixed ratios, so use a single first frame with Wan, or add a ratio override for that case.
Advanced Tips: Drafts, Routers, Threads and Exports
Iterate small, finish large. Gemini Omni Flash 1.1 offers a 360p draft size at $0.034 per second. Ten five-second drafts cost 170 credits ($1.70), and one 1080p final adds 75 credits ($0.75), for 245 credits in total. Ten finals at 1080p would cost 750 credits ($7.50). A cheap draft predicts composition and mood, not exact frames, so confirm the winner at full size before you promise anything to a client.
Price the extension path. Omni extends a clip by up to 10 seconds from a source of up to 30 seconds. Extend and edit render at 720p, the extension bills for the footage it adds, and input video costs 1 credit per second. By those rules, adding 10 seconds to a 30-second clip works out to 130 credits ($1.30), which the estimator confirms with the video-in flag. Check it against your first real extension.
Let a router pick, then log what it picked. Runway Dev offers Model Router, a saved config where you set what to optimize for (cost, quality or latency), a maximum cost per generation and the models allowed. You send its configId to the generate endpoint instead of a model ID, and a dryRun flag shows which model it would choose without creating a task. If you adopt it, record the dry-run answer in the ledger and add the resolved model after the task finishes, because your price table needs a real model key to cost a job.
Run jobs in parallel carefully. A ThreadPoolExecutor with two or three workers speeds the matrix up. Give each thread its own sqlite3 connection, because SQLite connections do not cross threads by default, and keep an eye on 429 responses. The submit function already backs off and retries.
Export for finance. The ledger is a plain SQLite file, so a spreadsheet export takes one command.
sqlite3 -header -csv ledger.db \
"SELECT created_at, label, model, tier, seconds, est_usd, status, usable FROM jobs ORDER BY id" > video-spend.csv
Write better prompts with a text model. Audio cues in the prompt change results, and a text model can draft them in bulk. Our Gemini 3.8 Flash API tutorial shows how to wire one in, and the ledger will price the video side of any Gemini AI video generator pipeline you build on top. When Kling 4.0’s developer API ships with published prices, add a ModelSpec for it and rerun the matrix. The Seedance 2.5 and Kling O3 rows will tell you quickly whether the newer model earns its place.
FAQ: Gemini AI Video Generator Costs and Setup
What is Gemini Omni Flash 1.1?
Gemini Omni Flash 1.1 is Google’s multimodal video model, released on August 27, 2026. It makes 3 to 10 second clips with native audio from text, images or video, and it can extend or edit existing clips. Google’s suggested replacement for the Veo 3.1 preview models is this model, per useapi.net.
How much does the Gemini AI video generator API cost per second?
On Runway Dev, Gemini Omni Flash 1.1 costs $0.034 per second at 360p, $0.10 at 720p, $0.15 at 1080p and $0.30 at 4K, with sound included at every size. Reference images add 1 credit each and input video adds 1 credit per second. Prices were current on October 24, 2026.
Is Veo 3.1 still available?
Google shut down the Veo 3.1 preview models on the Gemini API on October 22, 2026, according to useapi.net. Runway Dev still lists Veo 3.1 at $0.40 per second with audio and $0.20 without, and Veo 3.1 Fast at $0.15 and $0.10. Google’s own route moved to Agent Platform on Google Cloud.
Do I need a Google Cloud account to use Gemini Omni Flash 1.1?
Not through Runway Dev. A Runway Dev account, an API key and a credit balance are enough, with no Google Cloud project, billing account or region to configure.
Are failed generations charged?
A request rejected with a 400 before a task exists costs nothing. A task that fails input moderation (SAFETY.INPUT codes) is still charged. Other failures are not documented in the guides used here, so the ledger keeps them charged until you check the dashboard and forgive them.
Can Kling 4.0 go into the same ledger?
Not yet. Kuaishou launched Kling 4.0 on October 15, 2026 and described developer API access as forthcoming, without prices. Kling O3 is already in the matrix. Once Kling 4.0 has an API and a price list, one new entry in prices.py adds it.
Where do I find more AI video model coverage?
Our AI and machine learning section tracks new video, image and text models as they ship, and the ledger gives you a fixed yardstick to price each new arrival.





