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.

A [redacted_url] marker inside a failed Sume job message means the provider's failure sentence contained a web address and Sume masked it before the error reached you. The rest of the sentence is still the provider's reason, so read it, and then use public_reason, next_action and retryable to decide what to do next.
The mask is deliberate. Providers often quote the link that failed, and that link can be a signed URL to your own file or an internal host. Sume replaces every http:// or https:// run with a fixed token and never forwards the link, which matches the rule in Jobs and results that public output shows no raw provider task ids or raw provider URLs.
What Sume masks in a failure message
The job-error remap in packages/api-jobs runs every provider sentence through the same filters before it becomes error.message or error.details.provider_error_message. These are the patterns it replaces.
| Text in the provider sentence | Becomes | Reason |
|---|---|---|
| Anything that starts with http:// or https:// up to the next space | [redacted_url] | Signed links and private hosts stay private |
| fal request ids that begin fal_req_ | [redacted_provider_id] | Raw provider task ids are not public output |
| Internal setting names that begin SUME_COM_ or CARTESIA_ | [redacted_env] | Configuration names are not API output |
| Authorization or api-key assignments, Bearer and Key tokens, sk- keys, PEM blocks | [redacted_secret] | Credentials must never travel in an error |
When the whole reason is dropped
Only unclassified provider failures use this pass-through path, and three rules decide whether any text survives.
When nothing useful remains, you get the fixed sentence for the stage: "Generation failed." for a job that was already running and "Generation could not start." for a submit-time refusal. A sentence that does survive is also published as details.provider_error_message, so a client can show it without parsing the message.
- The sentence is cut to 300 characters after masking.
- Opaque wrapper sentences are discarded as uninformative: "Provider job failed.", "Generation failed.", "Generation could not start." and the generic fal queue failure lines.
- A sentence that is only redaction tokens is discarded, and so is leftover glue such as "Rejected [redacted_url] with [redacted_env]".
Typed failures never show the provider sentence
Several failure classes are recognised first and answered with a fixed message, so the provider's own wording, and any URL in it, never appears.
- An input URL the provider could not download reads "Could not download an input media URL (image_url). Verify the URL is publicly reachable, then retry." and carries the field name in
details.input_field. The fix guide is unreachable input media. - A provider refusal that mentions content policy gets a fixed sentence for its media type.
- A stored output that was too large for the artifact upload limit gets a message that says to produce a smaller file.
Read the error object, not only the message
A masked sentence is rarely the whole story. GET /v1/jobs/{id} returns the full error object under data.job.error, and its public_reason, next_action and retryable fields are stable enough to branch on. This script prints them.
If next_action is fix_input and retryable is false, change the request, because the same body will fail the same way. If it is retry_later, wait for retry_after_seconds when the error carries one. If the message is the bare "Generation failed." and next_action is inspect_events, read GET /v1/jobs/{id}/events for the timeline before you pay for another job. The full decision table is in one switch on next_action.
import json, os, urllib.request
def get(path):
req = urllib.request.Request(
"https://api.sume.com" + path,
headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"]},
)
with urllib.request.urlopen(req) as res:
return json.load(res)
job = get("/v1/jobs/" + os.environ["SUME_JOB_ID"])["data"]["job"]
error = job.get("error") or {}
for key in ("code", "message", "public_reason", "next_action", "retryable"):
print(key, "=", error.get(key))
print("details =", error.get("details"))Sources
Related posts
More in Developers
- Is there a lipsync-1.0 endpoint on Sume? Old paths 404; use Fabric
Sume's old /v1/lipsync-1.0 paths return 404 and its model ids return model_not_found. Send the same still and audio to veed/fabric-1.0 or H3 Max lip-sync.
- Make a vertical Short clip with curl and jq: submit, poll, download
A 20-line shell script that asks Sume for an 8-second 9:16 clip at 1080p, polls the job, and saves short.mp4, matched to YouTube's Shorts page.
- Node script for a 9:16 TikTok video: check the model, then submit
A Node 20 fetch script that confirms a Sume model lists 9:16 and your duration, submits one 12-second 720p job, and saves an MP4 that fits TikTok's API limits.
- Revoked a Sume API key and it still works? Up to 15 seconds
After you revoke a Sume API key, the API can keep accepting it for up to 15 seconds by default, because the key lookup is cached. What that means for a leak.
Written by Sume