Image / video / music APIs

/v1/images/generations and /v1/images/edits billed per image · /v1/videos/generations async, billed per second · /v1/music/generations async, billed per generation · /v1/transcripts/extract async video-to-script, billed per call

Alongside the four chat protocol endpoints, the platform offers three families of media endpoints. Authentication is exactly as it is for chat — the same sk-gpushare-* key, in whichever of the four forms you like (x-api-key or x-goog-api-key header, ?key= query, Authorization: Bearer); see Authentication. Everything bills against your account balance (shared by all keys), and an insufficient balance returns 402 quota_exceeded.

EndpointPurposeBilling
POST /v1/images/generationsText-to-image and image-to-image (synchronous, or "async": true for a task)per image
POST /v1/images/editsImage-to-image, accepting JSON or a multipart file upload (synchronous, or "async": true)per image (same price as above)
POST /v1/videos/generationsText-to-video and image-to-video (async task)per second
GET /v1/videos/generations/{id}Poll a video taskfree
GET /v1/videos/generationsList this account's video tasks (all statuses by default; ?limit= default 30, max 100; ?status= to filter)free
POST /v1/music/generationsAI music generation (Suno, async task)per generation (2 songs each)
GET /v1/music/generations/{id}Poll a music taskfree
GET /v1/music/generationsList this account's music tasks (same shape)free
POST /v1/audio/speechSpeech synthesis (synchronous, or "async": true for a task)per character
GET /v1/audio/speech/{id}Poll a speech taskfree
GET /v1/audio/speechList this account's speech tasks (same shape)free
POST /v1/transcripts/extractShort-video link → spoken script (synchronous)per call

Every response from these endpoints carries an x-gateway-trace header — including 4xx/5xx errors. Include it with the timestamp, the model and the full error body when reporting a problem, and paste it into logs.dflop.top to look the call up.


Retries don't double-charge: Idempotency-Key#

Every billed POST endpoint (image, video, music, speech, voice clone, digital-human avatar, transcript) accepts an Idempotency-Key request header. With it, the same request can be sent any number of times and is executed exactly once:

IDEM=$(uuidgen)   # one key per submit intent, reused by every retry of it

curl https://zhonkezhonkeapi.dflop.top/v1/videos/generations \
  -H "Authorization: Bearer $PLATFORM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEM" \
  -d '{"model":"doubao-seedance-2-0-260128","prompt":"sunset over the sea","duration":5}'
  • Same key + same request body → the original response is returned verbatim (same task id), with an extra Idempotency-Replayed: true header. No second task, no second charge.
  • This is also the easiest way to recover a task id: forgot to save the id? Re-run the exact same curl (same key, same body) and you get the original task back.
  • Keys are retained for 7 days; after that the same key counts as a new request.
  • Key format: 1–200 printable ASCII characters (a UUID is the obvious choice). One key per submit intent — do not reuse it across different requests.
  • Scoped to the account, not to a single API key: replaying the same Idempotency-Key from a different sk-gpushare-* key on the same account still hits the replay. (The wallet is shared account-wide, so anything narrower would leak duplicate charges.)
  • Concurrency: when two requests carrying the same key arrive at once, exactly one executes and the other immediately gets 409 idempotency_in_flight — they never both run.
import uuid, requests

idem = str(uuid.uuid4())           # one key per submit intent
body = {
    "model": "doubao-seedance-2-0-260128",
    "content": [{"type": "text", "text": "sunset over the sea, drone shot"}],
    "duration": 5,
}

def submit():
    r = requests.post(
        "https://zhonkezhonkeapi.dflop.top/v1/videos/generations",
        headers={
            "Authorization": f"Bearer {PLATFORM_API_KEY}",
            "Idempotency-Key": idem,          # same key on every retry
        },
        json=body,
        timeout=60,
    )
    r.raise_for_status()
    # "true" on a replay: you got the first call's result, with no second charge
    replayed = r.headers.get("Idempotency-Replayed") == "true"
    return r.json()["id"], replayed

task_id, _ = submit()
task_id_again, replayed = submit()   # lost the id? just call it again
assert task_id == task_id_again and replayed
const idem = crypto.randomUUID();          // one key per submit intent

