88API88API
User GuideAI ApplicationsAPI ReferenceHelp & Support
Video

Complete Video API Standard

Unified 88API video generation standard covering references, keyframes, callbacks, polling, downloads, and errors.

This is the customer-facing standard for asynchronous video generation through 88API. Always use the public 88API model name and the public task id returned by the API.

All current public video models share the create, polling, and download lifecycle. Webhooks are available only for models that explicitly support them. Reference-media fields are model-family specific, however; do not apply one family's media example to every model.

Updated 2026-10-10. Model-specific parameters and examples now live on separate brand pages; see Brand pages. Reuse an accessible HTTPS reference directly, or upload a local file first. Then create a task, retrieve its result URL, and use that URL for playback, download, or another model's reference input. Storage support does not imply model support or automatic transcoding.

Task API (2026-09-15): SD2.0/SD2.5, official Seedance and Mini, Seedance Official Relay, Wan3, Kling Turbo, and MiniMax H3 use POST /v1/videos. Save the returned id and poll GET /v1/videos/{id}. Do not depend on task_id or send callback_url: new requests for these models currently support polling only, and a nonempty callback URL is rejected before submission. Model names and authentication are unchanged. The old /v1/video/generations endpoint is retained for transitional use on selected models only; new integrations must use /v1/videos.

Endpoints

PurposeMethod and path
Create a videoPOST https://api.88api.ai/v1/videos
Retrieve task statusGET https://api.88api.ai/v1/videos/{id}
Request an upload permitPOST https://api.88api.ai/v1/media/uploads
Upload file bytesPUT {upload_url} with the returned upload headers
Download the resultGET {url} using the completed response's URL
Legacy archived-video redirectGET https://api.88api.ai/v1/videos/{id}/content — not universal for every result
Receive a terminal callbackSet callback_url only for models that support webhooks
Authorization: Bearer sk-your-api-key
Content-Type: application/json
Accept: application/json

Keep API keys on your server. Never embed them in browser, mobile, or desktop client code.

Creating/querying tasks and requesting upload permits require Bearer authentication at api.88api.ai. Uploads use only the scoped X-Media-Upload-Token and MIME header returned in the permit. Public assets.88api.ai media reads require no Bearer or cookies. Never forward your API key to media URLs, official result hosts, or redirect destinations. Anyone holding a public URL can read the media during its lifetime; task details remain authenticated.

Basic text-to-video request

curl --request POST 'https://api.88api.ai/v1/videos' \
  --header 'Authorization: Bearer sk-xxxx' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "model": "SD2.5",
    "resolution": "720p",
    "prompt": "A silver sports car driving through a neon city at night, cinematic tracking shot",
    "duration": 8,
    "size": "16:9"
  }'

You may use an integer duration or a string seconds, for example "8".

Common request fields

FieldTypeRequiredDescription
modelstringyesExact public model name shown by 88API
promptstringconditionalPrompt; only selected reference-capable models allow it to be empty
negative_promptstringnoNegative prompt for multi-reference models; Veo uses metadata.negativePrompt
durationintegernoDuration in seconds; mutually exclusive with seconds
secondsstringnoDuration as a string
sizestringnoAspect ratio or size, such as 16:9 or 1280x720
imagestringnoOne reference image URL
imagesstring[]noReference image URLs; recommended image form
video / videosstring / string[]noGemini Omni requires these top-level fields; multi-reference families use metadata.referenceVideos
seedintegernoRandom seed
generate_audiobooleannoGenerate audio on models that explicitly support it
camera_controlobjectnoModel-specific camera control
callback_urlstringnoOnly for models that support webhooks; unavailable for the polling models listed above
metadataobjectnoVideo/audio references, keyframes, and other extensions

Some models fix the output resolution in the public model name; resolution cannot override those. Others (for example SD, official Seedance, Kling, Google, MiniMax, Seedance Official Relay, Wan and Grok) select it with resolution. Field names also differ by brand; follow the brand pages.

Reference media

Reference images

Use the top-level images array for consistent behavior across current models:

