C# HttpClient: submit a Seedance 2.5 job and poll until completed
A .NET top-level-statements script that posts a seedance-2.5 job to Sume, polls every 30 seconds, and stops on completed, failed or cancelled.

In C#, use one HttpClient with a Bearer header, POST JSON to https://api.sume.com/v1/videos, then poll the polling_url from the 202 response until the status is terminal. The script below uses only the .NET base library (6 or newer) and seedance-2.5, which takes 4 to 30 seconds at 480p, 720p or 1080p.
Seedance 2.5 on Sume bills about $0.27 per second at 480p (9:16), so the 8-second job below is roughly $2.15. A 4-second 480p clip is about $1.07.
The script
Save it as Program.cs in a console project with top-level statements. The key comes from SUME_API_KEY, and the loop sleeps 30 seconds between polls.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var key = Environment.GetEnvironmentVariable("SUME_API_KEY")
?? throw new InvalidOperationException("Set SUME_API_KEY");
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", key);
var body = JsonSerializer.Serialize(new {
model = "seedance-2.5",
prompt = "A barista pours latte art, close-up, warm morning light",
duration = 8,
resolution = "480p",
aspect_ratio = "9:16"
});
var res = await http.PostAsync("https://api.sume.com/v1/videos",
new StringContent(body, Encoding.UTF8, "application/json"));
res.EnsureSuccessStatusCode();
using var job = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
var pollingUrl = job.RootElement.GetProperty("polling_url").GetString()!;
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(30));
using var st = JsonDocument.Parse(await http.GetStringAsync(pollingUrl));
var status = st.RootElement.GetProperty("status").GetString();
Console.WriteLine(status);
if (status is "completed" or "failed" or "cancelled") break;
}What to do on each outcome
After the loop, read unsigned_urls[0] on completed and error on failed (video generation docs). A failed job answers the content endpoint with 409 job_failed, which is not retryable, so do not loop on it.
| Clip | Resolution | Billed on Sume |
|---|---|---|
| 4 seconds | 480p | about $1.07 |
| 4 seconds | 720p | about $2.31 |
| 10 seconds | 720p | about $5.78 |
| 30 seconds | 1080p | $42.65 |
Production notes
Reuse one HttpClient for the life of the process. Send an Idempotency-Key header on the POST so a retried request returns the original job instead of a second charge, and set a deadline on the loop so a stuck job cannot hold a worker forever (jobs and results).
For a webhook instead of polling, pass an HTTPS callback_url in the body.
Practical notes
In ASP.NET, put the submit behind a controller action that returns the job id, and run the polling in a hosted service or a queue worker, not inside the request. Requests that wait for video time out; jobs that run in the background do not.
Log usage.cost from the completed poll response next to your own order id. It is the billed amount, so it is the number to reconcile against your invoice.
Sources
Related posts
More in Developers
- callback_url or webhook_url: which field each Sume video route takes
POST /v1/videos takes callback_url; motion control, lip-sync and image routes take mode plus webhook_url. The field names and what they share.
- Cancel a wrong video job after a Sora port: only before it starts
Ported prompts on the wrong model burn money. Sume cancels a video job only before generation starts; later you get 409 job_generation_already_started.
- Cancel a Format run: cancel_effect canceled vs no_op, and the bill
Cancel is idempotent. cancel_effect says canceled or no_op, a canceled run never sends a webhook, and generation that finished is still billed.
- Cap Sume spend from an agent loop: dry_run, max_spend_usd and run caps
Four optional guards cap what an automated Sume caller can spend: dry_run, max_spend_usd, generation_spend_cap_usd on Formats, and a balance check.
Written by Sume