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.

4 min readSume
All posts

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.

Masking rules in the job-error remap, packages/api-jobs/src/public-job.ts (source read 2026-10-10)
Text in the provider sentenceBecomesReason
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

All Developers posts

Written by Sume