Open Graph image 1200x630 with an AI API: generate 16:9, then crop
Meta recommends og:image at 1200 x 630 and a 1.91:1 ratio. Sume has no 1.91:1 option, so generate 16:9 and crop with 12 lines of Pillow. Steps and limits.

Generate at aspect_ratio: "16:9", then center-crop to 1200 x 630 in code. Meta's sharing guide says to 'use images that are at least 1200 x 630 pixels' and to 'keep your images as close to 1.91:1 aspect ratio as possible' so they show in Feed without cropping, with a file no larger than 8 MB (Meta for Developers, read 2026-10-01). Sume's normalized ratio list has 16:9 (1.78) and 2:1 (2.0) but no 1.91:1, so the final trim is a local step.
16:9 is 1.78 and 1200 x 630 is 1.905, so you lose a thin band at the top and bottom. Tell the prompt to keep the subject and any headline in the middle 80 percent of the frame.
What are the numbers?
Read from each source on the dates shown.
| Item | Value | Source |
|---|---|---|
| Minimum dimension | 200 x 200 px | Meta |
| Recommended | 1200 x 630 px or larger | Meta |
| Smallest acceptable | 600 x 315 px | Meta |
| Target ratio | 1.91:1 | Meta |
| Max file size | 8 MB | Meta |
| Sume ratios nearest 1.91:1 | 16:9 (also 2:1) | Image API |
How do I crop?
Download the result, resize to width 1200, then crop the height to 630 around the center.
import io
import os
import requests
from PIL import Image
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
r = requests.post("https://api.sume.com/v1/images", headers=H, timeout=60,
json={"model": "openai/gpt-image-2.5", "aspect_ratio": "16:9",
"prompt": "clean blog header, laptop on a desk, empty centre space"})
r.raise_for_status()
raw = requests.get(r.json()["data"][0]["url"], timeout=60).content
img = Image.open(io.BytesIO(raw)).convert("RGB")
img = img.resize((1200, round(img.height * 1200 / img.width)))
top = (img.height - 630) // 2
img.crop((0, top, 1200, top + 630)).save("og.jpg", quality=90)
print("og.jpg", os.path.getsize("og.jpg"), "bytes")Prompt for a safe crop zone
Because the crop removes about 6 percent of the height, ask for 'subject centered, generous empty margin at top and bottom'. If you add text later, draw it yourself with Pillow on the cropped image, so spelling and position are exact.
Test the result in a link preview tool or by sharing the URL to yourself before announcing a page. Platforms cache preview images, so a wrong first image can stick for a while.
Limits
If the 16:9 result is under 1200 pixels wide, the resize upscales it; pick a higher resolution where the model lists one. Do not ask the model to draw the site name in small type, because crops cut it and text can be misspelled. JPEG at quality 90 keeps most files well under 8 MB, but check the printed byte count.
Sources
Related posts
More in Developers
- OpenAI structured outputs: 5000 properties, 10 levels, vs Sume
OpenAI caps strict schemas at 5000 properties and 10 nesting levels. Sume's output_schema uses the same numbers but counts enums and strings differently.
- OpenRouter output_modalities=video vs Sume /v1/videos/models
OpenRouter lists video models three ways. Sume has a /v1/videos/models endpoint with the same shape plus a catalog. Fields to read before you submit.
- OpenRouter video is ZDR-ineligible: what Sume says about retention
OpenRouter says video generation cannot use Zero Data Retention because output is held for retrieval. Sume has no ZDR toggle; what to tell your security team.
- Pace bulk Sume submits with generation_limits, not wave_size_hint
Size in-flight work as concurrency minus active minus queued, capped by queue capacity. A short Node pacer that reads generation_limits from each submit.
Written by Sume