Building a reel downloader, a Telegram bot or a content archive sounds simple until you hit three problems: media links that expire, browsers that refuse to download them, and high-quality video that arrives without sound. This guide shows a clean way to solve all three with two API calls.
Download only content you have the right to use, and credit creators. Read the Acceptable Use Policy before you build a public tool.
The two-step flow
- Resolve the post URL with the Post & Reel Data API. You get the author, caption and a
formatslist with every quality. - Prepare the chosen quality with the Downloader API. You get a link to a stored copy that works in any browser.
Step 1: resolve the URL
curl "https://api.igramapi.com/v1/media?url=https://www.instagram.com/reel/Cx1AbCdEfGh/" \
-H "Authorization: Bearer $IGRAMAPI_KEY"
The interesting part of the response is formats:
"formats": [
{ "id": "dash-1080p", "quality": "1080p", "size_bytes": 4100000,
"url": "https://scontent.cdninstagram.com/.../v1080.mp4",
"audio_url": "https://scontent.cdninstagram.com/.../a.mp4" },
{ "id": "mp4-720p", "quality": "720p", "size_bytes": null,
"url": "https://scontent.cdninstagram.com/.../v720.mp4", "audio_url": null }
]
Formats are ordered best first. Show them to your users as choices.
Why some qualities have an audio_url
Instagram delivers its highest qualities as two separate streams: one with picture only and one with sound only. If a format has an audio_url, a plain download of url gives you silent video. The Downloader merges both streams into one MP4 for you.
Step 2: prepare the file
import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['IGRAMAPI_KEY']}"}
def prepare(fmt, filename):
body = {"type": "video", "url": fmt["url"], "filename": filename}
if fmt.get("audio_url"):
body["audio_url"] = fmt["audio_url"]
r = requests.post("https://api.igramapi.com/v1/downloads", json=body, headers=HEADERS, timeout=120)
r.raise_for_status()
return r.json()
media = requests.get(
"https://api.igramapi.com/v1/media",
params={"url": "https://www.instagram.com/reel/Cx1AbCdEfGh/"},
headers=HEADERS, timeout=30,
).json()
result = prepare(media["formats"][0], "my-reel")
print(result["download_url"], result["size_bytes"])
The response:
{
"download_url": "https://download.example.com/files/my-reel.mp4?signature=example",
"expires_at": "2026-10-31T12:00:00Z",
"filename": "my-reel.mp4",
"content_type": "video/mp4",
"size_bytes": 4100000
}
Send your user to download_url. The link needs no API key and works until expires_at, which is about an hour away.
Audio only (MP3)
requests.post(
"https://api.igramapi.com/v1/downloads",
json={"type": "audio", "url": media["video_url"]},
headers=HEADERS, timeout=120,
)
You receive an MP3 link. For the track’s title, artist and a direct audio link, use the Audio API.
Photos and carousels
For a carousel, slides lists every item with its own type and url. Send each image to the Downloader with "type": "image", and each video the same way as above.
Costs
| Action | Credits |
|---|---|
| Resolve a post | 1 |
| Prepare a video | 2 |
| Prepare merged HD video | 5 |
| Prepare MP3 or image | 1 |
Errors are free. If a media link has already expired you get media_url_expired and nothing is charged: resolve the post again for a fresh URL. Identical requests reuse the stored file, so a popular reel is quick the second time.
Tips for a production downloader
- Cache results in your own database by
shortcodeand quality, to avoid paying twice for the same file. - Debounce the paste box, and validate that the input looks like an Instagram URL before calling the API.
- Handle errors by code:
media_unavailable(private or deleted post),login_required(age-restricted),upstream_error(retry later). - Respect rate limits. Read
Retry-Afteron a 429. See credits and rate limits. - Be transparent about copyright and let rights holders reach you.
Next steps
List a creator’s reels with the Reels API, then feed each shortcode into this flow. More on building downloader tools: Reels downloader apps.