Discord API image data: send a Sume image as a data URI

Discord takes avatar and icon images as a data URI in JPEG, PNG or GIF. Fetch the Sume result, base64-encode it with the right content type, and send it.

5 min readSume
All posts

Discord's API reference says image data is sent as a data URI that supports JPG, GIF and PNG, and that the content type must match the bytes: image/jpeg, image/png or image/gif. A Sume image URL is not accepted as-is; you download the file, encode it, and put data:image/png;base64,... in the field.

The format on Discord's side

The reference states that you must use the proper content type for the data you provide. A WebP result from an image API is therefore a mismatch for the upload field even though Discord's CDN serves WebP, since the data URI rule lists only JPG, GIF and PNG.

Discord image data and CDN rules (read 2026-10-03)
TopicWhat the page says
Upload formatData URI, JPG, GIF or PNG
Content typeMust match the data: image/jpeg, image/png, image/gif
CDN formatsJPEG, PNG, WebP, GIF, Lottie, by endpoint
CDN size queryAny power of two from 16 to 4096

Asking Sume for the right format

Set output_format to png or jpeg in the request, both of which the Image API lists, and read data[].media_type in the response to choose the data URI prefix instead of guessing. A square ratio suits icons and avatars; use aspect_ratio: "1:1".

The Sume response also includes usage.cost, the billed amount, and failed generations are not billed.

Encode and build the body

The script returns the JSON body for a guild icon update. It stops on a 202, which means the 30 second block expired and the image is in the job result instead.

import base64, json, os, requests
r = requests.post(
    "https://api.sume.com/v1/images",
    headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
    json={"model": "bytedance-seed/seedream-4.5", "aspect_ratio": "1:1", "prompt": "friendly round mascot face, flat colors, no text", "output_format": "png"},
    timeout=90,
)
r.raise_for_status()
if r.status_code != 200:
    raise SystemExit("202: read the finished job from /v1/jobs/{id}/result")
item = r.json()["data"][0]
media_type = item["media_type"]
raw = requests.get(item["url"], timeout=60).content
assert media_type in ("image/png", "image/jpeg"), media_type
uri = f"data:{media_type};base64," + base64.b64encode(raw).decode()
print(json.dumps({"icon": uri})[:80], "...")

Choosing the size to send

Discord's reference says the CDN can resize on request using a power-of-two size between 16 and 4096, which is about reading images back, not about what you upload. For uploads, the page only names the formats. A 512 x 512 square is a common working size for an icon or avatar; the Image API takes resolution tiers of 512, 1K, 2K and 4K where a model lists them, so a smaller tier is one way to avoid downscaling. Whatever you choose, resize locally before encoding so the base64 string stays manageable. Keep the original download too; the data URI is a derived artifact. If the upload is refused, check the prefix first: a data:image/png prefix on JPEG bytes is the mismatch the page warns about, and reading media_type from the Sume response instead of hard-coding it prevents it.

Check

Resize the file before encoding if you want it small. Base64 adds about a third to the byte count, so a 1024 pixel PNG becomes a very long string; a 512 pixel square is plenty for an icon. The page's note on animated WebP and AVIF not converting cleanly to GIF is a reason to keep the source static.

Download the file right after the 200 response and keep your own copy; the data URI is built from those bytes.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume