Express 5.3 logs Error.cause: keep the Sume request_id
Express 5.3.0 prints the full error object, nested cause included. Wrap a failed Sume call so its code and request_id land in the log line you grep.

When an Express 5.3 route fails because a Sume call failed, throw new Error(message, { cause: body.error }) with the parsed Sume error object as the cause. Express 5.3.0, released on 2026-10-09, changed its default error handler to log the whole error object, so the Sume code and request_id print next to the stack without any custom logging.
That matters because the request id is the one thing Sume support asks for. The errors page says the id is safe to share and appears in the response body and headers. If your route turns a 429 into a bare 500, the id is gone by the time anyone reads the log.
What Express 5.3.0 changed
The facts below come from the Express v5.3.0 release notes (read 2026-10-10). Only the first row is about this post; the others are listed so you can judge the upgrade.
| Change in 5.3.0 | Detail from the notes | Why a Sume caller cares |
|---|---|---|
| Default error handler logs the full error object | Nested details such as Error.cause are preserved (#6464) | A wrapped Sume error shows its code and request_id in the default log |
res.send() header fix | Content-Length is only added when Transfer-Encoding is absent (#4893, #7459) | None for a JSON reply, but read it if you proxy artifacts |
| Dependency bumps | body-parser ^2.3.0, qs ^6.16.0, proxy-addr ^2.0.8, with CVE fixes named in the notes | Upgrade anyway if the same app receives Sume webhooks |
The route
The video docs show POST /v1/videos with model, prompt, and an optional integer duration. The route below submits one job. Express 5 forwards a rejected async handler to the error handler, so there is no try/catch and no next(err).
I ran this file on Express 5.3.0 against a stub that answered 429 with the documented error envelope. The client got a 500, and the default handler wrote the stack plus a [cause] block holding code: 'rate_limited' and the request_id.
import express from "express";
const app = express();
const SUME = process.env.SUME_BASE ?? "https://api.sume.com";
app.post("/render", async (req, res) => {
const key = req.get("x-render-key"); // one stable key per render request
if (!key) return res.status(400).json({ error: "x-render-key required" });
const r = await fetch(`${SUME}/v1/videos`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify({ model: "seedance-2.5", prompt: "Rain on a neon street", duration: 5 }),
});
const body = await r.json();
if (!r.ok) {
// Express 5 forwards a rejected async handler to the error handler.
throw new Error(`Sume submit failed with ${r.status}`, { cause: body.error });
}
res.status(202).json({ job_id: body.id, poll: body.polling_url });
});
app.listen(3000);What to put in the cause
Put only the parsed error object from the Sume response in cause. It holds code, message, request_id, and details, which are the public fields. Do not put the request headers or the Authorization value in it, because the default handler prints everything you attach.
- Keep the same
Idempotency-Keywhen you retry the route. Sume's jobs page says a retry with the same key returns the original job instead of billing a second one. - A
429 rate_limitedcarriesretry-afterwhen Sume sets it. Read the header before you throw, and put the number in the message if your callers need it. - A
402 insufficient_creditsis not retryable. Throw it with the cause too, but map it to a different status than a transient503so your own clients stop retrying. - If you already use a logger such as pino, serialize
err.causeyourself. The 5.3.0 change only affects the built-in handler.
This is a logging fix, not a retry policy. The route above still returns a plain 500 for every Sume failure; the improvement is that the next person to open the log can see which Sume error it was.
Sources
Related posts
More in Developers
- Failed Format runs: a dead-letter table you can retry from
Keep a dead-letter table for failed Sume Format runs: run id, thread id, failure code, retry flag, and a new idempotency key, so retries stay safe.
- Sume failed job says [redacted_url]: what was removed
A [redacted_url] in a Sume job error is by design: URLs, provider ids, env names and secrets are masked, and the provider reason is capped at 300 characters.
- FastAPI 0.143 makes OTel exporter setup opt-in: Sume webhook spans
FastAPI 0.143.0 stops auto-configuring OpenTelemetry exporters. If your Sume webhook route traces went quiet after the upgrade, here is the one-line opt-in.
- Prove a Sume retry makes one job: drop the response on purpose
A 28-line fake server creates the job, then hangs up before replying. A stable Idempotency-Key leaves one job; a fresh key per retry leaves two. Runs anywhere.
Written by Sume