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.

4 min readSume
All posts

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.

Cover fields on Meta's IG User Media page (read 2026-10-02)
FieldValueNotes
thumb_offsetMilliseconds into the videoDefault 0, the first frame; videos and Reels
cover_urlPublic image URLReels 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

All Integrations posts

Written by Sume