Skip to main content
Use polling as the baseline for video generation because every async job returns a task ID that you can query. Add webhooks only when the selected video endpoint supports callback URLs, and keep polling as the source of truth for missed or provider-specific callback delivery.

Create a video task

The following request creates a minimal video task and stores the returned ID. Add duration, resolution, or callback fields only when the selected model page documents those fields.
curl https://api.cometapi.com/v1/videos \
  -H "Authorization: Bearer $COMETAPI_KEY" \
  -F "model=doubao-seedance-2-0" \
  -F "prompt=A cinematic shot of a paper airplane crossing a desk"
The response includes a task ID and status:
{
  "id": "task_example",
  "task_id": "task_example",
  "object": "video",
  "model": "doubao-seedance-2-0",
  "status": "queued",
  "progress": 0,
  "created_at": 1779872000
}

Poll status

The following request checks the video task status:
curl https://api.cometapi.com/v1/videos/task_example \
  -H "Authorization: Bearer $COMETAPI_KEY"
The response changes as the task progresses. Completed responses can include video_url when the model adapter has a result URL; otherwise use model-specific result fields or the /v1/videos/{id}/content content route when that model supports proxied downloads.
{
  "id": "task_example",
  "object": "video",
  "model": "doubao-seedance-2-0",
  "status": "completed",
  "progress": 100,
  "completed_at": 1779872300,
  "video_url": "<generated-video-url>"
}

Receive a webhook

CometAPI does not define one universal callback payload for every video model. Treat callbacks as provider-specific pass-through events, store the raw body, and reconcile final state with polling. The following Express handler accepts a video callback and stores the event:
import express from "express";

const app = express();
app.use(express.json({ limit: "2mb" }));

app.post("/cometapi/video-webhook", async (request, response) => {
  const event = request.body;

  console.log("Task ID:", event.task_id || event.id);
  console.log("Status:", event.status);

  response.status(200).json({ received: true });
});

app.listen(3000);
A callback payload commonly includes task identity and status fields, but exact nesting depends on the selected model or provider:
{
  "task_id": "task_example",
  "status": "completed",
  "progress": 100,
  "result": {
    "video_url": "https://example.com/result.mp4"
  }
}

Common errors

ErrorFix
Lost callbackPoll by task ID until your app has stored a terminal state.
Duplicate callbackMake callback handling idempotent by task ID.
Callback rejectedReturn a 2xx response quickly, then process the job in the background.
Provider-specific payload mismatchStore the raw callback payload and normalize it in your app.
Missing video_urlTreat video_url as optional and use polling plus model-specific result fields or /v1/videos/{id}/content when available.
Last modified on May 28, 2026