What to log from a Sume API error: request_id, code, no secrets

Log the status, error.code, error.request_id, retry-after and the path without its query. Keep keys, signed URLs and media URLs out. A 25-line Python logger.

4 min readSume
All posts

Log the HTTP status, error.code, error.request_id, the retry-after header and the path without its query string. Do not log API keys, signed URLs, raw media URLs, or private workspace and user ids; the errors page says to leave them out of anything you share with Sume support.

The request id in the error body is safe to share with Sume, which makes it the join key between your logs and theirs. If you only log one field, log that one.

What each field is for

The table is a log schema. Each row says what the field lets you do later.

Log fields for Sume errors (Errors and rate limits docs, read 2026-10-10)
FieldWhere it comes fromWhy keep it
status, codeHTTP status and error.codeGroup failures by cause; feed the retry decision.
request_iderror.request_id (also a response header)Quote it to Sume support.
retry_afterretry-after header on 429Check that your backoff obeyed it.
ratelimit_remainingratelimit-remaining headerSee budget pressure before the 429.
path without queryYour requestQuery strings can carry URLs you should not store.
body_headFirst 60 characters, only if not JSONCatch proxies and edge blocks that do not send the Sume error shape.

A logger you can paste

It builds one JSON line per failure. The body_head field is filled only when the body was not a Sume error, which is exactly the case where request_id is missing. The sample runs as is and prints one line:

import json, logging, time

log = logging.getLogger("sume")

def log_sume_error(method, path, status, headers, body_text):
    """Structured line with what support needs and nothing secret."""
    try:
        err = json.loads(body_text).get("error", {})
    except ValueError:
        err = {}
    log.error(json.dumps({
        "ts": int(time.time()), "method": method, "path": path.split("?")[0],
        "status": status, "code": err.get("code"),
        "request_id": err.get("request_id"),
        "retry_after": headers.get("retry-after"),
        "ratelimit_remaining": headers.get("ratelimit-remaining"),
        "body_head": None if err else body_text[:60],
    }))

logging.basicConfig(level=logging.ERROR, format="%(message)s")
log_sume_error("POST", "/v1/image-1.0/generate?x=1", 429,
               {"retry-after": "12"},
               '{"error":{"code":"rate_limited","request_id":"req_demo"}}')

Two distinct ids

Do not confuse the error request_id with the job id. On a submit that succeeds, request_id in the response data is the job id you poll. On an error, request_id identifies the failed request. Store them in different columns, and never put a job id in a field named for requests.

Also keep the Idempotency-Key you sent in your own logs. Sume omits idempotency keys from usage rows, but job rows return the key they were created with, so you can still join your log line to a job after a crash.

  • Redact Authorization and x-api-key in any request dump.
  • Treat signed upload and download URLs as temporary secrets, per the authentication page.
  • Sample success logs; keep every failure.

A log line you can grep

Log one structured line per failure, with the same keys every time. That makes a support request a copy-paste job and keeps secrets out by construction, since the schema has no slot for them.

Fields to log for a failed Sume call, based on the errors doc read 2026-10-10
LogNever log
HTTP status and error.codeThe API key or the Authorization header
request_id from the body or headersSigned or raw media URLs
Your item id and idempotency keyPrivate workspace or user ids
Elapsed time and attempt numberThe full upstream response body

Sources

Related posts

More in Developers

All Developers posts

Written by Sume