{
  "model": "SD2.5",
  "resolution": "720p",
  "prompt": "Keep the same character identity and clothes, walking by the sea",
  "duration": 10,
  "size": "16:9",
  "images": [
    "https://cdn.example.com/character-front.jpg",
    "https://cdn.example.com/character-side.jpg"
  ]
}

Reference videos and audio for multi-reference models

For the SD, Seedance, H3 and Kling families that support multiple references, put video and audio references in metadata (Wan recommends the media array; see Wan Video API):

{
  "model": "SD2.5",
  "resolution": "720p",
  "prompt": "Use the product appearance, camera motion, and sound rhythm from the references",
  "duration": 12,
  "size": "16:9",
  "images": ["https://cdn.example.com/product.jpg"],
  "metadata": {
    "referenceVideos": [
      "https://cdn.example.com/camera-motion.mp4"
    ],
    "referenceAudios": [
      "https://cdn.example.com/sound-reference.mp3"
    ]
  }
}

Do not submit multiple aliases for the same media type. For example, do not send both top-level images and metadata.referenceImages; compatibility aliases are selected, not guaranteed to be merged.

For MiniMax H3 reference images, do not use top-level images; use metadata.referenceImages as described in MiniMax Video API.

First and last frames

{
  "model": "seedance-2.5官方版",
  "prompt": "Smoothly transition from the morning frame to the night frame",
  "duration": 10,
  "size": "16:9",
  "metadata": {
    "firstFrame": "https://cdn.example.com/start.jpg",
    "lastFrame": "https://cdn.example.com/end.jpg"
  }
}

lastFrame requires firstFrame. For models marked as exclusive below, keyframes cannot be mixed with ordinary image, video, or audio references. Veo instead uses ordered images with metadata.video_mode, as described in Google Video API.

Media URL requirements

  • Use directly downloadable public HTTPS URLs for reference video/audio; local paths, blob: and data:video/... are not a portable reference-video protocol. Upload local files first.
  • URLs must not require cookies, login state, Referer, or custom headers.
  • Do not use localhost, private IPs, container hostnames, or intranet-only URLs.
  • Keep URLs valid for at least 60 minutes after task creation.
  • Recommended formats: JPG/PNG/WebP, MP4/MOV, and MP3/WAV.
  • Keep each reference video below 50 MB and each reference audio file below 15 MB when possible.
  • Only submit material you are authorized to use.

Local media: permit, upload, reference

Send JSON to POST /v1/media/uploads with your server-side Bearer key:

{
  "size": 1234567,
  "mime_type": "video/mp4",
  "sha256": "<SHA-256 of the raw file encoded as unpadded Base64URL>"
}

size is an integer from 1 to 100,000,000 bytes. sha256 encodes the 32-byte digest, not its hexadecimal string. Accepted MIME types: video/mp4, video/webm, video/quicktime; audio/mpeg, audio/wav, audio/ogg, audio/mp4, audio/flac; image/png, image/jpeg, image/webp, image/gif. Model-specific size, duration, codec and reference limits still apply.

The HTTP 200 receipt contains url, upload_url, method: "PUT", headers (Content-Type and X-Media-Upload-Token), and expires_at (Unix seconds). This expiry is the approximately 10-minute upload permit deadline, not the media retention deadline. The URL is not ready until the upload succeeds.

Send the original binary file to upload_url with the returned method and headers. Supply the exact Content-Length and MIME; browsers sending File/Blob calculate the length automatically. Do not send multipart/form-data, JSON Base64, your API key, or an unknown-length chunked stream.

curl --request PUT "$UPLOAD_URL" \
  --header 'Content-Type: video/mp4' \
  --header "X-Media-Upload-Token: $UPLOAD_TOKEN" \
  --data-binary @reference.mp4

HTTP 201 confirms storage and returns url, size, mime_type, and expires_at. This second expiry is the media retention deadline, 30 days from object upload. Use the returned URL in the target model's reference field only after success. The upload service does not transcode, compress or extract frames. Repeated PUTs cannot overwrite the object (409); after an ambiguous upload, HEAD the permit's URL before allocating another object. Browser apps should obtain permits through their own backend, without exposing a long-lived API key.

Current public model capabilities

Brand pages

Parameter names and media fields differ between brands. Read the brand page before integrating:

