Coffee shop loyalty card art with an API: stamps drawn in code
Generate 3:2 loyalty card art on Sume with empty stamp spaces, then draw each customer's stamps in code so the count is always right and the art is reused.

For coffee shop loyalty card art, generate a 3:2 card on Sume with a shop name and empty circles for stamps, then draw each customer's stamps in code. The count is data, and drawing it yourself means the art is paid for once while every card is a free local render.
Ideogram 4.5 suits the shop name; fal's page describes it as strong at posters and logos with accurate text rendering, and lists $0.03, $0.06 and $0.22 per image for low, medium and high (read 2026-10-06, fal). Sume bills the list price times 1.25. As always, proofread the name in the result, and see the Image API docs for the ratios each model accepts.
What the model draws and what you draw
The model draws the card: background, shop name and ten empty circles. Your script draws stamps in the circles. Because the circle positions come from the generated art, you place the stamps by measuring or by fixing the layout in the prompt.
| Part | Source | Changes per customer |
|---|---|---|
| Background and shop name | Generated, 3:2 | No |
| Ten empty circles | Generated, check positions | No |
| Stamps | Code | Yes |
| Customer name | Code, optional | Yes |
Generate, then stamp
The script creates the card, then draws a dot in the first N circles. The coordinates here assume a layout of five circles per row; measure your own result and adjust them.
import io
import os
import requests
from PIL import Image, ImageDraw
r = requests.post(
"https://api.sume.com/v1/images",
headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
json={
"model": "ideogram/ideogram-v4.5",
"prompt": 'A coffee shop loyalty card reading "Corner Beans" at the '
"top, with two rows of five empty circles below. Cream background, "
"brown ink.",
"aspect_ratio": "3:2",
"quality": "medium",
},
timeout=60,
)
r.raise_for_status()
raw = requests.get(r.json()["data"][0]["url"], timeout=60).content
img = Image.open(io.BytesIO(raw)).convert("RGB")
w, h = img.size
d = ImageDraw.Draw(img)
stamps = 4
for i in range(stamps):
x = int(w * (0.15 + 0.175 * (i % 5)))
y = int(h * (0.50 + 0.25 * (i // 5)))
d.ellipse((x - 25, y - 25, x + 25, y + 25), fill=(90, 50, 20))
img.save("card-4-stamps.png")Design choices for a punch card
A loyalty card is small and handled often, so the design should be plain and sturdy. Keep the art calm and leave obvious, large spaces for the stamps.
- Ten stamp circles in two rows are easy to count and to fill.
- Use a light background so a stamp's ink shows.
- Keep the shop name large and the rest small.
- Leave the back blank or add your address in code.
- Print on card stock that holds up in a wallet.
Digital option
The same approach works on screen. Instead of printing, render the card with the stamps already drawn for a customer's current count, and send that image to their phone. The art is generated once; each customer's card is a free local render from their count. Keep the count in your own system, since the image is only a view of it.
Seasonal cards without a new design
A shop may want a pumpkin card in autumn and a snowflake card in winter. Keep the layout sentence the same, the shop name quoted, and the ten circles, and change only the decoration. Customers then recognise the card and still feel a seasonal change. Each season costs one image at the quality you choose, and the stamping script does not change.
Retire old designs by date in your code, so a card printed for last winter does not keep appearing in a digital wallet.
Fraud and fairness
A drawn card is only a picture. If a stamp has real value, such as a free coffee, keep the true count in your own database and treat the image as a view of it. A customer who edits an image on their phone should not be able to claim a reward that your records do not show. The code that renders the card should read the count from the same place that redeems it.
Print one card and stamp it with your actual stamp to see if the circles are the right size. A circle that looks fine on screen may be too small for a stamp pad, and it is cheaper to learn that from one test card than from a printed run. Adjust the prompt to ask for larger circles if needed.
Open the generated card and confirm that the circles are where you expect. If they drift, ask for a simpler layout or place the circles yourself in code and generate only the background. Test a full ten-stamp card too, since the last circle is the one customers look at.
Sources
Related posts
More in Use cases
- Criteo Retail Media onsite video: 720p, 15 s, a Sume avatar clip
Criteo onsite video asks for MP4 at 720p, 15 s recommended and 30 s max, in 16:9, 1:1 or 9:16. Sume avatar output is 720p, so script to 15 seconds.
- Narration budget for a daily short-video channel: 30 videos a month
Thirty 750-character narrations a month cost $1.20 on Sume TTS with per-job rounding. The same characters are about $0.50 on MAI-Voice-2.1 and $0.34 on Flash.
- Dark mode hero image: one Ideogram 4.5 edit keeps the text
Make a dark-theme twin of a light hero on Sume with one Ideogram 4.5 edit: the background changes, the headline and layout stay, and the shape matches.
- Digital gift card art via API: generate the art, print the code
Make digital gift card artwork with GPT Image 2.5 on Sume, leave a clear text band, then print the amount and code in Pillow so no model ever types a real code.
Written by Sume