Instagram Reels API cover_url vs thumb_offset: which one wins?
If a Reel container sets both cover_url and thumb_offset, Meta uses cover_url and ignores thumb_offset. How each works, and how to pull a cover frame.
cover_url wins. Meta's IG User Media reference says that for Reels, if you specify both cover_url and thumb_offset, it uses cover_url and ignores thumb_offset (read 2026-10-02). With neither set, thumb_offset defaults to 0, which is the first frame of the Reel.
So there are two covers you can control from the API: a frame you point to by time, or an image you host. Sume's video frames route can produce either input: the frame time, or the image file.
What do the two fields take?
thumb_offset is a position in milliseconds. cover_url is a path to an image that Meta fetches with cURL, so it must be on a public server.
| Field | Value | Notes |
|---|---|---|
| thumb_offset | Milliseconds into the video | Default 0, the first frame; videos and Reels |
| cover_url | Public image URL | Reels only; must meet the Reels cover spec |
| Both set | - | cover_url used, thumb_offset ignored |
What must the cover image look like?
The page lists JPEG, 8 MB maximum and sRGB for a Reels cover, with other color spaces converted to sRGB. It recommends 9:16, and crops other shapes to the middle 9:16 rectangle. For a Reel also shared to the feed, the feed post uses the middle 1:1 square.
Video frames returns JPEG by default, at the source size unless you set max_edge (16 to 2160). The docs do not say what color space a frame is in, so I would not rely on that; Meta says it converts other spaces to sRGB.
How do I pick a cover frame from a Sume clip?
Choose the instant, then either send it as thumb_offset (seconds times 1000) or hand Meta the extracted still as cover_url. Video frames is an async job that submits with 202 and costs nothing; each at value must satisfy 0 <= t < duration, up to 24 values per call. Source clips are limited to 300 seconds on that route.
import os, time, requests
BASE = "https://api.sume.com"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
r = requests.post(f"{BASE}/v1/video-frames", timeout=60,
headers={**H, "Idempotency-Key": "cover-frame-001"},
json={"video_url": "https://media.sume.com/artifacts/artf_demo/talk.mp4",
"at": [2.5], "format": "jpeg"})
r.raise_for_status()
rid = r.json()["data"]["video_frames_id"]
while True:
d = requests.get(f"{BASE}/v1/video-frames/{rid}", headers=H, timeout=30).json()["data"]
if d["resource_status"] == "ready":
break
if d["resource_status"] in ("failed", "canceled"):
raise RuntimeError(d["error"])
time.sleep(2)
print("cover_url candidate:", d["frames"][0]["url"])
print("or thumb_offset =", 2500)What should I do?
Pick one field per container. Use thumb_offset when any frame of the clip will do, and cover_url when the cover needs text or a layout the video does not contain. Open the Meta URL from a logged-out browser first, because the image must be reachable without credentials.
Sources
Related posts
More in Integrations
- Instagram Reels API: is_paid_partnership and sponsor IDs
The Instagram Content Publishing API takes is_paid_partnership and branded_content_sponsor_ids for Reels, sponsor ids need Facebook Login. Request-body example.
- Instagram Reels API share_to_feed: true does not guarantee Reels
share_to_feed=true lets a Reel appear in Feed and Reels; false limits it to Reels. Neither value guarantees placement. What Meta's page says, and what to prep.
- Instagram Reels API limits: 3 collaborators, no carousel, audio tags
Meta's Reels API allows up to 3 collaborators, keeps Reels out of carousels, and limits music tagging to original audio. The limits that shape a batch plan.
- Jan app MCP server: connect Sume over Streamable HTTP and OAuth
Jan can connect to a remote MCP server over Streamable HTTP and sign in with OAuth and PKCE. How that maps onto Sume's hosted MCP endpoint and its scopes.
Written by Sume