Brand pageModels
Seedance Official Editionseedance-2.5官方版, seedance-2.0官方版, seedance-2.0-fast官方版, seedance-2.0-mini官方版
SD Cloud EditionSD2.5, SD2.0
Seedance Official RelaySeedance 2.0 Mini (multi-resolution), Seedance 2.0 (multi-resolution), Seedance 2.0 Fast (multi-resolution), Seedance 2.5 (multi-resolution)
Googleveo-3.1, veo-3.1-fast, gemini-omni-flash, gemini-omni-flash-1.1
Klingkling-3.0-turbo
MiniMaxminimax-h3 (same as minimax_h3)
Wanwan3.0-video, wan3.0-video-prime
Grokgrok-imagine-video, grok-imagine-video-1.5

Capability overview

Public modelDurationRatiosOutputImage/video/audio limitsKeyframesAudio control
seedance-2.5官方版4–30s, default 4s16:9, 9:16, 1:1, 4:3, 3:4, 21:9720P30 / 10 / 10yes, exclusivenot configurable
seedance-2.0官方版4–15s, default 4ssame as above720P9 / 3 / 3yes, exclusivenot configurable
seedance-2.0-fast官方版4–15s, default 4ssame as above720P9 / 3 / 3yes, exclusivenot configurable
seedance-2.0-mini官方版4–15s, default 5ssame as above480P/720P, default 480P9 / 3 / 3not published; omitnot configurable
SD2.54–30srequired: 16:9, 9:16, 1:1, 4:3, 3:4, 21:9480P/720P/1080P, default 480P30 / 10 / 10yes, exclusivegenerate_audio
SD2.04–15s1:1 (default), 21:9, 16:9, 9:16, 3:4, 4:3480P/720P/1080P/4K, default 480P9 / 3 / 3; 12 totalyes, exclusivenot configurable
Seedance 2.0 Mini (multi-resolution)4–15s, default 5s; -1 allowedadaptive (default), 16:9, 4:3, 1:1, 3:4, 9:16, 21:9480P/720P9 / 3 / 3yes, exclusiveaudio on by default; can be turned off
Seedance 2.0 (multi-resolution)4–15s, default 5s; -1 allowedsame as above480P/720P/1080P/4K9 / 3 / 3yes, exclusiveaudio on by default; can be turned off
Seedance 2.0 Fast (multi-resolution)4–15s, default 5s; -1 allowedsame as above480P/720P9 / 3 / 3yes, exclusiveaudio on by default; can be turned off
Seedance 2.5 (multi-resolution)4–30s, default -1 (automatic)same as above480P/720P/1080P30 / 10 / 10yes, exclusiveaudio on by default; can be turned off
veo-3.1-fast4/6/8s; reference images 8s; extension adds 7s16:9, 9:16720P/1080P/4K3 / 0 / 0first frame, first and last frames, reference imagesnative audio; generate_audio can turn it off
veo-3.14/6/8s; reference images 8s; extension adds 7s16:9, 9:16720P/1080P/4K3 / 0 / 0first frame, first and last frames, reference imagesnative audio; generate_audio can turn it off
gemini-omni-flash3–10s16:9, 9:16720P10 / 1 / 0first frame, first and last framesno
gemini-omni-flash-1.13–10s; extension adds about 10s16:9, 9:16720P/1080P/4K10 / 3 / 0first frame, first and last framesno
kling-3.0-turbo4–15s16:9, 9:16, 1:1720P/1080P/2K/4K, default 720P30 (including keyframes) / 10 / 0yes, can be combined with reference imagesgenerate_audio
minimax-h34–15s, default 5s16:9 (default), 9:16, 1:1, 4:3, 3:4, 21:9768P/1080P/2K (upscaled), default 768P9 / – / 3yes, exclusivealways includes audio; not configurable
wan3.0-video2–30s, default 5s; -1 allowedadaptive (default), 16:9, 9:16, 1:1, 4:3, 3:4, 21:9480P/720P/1080P, default 1080P10 / 5 / 5 (in the media array)yes, exclusivenot configurable
wan3.0-video-primesame as abovesame as abovesame as abovesame as abovesame as abovenot configurable
grok-imagine-video1–15s, default 8s16:9, 9:16, 1:1, 4:3, 3:4, 3:2, 2:3 (via aspect_ratio)480P/720P1 / 0 / 0nono
grok-imagine-video-1.51–15s, default 8ssame as above480P/720P/1080P1 / 0 / 0nono

