Bun 1.4.1 Bun.write(path, response): save Sume artifacts to disk

Bun 1.4.1 streams a Response body to disk instead of buffering it. Read GET /v1/jobs/:id/result, then Bun.write each artifact URL, checking the status first.

5 min readSume
All posts

Short answer

After a Sume job completes, read GET /v1/jobs/:id/result, then await Bun.write(path, await fetch(artifact.url)) for each artifact. Bun v1.4.1, released 4 September 2026, changed Bun.write to stream a Response, Request or ReadableStream body into the file instead of reading it into memory first.

That matters for video artifacts. The release notes measure a 128 MiB download: before the change it added 161 MB to peak RSS, after it adds 13 MB. Check response.ok before writing, or an error page ends up in a file named like your video.

What 1.4.1 changed

Two other items in the same release touch HTTP code: Bun.serve gained HTTP/2, and fetch() now verifies TLS against the URL rather than the Host header. Neither changes how you call Sume.

Bun v1.4.1 items (read 2026-10-03)
ItemDetail
Bun.write(path, response)streams the body to disk; 128 MiB download: +161 MB RSS before, +13 MB after
Bun.serveHTTP/2 on the same port as HTTP/1.1, same routes and fetch handler
fetch()TLS verified against the URL, not the Host header; unix socket connections reused

The result shape

A completed job returns data.result.artifacts, each entry with id, type, url and content_type. The URLs are Sume media URLs; raw provider URLs are not public outputs. The result route answers 409 job_not_completed before completion, so call it after the status poll reports result_ready.

Artifact fields used by the script (as of 2026-10-03)
FieldExample
idartf_...
typeimage
urlhttps://media.sume.com/artifacts/...
content_typeimage/png

The script

Run SUME_API_KEY=... bun save.mjs job_123. The API key goes only to api.sume.com; the media download is a plain fetch with no credential, so the key is not sent to the CDN host.

const id = process.argv[2];
const res = await fetch(`https://api.sume.com/v1/jobs/${id}/result`, {
  headers: { Authorization: `Bearer ${process.env.SUME_API_KEY}` },
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);

const { data } = await res.json();
for (const a of data.result.artifacts) {
  const ext = (a.content_type.split("/")[1] ?? "bin").replace("jpeg", "jpg");
  const dl = await fetch(a.url);
  if (!dl.ok) throw new Error(`download ${a.id}: ${dl.status}`);
  const bytes = await Bun.write(`./${a.id}.${ext}`, dl);
  console.log(a.id, a.type, bytes, "bytes");
}

What Sume does and does not do

Sume stores outputs behind durable media URLs and lists them in the job result, so you can re-run this script later against the same job id. Do not store a raw provider URL; the public API does not return one.

Sume does not guarantee a file extension from type; derive it from content_type, as above, and treat an unknown type as a binary file.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume