Video generation Async tasks OpenAI compatible Media proxy API integration

Video Generation API Integration: Tasks, Polling, and Content

Integrate video generation with create/poll/content flows, edits and extensions awareness, webhooks, duration bounds, and authorized media delivery.

AveMujica API 9 min read

Video generation API integration is an asynchronous media workflow: you create a job, wait until it completes or fails, then download bytes through a content URL that may expire and must be authorized. In OpenAI-compatible gateways that follow the videos surface, the core routes are POST /v1/videos, GET /v1/videos/{task_id}, and GET /v1/videos/{task_id}/content. Official OpenAI docs now center edits, extensions, characters, webhooks, and Batch alongside create/retrieve—while the older remix endpoint is deprecated for new work. This article shows how to integrate those flows safely using repository-visible behavior and official documentation, without invented latency or price claims.

Date context (2026-07-15): OpenAI announced deprecation of the Videos API and Sora 2 model aliases with shutdown on 2026-09-24 (OpenAI deprecations). Do not start a new long-lived product solely on Sora 2. If you still operate Sora traffic through a gateway, treat this as a migration window: keep create/poll/content working, avoid building new UX around remix-only flows, and plan dual-vendor aliases now.

Direct answer: treat video as a task, not a chat completion

# 1) create
curl -sS https://YOUR_GATEWAY/v1/videos \
  -H "Authorization: Bearer $GATEWAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "YOUR_VIDEO_MODEL_ALIAS",
    "prompt": "Slow aerial push-in over a misty cedar forest at dawn",
    "seconds": "4",
    "size": "1280x720"
  }'

# 2) poll (prefer webhooks when the upstream supports them; see below)
curl -sS https://YOUR_GATEWAY/v1/videos/TASK_ID \
  -H "Authorization: Bearer $GATEWAY_KEY"

# 3) download when status=completed
curl -sS https://YOUR_GATEWAY/v1/videos/TASK_ID/content \
  -H "Authorization: Bearer $GATEWAY_KEY" \
  -o forest.mp4

Statuses you should handle explicitly (as modeled in this codebase’s video DTO) include queued, in_progress, completed, and failed. Fields commonly present on the task object include id, model, status, progress, created_at, completed_at, expires_at, seconds, size, optional error, and lineage fields such as remixed_from_video_id when a derivative job exists.

