A JSONL ledger for image edits: model, prompt, cost and URL on Sume
Every paid image edit should leave one line: model, prompt, references, status, usage.cost and result URL. 25 lines of Python make that audit trail.

When four vendors ship better editing in the same month, teams try all four and then cannot say which edit came from where. The cure is dull and cheap: one JSON line per paid call. Sume gives you most of the fields. A completed request returns usage.cost, the billed USD amount, and the model you asked for; a slow one returns 202 with a job id and a status URL (Sume Image API).
What to write down
Sume stores metadata on the job and does not send it to the provider, so it is a safe place for your own ids.
| Field | Where it comes from | Why keep it |
|---|---|---|
| model | Your request (echoed in the response) | Which family made the image |
| prompt, references | Your request | Reproduce or audit the edit |
| http_status | The response | 200 is a result, 202 is a job |
| usage.cost | Response on 200 | What you were billed |
| job id and status_url | 202 envelope | Recover the result later |
| metadata | Request field Sume stores on the job | Tie the edit to your ticket or SKU |
The wrapper
Call it with a request body and a ref such as an order id. On a 202, read the finished job from status_url and result URL later, then append a second line.
import json
import os
import time
import requests
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
def edit(body: dict, ref: str, ledger: str = "edits.jsonl") -> dict:
body = {**body, "metadata": {"ref": ref}}
r = requests.post("https://api.sume.com/v1/images", headers=H, json=body, timeout=90)
out = r.json()
line = {"t": int(time.time()), "ref": ref, "model": body["model"],
"prompt": body["prompt"], "status": r.status_code}
if r.status_code == 200:
line["cost"] = out["usage"]["cost"]
line["urls"] = [d["url"] for d in out["data"]]
elif r.status_code == 202:
line["job"] = out["data"]["job"]["id"]
line["status_url"] = out["data"]["status_url"]
with open(ledger, "a") as f:
f.write(json.dumps(line) + "\n")
return out
Using the ledger
A ledger is only useful if every call goes through the wrapper, so make it the only function that touches the API.
- Sum cost by model to see what each family costs you.
- Filter by ref to answer why this image looks like this.
- Count 202 lines to see how often your settings outgrow the 30-second wait.
Sources
Related posts
More in Developers
- Jupyter contact sheet: one prompt across four Sume image models
A notebook cell that sends one prompt to four Sume image models, tiles the results into a labeled contact sheet with Pillow, and prints each billed cost.
- Keep your Sora-style create_video() call: map it onto Sume
Sora's seconds, size and input_reference become duration, resolution plus aspect_ratio, and a first frame. Here is that map as a Python wrapper over Sume.
- Kestra webhook trigger has no HMAC: verify Sume deliveries first
Kestra's Webhook trigger is protected by its key alone. Verify Sume's sume-v1 signature in a thin relay, then start the flow, and submit with a stable key.
- Kling 3 on Sume rejects reference_*_urls: which model takes references
The kling-3 row supports text-to-video and start/end frames only. Send reference_image_urls and you get a 400; here is where references do work.
Written by Sume