PHP: submit a 30-second Wan 3.0 clip, poll it, save the MP4
Plain PHP 8 with curl and no SDK: send a 30-second wan-3.0 request, poll the job, and write the MP4 to disk. The 720p run is reserved at $3.75.

You can drive a 30-second Wan 3.0 clip from plain PHP 8 with curl: POST to /v1/videos with "model": "wan-3.0" and "duration": 30, poll GET /v1/videos/{id} until the status is completed, then save /content?index=0. At 720p Sume reserves $3.75 for the job. Alibaba lists wan3.0-video at 2 to 30 seconds and 30 fps MP4 (read 2026-10-08), and the Video generation docs give the same 2 to 30 second range for the wan-3.0 id.
The script
One helper does every call. CURLOPT_FAILONERROR turns any HTTP 4xx or 5xx into a failed request, so a 402 insufficient_credits stops the script instead of being parsed as a job.
<?php
$base = "https://api.sume.com";
$key = getenv("SUME_API_KEY") ?: exit("SUME_API_KEY is empty\n");
function sume(string $method, string $url, string $key, ?array $body = null, string $idem = ""): string {
$headers = ["Authorization: Bearer $key", "Content-Type: application/json"];
if ($idem !== "") $headers[] = "Idempotency-Key: $idem";
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true, CURLOPT_FAILONERROR => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$out = curl_exec($ch);
if ($out === false) exit("request failed: " . curl_error($ch) . "\n");
return $out;
}
$job = json_decode(sume("POST", "$base/v1/videos", $key, [
"model" => "wan-3.0", "prompt" => "A fishing boat leaving a harbor at dawn",
"duration" => 30, "resolution" => "720p", "aspect_ratio" => "16:9",
], "wan-php-001"), true);
do {
sleep(5);
$s = json_decode(sume("GET", "$base/v1/videos/{$job['id']}", $key), true);
} while (in_array($s["status"], ["pending", "in_progress"], true));
if ($s["status"] !== "completed") exit("ended as {$s['status']}\n");
file_put_contents("clip.mp4", sume("GET", "$base/v1/videos/{$job['id']}/content?index=0", $key));What each piece is for
Idempotency-Keyon the POST only: a retry of the same key returns the original job instead of a second charge. Change the key for a different request.durationis an integer. Send30, not30.0or"30s".- The poll loop treats
pendingandin_progressas running and everything else as terminal;failedandcancelledfall through to the exit message.
Price before you run it
The reserve is flat per second, so the whole table is known before you send anything.
| Resolution | Per second | 30-second clip |
|---|---|---|
| 480p | $0.0625 | $1.875 |
| 720p | $0.125 | $3.75 |
| 1080p | $0.25 | $7.50 |
Failure cases to handle
Three responses matter for a paid submit.
402 insufficient_credits: the balance cannot cover the reserve. Submit a cheaper request, or change plan, then retry with a fresh key if the request changed.409 idempotency_conflict: the key was used for a different payload. Mint a new key for a genuinely new request.429: the workspace has no accepted-job capacity right now. Wait, then retry with the same key; the Generation admission page lists the codes.
Before production
Replace the five-second sleep with a callback_url if the script runs under a web request limit; the callback must be HTTPS. Keep the key in the environment, never in the file, and log the job id next to the prompt so a cost line can be traced to its request.
Sources
Related posts
More in Developers
- Pointing the OpenAI SDK at api.sume.com: why Sora calls still fail
Changing only the base URL does not carry a Sora call to Sume. Five documented differences: model ids, body format, seconds and size, status words, download.
- Poll /v1/jobs/{id}/status, then /result, in Node with backoff
A 25-line Node function that polls a Sume job on its own next_poll_after_seconds, reads /result only when the job completed, and keeps the id on timeout.
- Polling Omni jobs can't 429 your submits on Sume
Sume gives each API key separate read and write budgets: 120 writes and 4,800 reads a minute on Free, 1,200 and 48,000 on Scale. The math for an Omni batch.
- Port an OpenRouter video client to Sume: base URL, key, model ids
Sume's /v1/videos follows the OpenRouter video wire. Three edits move a client: base URL, API key, bare model id. The six differences that still bite.
Written by Sume