PWA manifest icons: any, maskable and monochrome from AI images

Make the three PWA icon purposes with the Sume image API: transparent, full-bleed maskable and one-color monochrome masters, plus the cost of each.

5 min readSume
All posts

A web app manifest icon can declare one of three purposes, and each one wants a different picture: any is a normal icon, maskable is designed with a safe zone so the OS can crop it, and monochrome uses the alpha channel as a mask for a solid fill. So one AI render is not enough; you need three masters. On Sume that is three square calls to POST /v1/images on openai/gpt-image-2.5 at medium quality, an estimated $0.0165 each (about $0.05 for the set).

This post covers what MDN says each purpose means, how to ask for each master, the one Sume parameter that decides whether you get transparency, and a short script that writes the files.

What the three purposes ask of the picture

The facts below come from MDN's manifest icons reference (read 2026-10-03). Sizes are yours to choose; MDN's example lists several, and the browser picks the best fit.

Manifest icon purposes per MDN (read 2026-10-03)
purposeWhat MDN saysWhat the master needs
anyCan be used in any context (the default)The mark on a transparent or plain ground
maskableDesigned with a safe zone so it survives maskingFull-bleed background, mark kept small and centered
monochromeUsed as a monochrome icon with solid fill; alpha is the maskOne flat shape on transparent, no gradients

The Sume parameter that decides transparency

The Image API docs list background: auto | transparent | opaque as a ChatGPT Image 2.5 parameter. Other catalog rows do not take it, and a request that sets a parameter the model does not list is rejected with 400 unsupported_parameter. So the transparent and monochrome masters belong on openai/gpt-image-2.5 with background: "transparent" and output_format: "png", and the maskable master uses background: "opaque".

The monochrome purpose uses only the alpha channel, so that master needs real transparency. Ask for it with the background field rather than in the prompt, and open the PNG to confirm it has an alpha channel before you ship it. The Image API docs also point to Image 1.0 with transparency: true for transparent stills, so use that route if the check fails.

Prompts that fit each purpose

Keep the subject one simple mark. Icons shrink hard, and detail turns to mush.

  • any: 'a bold geometric fox head, flat vector style, centered, generous padding'
  • maskable: 'fills the whole square with a solid orange background, the same fox head small in the exact center'. Check the result against a circle and a rounded-square mask before you ship it; the mark must stay inside the middle.
  • monochrome: 'a solid black fox head silhouette only, no shading, no outline, no background'

A script that writes all three

The script follows the response rule in the docs: 200 is the image body with data[].url, and 202 is a job envelope you poll instead. It needs requests and Pillow, and SUME_API_KEY in the environment.

import os, requests
from io import BytesIO
from PIL import Image

def make(prompt, background):
    r = requests.post(
        "https://api.sume.com/v1/images",
        headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
        json={"model": "openai/gpt-image-2.5", "prompt": prompt,
              "aspect_ratio": "1:1", "quality": "medium",
              "background": background, "output_format": "png"},
        timeout=90)
    if r.status_code != 200:
        raise SystemExit(f"{r.status_code}: poll {r.json()['data']['status_url']}")
    raw = requests.get(r.json()["data"][0]["url"], timeout=60).content
    return Image.open(BytesIO(raw)).convert("RGBA")

mark = "a bold geometric fox head, flat vector style"
masters = [
    ("any", "transparent", mark + ", centered, generous padding"),
    ("maskable", "opaque", mark + " small in the exact center on a solid orange background filling the square"),
    ("monochrome", "transparent", "a solid black silhouette of " + mark + ", no shading, no background"),
]
for name, bg, prompt in masters:
    img = make(prompt, bg)
    for size in (192, 512):
        img.resize((size, size)).save(f"icon-{name}-{size}.png")

Cost and what to check

Prices below are the repo's estimate for a 1024x1024 GPT Image 2.5 output times the 1.25 house margin (read 2026-10-03). The endpoint record from GET /v1/images/models/{id}/endpoints and each response's usage.cost are what you are actually billed; a failed generation is not billed.

  • Medium is enough for an icon you will shrink to 512 px or less; high is the default if you omit quality.
  • Run the three masters again when the brand mark changes; a manifest points at files, not at jobs.
  • Resize with Pillow, not with another model call.
GPT Image 2.5 square output, Sume price vs. list estimate (read 2026-10-03)
qualitySume price per imageList estimate
Low$0.0074$0.0059
Medium$0.0165$0.0132
High (default)$0.0659$0.0527
XHigh$0.1171$0.0937
Max$0.2635$0.2108

What Sume does and does not do

Sume returns the image files. It does not write your manifest.webmanifest, validate maskable safe zones, or know which sizes your install flow needs; that stays in your build.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume