pino logs for Sume API calls: job_id, request_id, redacted keys
Log every Sume call with pino child loggers that carry job_id and the error request_id, and redact the Authorization header so keys never reach the log store.

Create one pino logger with redact set for the Authorization header and your signing secret, then bind a child logger to each job with log.child({ job_id }). Every line from that job's submit, polls and webhook now carries the same job_id, and a failed call also logs the request_id from Sume's error envelope. A support ticket then needs one string, not a time window.
The id to join on is the job id. It is what Jobs and results tells you to store from the submit response, and it is the job_id on the webhook, so submit logs, poll logs and webhook logs line up without a lookup table.
A logger that cannot leak the key
The helper below submits a video job, logs the outcome and returns the id. Note what it does not log: the Authorization header, the request body with the prompt if you treat prompts as sensitive, and the signing secret.
import pino from "pino";
const log = pino({
redact: { paths: ["headers.authorization", "secret", "*.secret"], censor: "[redacted]" },
});
const key = process.env.SUME_API_KEY;
if (!key) throw new Error("SUME_API_KEY is empty");
export async function submit(prompt, idempotencyKey) {
const l = log.child({ idempotency_key: idempotencyKey });
const res = await fetch("https://api.sume.com/v1/videos", {
method: "POST",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json", "Idempotency-Key": idempotencyKey },
body: JSON.stringify({ model: "sume/auto", prompt, duration: 5 }),
});
const body = await res.json();
if (!res.ok) {
l.error({ http_status: res.status, code: body.error?.code, request_id: body.error?.request_id }, "submit failed");
throw new Error(body.error?.code ?? "submit_failed");
}
l.child({ job_id: body.id }).info({ status: body.status }, "job accepted");
return body.id;
}Which fields to log
Keep the set small and join on ids. Fields with unbounded values, such as prompts, belong in your own data store, not in every log line.
| Field | Where it comes from | Why |
|---|---|---|
| job_id | Submit response id, webhook job_id | Primary join key across submit, polls and webhooks |
| idempotency_key | Your own header value | Shows whether a retry reused the key |
| http_status and error.code | Submit or poll response | 429 queue_full and rate_limited need different handling |
| error.request_id | Error envelope | What Sume support can look up for a failed call |
| status or event | Poll status, webhook event | job.completed, job.failed or job.canceled |
Caveats
- Redaction paths match object structure, not strings. Log request headers under a
headerskey, or add the path you actually use. - Never log the signing secret or compare values in the failure path of a webhook check. Each delivery carries an
x-sume-webhook-secret-fingerprintheader, so you can compare secrets without printing them. - A log line is not a recovery plan. Keep the id in durable storage, because a restarted process needs it to resume polling instead of submitting again.
Sources
Related posts
More in Developers
- Poll 40 Sume jobs without bursts: jitter, retry-after, next poll hint
Watch many Sume jobs in asyncio: stagger the first poll, sleep next_poll_after_seconds with jitter, and back off on a 429 retry-after from the status route.
- Poll Sume audio jobs at the recommended interval, not a fixed sleep
The Sume job status response tells you when to poll next and whether the job is terminal. Read those fields instead of hardcoding sleep(2).
- Ported Sora wrapper blocked until done? Sume sync stops at 30 s
A wrapper that blocks until the video is done will time out on Sume sync mode, which waits at most 30 seconds. Return the job id and poll, never resubmit.
- Pre-flight tool access: tools_list on Sume's MCP
Notion added a tool that reveals connection-scoped capabilities before requests. Sume's MCP does the same job with tools_list, tools_schema and mcp_health.
Written by Sume