async function submit() {
  const res = await fetch("https://zhonkezhonkeapi.dflop.top/v1/videos/generations", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${PLATFORM_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": idem,             // same key on every retry
    },
    body: JSON.stringify({
      model: "doubao-seedance-2-0-260128",
      content: [{ type: "text", text: "sunset over the sea, drone shot" }],
      duration: 5,
    }),
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  return {
    id: (await res.json()).id,
    replayed: res.headers.get("Idempotency-Replayed") === "true",
  };
}
CaseResponse
Header absentBehaves exactly as before (no deduplication at all)
Malformed key400 invalid_idempotency_key
Same key, different body409 idempotency_key_reuse — use a fresh key
Same key, previous call still running409 idempotency_in_flight — wait and retry with the same key (a new key really would submit again). Usually a few seconds; if the previous call was cut off client-side, the key is held for up to 10 minutes before it frees itself
Same key, original response too large to retain409 idempotency_response_not_cached — only reachable for image calls that explicitly ask for response_format:"b64_json" (the bytes are too large to retain). Use the default response_format:"url" and replay works normally; the call that already happened can only be re-sent under a fresh key (and is billed again)

Only 2xx responses are remembered. Upstream errors and validation failures do not consume the key — retry with the same one.


Recovering a task id#

Async tasks (video, music, speech) return a task id on submit. If you didn't save it, there are three ways back, easiest first:

  1. Re-send the exact same curl (same Idempotency-Key) → you get the original task id straight back. See the section above.
  2. List your tasks: GET /v1/videos/generations, GET /v1/music/generations, GET /v1/audio/speech. All statuses are returned by default (including queued, running and failed), newest first; ?limit= defaults to 30, max 100.
    curl "https://zhonkezhonkeapi.dflop.top/v1/videos/generations?limit=10" \
      -H "Authorization: Bearer $PLATFORM_API_KEY"
    
    ?status= filters, comma-separated for multiple: video accepts queued,running,succeeded,failed,expired,cancelled,all; music processing,succeeded,failed,expired,cancelled,all; speech pending,succeeded,failed,all. Pass ?status=succeeded for the pre-2026-07-31 default.
  3. The call log site logs.dflop.top: the search box matches both task ID and request ID (paste it in — you don't have to know which kind of id you're holding). Async-task records (image with "async": true, video, music, speech, avatar, voice) carry a task ID; calls with no task row behind them (chat, synchronous image, transcript) carry only a request ID.
    • The request ID is the value of the x-gateway-trace response header (present on every response, including 4xx/5xx).
    • ⚠️ An async task only becomes a ledger row once it reaches a terminal state and settles; a task still generating shows in the In progress strip at the top of the log page (with its task id and the reserved credits), or via the list endpoints in point 2. The log defaults to the last 30 days. See Reconciliation and the call log below for the exact semantics.

Reconciliation and the call log#

logs.dflop.top (sign in with your zhonkemodel.dflop.top account) is the gateway's per-call ledger and the reference for reconciling against your own records. The semantics:

  • One row = the terminal outcome of one external call. Failed attempts produced by the gateway's automatic failover across upstream lanes are not separate rows, not billed and not counted as requests; they are folded into the terminal row's "internal attempts (not billed)" timeline (the row is tagged 换道 ×N).
  • Four statuses: success / error / interrupted (client disconnected or gateway timeout) / rejected. Rejected = refused by the gateway at submit time (bad parameters, unavailable model, insufficient balance, content block); cost is always 0, but the row is recorded so you can account for requests that were sent but never ran. total_requests on GET /api/v1/usage includes rejected rows and excludes internal attempts.
  • In progress: async tasks (image / video / music / speech) that have not reached a terminal state appear in the In progress strip at the top of the log page, with the task id, submit time and the reserved credits; on settlement the reservation is trued up to actual output and the task becomes a normal row.
  • Submitted / completed time: async tasks record both; synchronous calls record the completion time and derive the submit time from latency.
  • Result links: successful image / video / music / speech rows carry result_urls and result_expires_at (platform-hosted images are swept after 24 hours; video / music / speech links are long-lived or carry their own expiry). After expiry the log keeps only the link text.
  • Lookup by id: the search box matches both the task id and the request id (= the x-gateway-trace response header).
  • CSV export columns: id, created_at, key_name, key_prefix, model, status, error_code, error_message, input_tokens, output_tokens, cached_tokens, unit_count, unit_type, cost_usd, latency_ms, request_id, task_id, submitted_at, attempts_count, result_urls, result_expires_at. Multiple result_urls are joined with |; the cost_usd column name is historical — the value is in credits. One export is capped at 10,000 rows; past that the response carries x-truncated: true, so narrow the date range and export in slices.

POST /v1/images/generations#

OpenAI Images API-compatible shape, returned synchronously by default; add "async": true for submit + poll on long generations (see "Async mode" below).

Available models#

Model IDDisplay namePrice (per image)Notes
doubao-seedream-4-0-250828Seedream 4.011.73size ≥ 960×960
doubao-seedream-4-5-251128Seedream 4.514.96size must be ≥ 1920×1920, or upstream returns 400
doubao-seedream-5-0-260128Seedream 5.012.94size must be ≥ 1920×1920, or upstream returns 400
doubao-seedream-5-0-pro-260628Seedream 5.0 Pro17.79 for output ≤ 2.36 MP, 35.59 above thatsize ≥ 960×960. Omitting size means upstream defaults to 2048×2048 and you pay the 35.59 tier — to stay on the lower tier, pass a size ≤ 2.36 MP explicitly (e.g. 1536x1536). Each image[] reference adds 1.21 of input (on the same billing line)
grok-imagine-imageGrok Imagine (Image)28.31standard tier
grok-imagine-image-qualityGrok Imagine (Quality)28.31high-quality tier
gpt-image-2GPT-Image 223.86text-to-image and image-to-image (references are not charged). The upstream ignores size — put the aspect ratio in the prompt; measured latency 30–215 seconds. The older ids gpt-image-2-low/medium/high and tvod-gpt-image2-* resolve here as aliases
tvod-midjourney-v8.1Midjourney v8.140.44Every request returns exactly 4 images (a 2×2 grid); n does not control the count ⇒ 161.76 per request. size only sets the aspect ratio, never the pixel dimensions. Pick the output tier by appending --sd / --hd to the prompt
tvod-midjourney-v7Midjourney v732.35Same — fixed 4 images ⇒ 129.41 per request

size is passed straight through and never rewritten by the gateway — asking Seedream 4.5/5.0 for less than 1920×1920 gets you the upstream's 400 directly. Seedream 5.0 Pro is tiered on the requested output pixel area (the threshold is 2.36 MP ≈ 1536×1536). gpt-image-2 is the exception: its upstream never reads size — to control the frame, write "Aspect ratio: 16:9 (landscape)" into the prompt, which overrides size.

The two Midjourney entries differ from every other model on this endpoint in two ways — integrate against the table notes above:

  1. n does not control the image count. The upstream produces a 2×2 grid, so a request always returns 4 images (measured 2026-09-01: both n=1 and n=2 came back with 4). Pass n: 1. Billing settles on the number actually returned, so one v8.1 request costs 40.44 × 4 = 161.76 credits and your balance must cover that. The gateway's pre-flight check also gates on 4 images, so an insufficient balance is a 402 at submit time rather than an overdraft afterwards.
  2. size expresses an aspect ratio, not pixels. The gateway maps "WxH" onto the nearest supported frame (1:1, 16:9, 9:16, 4:3, 3:4) and hands that to the upstream; the actual pixel dimensions follow from the model and the output tier. Measured: 2048x2048 → four 1024x1024; 2048x1152 → four 1456x816; the same 2048x1152 with --hd → four 2944x1648. Do not treat size as a promise of exact resolution. A ratio that doesn't reduce to one of those five falls back to the model's default frame.

The output tier goes at the end of the prompt (--sd for the standard tier, --hd for the higher one), like any other Midjourney flag — the prompt is forwarded verbatim. Reference images and the advanced flags (--iw, --sref, …) have not been regression-tested on this channel and are not advertised as supported. Because a request takes a while, prefer the async mode below ("async": true plus polling).

The request#

{
  "model": "doubao-seedream-4-5-251128",
  "prompt": "A panda drinking tea in a bamboo grove, watercolour style",
  "size": "2048x2048",
  "n": 1
}
FieldRequiredNotes
modela Model ID from the table above
promptthe description
size"WxH", passed through upstream (mind each SKU's minimum)
nhow many images, default 1, max 10 (more returns 400 invalid_request). unit price × n is held on submit and settled against the number actually returned. ⚠️ Fixed-grid SKUs ignore n (Midjourney always returns 4): those gate on unit price × max(n, 4), so pass n: 1 — see the model-table notes above
imagereference image(s) for image-to-image: a string or an array, each item a public https URL or data:image/...;base64,.... Omit it for text-to-image. gpt-image-2, Seedream and nano-banana-2 take data URIs; nano-banana and nano-banana-pro take public URLs only
image_urlsequivalent spelling (array); both are merged and wire order is preserved (order carries meaning for multi-image composition)

Image-to-image (references)#

Same endpoint — pass image and it's image-to-image. Full examples, SDK usage, aspect-ratio control and the common pitfalls are in Image-to-image editing.

curl https://zhonkezhonkeapi.dflop.top/v1/images/generations \
  -H "Authorization: Bearer $PLATFORM_API_KEY" \
  -H "Content-Type: application/json" \
  --max-time 300 \
  -d '{
    "model": "gpt-image-2",
    "prompt": "Make the sofa blue, leave everything else unchanged. Aspect ratio: 3:2 (landscape)",
    "image": ["https://your-host.com/original.png"]
  }'

⚠️ When a reference is a URL, the server fetching it is the upstream's, not this gateway's — object storage inside China, intranet addresses and image hosts behind auth or bot protection come back as an upstream 400, Unable to download content from the provided URL. Send a data URI instead, or upload the raw file through /v1/images/edits below.

The response#

{
  "model": "doubao-seedream-4-5-251128",
  "created": 1765432100,
  "expires_at": 1765518500,
  "data": [{ "url": "https://...", "size": "2048x2048" }],
  "usage": { "generated_images": 1, "output_tokens": 4096, "total_tokens": 4096 }
}

The response is the upstream's own (OpenAI Images shape); the usage fields are whatever upstream actually returns. expires_at (a gateway extension) is when data[].url expires (Unix seconds); it appears only when the image is hosted by this platform (r2.dflop.top) — upstream pre-signed links carry no such field.

curl#

curl https://zhonkezhonkeapi.dflop.top/v1/images/generations \
  -H "Authorization: Bearer $PLATFORM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "doubao-seedream-4-5-251128",
    "prompt": "A panda drinking tea in a bamboo grove, watercolour style",
    "size": "2048x2048"
  }'

Limitations#

  • Returned image URLs are always temporary — most SKUs return an upstream pre-signed link (about 24 hours), while gpt-image-2 measurably returns a platform-hosted https://r2.dflop.top/gateway/images/ephemeral/<uuid>.png (swept after 24 hours; the top-level expires_at in the body is the exact expiry; images served through a relay lane are re-hosted on the same domain). Neither is permanent hosting — download and store them promptly
  • Synchronous by default. The gateway's per-attempt upstream timeout is 240 seconds (IMAGES_UPSTREAM_TIMEOUT_SECS) and the whole channel ladder is capped at 280 seconds (IMAGES_LADDER_DEADLINE_SECS). Most SKUs finish in 5–20 seconds, but gpt-image-2 measurably takes 30–215 seconds — set your client timeout to ≥ 300 seconds, or your own timeout will cut off a request the gateway is still legitimately waiting on (you are still billed, you just never receive the result)
  • Errors use the OpenAI shape {"error": {"code", "message", "param", "type"}}; upstream 4xx/5xx pass through with their original status and body (and aren't billed)

Async mode (use it for long generations)#

Synchronous calls are bounded by a CDN non-streaming response limit of roughly 125 seconds. The gateway keeps the connection alive (past 90 seconds it commits the response headers and drips whitespace while it waits, so synchronous calls are no longer cut off by the CDN). The cost of that trick: a turn that fails after the 90-second mark cannot return a truthful HTTP status code — it comes back as 200 with an {"error": ...} body.

For clean failure semantics — or simply to avoid holding an HTTP connection open for minutes — add "async": true to the request body to switch to submit + poll:

# 1. Submit — returns immediately, does not wait for the image
curl https://zhonkezhonkeapi.dflop.top/v1/images/generations \
  -H "Authorization: Bearer $PLATFORM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-image-2", "prompt": "a panda drinking tea in a bamboo forest", "async": true}'
# → {"id": "9f1e...", "status": "queued", "model": "gpt-image-2", "created_at": 1786000000}

# 2. Poll
curl https://zhonkezhonkeapi.dflop.top/v1/images/generations/9f1e... \
  -H "Authorization: Bearer $PLATFORM_API_KEY"

The polling contract is a single rule:

ResponseMeaning
202 + {"id","status","model","created_at"}Still running — keep polling (every 2–3 seconds is fine)
Any other status codeThis is the answer — byte-for-byte the same response a synchronous call would have produced (200 + data[] on success; the upstream's own status code and body on failure)
  • Bad parameters, unavailable models, insufficient balance and content blocks are still returned synchronously as truthful 4xx at submit time — you never get a task id for a request that was doomed
  • A task runs for at most 280 seconds (the whole channel-ladder budget), so budget ≥ 5 minutes of polling; a client-side timeout is not a failure — the task keeps running and settles as usual, so recover the result by id from the list endpoint below instead of resubmitting
  • Balance is reserved at submit for the worst case (per-image price × n plus reference-image cost) and settled against actual output; zero output is refunded in full
  • GET /v1/images/generations lists your own tasks (all statuses by default, newest first) — if you lose a task id, recover it here instead of resubmitting; parameters and fields are under "List tasks" below
  • /v1/images/edits supports it too: add "async": true to a JSON body, or an extra -F "async=true" field to multipart
  • Tasks interrupted by a deploy are refunded in full and marked failed — you are never charged twice

List tasks#

GET /v1/images/generations filters and paginates (since 2026-10-16); for reconciliation, pull the full range by submit time:

ParameterMeaning
statusqueued / running / succeeded / failed / in_flight (queued + running); default all
from / toFilter by submit time; accepts unix seconds, RFC 3339 or YYYY-MM-DD (to is exclusive)
limit1–100, default 30
cursorThe previous page's next_cursor; null means the end

Each item: id / status / model / endpoint (generations or edits) / created_at (submit) / started_at / completed_at (unix seconds) / unit_count (images actually produced) / cost (settled credits, 0 on failure) / error_code / request_id (= x-gateway-trace). succeeded items also carry result: {"urls": [...], "expires_at": <unix seconds|null>}urls are byte-identical to the polling response's data[].url, and expires_at appears only on platform-hosted links (24 hours).

curl "https://zhonkezhonkeapi.dflop.top/v1/images/generations?status=succeeded&from=2026-08-30&to=2026-08-31&limit=100" \
  -H "Authorization: Bearer $PLATFORM_API_KEY"

The Nano Banana family (nano-banana 15.77, nano-banana-pro 54.19, nano-banana-2 16.18 per image) also runs through this endpoint and bills per image. nano-banana-2 returns b64_json (its first hop is our own pool); the others usually return URLs. The older ids (gemini-2.5-flash-image, gemini-3-pro-image-preview, gemini-3.1-flash-image(-preview), tvod-nano-*) remain supported as aliases.


POST /v1/images/edits#

The other way into image-to-image. It exists so you can use the call shape your OpenAI SDK already speaks (client.images.edit(image=...) sends multipart), or upload raw local file bytes without base64-encoding them yourself.

Models, pricing, the channel ladder, Idempotency-Key, the response and the error shape are identical to /v1/images/generations — multipart is normalised into the same JSON envelope and continues down the same path. The one behavioural difference: a request with no reference is rejected with 400 here (on this endpoint a missing reference is a caller bug, and it will never silently degrade into text-to-image).

Two request shapes#

JSON (same shape as generations):

curl https://zhonkezhonkeapi.dflop.top/v1/images/edits \
  -H "Authorization: Bearer $PLATFORM_API_KEY" \
  -H "Content-Type: application/json" \
  --max-time 300 \
  -d '{
    "model": "gpt-image-2",
    "prompt": "Make the sofa blue, leave everything else unchanged",
    "image": ["data:image/png;base64,iVBORw0KGgo..."]
  }'

