Nushell: submit a Sume job and poll it to a result, no jq needed
A Nushell script that POSTs a Sume image job with an Idempotency-Key, polls /v1/jobs/{id}/status by next_poll_after_seconds, then fetches the result.

In Nushell, submit a Sume job with http post --headers and mode: "async", keep the job id from the first response, then loop on GET /v1/jobs/{id}/status until terminal is true, sleeping for next_poll_after_seconds between reads, and fetch /result only when sume_status is completed. Because Nushell returns parsed records, there is no jq: you read $s.terminal directly. Send an Idempotency-Key on the submit so that a retried script returns the original job instead of billing a second one.
The loop follows Sume's Jobs and results page (read 2026-10-11), which calls it the client-side equivalent of subscribe().
Which fields should the loop read?
Sume jobs have five statuses: queued, processing, completed, failed, canceled. The last three are terminal. The docs recommend polling on the booleans terminal and result_ready or on sume_status, and warn against mixing them with the queue-shaped status field (IN_QUEUE, IN_PROGRESS, COMPLETED, ...), which maps one to one onto sume_status.
GET /v1/jobs/{id}/result answers 409 job_not_completed unless the job completed, so a failed job needs its error from the job record (GET /v1/jobs/{id}), not from /result.
| sume_status | Terminal | What the script does |
|---|---|---|
queued | No | Sleep, then poll again |
processing | No | Sleep, then poll again |
completed | Yes | GET /v1/jobs/{id}/result |
failed | Yes | Read the error from GET /v1/jobs/{id} |
canceled | Yes | Stop; nothing to fetch |
What is the script?
It stops with an error if the key is missing, derives the idempotency key from the prompt hash, and gives up after ten minutes with a message that the job keeps running and billing. The response handling accepts either a bare record or one wrapped in data.
# nu poll.nu "a matte black bottle on marble"
def main [prompt: string] {
let key = ($env.SUME_API_KEY? | default "")
if ($key | is-empty) { error make {msg: "set SUME_API_KEY"} }
let base = ($env.SUME_API_BASE? | default "https://api.sume.com")
let h = {Authorization: $"Bearer ($key)", Idempotency-Key: $"nu-(($prompt | hash sha256) | str substring 0..15)"}
let sub = (http post --content-type application/json --headers $h $"($base)/v1/image-1.0/generate" {prompt: $prompt, mode: "async"})
let id = ($sub.request_id? | default $sub.id?)
mut s = {terminal: false}
mut waited = 0sec
while (not $s.terminal) and $waited < 10min {
let r = (http get --headers $h $"($base)/v1/jobs/($id)/status")
$s = ($r.data? | default $r)
if $s.terminal { break }
let pause = (($s.next_poll_after_seconds? | default 3) * 1sec)
sleep $pause
$waited = $waited + $pause
}
if not $s.terminal { error make {msg: $"still running, job ($id) keeps billing"} }
if $s.sume_status != "completed" { error make {msg: $"job ($id) ended ($s.sume_status)"} }
http get --headers $h $"($base)/v1/jobs/($id)/result"
}What did I actually run?
I ran this on Nushell 0.116 against a small local stub that answers the submit with 202 and a request_id, answers the status twice as processing with next_poll_after_seconds: 1, then as completed, and returns an artifact list from /result. It sent one Idempotency-Key, made three status reads, and fetched the result once. I did not run it against the live API, so check the field names against the live OpenAPI at https://api.sume.com/reference/json before you rely on them.
A client-side timeout does not cancel the job. Cancel works only before generation work starts; afterward, POST /v1/jobs/{id}/cancel returns 409 job_generation_already_started.
What if the submit itself fails?
Retry the submit with the same Idempotency-Key. Do not generate a new key per attempt, or you pay twice. A 429 carrying rate_limited or queue_full should wait for retry-after when present, and Sume's error page lists the other codes: 402 insufficient_credits, 409 for job-state conflicts, 413 payload_too_large, and 415 unsupported_media_type. Nushell's http post raises on non-2xx, so wrap it in try when you want to read the error body.
Sources
Related posts
More in Developers
- One-job limits in Sume's TTS Router: v3 5,000, v4 10,000, Sonic 20,000
Each TTS Router id has its own max_characters. A Python planner splits a script on paragraph breaks so each request fits the row you picked.
- Phoenix: cache the raw body with Plug.Parsers body_reader for Sume
Phoenix parses JSON before your controller runs. Cache the raw bytes with a Plug.Parsers body_reader, then verify the Sume webhook HMAC signature.
- PowerShell Invoke-RestMethod: a Sume video job to replace Sora
Replace a PowerShell script that called the Sora Videos API: submit to Sume /v1/videos with Invoke-RestMethod, poll polling_url, download the mp4.
- pplx-embed-v2-late for storyboard frames: Sume has no embeddings route
Perplexity's pplx-embed-v2-late embeds images and pages. Sume lists no embeddings route; use video_frames_create for stills and index them on your side.
Written by Sume