Available models and account access may change. Use the live model list for discovery.

Model-specific rules that are easy to get wrong (see the brand pages above for the full rules):

  • Wan wan3.0-video*: send first frames, last frames and references in the media array (recommended). metadata.firstFrame/lastFrame/referenceImages/referenceVideos/referenceAudios (snake_case forms too), a top-level firstFrame/first_frame/lastFrame/last_frame and top-level images (1st = first frame, 2nd = last frame, at most 2) are also converted to media. Material fields that cannot be read return 400.
  • Grok treats aspect_ratio and ratio as equivalent; if you send several ratio fields, their values must match. At most one input image.
  • minimax-h3 takes reference images in metadata.referenceImages and first/last frames in metadata.firstFrame/lastFrame; the meaning of top-level images varies with the assigned route, so do not rely on it. 480P and 4K have been retired.
  • Veo and Gemini Omni take reference videos in top-level video/videos, not metadata.referenceVideos.
  • Seedance Official Relay also accepts the singular image, input_reference, video and audio fields as reference media, and the same media sent twice counts once; a size that conflicts with ratio, aspect_ratio or resolution returns 400. The group also supports the virtual portrait library, 2.5 drafts, last frames, web search, cancelling queued tasks and official task IDs; see Seedance Official Relay.
  • SD2.5 requires an explicit ratio and does not support auto.
  • Official Seedance, SD2.5, SD2.0 and kling-3.0-turbo each sell one model name; resolution selects and bills the tier. Without it the lowest tier is used, and an unsupported tier returns 400. The earlier names with a tier suffix (such as SD2.5 720P or SD2.0 4K) still work at the fixed tier in their name, but are no longer listed in the model square. The Kling names with a tier suffix have been retired. Uploading cannot add unsupported model capabilities.

Create response

{
  "id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "SD2.5",
  "status": "queued",
  "progress": 0,
  "created_at": 1788249600
}

Save id. The standard interface does not guarantee task_id; new integrations must use id. HTTP 200 means the task was accepted, not that generation has finished.

Polling

curl 'https://api.88api.ai/v1/videos/task_xxx' \
  --header 'Authorization: Bearer sk-xxxx'
StatusMeaningAction
queuedwaitingkeep waiting
in_progressgeneratingkeep waiting
completedfinisheduse the returned result url
failedfailedinspect error.code and error.message
unknownunknownretry briefly, then contact support if persistent

Wait 3–5 seconds before the first query and poll every 10–15 seconds. Interactive clients may stop waiting after 15 minutes, persist the task ID and resume querying later or accept a webhook. A client deadline does not cancel, fail or refund the task; per-request HTTP timeouts and server task deadlines are separate. Use status, not progress alone. Never create a duplicate simply because polling timed out.

On completion, read url (compatible aliases: video_url, result_url). Archived results use assets.88api.ai; eligible official URLs remain unchanged. Save the returned address rather than constructing a content path.

Webhooks

This section applies only to models with explicit webhook support. Google video models (Veo, Gemini Omni) and the SD, official Seedance, Seedance Official Relay, Wan3, Kling Turbo and MiniMax H3 models listed above use polling for new requests; omit callback_url.

Set a public callback_url in the create request. It must be no longer than 2048 characters, must not target localhost or private networks, and should return a 2xx response within 15 seconds.

Terminal events are video.completed and video.failed:

{
  "id": "evt_task_xxx_completed",
  "type": "video.completed",
  "created_at": 1788250200,
  "data": {
    "task_id": "task_xxx",
    "model": "SD2.5",
    "status": "completed",
    "progress": "100%",
    "output_url": "https://assets.88api.ai/media/task-videos/2026/09/<object-key>.mp4",
    "created_at": 1788249600,
    "completed_at": 1788250200
  }
}

Relevant headers:

X-NewAPI-Event: video.completed
X-NewAPI-Delivery: evt_task_xxx_completed
X-NewAPI-Timestamp: 1788250200
X-NewAPI-Signature: sha256=<hex-hmac>

