PHP cURL: submit a Seedance 2.5 video job, poll it, save the MP4

PHP with only ext-curl: POST /v1/videos for seedance-2.5, poll polling_url until completed, then download content?index=0; a 5-second 480p test costs $1.34.

5 min readSume
All posts

With plain PHP cURL you submit POST https://api.sume.com/v1/videos, read polling_url from the 202 response, poll it until status is completed, and download GET /v1/videos/{id}/content?index=0 with the same bearer key. A 5-second 480p seedance-2.5 test clip costs $1.34 on Sume (Video generation, read 2026-10-05).

What does the whole script look like?

This uses only the cURL extension and json_decode, with no Composer packages. Set SUME_API_KEY in the environment first.

<?php
$key = getenv('SUME_API_KEY');
$headers = ["Authorization: Bearer $key", 'Content-Type: application/json'];
function call($url, $headers, $body = null) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true]);
    if ($body !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body)); }
    $out = curl_exec($ch);
    curl_close($ch);
    return json_decode($out, true);
}
$job = call('https://api.sume.com/v1/videos', $headers, [
    'model' => 'seedance-2.5',
    'prompt' => 'A paper boat drifts down a rain gutter, soft overcast light',
    'duration' => 5, 'resolution' => '480p', 'aspect_ratio' => '16:9',
]);
do {
    sleep(10);
    $s = call($job['polling_url'], $headers);
    echo $s['status'], "\n";
} while (in_array($s['status'], ['pending', 'in_progress'], true));
if ($s['status'] === 'completed') {
    $ch = curl_init("https://api.sume.com/v1/videos/{$job['id']}/content?index=0");
    curl_setopt_array($ch, [CURLOPT_HTTPHEADER => $headers, CURLOPT_FOLLOWLOCATION => true, CURLOPT_RETURNTRANSFER => true]);
    file_put_contents('clip.mp4', curl_exec($ch));
}

Which statuses do I handle?

The loop stops on any status other than pending or in_progress. Read the error field on failed.

Video job statuses on /v1/videos (read 2026-10-05)
StatusMeaningWhat the script does
pendingqueuedkeeps polling
in_progressgeneratingkeeps polling
completedready to downloaddownloads the MP4
failedgeneration failed, see errorstops; no file
cancelledcanceled before completingstops; no file

What about retries and callbacks?

Add an Idempotency-Key header on the submit call so a timeout-and-retry returns the original job instead of a second charge. If you would rather not poll, send an HTTPS callback_url and verify the signed webhook; the signature header is x-sume-webhook-signature. For the same flow in another language, see the Ruby version.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume