Streamlit image generator app with the Sume Images API (30 lines)

A 30-line Streamlit app: pick a Sume image model from the live catalog, type a prompt, show the result and the billed cost. Handles 200 and 202 responses.

5 min readSume
All posts

A Streamlit image app for Sume needs about 30 lines: fetch the model list from GET /v1/images/models, show it in a selectbox, post the prompt to POST /v1/images, then render the first data[].url and the billed usage.cost. Save it as app.py, export SUME_API_KEY and run streamlit run app.py.

The one detail that trips demos is the response code. Sume waits up to 30 seconds for a synchronous image, then returns 202 with a job envelope instead of the image body, so the app should branch on status rather than assume data exists.

What does the app need from the API?

Calls the app makes, read 2026-10-04
StepCallField used
Fill the model pickerGET /v1/images/modelsdata[].id, data[].name
GeneratePOST /v1/imagesmodel, prompt, n
Show the image200 responsedata[0].url
Show the price200 responseusage.cost (billed USD)
Slow job202 responsedata.status_url

What is the code?

Keep the key in the environment, never in the page. Streamlit reruns the script on every interaction, so the generate call sits behind a button.

import os, requests, streamlit as st

API = "https://api.sume.com/v1"
HEAD = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}

@st.cache_data(ttl=600)
def models():
    r = requests.get(f"{API}/images/models", headers=HEAD, timeout=30)
    r.raise_for_status()
    return {m["name"]: m["id"] for m in r.json()["data"]}

st.title("Sume image test bench")
choices = models()
name = st.selectbox("Model", list(choices))
prompt = st.text_area("Prompt")
if st.button("Generate") and prompt:
    with st.spinner("Generating..."):
        r = requests.post(f"{API}/images", headers=HEAD, timeout=60,
                          json={"model": choices[name], "prompt": prompt})
    if r.status_code == 200:
        body = r.json()
        st.image(body["data"][0]["url"])
        st.caption(f"Billed: ${body['usage']['cost']}")
    elif r.status_code == 202:
        st.info("Still running: " + r.json()["data"]["status_url"])
    else:
        st.error(r.text)

What should I add before sharing it?

Add a spend cap before anyone else uses it. Each button press is a paid generation, and Sume bills completed ones in full. A simple counter in st.session_state that stops at a dollar total is enough for a demo; see the usage-cost budget guard for a version that reads usage.cost.

Streamlit runs your script server-side, so the key stays off the browser, but anyone with the URL can spend your balance. Put the app behind a login or keep it local.

Why read the catalog instead of hard-coding models?

The catalog is the source of truth for what a key can call, and it lists per-model limits such as the n range and reference count. Reading it at startup means a new model shows up in the picker without a code change, and a retired id disappears instead of failing. The sume/auto value is not a catalog row, so add it to the picker yourself if you want Sume to choose the family.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume