Dart: submit a 30-second Wan 3.0 clip with package:http
Server-side Dart with package:http: POST wan-3.0 for 30 seconds, poll until completed and write the MP4. Keep the key out of the Flutter app.

Call Sume from Dart on the server, not from the Flutter app: the API key authorises spend, and a 30-second Wan 3.0 job at 720p reserves $3.75. The script below uses package:http to submit wan-3.0, poll the job and save clip.mp4. Run it in a Dart server, a Cloud function or a CLI, and let the app ask your backend for the result.
bin/clip.dart
Add http to pubspec.yaml and run dart run bin/clip.dart with SUME_API_KEY set.
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
Future<void> main() async {
final key = Platform.environment['SUME_API_KEY'] ?? '';
if (key.isEmpty) throw StateError('SUME_API_KEY is empty');
final auth = {'Authorization': 'Bearer $key'};
const base = 'https://api.sume.com/v1/videos';
final res = await http.post(Uri.parse(base),
headers: {...auth, 'Content-Type': 'application/json', 'Idempotency-Key': 'wan-dart-001'},
body: jsonEncode({
'model': 'wan-3.0', 'prompt': 'A cyclist crossing a bridge in morning fog',
'duration': 30, 'resolution': '720p', 'aspect_ratio': '9:16',
}));
if (res.statusCode >= 300) throw StateError('submit failed: ${res.statusCode}');
final id = jsonDecode(res.body)['id'];
var status = 'pending';
while (status == 'pending' || status == 'in_progress') {
await Future.delayed(const Duration(seconds: 5));
final s = await http.get(Uri.parse('$base/$id'), headers: auth);
status = jsonDecode(s.body)['status'];
}
if (status != 'completed') throw StateError('job ended as $status');
final mp4 = await http.get(Uri.parse('$base/$id/content?index=0'), headers: auth);
await File('clip.mp4').writeAsBytes(mp4.bodyBytes);
}Points to check
- A missing key throws before any request is made.
durationis the integer30. Thewan-3.0id accepts 2 to 30 seconds according to the Video generation docs.- The idempotency key
wan-dart-001is fixed for the demo. Build one per request in real code, for example from the user id and a request counter.
What your app can show the user
Quote the cost before the user taps generate. The per-second rate is constant for each resolution, so the quote is multiplication:
| Length | 480p | 720p | 1080p |
|---|---|---|---|
| 5 s | $0.3125 | $0.625 | $1.25 |
| 15 s | $0.9375 | $1.875 | $3.75 |
| 30 s | $1.875 | $3.75 | $7.50 |
Where the key lives
A Flutter binary can be unpacked, so any key inside it is public. Store SUME_API_KEY in your server's secret store, authenticate your own users to your own backend, and have the backend submit to Sume. That also lets you enforce a per-user limit before a 30-second job reserves its price.
Return a job id, not a blocked request
Mobile clients should not hold a connection open for the length of a generation. Have your backend return the Sume job id right after the submit, then expose a status endpoint that your app polls, or push a notification when the callback arrives.
Sources
Related posts
More in Developers
- Deno 2.9.7 traceparent fix: tag a Sume job id in a Deno.serve log
Deno 2.9.7 extracts traceparent from Deno.serve regardless of header case. In a Sume webhook handler, log the job_id with the trace so a render is traceable.
- Deno: a 30-second Wan 3.0 video with fetch and top-level await
A short Deno script that submits a 30-second wan-3.0 job, polls to completion and writes clip.mp4, using only fetch and no npm packages.
- Deploy check: does your video model id and duration exist on Sume?
A Python preflight that reads GET /v1/videos/models and fails the deploy when VIDEO_MODEL is not a catalog id or the duration is outside supported_durations.
- detach_source_has_no_audio: check probe.has_audio first
Audio detach fails with detach_source_has_no_audio on silent video. Probe has_audio with a free frames-false video inspect first. Refusal codes listed.
Written by Sume