multipart/form-data (file field image, or image[] for several; other text fields pass through verbatim):

curl https://zhonkezhonkeapi.dflop.top/v1/images/edits \
  -H "Authorization: Bearer $PLATFORM_API_KEY" \
  --max-time 300 \
  -F "model=gpt-image-2" \
  -F "prompt=Make the sofa blue, leave everything else unchanged" \
  -F "image[][email protected]" \
  -F "image[][email protected]"

With the OpenAI SDK#

from openai import OpenAI

client = OpenAI(
    api_key=PLATFORM_API_KEY,
    base_url="https://zhonkezhonkeapi.dflop.top/v1",
    timeout=300.0,                       # gpt-image-2 measurably takes 30–215s
)

result = client.images.edit(
    model="gpt-image-2",
    image=open("original.png", "rb"),
    prompt="Make the sofa blue, leave everything else unchanged. Aspect ratio: 3:2 (landscape)",
)
print(result.data[0].url)

Limitations#

  • No image400 (the error tells you to use /v1/images/generations for text-to-image)
  • Request body limit 95 MB; base64 inflates size by roughly 33%, so real phone photos (~3 MB each) cap out around 20 images
  • Everything else (24h URL expiry, timeout budgets, error shape) matches /v1/images/generations
  • For fuller usage and how to choose a route, see Image-to-image editing

POST /v1/videos/generations#

An async task: submitting returns a task id immediately, and you poll until succeeded.

Available models#

Model IDDisplay namePrice (per second)Notes
doubao-seedance-1-0-pro-fast-251015Seedance 1.0 Pro Fast32.35
doubao-seedance-1-0-pro-250528Seedance 1.0 Pro60.66
doubao-seedance-1-5-pro-251215Seedance 1.5 Pro72.79
doubao-seedance-2-0-fast-260128Seedance 2.0 Fast48.53
doubao-seedance-2-0-260128Seedance 2.088.97
doubao-seedance-2.0Seedance 2.0by resolution: 480p 33.16 / 720p 59.45 / 1080p 147.61 / 2k 291.17 / 4k 355.87supports real-person photos on camera; reference images are moderated and registered automatically. Turns carrying a reference video are billed by token (usage includes the reference video duration; resolution becomes required — omitting it returns 400. See the billing section below)
doubao-seedance-2.0-fastSeedance 2.0 Fastby resolution: 480p 23.86 / 720p 47.72 / 1080p 117.28 / 2k 141.54 / 4k 169.85Turns carrying a reference video are billed by token (usage includes the reference video duration; resolution becomes required — omitting it returns 400. See the billing section below)
doubao-seedance-2.0-miniSeedance 2.0 Miniby resolution: 480p 14.96 / 720p 29.93lightweight tier; 480p/720p only, 4–15 seconds. Turns carrying a reference video are billed by token (usage includes the reference video duration; resolution becomes required — omitting it returns 400. See the billing section below)
doubao-seedance-2.5Seedance 2.5by resolution: 480p 42.06 / 720p 90.59 / 1080p 226multimodal references from images (≤30), video and audio; can generate sound; 4–30 seconds. Requests carrying reference video bill by token (2499.19/1M; 4165.32/1M without video input; the 1080p tier bills at 2790.12/1M and 4650.21/1M — see the billing section below)
doubao-seedance-2.0-liteSeedance 2.0 Liteby resolution: 720p 29.93 / 1080p 64.7value tier; resolution must be passed explicitly (720p/1080p only). Turns carrying a reference video are billed by token (usage includes the reference video duration; resolution becomes required — omitting it returns 400. See the billing section below)
doubao-seedance-2.0-fast-liteSeedance 2.0 Fast Liteby resolution: 720p 24.67 / 1080p 52.57as above, lower latency. Turns carrying a reference video are billed by token (usage includes the reference video duration; resolution becomes required — omitting it returns 400. See the billing section below)
doubao-seedance-2.0-mini-liteSeedance 2.0 Mini Liteby resolution: 720p 16.18 / 1080p 34.78as above, cheapest for lightweight work. Turns carrying a reference video are billed by token (usage includes the reference video duration; resolution becomes required — omitting it returns 400. See the billing section below)
doubao-seedance-2.5-liteSeedance 2.5 Liteby resolution: 720p 44.48 / 1080p 95.84as above; same capabilities as doubao-seedance-2.5 (4–30 seconds, multimodal references, sound). Its 1080p tier is far cheaper than the full-price card (95.84 vs 226) — same delivered resolution, different cost structure. Requests carrying reference video bill by token (2758.01/1M; 4424.14/1M without video input)
grok-imagine-videoGrok Imagine Video283.08text-to-video and image-to-video
grok-imagine-video-1.5-previewGrok Imagine Video 1.5586.38image-to-video only (upstream 400s without a reference image)
dh-avatarDigital human videoflat per video second (see the in-app catalogue for pricing)requires a reusable avatar first (created from a photo or video); avatar + driving audio (or text + voice) → a talking video
clip-realmanSmart edit · talking head4.04/second (of finished video)a talking-head source video + a template → a finished cut with title, subtitles, name bar and music
clip-mixcutSmart edit · asset mixcut4.04/second (of finished video)narration audio + image/video assets + a template → a subtitled, packaged cut
clip-newsSmart edit · news brief2.43/second (of finished video)a headline + image/video assets + a template → a news-style short, 5–300 seconds

Submitting#

{
  "model": "doubao-seedance-1-0-pro-fast-251015",
  "content": [
    { "type": "text", "text": "Sunset over the sea, aerial drone view --ratio 16:9" }
  ],
  "duration": 5
}
FieldRequiredNotes
modela Model ID from the table above (grok SKUs use the same shape; the gateway translates)
contentan array: {type:"text", text} is mandatory; for image-to-video append {type:"image_url", image_url:{url:"https://..."}}
durationseconds. Omitted, we hold 12 seconds' worth (Seedance's ceiling) and settle on the real length — pass it explicitly
ratio / resolution / watermarkpassed through upstream (Seedance's own semantics). ⚠️ resolution is required on the four Lite (-lite) SKUs: the whole card prices on delivered resolution, so omitting it returns 400; the vocabulary is 720p / 1080p only, and anything else 400s too
video_modeonly meaningful on grok SKUs with a reference video: "extend" continues it, anything else (or omitted) rewrites it (see below)

How grok video is translated: grok SKUs run on an xAI upstream, and the gateway converts the Seedance shape above into xAI's. Appending {type:"video_url", video_url:{url:"https://..."}} routes the task to the video-to-video endpoint, which rewrites by default (redrawing the whole clip from your prompt, keeping the source aspect ratio and resolution, ignoring a custom duration); video_mode: "extend" switches to continuation (carrying on from the last frame for duration seconds, 2–10). For image-to-video the gateway deliberately withholds ratio so the source image's native aspect ratio is preserved and nothing gets stretched.

Real people on camera#

doubao-seedance-2.0, doubao-seedance-2.0-fast and doubao-seedance-2.0-mini support uploading a photo of a real person and generating video of them on camera. The compliance path runs entirely inside the gateway and needs nothing special from you — submit the ordinary image-to-video shape and the gateway moderates and registers the reference image (swapping in a compliant asset handle) before generating. If the preferred channel refuses a real-person image, the gateway transparently switches to one that supports registering real-person assets.

{
  "model": "doubao-seedance-2.0",
  "resolution": "720p",
  "duration": 5,
  "content": [
    { "type": "text", "text": "The person in the photo smiles and waves at the camera, background unchanged" },
    { "type": "image_url", "image_url": { "url": "https://your-cdn.com/face.jpg" } }
  ],
  "portrait_auth": true
}
FieldNotes
image_url.urlmust be a publicly fetchable http(s) URL (the upstream pulls it from the open internet). base64 / data: inline images are not supported and are rejected with 400. The image must be reachable from mainland China (our own r2.dflop.top links work; some overseas hosts can't be fetched).
portrait_authOptional boolean asserting "I have the portrait rights for the real person shown", recorded for audit. It does not affect whether video is produced (real-person routing follows the SKU), but passing true explicitly is recommended whenever real people are involved, as a statement of responsibility.
resolutionThe real-person vocabulary: doubao-seedance-2.0 takes 480p/720p/1080p/2k/4k, -fast takes 480p/720p/1080p, -mini only 480p/720p. Omitted, upstream picks its default and the flat rate applies.

Multimodal references (Seedance 2.0 only): beyond a single first-frame image, content[] can carry reference media items tagged with a role{type:"image_url", role:"reference_image", image_url:{url}}, {type:"video_url", role:"reference_video", video_url:{url}} and {type:"audio_url", role:"reference_audio", audio_url:{url}} (up to 10 reference images). Every external image follows the same public-URL and automatic-moderation rules above.

Response:

{ "id": "9f2c...", "status": "queued", "model": "doubao-seedance-1-0-pro-fast-251015", "created_at": 1765432100 }

Submission itself is a synchronous HTTP call (with a 60-second gateway-to-upstream timeout); generation then proceeds in the background and doesn't hold the request open.

Digital-human fields (dh-avatar)#

dh-avatar reuses the same video submission endpoint, adding the fields below at the top level of the body (duration is required — the estimated seconds of the driving audio or script; omitting it returns 400). ⚠️ Digital human is a two-stage process: you need a reusable avatar first, created in the app under "Clone avatar" from a photo or video (the platform's shared avatars have been withdrawn from the UI because upstream stopped returning previews). Calling with an sk-key, just pass an existing avatar id.

The video is as long as its driving audio or script (upstream measures the real length; there's no fixed cap). duration only sizes the billing hold, and settlement uses upstream's actual seconds.

FieldRequiredNotes
avatarthe avatar id (created once under "Clone avatar" in the app, then reusable)
audio_urlone of the twodriving audio (public URL, mp3/wav) — makes the avatar speak it
voice + textone of the twotext-driven: voice is a voice id (shared or cloned) and text the script (≤10,000 characters); upstream synthesises it and drives the avatar in one step
titlea name for the piece (≤20 characters)

Things to note:

  • input media URLs must be publicly reachable (anything you've uploaded to our r2.dflop.top works directly);
  • finished videos carry an automatic "AI generated" mark, as China's AIGC labelling rules require.

Smart-edit fields (clip-realman / clip-mixcut / clip-news)#

The three smart-edit SKUs reuse the same video submission endpoint, adding the fields below at the top level. All three share style_id (the template id), title, language, materials[], bgm and cover_url, each with its own extra requirements. The length of the finished video comes from the source media (realman from the source video, mixcut from the narration audio, news from duration); for realman and mixcut, duration is only a billing hint and is never sent upstream.

{
  "model": "clip-realman",
  "style_id": "tpl_xxx",
  "title": "Today's headlines",
  "source_video_url": "https://your-cdn.com/talk.mp4",
  "materials": [
    { "type": "image", "file_url": "https://your-cdn.com/a.jpg" },
    { "type": "video", "file_url": "https://your-cdn.com/b.mp4", "sound_switch": false }
  ],
  "bgm": { "mode": "auto" }
}
FieldApplies toNotes
style_idall ✓template id (from the platform's smart-edit template library)
source_video_urlrealman ✓the talking-head source video (public URL)
audio_urlmixcut ✓the narration audio (public URL)
materialsmixcut/news ✓, optional for realmanan array of {type:"image"|"video", file_url, sound_switch?}, at most 10
titlenews ✓, optional elsewherethe piece's or story's headline
durationnewstarget length in seconds, 5–300 (clamped if out of range); a billing hint only for realman/mixcut
material_compositionnewsrandom or order; random by default
preprocessrealmanasset pre-processing: roughCut or sliceMerge
bgmall{mode:"auto"|"none"|"custom", url?, volume?}; follows the template by default
cover_urlalla custom first-frame cover (public image URL)
introduce_cardallthe name bar, {name, description}
languageallsubtitle language

Billing: charged on the actual length of the finished video (the real seconds reported by polling). Submissions with an external sk-key hold the clip ceiling (300 seconds) and settle down to the real length on success; passing a longer duration explicitly (for a long source video, say) holds that instead. Insufficient balance returns 402, and failures or expiry are refunded in full.

Template ids: style_id comes from the platform's smart-edit template library. Template discovery currently exists only inside the Digital Human studio in the app, so calling directly with an sk-key means using a template id you already know.

Asset and media requirements (upstream hard limits)#

Every URL must be publicly fetchable. The limits below are the upstream's own, and anything outside them is rejected there (the in-app Digital Human studio validates format, resolution, length and size at upload time).

MediumFormatSizeResolutionLength
Talking-head source video source_video_urlmp4 / mov (h264 or HEVC, 10–60fps, 25 recommended)< 500MB< 2000px per side< 5 minutes
Asset image materials[].file_url (image)static jpg / png / webp< 2000px per sidecounts as 2s each
Asset video materials[].file_url (video)mp4 / mov< 500MB< 2000px per side≤ 60s each
Narration audio audio_url (mixcut)mp3 / wav / m4a≤ 120MB≤ 5 minutes, must be transcribable
Background music bgm.urlmp3 / wav / m4a≤ 120MB≤ 5 minutes
First-frame cover cover_urljpg / jpeg / png≤ 10MB< 2000px per side
  • Total asset length ≤ 5 minutes: images count as 2s each and videos at their real length; upstream rejects anything longer.
  • The talking-head source video's audio must be transcribable (it drives the automatic subtitles); without clear speech the task fails.
  • clip-news output length is set by duration (5–300s); clip-realman and clip-mixcut follow the source video and the narration audio respectively.

Polling#

curl https://zhonkezhonkeapi.dflop.top/v1/videos/generations/$TASK_ID \
  -H "Authorization: Bearer $PLATFORM_API_KEY"

status runs queuedrunningsucceeded / failed / expired / cancelled.

In-flight tasks (queued / running) may carry progress (an integer 0–100 from upstream). It appears only when upstream reports progress — today just the Seedance 2.0 real-person tiers — and its absence means that model has no progress data, not 0%.

On success:

{
  "id": "9f2c...",
  "status": "succeeded",
  "model": "doubao-seedance-1-0-pro-fast-251015",
  "created_at": 1765432100,
  "updated_at": 1765432460,
  "ratio": "16:9",
  "resolution": "1080p",
  "duration": 5,
  "generate_audio": true,
  "seed": 33608,
  "framespersecond": 24,
  "content": {
    "video_url": "https://...",
    "last_frame_url": "https://..."
  },
  "usage": { "completion_tokens": 411300, "total_tokens": 411300 },
  "video_url": "https://...",
  "expires_at": 1765435700,
  "duration_sec": 5.0
}

Failures carry error: {code, message}. Generation usually takes 1–5 minutes; poll every 5–10 seconds.

Task ids are visible only to their own account — polling a nonexistent task or someone else's returns the same 404 (code: "not_found"), with no distinction drawn.

Response fields#

FieldPresent whenNotes
idalwaysTask id
statusalwaysqueued / running / succeeded / failed / expired / cancelled
modelalwaysThe submitted Model ID (canonicalised)
created_atalwaysTask creation time, Unix seconds
updated_atalwaysWhen the status last changed, Unix seconds. For terminal rows this is when the task settled; while in flight it falls back to created_at (we don't stamp the intermediate queued/running transitions). Always ≥ created_at and never in the future
ratio / resolution / frames / seed / generate_audio / output_format / service_tier / safety_identifier / execution_expires_after / draftonly if you sent themThe value you specified in the create request, echoed back. Keys you didn't send are absent — we don't invent upstream defaults on your behalf (upstream applies its own, and we can't observe them). Request-side "you decide" sentinels are likewise not echoed: seed: -1 (random) and ratio: "adaptive" (auto-fit) would read as a concrete answer in a response, and we don't know what upstream actually picked
durationsucceeded onlyDelivered length in whole seconds. This is the delivered duration (same source as duration_sec, rounded), not the duration you submitted. Equal for Seedance; they differ on SKUs that estimate length from a script (digital human text-driven), where this field is authoritative
contentsucceeded onlyOutput block. video_url = the finished asset; last_frame_url = the last frame (see below)
content.last_frame_urlsent return_last_frame: true on create, and succeededPNG of the video's final frame, same dimensions as the video, no watermark. Use it to chain long videos: feed it as the first frame of the next task. Already copied into our object storage, so the link is permanent (the upstream original expires in 24h)
framespersecondonly when upstream reports itFrame rate of the finished video. Absent when upstream didn't report one (it does not mean 24)
usagesucceeded, and only when upstream reports token usage{completion_tokens, total_tokens}. Video models don't count input tokens, so the two are equal. This is upstream's usage figure, not this platform's billing basis — see below
video_urlsucceeded onlySame value as content.video_url, flattened
expires_atsucceeded onlyWhen video_url expires, Unix seconds
duration_secsucceeded onlySame as duration but keeps fractional precision. A gateway extension; Ark has no such field
output_filessucceeded, subtitle SKUs onlyOne download link per language
progressqueued/running, and only when upstream reports itInteger 0–100. An absent field means the model has no progress data — not 0%
errorfailed/expired/cancelled only{code, message}

How usage relates to billing#

usage.completion_tokens is passed through verbatim from upstream: if upstream reports it, we emit it, regardless of how that SKU is priced here. Seeing a token count does not mean the SKU is billed per token.

Each model's pricing basis is listed in Models & pricing and is one of two things:

  • Per second (most video SKUs): charge = unit price × delivered seconds; reconcile against duration_sec. usage is informational here and plays no part in pricing.
  • Per token (the whole Volcano Seedance family — both the 2.0 and 2.5 series): charge = unit price × usage.completion_tokens; reconcile against that.
When Seedance bills per second and when it bills per token

Upstream meters (reference video duration + output video duration) × width × height × fps / 1024, so cost grows with the reference video while the output length does not move at all — per-second arithmetic cannot express that. The test is a single thing: whether content[] carries a video_url item.

This requestBillingHold at submit
No reference video (text-to-video, image-to-video, first/last frame, reference images, reference audio)Per second × the tier rate — byte-for-byte what it always wasunit price × the duration you sent
Carries a reference video ({type:"video_url", …})Per token, rates per delivery tier in Models & pricingunit price × (duration + 30 s)
  • Reference images and the text prompt cost no tokens (upstream reports prompt_tokens: 0); extra reference images are free.
  • Reference audio does not switch you to token billing — the official formula has no audio term, so those turns stay per second.
  • ⚠️ Budget for duration + 30, not duration. The server cannot measure your reference video (and must not simply trust a self-reported value), so it freezes the worst case allowed upstream — 30 s of total input. Settlement then uses the real usage.completion_tokens reported by upstream and refunds the rest. Insufficient balance returns 402 at submit time with nothing charged.
  • ⚠️ resolution becomes required when a reference video is present; omitting it returns 400. Token rates are graded by delivery tier, so without a tier there is no price. Requests without a reference video are unaffected and still fall back to the flat rate when resolution is omitted (see the resolution notes above).

Under either basis, the authoritative charge is the record for that request on the console's usage page.

Alignment with Volcengine Ark#

For the Seedance SKUs, request and response fields follow Volcengine Ark's create / query video generation task contract, so code written against Ark's docs or official SDK can be pointed here unchanged. Five differences to know about:

  • The asset URL appears twice: content.video_url (Ark's shape) and top-level video_url (this gateway's original shape) are always emitted together with the same value. Read either.
  • Task ids are this platform's own UUIDs (e.g. 9f2c8a1e-…), not Ark's cgt--prefixed format. Store the id as an opaque string; don't validate its prefix or length.
  • Parameter fields are "what you sent", not "what upstream picked". Ark defines ratio / resolution / seed and friends as properties of the finished video; this gateway echoes the value you specified on create. The two agree whenever you specified a concrete value, so we only emit these keys when you actually did, and we skip "upstream decides" sentinels like adaptive / -1 — a missing key beats reporting a number we never observed.
  • Gateway extensions: expires_at / duration_sec / output_files / progress don't exist upstream. The extra keys are harmless to an Ark-shaped parser.
  • Ark fields not yet provided: tools (tools actually used), usage.tool_usage (tool call counts), and priority (this platform has no priority scheduling). The request side forwards them verbatim, but you won't see them in the response.
  • Not applicable on this platform: draft / draft_task_id (draft mode). Ark restricts these to Seedance 1.5 Pro, which this platform does not carry, so they are never returned.

List tasks#

GET /v1/videos/generations (no id) — this account's video tasks, newest first. Free. Use it to recover a task id you didn't save, or just as a generation history.

Query paramDefaultNotes
limit301–100; anything larger is clamped to 100
status(all)queued / running / succeeded / failed / expired / cancelled / all, comma-separated for multiple (e.g. ?status=queued,running). An unrecognised value returns 400 invalid_request

All statuses are returned by default as of 2026-07-31. The previous default returned only succeeded, which made the list look empty while a task was still running. Pass ?status=succeeded for the old behaviour.

{
  "data": [
    {
      "id": "9a31d5c2-5c13-4caf-ad8b-1ee70ff5887f",
      "status": "running",
      "model": "doubao-seedance-2.0-fast-lite",
      "created_at": 1785495460,
      "progress": 42
    },
    {
      "id": "ed2ab10b-ceb1-4d0c-8c25-84ade94c95ac",
      "status": "succeeded",
      "model": "doubao-seedance-1-0-pro-fast-251015",
      "created_at": 1785490000,
      "content": { "video_url": "https://..." },
      "video_url": "https://...",
      "expires_at": 1786094800
    }
  ]
}
FieldPresent whenNotes
idalwaysThe task id — the same value GET /v1/videos/generations/{id} takes
statusalwaysSame vocabulary as the polling endpoint
modelalwaysThe submitted Model ID (canonicalised)
created_atalwaysSubmit time, Unix seconds
progressqueued/running, and only when upstream reports itInteger 0–100. An absent field means the model has no progress data — not 0%
contentsucceeded only{video_url}, the same shape as the polling endpoint. ⚠️ Not necessarily the same URL though: this endpoint prefers a permanent public URL while the polling endpoint returns a presigned one. Both point at the same asset — don't compare or de-duplicate them as strings
video_urlsucceeded onlySame value as content.video_url. A presigned link valid for 7 days; calling this endpoint again re-signs an expired one
expires_atsucceeded onlyWhen video_url expires, Unix seconds
output_filessucceeded, subtitle SKUs onlyOne download link per language
errorfailed/expired/cancelled only{code, message}

This endpoint is a gateway extension (Ark has no list endpoint). Each entry also carries card fields — mode (t2v/i2v), prompt, ratio, resolution, duration — so a "generation history" UI can render straight from it. Note these are reconstructed from the create request (duration is the length you ordered), which is a different basis from the delivered duration on the polling endpoint.

import requests
r = requests.get(
    "https://zhonkezhonkeapi.dflop.top/v1/videos/generations",
    headers={"Authorization": f"Bearer {PLATFORM_API_KEY}"},
    params={"limit": 20},                    # in-flight only: {"status": "queued,running"}
    timeout=30,
)
for t in r.json()["data"]:
    print(t["id"], t["status"], t.get("video_url", ""))

How billing works#

  • On submit, unit price × duration is held against the balance (12 seconds if duration is missing); an insufficient balance returns 402
  • Settlement on a final state: success charges the actual length (falling back to the requested seconds when upstream doesn't report one), and failure or expiry is refunded in full; any failure during submission (an upstream error, a failed insert) releases the hold immediately
  • GET /v1/videos/generations (with no id) lists this account's tasks, all statuses by default (?limit= default 30, max 100; ?status=succeeded for successes only) — useful both as a "generation history" and to recover a lost task id (see Recovering a task id)

Limitations#

  • Successful videos are copied into our own object storage, and video_url is a pre-signed link valid for 7 days (expires_at is when it dies); calling the list endpoint again after expiry re-signs a fresh link
  • Upstream moderation can intercept after generation finishes (error codes like OutputVideoSensitiveContentDetected); that counts as a failure and isn't billed

POST /v1/music/generations#

Suno AI music generation, with the same async task shape as video: submit for a task id, then poll to a final state. One generation yields 2 complete songs (with lyrics and cover art).

Available models#

Model IDDisplay namePrice (per generation)
suno-v3.5Suno V3.519.41
suno-v4Suno V419.41
suno-v4.5Suno V4.519.41
suno-v5Suno V519.41
suno-v5.5Suno V5.5 (latest)19.41

The request#

curl https://zhonkezhonkeapi.dflop.top/v1/music/generations \
  -H "Authorization: Bearer $GPUSHARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "suno-v5.5",
    "prompt": "An upbeat Mandarin pop song about a summer walk by the sea"
  }'
FieldTypeNotes
modelstringrequired; any id from the table above
promptstringinspiration mode (≤200 characters): the AI writes the lyrics, picks a title and sings
lyricsstringyour own lyrics (≤3000 characters); passing them switches to custom mode and prompt is ignored
titlestringsong title (custom mode)
tagsstringstyle, e.g. "synthwave, female vocal"
negative_tagsstringstyles to avoid
instrumentalboolinstrumental only (lyrics ignored)

Pass at least one of prompt and lyrics (both may be omitted when instrumental: true). Response: {"id": "<task_id>", "status": "queued", "model": "...", "created_at": ...}.

Polling the task#

curl https://zhonkezhonkeapi.dflop.top/v1/music/generations/$TASK_ID \
  -H "Authorization: Bearer $GPUSHARE_API_KEY"

status is one of processing | succeeded | failed | expired. On success the tracks array gives each song:

{
  "id": "…",
  "status": "succeeded",
  "tracks": [
    {
      "clip_id": "…",
      "title": "Slow Sea Breeze",
      "duration_sec": 192.0,
      "audio_url": "https://…mp3",
      "image_url": "https://…jpeg",
      "lyrics": "[Verse]…"
    }
  ]
}

Generation usually takes 2–4 minutes; poll every 10–20 seconds. Task ids are private to their account, and someone else's or a nonexistent one always returns 404.

List tasks#

GET /v1/music/generations (no id) — this account's music tasks, newest first. Free. Use it to recover a task id you didn't save.

Query paramDefaultNotes
limit301–100
status(all)processing / succeeded / failed / expired / cancelled / all, comma-separated for multiple. ⚠️ The music family has no queued/running — everything between submit and terminal is processing (same vocabulary as the polling endpoint). An unrecognised value returns 400

The response is {"data": [ … ]}, and each item is field-for-field identical to the single-task polling response above (id / model / status / upstream_status / tracks[] / error_code / error_message / created_at / completed_at), so a list item can be consumed exactly like a poll result — no second parser needed.

curl "https://zhonkezhonkeapi.dflop.top/v1/music/generations?limit=10&status=processing" \
  -H "Authorization: Bearer $PLATFORM_API_KEY"

How billing works#

  • The flat unit price is held on submit (one generation = 2 songs, already included); an insufficient balance returns 402
  • Settlement: one successful song is enough to charge full price; both failing, or 30 minutes without completion (expiry), is refunded in full, and any failure during submission releases the hold immediately
  • Audio and cover art are copied into our own object storage, with audio_url / image_url as 7-day pre-signed links; if the copy fails we fall back to the upstream's original links

POST /v1/audio/speech#

Speech synthesis: text (≤5000 characters) → MP3, with cloned voices and speed adjustment (speed applies to cloned voices only). Billed per input character (voice-tts-pro, 125.36 per 1,000 characters).

⚠️ Use async mode for long scripts. Synthesis runs on an upstream async queue (seconds for short text, minutes for long), while a synchronous call is bound by the CDN's roughly 100-second ceiling on non-streaming responses — and when that cuts you off, the audio still renders and you're still charged, but you get nothing back. Add "async": true to the body to switch to submit-and-poll.

The request#

{
  "model": "voice-tts-pro",
  "input": "Hello, and welcome to speech synthesis.",
  "voice": "<optional: a platform preset voice id, or a cloned voice id from /v1/audio/voices; omitted means the default voice>",
  "speed": 1.0,
  "async": false
}

The response (synchronous, async omitted or false)#

{
  "model": "voice-tts-pro",
  "audio_url": "https://r2.dflop.top/audio-speech/…/xxx.mp3",
  "characters": 12,
  "cost_usd": "0.0037"
}

audio_url is a permanent public link in our object storage, and can be fed straight into a digital human's (dh-avatar) audio_url.

The response("async": true)#

Returns a task id immediately, without blocking:

{ "id": "3a8e…", "model": "voice-tts-pro", "status": "pending", "characters": 1200, "created_at": "…" }

Then poll GET /v1/audio/speech/{id} (free):

{
  "id": "3a8e…", "model": "voice-tts-pro", "status": "succeeded",
  "characters": 1200, "duration_sec": "86.40",
  "audio_url": "https://r2.dflop.top/audio-speech/…/xxx.mp3", "created_at": "…"
}

status has three states: pending / succeeded / failed. Failures are refunded in full automatically; you're only charged on success, and the audio again lands on a permanent link.

List tasks#

GET /v1/audio/speech (no id) — this account's synthesis tasks, newest first. Free. Use it to recover the task id from an "async": true submit you didn't save.

Query paramDefaultNotes
limit301–100
status(all)pending / succeeded / failed / all, comma-separated for multiple. An unrecognised value returns 400

The response is {"data": [ … ]}, and each item is field-for-field identical to GET /v1/audio/speech/{id} (id / model / status / characters / created_at, plus audio_url / duration_sec on success and error.message on failure).

curl "https://zhonkezhonkeapi.dflop.top/v1/audio/speech?limit=10" \
  -H "Authorization: Bearer $PLATFORM_API_KEY"

/v1/audio/voices — voice cloning and management#

MethodPathNotes
POST/v1/audio/voicesClone a voice: {name, audio_url, async?} (a public reference-audio URL, 5 seconds to 3 minutes of clear speech). Billed per call (voice-clone-pro, 40.44 each)
GET/v1/audio/voicesList this account's cloned voices plus the platform presets: {voices:[…], presets:[{id, name}]}
GET/v1/audio/voices/{id}Check one voice's status (pending / ready / failed)
DELETE/v1/audio/voices/{id}Delete a voice (our record of it)

Cloning also supports "async": true: it returns {id, status:"pending"} immediately and you poll GET /v1/audio/voices/{id} until ready. By default it blocks until ready (tens of seconds to minutes) — which, as above, runs into the CDN's ~100-second ceiling, so new integrations should always use async. Failures are refunded in full automatically.

Pass the resulting voice id as voice on /v1/audio/speech to synthesise with it, or as the voice for a text-driven digital human (dh-avatar; see Digital human API). Before cloning a real person's voice, make sure you have their permission.

The platform also offers a set of shared voices (the presets in the GET /v1/audio/voices response) — pass one of their ids as voice with no cloning required.


POST /v1/transcripts/extract#

Short-video link → spoken script: paste a share link or share token from a short video and get back the video's spoken script plus metadata (title, cover, platform, length). The upstream detects the platform itself (Douyin, Kuaishou, Xiaohongshu, Bilibili, WeChat Channels and other major sites), so you don't specify a source.

The server blocks synchronously until extraction finishes (internally: create a task, then poll upstream — usually 5–40 seconds, at most about 55). Set a generous client read timeout (≥ 90 seconds recommended). The upstream's concurrency ceiling is low, so heavy parallel use queues up and slows down.

The request#

{
  "url": "https://v.douyin.com/xxxxxx/   — or just paste the raw share token"
}
FieldNotes
urlRequired. A short-video share link or raw share token (≤ 2000 characters). input is an equivalent alias.

The response#

{
  "model": "video-transcript",
  "content": "the extracted spoken script…",
  "title": "the original video title",
  "cover": "https://… cover image URL",
  "platform": "douyin",
  "duration_sec": 42,
  "origin_link": "https://… the original link echoed by upstream"
}

platform is the platform upstream identified (douyin, kuaishou and so on). content is the script itself; title, cover and duration_sec are supporting metadata and may be empty when the video has no such field.

curl#

curl -X POST https://zhonkezhonkeapi.dflop.top/v1/transcripts/extract \
  -H "Authorization: Bearer $GPUSHARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://v.douyin.com/xxxxxx/"}'

How billing works#

  • A flat per-call charge (video-transcript, 20.22 each) against the account balance (shared by all keys).
  • Only successes are billed: an unparseable link, an unsupported video, an extraction timeout, or a script blocked by content safety are all free; only a clean, successful script costs you one call.
  • Each call is recorded in usage and the call log as unit_type=transcript (the response carries x-gateway-trace; include it with the timestamp when reporting a problem).

Limits and errors#

  • The cover URL may be time-limited (roughly 24 hours) — download and store it yourself if you need it long-term.
  • Input is capped at 2000 characters, and the upstream's low concurrency ceiling means heavy parallel use queues.
  • Errors use the normalised shape shared with every other endpoint: an invalid link or unsupported video → 400, an extraction timeout → 504, service quota temporarily exhausted → 503, an upstream connection failure → 502, insufficient balance → 402.
  • To restrict a key to this capability only, put video-transcript in its allowed_models (no allowed_models means every model the account can use).