Verify HMAC-SHA256 over:

X-NewAPI-Timestamp + "." + raw request body bytes

Use the full API key, including the sk- prefix, as the HMAC secret. Compare signatures in constant time and deduplicate deliveries using X-NewAPI-Delivery or the event id.

Reject callbacks whose timestamp differs from your server time by more than five minutes to reduce replay risk.

Failed deliveries use exponential backoff. Each attempt has a 15-second timeout; delivery is attempted up to eight times, with a maximum backoff of 15 minutes. Polling may be used as a fallback alongside webhooks.

Downloading the result

After status=completed:

curl --fail --location "$RESULT_URL" --output result.mp4

Use the result URL returned by polling, or data.output_url from the webhook. Follow redirects without forwarding your API key. The old /v1/videos/{id}/content path redirects archived videos but is not universal for official direct results. On a legacy 404, query the task for its actual URL instead of generating again.

Treat webhook output_url as opaque and potentially temporary. Do not parse or depend on its hostname or path layout.

Retention and cross-model reuse

Stored references and archived videos last 30 days from their respective object uploads. Reads, task queries and repeated references do not renew them. Official direct URLs have their own lifetime, not a guaranteed 30 days. Expired media may return410, or404 after deletion; an existing task record does not guarantee that its bytes still exist. Copy results to your own storage for longer retention.

Public media supports GET, HEAD, single-range requests (206), and browser CORS. X-Media-Expires-At is Unix seconds. Caching does not extend retention or guarantee identical regional download speeds. Preserve any capability parameters on URLs returned by the API; do not add query parameters to assets URLs (400), replace domains, or strip required parameters.

A Veo output can become another model's video reference if that model supports video input and the file meets its limits. Put the complete result URL directly in that model's reference field, with no download/reupload step. Veo itself accepts an input video only for extension, not as a reference. Public URLs allow anyone holding them to read the file while available.

Complete upload and polling example

Node.js 22+; set server-side API_KEY and REFERENCE_FILE (an MP4). To resume without creating a new task, set TASK_ID. The example creates one paid generation; the next request body is printed only. It buffers at most one permitted file, so bound concurrency for large files.

// video-api-example.mjs — Node.js 22+, run with API_KEY, MODEL_ID, and REFERENCE_FILE.
import { readFile, stat } from "node:fs/promises";
import { createHash } from "node:crypto";

