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.
- Lambda 1,000 concurrency and Sume webhook bursts finishing together
Jobs from one bulk queue often finish together. Lambda starts at 1,000 concurrent executions, lower on new accounts. Here is how Sume's retries absorb a burst.
Written by Sume