Always re-check parameter matrices on the vendor’s current docs—OpenAI’s video generation guide and Videos API reference. Other vendors (Google Veo / Gemini video surfaces, Runway, Luma, Kling, ByteDance/Volcengine) publish first-party references that may use different job shapes. A gateway may also expose vendor-specific routes such as /v1/video/generations or Kling-style /kling/v1/videos/* depending on channel adapters. Prefer the normalized OpenAI-style task API in application code when the gateway supports it.

Official OpenAI surface vs what this gateway registers today

Capability (OpenAI docs, 2026-07-15)Official route / eventGateway reality in this repository
Create videoPOST /v1/videosSupported as the primary task submit path for OpenAI-style adapters
Retrieve / pollGET /v1/videos/{id}Supported
Download contentGET /v1/videos/{id}/contentSupported with ownership checks
Edit existing videoPOST /v1/videos/edits (preferred; remix deprecated)Not normalized as a first-class gateway route set today
Extend videoPOST /v1/videos/extensionsNot normalized today
CharactersPOST /v1/videos/characters + characters on createNot normalized today
Legacy remixPOST /v1/videos/{id}/remix (deprecated for new integrations)Still detected/registered on remix-style paths for existing Sora-style adapters
Webhooksvideo.completed, video.failedUpstream OpenAI feature; gateway clients should still implement poll fallback
BatchBatch API lines to POST /v1/videosUpstream feature; not a substitute for product job tables

State this gap explicitly in architecture reviews: product code that only knows create/poll/content will survive multi-vendor routing; product code that hard-depends on remix as the “edit” button will fight both OpenAI’s deprecation and incomplete gateway normalization of edits/extensions/characters.

Why video integration is harder than image integration

ConcernImages (typical)Videos (typical)
Latencysecondstens of seconds to minutes
API shapeoften single responsecreate + wait + content
Payload sizeKB–MBmulti-MB easily
Failure modesmoderation / bad sizelong timeouts, partial progress, expired content
Cost riskn and resolutionduration, resolution, edit/extension chains
AuthZ after createoptionalmandatory on content download

If you apply chat-completion timeouts (for example 60s hard cancel without polling), video integration will look “broken” even when jobs succeed later. Design a job table in your application from day one.

Recommended application data model

video_jobs
  id (your id)
  provider_task_id
  model
  prompt_hash / prompt_text (policy-dependent)
  status
  progress
  seconds
  size
  billing_owner_type / billing_owner_id
  team_id / project_id (nullable)
  content_expires_at
  storage_key (nullable until persisted)
  source_task_id (edit/extension/remix lineage)
  last_error
  created_at / updated_at

Workers update status and progress from poll or webhook. On completed, a download worker fetches /content once, stores to object storage, and sets storage_key. On failed, surface error.message / error.code without infinite retries.

Duration and billing bounds

Duration is a billing multiplier. This repository caps user-supplied video duration with MaxTaskDurationSeconds = 3600 and rejects out-of-range seconds during validation. Your product should set a tighter UX maximum (for example 4–12 seconds for previews) even if the platform allows more. Official OpenAI create examples commonly use discrete second buckets (including longer 16/20-second variants on current Sora docs); never invent free durations.

Integration rules:

  • parse seconds carefully; the DTO may carry it as a string
  • never trust client-provided duration for price display without server re-validation
  • pre-consume quota against the billing owner (user or team), not only the actor
  • settle or refund when the task fails after pre-consume
  • apply the same discipline to edit/extension/remix chains so users cannot amplify cost accidentally

See multi-tenant LLM gateway architecture for owner vs actor separation and LLM usage logs and cost tracking for attribution fields.

Waiting strategy: webhooks first, polling as fallback

OpenAI’s video guide recommends polling about every 10–20 seconds with backoff, and prefers webhooks (video.completed / video.failed) when available. Download URLs are documented as short-lived (about 1 hour after generation on OpenAI’s guide)—copy to your storage promptly.

Practical defaults for product workers:

  1. Register webhooks when the provider and your gateway/ops path support signed delivery.
  2. Poll as the safety net: start near 10s, add jitter, back off toward 20–30s under load or repeated in_progress.
  3. Honor Retry-After when present.
  4. Cap total wait with a hard timeout and a terminal expired/failed local state.
  5. Never poll every few hundred milliseconds per browser tab.
type VideoTask = {
  id: string;
  status: "queued" | "in_progress" | "completed" | "failed" | string;
  progress?: number;
  expires_at?: number;
  error?: { message?: string; code?: string };
};

async function sleep(ms: number) {
  await new Promise((r) => setTimeout(r, ms));
}

async function pollVideo(taskId: string, timeoutMs = 15 * 60_000): Promise<VideoTask> {
  const started = Date.now();
  let interval = 10_000;
  while (Date.now() - started < timeoutMs) {
    const res = await fetch(`${process.env.GATEWAY_BASE}/v1/videos/${taskId}`, {
      headers: { Authorization: `Bearer ${process.env.GATEWAY_KEY}` },
    });
    if (res.status === 429) {
      const ra = Number(res.headers.get("retry-after"));
      await sleep(Number.isFinite(ra) ? ra * 1000 : interval);
      interval = Math.min(interval + 5_000, 30_000);
      continue;
    }
    if (!res.ok) throw new Error(await res.text());
    const task = (await res.json()) as VideoTask;
    if (task.status === "completed" || task.status === "failed") return task;
    const jitter = Math.floor(Math.random() * 2_000);
    await sleep(interval + jitter);
    interval = Math.min(Math.floor(interval * 1.2), 30_000);
  }
  throw new Error("video poll timeout");
}

Tune from measured completion times for your models and region—not from another team’s blog post.

AuthZ on content download and workbench scope

Creating a task is not the same as being allowed to stream its bytes later. In multi-tenant gateways, /v1/videos/{id}/content must enforce ownership: the same user/team/project scope that created the job (or an explicit share policy). Cross-tenant task id guessing must fail closed.

Workbench paths in this repository:

  • UI: video workbench
  • personal APIs under /api/user/video-workbench
  • team APIs under /api/teams/:team_id/video-workbench with project-scoped actions
  • distinct grants such as generate videos vs manage video assets

This is different from the personal-only image workbench. If object storage is required for durable workbench playback, configure it before promising permanent history. Fallback play URLs that point at /v1/videos/{id}/content still need live authZ and may be subject to expires_at.

Edits, extensions, characters, and legacy remix

Official OpenAI guidance for new integrations:

  • Edits: POST /v1/videos/edits with a prompt and source video for targeted changes; remix is deprecated for this job.
  • Extensions: POST /v1/videos/extensions to continue a completed clip (stitched total duration; characters not supported on extensions per OpenAI’s guide).
  • Characters: POST /v1/videos/characters, then reference character IDs on create for consistency.
  • Batch: offline queues of POST /v1/videos JSON lines; batch outputs have their own retention window in OpenAI docs.

Integration tips that still apply on any derivative job (including legacy remix still present in this gateway):

  • only allow derivative jobs when the caller can read the source task
  • copy tenancy fields from the source; do not orphan derivatives under the wrong project
  • show users that edit/extension/remix is a new billable job, not a free tweak
  • persist lineage for support and moderation
  • feature-flag any vendor-only metadata bag; validate duration/size with global bounds

Until the gateway normalizes edits/extensions/characters, keep those calls behind explicit adapter flags or native upstream clients—do not pretend remix is the modern OpenAI path.

Persistence policy

Assume:

  1. upstream content URLs and even gateway temporary proxies can expire (expires_at is a first-class field; OpenAI documents ~1 hour for interactive downloads)
  2. product pages need your object keys
  3. thumbnails are derived after download, not scraped ad hoc from provider CDNs
  4. deletion must be possible when a team leaves or a user exercises retention rights

Safe pipeline:

completed → authorized content fetch → virus/type sniff if required → object storage put → DB storage_key → serve via your CDN with app auth.

Skipping storage “until we need it” is how demos become incident tickets—especially painful if the upstream Videos API itself is sunsetting.

Observability and support

Log at least gateway request id / task id, model alias, seconds, size, status transitions, billing owner and actor, webhook vs poll path, and final error code. Dashboards should separate video failure classes from chat failures. Support tooling should open the task and usage log without downloading the full MP4 into the ticket system.

Alternate routes you may encounter

Depending on channel adapters, operators might also enable:

  • /v1/video/generations style endpoints
  • Kling /kling/v1/videos/*
  • Gemini/Vertex Veo task adapters
  • other vendor task APIs behind Jimeng or similar routes

Application teams should not hard-code every vendor path. Prefer one normalized client (/v1/videos + stable aliases) and let the gateway map channels. When a feature only exists on a vendor path (motion brushes, cinema presets), isolate it behind a feature flag and document the non-portable contract.

Related reading

Sources

Operational Q&A

Can I block the HTTP request until the video is done?

Only for short internal tools. Product UIs should create a job record and use webhooks or polling. Long synchronous requests amplify gateway and load balancer timeouts.

What polling interval should I use?

Default near 10 seconds with jitter, back off toward 20–30 seconds, honor Retry-After, and prefer video.completed / video.failed webhooks when available. OpenAI’s guide cites roughly 10–20 second polling.

Why did content download fail after completed status?

Common causes: expired content, wrong auth scope, task owned by another project, or storage/proxy misconfiguration. Check expires_at and tenancy fields before retrying generation.

Should new products use remix?

No as the primary edit path. Official OpenAI docs direct new edit work to POST /v1/videos/edits; remix is deprecated. This gateway may still accept remix-style routes for compatibility—treat that as a legacy bridge, not a greenfield design.

Do I need the video workbench if I already call /v1/videos?

No for pure backend pipelines. Yes when humans need shared history, presets, or team-scoped asset management in the UI.

First worker to ship

Implement a single worker that creates a short video via a stable non-Sora alias if you have one, or a clearly temporary Sora alias with a written migration date before 2026-09-24. Poll with 10s+jitter (webhook optional), download /content once, upload to object storage, and prove that a second team’s key cannot download the task id. Only then wire the happy path into product UI.