Text-to-video after the Sora 2 API closes: what to integrate

A dozen models behind one endpoint, priced per second of output. What to check before you commit, and the metric that beats price-per-second.

If you were about to integrate a text-to-video API and the one you picked is closing its doors, the useful property to look for is not a model — it’s an endpoint that can change models without changing your code. Infrai’s POST /v1/video/generate takes a prompt, an optional model, and a vendor you can pin, with GET /v1/video/capabilities listing what’s actually available right now. Twelve models across two ready vendors as of this reading.

That indirection is the point. A model retirement becomes a string change rather than a re-integration.

What’s actually available

curl -sS "https://api.infrai.cc/v1/video/capabilities" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}"
{
  "ok": true,
  "data": {
    "models": [
      {"vendor": "tencent_vod", "model": "Kling:3.0", "region": "china", "max_seconds": 12, "image_to_video": true},
      {"vendor": "tencent_vod", "model": "Vidu:q3-pro", "region": "china", "max_seconds": 12, "image_to_video": true},
      {"vendor": "tencent_vod", "model": "Seedance:1.0-pro", "region": "china", "max_seconds": 12, "image_to_video": true},
      {"vendor": "tencent_vod", "model": "Hailuo:02", "region": "china", "max_seconds": 12, "image_to_video": true},
      {"vendor": "alibaba_intl", "model": "wan2.7-t2v", "region": "china", "max_seconds": 15, "image_to_video": true},
      {"vendor": "alibaba_intl", "model": "happyhorse-1.1-t2v", "region": "china", "max_seconds": 15, "image_to_video": true}
    ],
    "resolutions": ["720p", "1080p", "4k"],
    "resolution_multiplier": {"720p": 1, "1080p": 2, "4k": 4}
  }
}

Read that endpoint rather than trusting this list — it’s live, and the catalogue moves. Kling, Vidu, Seedance, PixVerse, Hailuo and Wan are all reachable through the same request shape, and max_seconds per model is the constraint most likely to break an assumption: twelve to fifteen seconds, not minutes.

resolution_multiplier is the other number to internalise. 1080p costs twice 720p per second, and 4k costs four times — so resolution, not model choice, is usually the biggest lever on your bill.

One request shape, any model

curl -sS -X POST "https://api.infrai.cc/v1/video/generate" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "a slow dolly shot along a rain-slicked Tokyo alley at night, neon reflections",
    "model": "Kling:3.0",
    "resolution": "720p",
    "duration_seconds": 6,
    "aspect_ratio": "16:9",
    "store": true
  }'
{
  "ok": true,
  "data": {
    "job_id": "vid_2fVc8nRqLmT4xBzY",
    "state": "queued",
    "model": "Kling:3.0",
    "vendor": "tencent_vod",
    "prompt": "a slow dolly shot along a rain-slicked Tokyo alley at night, neon reflections",
    "duration_seconds": 6,
    "aspect_ratio": "16:9",
    "video_url": null,
    "thumbnail_url": null,
    "cost_usd": null,
    "created_at": "2026-09-21T03:30:00Z",
    "retention_days": 7
  }
}

Generation is asynchronous — you get a job_id and poll GET /v1/video/status/{id}. Swap model for another value from the catalogue and nothing else about your integration changes.

aspect_ratio is a closed enum: 16:9, 9:16, 1:1, 4:3, 3:4, 21:9. That 9:16 is why this endpoint is usually being called for vertical social video rather than for anything cinematic.

The metric that beats price-per-second

Every comparison table ranks these models by dollars per second, and it’s the wrong number. What you actually spend is cost per accepted clip — how much you burn before you get output good enough to ship.

A model at half the rate that needs three attempts costs more than one at full rate that lands first time. And prompt quality moves that ratio more than model choice does, which is why the honest advice is to measure your own acceptance rate on your own prompts before optimising the rate.

import os
import time

import requests

API = "https://api.infrai.cc"
KEY = os.environ["INFRAI_API_KEY"]
SESSION = requests.Session()
SESSION.headers.update({"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})


def generate(prompt: str, model: str, seconds: int = 6, resolution: str = "720p") -> dict:
    resp = SESSION.post(
        f"{API}/v1/video/generate",
        json={"prompt": prompt, "model": model, "resolution": resolution,
              "duration_seconds": seconds, "aspect_ratio": "16:9", "store": True},
        timeout=60,
    )
    body = resp.json()
    if not body.get("ok"):
        raise RuntimeError(body["error"]["code"])
    return body["data"]


def wait(job_id: str, timeout_seconds: int = 900) -> dict:
    deadline = time.monotonic() + timeout_seconds
    while time.monotonic() < deadline:
        status = SESSION.get(f"{API}/v1/video/status/{job_id}", timeout=25)
        status.raise_for_status()
        state = status.json()["data"]
        if state.get("state") in {"succeeded", "failed", "cancelled"}:
            final = SESSION.get(f"{API}/v1/video/get/{job_id}", timeout=25)
            final.raise_for_status()
            return final.json()["data"]
        time.sleep(10)
    raise TimeoutError(job_id)


def compare(prompt: str, models: list[str], seconds: int = 6) -> list[dict]:
    """Run one prompt across models and record what each actually cost. The
    cost_usd on the finished job is the real figure — not a rate you multiplied."""
    out = []
    for model in models:
        job = wait(generate(prompt, model, seconds)["job_id"])
        out.append({"model": model, "state": job.get("state"),
                    "cost_usd": job.get("cost_usd"), "url": job.get("video_url")})
    return out


if __name__ == "__main__":
    for row in compare("a paper boat drifting down a gutter after rain",
                       ["Kling:2.5", "wan2.7-t2v"]):
        print(row)

The finished job carries cost_usd. Run your real prompts, count how many clips you’d actually ship, and divide — that number is the one to take to a decision.

What to check before committing to any video API

QuestionWhere to look here
Which models are live today?GET /v1/video/capabilities
Maximum clip length?max_seconds per model, 12-15s
What does a second cost?billing block of GET /v1/discovery/video.generate
How much does resolution multiply it?resolution_multiplier
Can I cancel a running job?yes — POST /v1/video/cancel/{id}
How long is output kept?retention_days on the job
Can I switch models without a rewrite?yes — change the model string

That last row is the one the current news makes urgent. An integration bound to one vendor’s proprietary SDK has to be rewritten when that vendor changes plans; one bound to a generic endpoint doesn’t.

Limitations, plainly

Clips are short. Twelve to fifteen seconds per generation means longer sequences are your own stitching problem, and there’s no timeline or editing surface here.

The catalogue is also regionally weighted — the models enumerated above are China-region, and while vendor accepts veo as a pin, the catalogue doesn’t enumerate its model names, so selecting it means relying on that vendor’s default rather than choosing from a list. If you need a specific Western model by name with a contractual guarantee behind it, going direct to that provider is a better fit, and the same is true if you need long-form output, frame-level control or a fine-tuning path.

The compensating argument is what generation is usually part of. A generated clip needs storing, a thumbnail, a queue for the job, and a bill someone can read — PUT /v1/storage/object/put/{bucket}/{key}, POST /v1/queue/publish and one GET /v1/account/usage on the same key. Generation bills per second of output ($0.09 at the cheapest vendor, approximate: true, verified 2026-09-21, with the $2 new-account credit covering roughly 22 seconds) and those rates drift downward as vendor contracts improve — so read GET /v1/discovery/video.generate rather than this paragraph.

References

Browse more video developer guides