const apiKey = process.env.API_KEY;
if (!apiKey) throw new Error("Set API_KEY on your server");
const baseUrl = "https://api.88api.ai";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function apiJSON(path, body) {
  // No automatic POST retry: a timed-out create may already have been accepted.
  const response = await fetch(baseUrl + path, {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(120000),
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  return response.json();
}

async function uploadMedia(file, mimeType) {
  const info = await stat(file);
  if (!info.isFile() || info.size < 1 || info.size > 100000000) {
    throw new Error("File must contain 1–100000000 bytes");
  }
  const bytes = await readFile(file);
  const permit = await apiJSON("/v1/media/uploads", {
    size: bytes.length, mime_type: mimeType,
    sha256: createHash("sha256").update(bytes).digest("base64url"),
  });
  const target = new URL(permit.upload_url);
  if (target.origin !== "https://assets.88api.ai" || permit.method !== "PUT") {
    throw new Error("Unexpected upload destination; do not send credentials");
  }
  const response = await fetch(target, {
    method: "PUT", headers: permit.headers, body: bytes,
    redirect: "error", signal: AbortSignal.timeout(120000),
  });
  // A 409/ambiguous upload is not automatically retried; HEAD permit.url first.
  if (response.status !== 201) throw new Error(`Upload HTTP ${response.status}`);
  return response.json();
}

async function waitForVideo(id, budgetMs = 15 * 60 * 1000) {
  const deadline = Date.now() + budgetMs;
  let delay = 4000;
  while (Date.now() < deadline) {
    await sleep(Math.min(delay, deadline - Date.now()));
    const remaining = deadline - Date.now();
    if (remaining <= 0) break;
    let response, state;
    try {
      response = await fetch(`${baseUrl}/v1/videos/${encodeURIComponent(id)}`, {
        headers: { Authorization: `Bearer ${apiKey}` },
        signal: AbortSignal.timeout(Math.min(30000, remaining)),
      });
      if (response.ok) state = await response.json();
    } catch {
      delay = Math.min(delay * 2, 60000);
      continue; // Retry a read, never create a replacement task.
    }
    if (response.status === 429 || response.status >= 500) {
      const retryAfter = response.headers.get("Retry-After");
      const seconds = retryAfter && /^\d+$/.test(retryAfter)
        ? Number(retryAfter)
        : (Date.parse(retryAfter || "") - Date.now()) / 1000;
      delay = Number.isFinite(seconds) && seconds > 0
        ? Math.max(1000, seconds * 1000)
        : Math.min(delay * 2, 60000);
      await response.body?.cancel();
      continue;
    }
    if (!response.ok) throw new Error(`Task ${id}: HTTP ${response.status}`);
    if (state.status === "completed") {
      const result = state.url || state.video_url || state.result_url;
      if (typeof result !== "string" || new URL(result).protocol !== "https:") {
        throw new Error(`Task ${id} completed without a usable HTTPS result URL`);
      }
      return result; // Keep official URLs and capability query parameters intact.
    }
    if (state.status === "failed") {
      throw new Error(`Task ${id}: ${state.error?.message || "video generation failed"}`);
    }
    if (!["queued", "in_progress", "unknown"].includes(state.status)) {
      throw new Error(`Task ${id}: unexpected status ${state.status}`);
    }
    delay = 12000;
  }
  throw new Error(`Stopped waiting for ${id}; save this ID and query later. Not cancelled.`);
}

async function main() {
  // Resume a saved task without uploading or creating another paid task.
  let id = process.env.TASK_ID;
  if (!id) {
    if (!process.env.MODEL_ID) throw new Error("Set MODEL_ID from GET /v1/models");
    if (!process.env.REFERENCE_FILE) throw new Error("Set REFERENCE_FILE");
    const media = await uploadMedia(process.env.REFERENCE_FILE, "video/mp4");
    const task = await apiJSON("/v1/videos", {
      model: process.env.MODEL_ID, prompt: "Preserve the subject and motion of the reference video and continue the shot",
      duration: 4, ratio: "16:9", metadata: { referenceVideos: [media.url] },
    });
    id = task.id;
    if (!id) throw new Error("Create response did not contain a task ID; do not blindly retry");
    console.log("Save task ID:", id); // Persist to your database in production.
  }
  const resultURL = await waitForVideo(id);
  console.log("Result URL:", resultURL); // This grants media access; do not log publicly.
  // This only shows the NEXT request body; it does not create a second paid task.
  console.log(JSON.stringify({
    model: process.env.MODEL_ID, prompt: "Preserve the subject and motion of the reference video and continue the shot",
    duration: 4, ratio: "16:9", metadata: { referenceVideos: [resultURL] },
  }, null, 2));
}

await main();

Retry and error rules

  • 400: invalid model, duration, ratio, field placement, or reference count.
  • 401: missing or invalid API key.
  • 403: the key cannot access the model or group.
  • 404: task not found, not owned by the account, or content not ready.
  • Upload409: object already exists; HEAD and reuse a confirmed successful upload.
  • Media410: expired; references do not renew retention (404 is possible after cleanup).
  • Upload415: file signature does not match its MIME type.
  • Media416: invalid or out-of-bounds Range; check length with HEAD.
  • 429: apply Retry-After or exponential backoff.
  • 5xx: status queries may be retried with backoff.
  • status=failed: the HTTP query succeeded but generation failed; inspect error.message.
  • unsupported_reference_media: unsupported model capability, not fixed by uploading.
  • must be a public HTTPS URL: upload local video first; no Blob, Data URL, HTTP or login-dependent URL.
  • input_download_failed: check upstream reachability, DNS/network and expiry, not just playback on your computer.

POST /v1/videos does not currently guarantee customer-provided idempotency keys. If a fully transmitted create request ends with an ambiguous network timeout, do not blindly resubmit it because duplicate tasks may be generated and billed.