Agent skill

Instagram Post Downloader

by mohitagw15856 in mohitagw15856/pm-claude-skills

Download and save Instagram posts as high-resolution files. An agent skill from mohitagw15856/pm-claude-skills.

MITAuto-check passedDocuments & Office

Install Instagram Post Downloader

skills CLI
$ npx skills add mohitagw15856/pm-claude-skills --skill instagram-post-downloader -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install mohitagw15856/pm-claude-skills instagram-post-downloader --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/mohitagw15856/pm-claude-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/instagram-post-downloader .claude/skills/instagram-post-downloader && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
instagram-post-downloader
GitHub stars
1.4k
Token cost
~6.1k tokens
SKILL.md length
1,436 words
Files
1
Skills in repo
1,348
Repo updated
First seen
Licence
MIT

At a glance

Download and save Instagram posts as high-resolution files. An agent skill from mohitagw15856/pm-claude-skills.

  • Works in 10 steps: Collect and validate inputs → For each URL: fetch the post page → Extract CDN image URLs → …
  • Asked to download
  • SKILL.md covers PREREQUISITE — Domain Allowlist, Required Inputs, Output Structure and How Claude Should Execute This…, plus 6 more sections
  • Calls yt-dlp and pip; reaches instagram.com and scontent.cdninstagram.com

What it does

Instagram Post Downloader is an agent skill from mohitagw15856/pm-claude-skills. Download and save Instagram posts as high-resolution files. Use when asked to download, save, or archive an Instagram post, reel thumbnail, or carousel. Produces saved high-res images in a named folder, with carousel slides stitched into a single PDF; supports batch downloading of multiple URLs at once.

Its SKILL.md is about 6.1k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Documents & Office, covering Slides and decks and PDF. It works with Instagram. The repository describes itself as: 1255 professional Agent Skills for Claude, ChatGPT, Gemini, Cursor & Codex — PRDs, postmortems, leases, medical bills, layoffs, go-bags, new countries. Plain markdown, MIT, in… The licence is MIT.

When your agent uses it

  • Asked to download
  • Archive an Instagram post

Example prompts

  • “/instagram-post-downloader”

Requirements

  • Python 3

Workflow steps

10 steps, taken from the step headings in SKILL.md.

  1. Collect and validate inputs
  2. For each URL: fetch the post page
  3. Extract CDN image URLs
  4. Sanitise folder name
  5. Create output folder structure
  6. Download each image file
  7. Stitch carousel PDF (if applicable)
  8. Write metadata.txt
  9. Print completion summary
  10. Handle errors gracefully

What it can do on your machine

Read from SKILL.md and the folder at commit 1cbf1f0. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • yt-dlp
    • pip

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • instagram.com
    • scontent.cdninstagram.com
    • api.instagram.com
    • scontent-lax3-1.cdninstagram.com
    • instagram.fxxx1-1.fbcdn.net

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Instagram Post Downloader loads about 6.1k tokens when it runs. Until then it costs about 83 tokens; SKILL.md has 1,436 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~83
When it runs · the whole SKILL.md, loaded when a task matches
~6.1k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from mohitagw15856/pm-claude-skills at commit 1cbf1f0, republished under its MIT licence (© mohitagw15856). 1,436 words, ~6,082 tokens.

Download SKILL.mdSave it as .claude/skills/instagram-post-downloader/SKILL.md (or your agent's skills folder).
name
instagram-post-downloader
description
Download and save Instagram posts as high-resolution files. Use when asked to download, save, or archive an Instagram post, reel thumbnail, or carousel. Produces saved high-res images in a named folder, with carousel slides stitched into a single PDF; supports batch downloading of multiple URLs at once.

Instagram Post Downloader Skill

Downloads Instagram posts at full resolution from Instagram's CDN — no screenshots, no compression. Handles single images, carousels (multi-slide posts), and Reel cover images. For carousels, produces individual slide files plus a single stitched PDF. Supports batch URLs in one run.


PREREQUISITE — Domain Allowlist

Before this skill can fetch any media, you must add Instagram's CDN domain to Claude Code's allowlist:

Settings → Capabilities → Domain allowlist → Add:

*.cdninstagram.com

Without this, all CDN fetch calls will be blocked. If you see a permission error when Claude attempts a fetch to cdninstagram.com, this is the fix.


Required Inputs

Claude will ask for these if not provided upfront:

InputRequiredNotes
Instagram post URL(s)YesOne per line, or comma-separated. https://www.instagram.com/p/XXXX/ or https://www.instagram.com/reel/XXXX/ format
Output directoryNoDefaults to ./instagram-downloads/ in the current working directory
PDF stitch for carouselsNoDefaults to yes — produces carousel.pdf alongside individual slides
File naming prefixNoOptional prefix added before slide filenames, e.g. brand_ → brand_slide_01.jpg

Batch input example:

https://www.instagram.com/p/ABC123/
https://www.instagram.com/p/DEF456/
https://www.instagram.com/p/GHI789/

Output Structure

For each URL processed, Claude creates a folder named after the post caption (first 40 characters, sanitised — spaces become underscores, special characters stripped). If no caption is available, the folder is named after the post shortcode.

Single image post
instagram-downloads/
└── this_is_the_caption_first_40_chars/
    ├── image.jpg
    └── metadata.txt
Carousel post
instagram-downloads/
└── carousel_caption_first_40_chars/
    ├── slide_01.jpg
    ├── slide_02.jpg
    ├── slide_03.jpg
    ├── slide_04.jpg
    ├── carousel.pdf          ← all slides stitched in order
    └── metadata.txt
Batch run (3 URLs)
instagram-downloads/
├── first_post_caption_sanitised/
│   ├── image.jpg
│   └── metadata.txt
├── second_post_carousel_caption/
│   ├── slide_01.jpg
│   ├── slide_02.jpg
│   ├── carousel.pdf
│   └── metadata.txt
└── third_post_caption_here/
    ├── image.jpg
    └── metadata.txt
metadata.txt format
Post URL:       https://www.instagram.com/p/XXXX/
Shortcode:      XXXX
Type:           carousel | single_image | reel
Slide count:    4  (carousel only)
Caption:        [full caption text]
Username:       @username
Fetched at:     2026-05-27T14:32:00Z
CDN URLs:
  slide_01.jpg  https://scontent.cdninstagram.com/v/...
  slide_02.jpg  https://scontent.cdninstagram.com/v/...
Completion summary (printed to terminal)
Instagram Post Downloader — Batch Complete
==========================================
URLs processed:   3
Posts saved:      3
Total files:      11  (9 images + 2 PDFs)
Skipped:          0
Output dir:       /Users/you/project/instagram-downloads/

Results:
  ✓ this_is_the_caption_first_40_chars/     1 image
  ✓ carousel_caption_first_40_chars/        4 slides → carousel.pdf
  ✓ third_post_caption_here/                1 image

How Claude Should Execute This Skill

Step 1 — Collect and validate inputs
  1. Accept the URL(s) from the user. If the user pastes a comma-separated list, split on commas. If they paste one per line, split on newlines.
  2. Validate each URL matches instagram.com/p/, instagram.com/reel/, or instagram.com/tv/. Flag malformed URLs before proceeding.
  3. Confirm the output directory. If none provided, use ./instagram-downloads/ and tell the user.
  4. Ask about PDF stitching preference only if the user hasn't said either way. Default is yes.
Step 2 — For each URL: fetch the post page

Fetch the Instagram post page HTML:

GET https://www.instagram.com/p/{shortcode}/?__a=1&__d=dis

Instagram frequently changes its API surface. Use this fallback chain in order:

Attempt A — JSON endpoint:

https://www.instagram.com/p/{shortcode}/?__a=1&__d=dis

Parse the JSON response. Look for graphql.shortcode_media or data.shortcode_media.

Attempt B — Embed page (most reliable):

https://www.instagram.com/p/{shortcode}/embed/captioned/

Fetch this page's HTML and extract og:image meta tags and any window.__additionalDataLoaded or window.__StaticData JSON blobs embedded in <script> tags.

Attempt C — oEmbed endpoint:

https://api.instagram.com/oembed/?url=https://www.instagram.com/p/{shortcode}/&omitscript=true

This returns thumbnail_url — useful for single images, but only gives the first frame for carousels.

Headers to include on all requests:

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
Accept-Language: en-US,en;q=0.9
Accept: text/html,application/xhtml+xml,application/json
Step 3 — Extract CDN image URLs

From the fetched data, extract all high-resolution CDN URLs. Instagram CDN URLs follow these patterns:

https://scontent.cdninstagram.com/v/...jpg?...
https://scontent-lax3-1.cdninstagram.com/v/...jpg?...
https://instagram.fXXX1-1.fbcdn.net/v/...jpg?...

For single image posts:

  • Extract the single display_url or the largest display_resources entry (pick the one with the highest config_width).

For carousel posts:

  • Look for edge_sidecar_to_children.edges[] in the JSON. Each edge has its own node.display_url and node.display_resources[].
  • Iterate all edges in order. This determines slide numbering.
  • Pick the highest-resolution variant from each slide's display_resources array.

For Reels:

  • The cover image is extractable the same way as a single image.
  • The video file itself requires a third-party tool (see Bonus section).

If JSON extraction fails, fall back to scraping <meta property="og:image"> tags from the page HTML — this gives at least one image URL (the first slide or only image).

Step 4 — Sanitise folder name

Build the folder name from the post caption:

  1. Take the first 40 characters of the caption.
  2. Strip all characters that are not alphanumeric, spaces, or hyphens.
  3. Replace spaces and hyphens with underscores.
  4. Lowercase the result.
  5. Strip leading/trailing underscores.
  6. If the result is empty (e.g. caption was all emoji), use the post shortcode instead.
python
import re

def sanitise_folder_name(caption: str, shortcode: str) -> str:
    truncated = caption[:40]
    cleaned = re.sub(r'[^a-zA-Z0-9 \-]', '', truncated)
    underscored = re.sub(r'[\s\-]+', '_', cleaned).strip('_').lower()
    return underscored if underscored else shortcode
Step 5 — Create output folder structure
python
import os

base_dir = "./instagram-downloads"
folder_name = sanitise_folder_name(caption, shortcode)
post_dir = os.path.join(base_dir, folder_name)
os.makedirs(post_dir, exist_ok=True)

If a folder with that name already exists (e.g. running the same URL twice), append the shortcode to avoid collision: folder_name_SHORTCODE.

Step 6 — Download each image file

For each CDN URL, download the file with a streaming GET request:

python
import requests

def download_file(url: str, dest_path: str) -> bool:
    headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
        "Referer": "https://www.instagram.com/",
    }
    response = requests.get(url, headers=headers, stream=True, timeout=30)
    response.raise_for_status()
    with open(dest_path, "wb") as f:
        for chunk in response.iter_content(chunk_size=8192):
            f.write(chunk)
    return True

Name files:

  • Single image: image.jpg
  • Carousel slides: slide_01.jpg, slide_02.jpg, ... (zero-padded to 2 digits, or 3 digits if >99 slides)

Detect file format from the Content-Type header or URL extension. Instagram serves JPEG for photos and may serve WebP in some cases — preserve the actual extension.

After all slides are downloaded, stitch them into a single PDF using Pillow:

python
from PIL import Image

def stitch_to_pdf(image_paths: list[str], output_path: str) -> None:
    """
    Combine a list of image files into a single multi-page PDF.
    Each image becomes one page. Page size matches the image dimensions.
    """
    images = []
    for path in sorted(image_paths):  # sort ensures slide_01, slide_02, ... order
        img = Image.open(path).convert("RGB")
        images.append(img)

    if not images:
        return

    first = images[0]
    rest = images[1:]
    first.save(
        output_path,
        format="PDF",
        save_all=True,
        append_images=rest,
        resolution=150.0,
    )

Save as carousel.pdf in the post folder. If Pillow is not installed, run pip install Pillow first — or instruct the user to do so.

Dependency check at start of skill:

python
try:
    from PIL import Image
except ImportError:
    print("Pillow not installed. Run: pip install Pillow")
    print("PDF stitching will be skipped. Individual slides will still be downloaded.")
    skip_pdf = True
Step 8 — Write metadata.txt

Write a metadata.txt file into the post folder with all extracted metadata:

python
from datetime import datetime, timezone

def write_metadata(post_dir, post_url, shortcode, post_type, caption, username, cdn_urls):
    lines = [
        f"Post URL:       {post_url}",
        f"Shortcode:      {shortcode}",
        f"Type:           {post_type}",
    ]
    if post_type == "carousel":
        lines.append(f"Slide count:    {len(cdn_urls)}")
    lines += [
        f"Caption:        {caption}",
        f"Username:       @{username}",
        f"Fetched at:     {datetime.now(timezone.utc).isoformat()}",
        "CDN URLs:",
    ]
    for filename, url in cdn_urls.items():
        lines.append(f"  {filename:<16} {url}")

    with open(os.path.join(post_dir, "metadata.txt"), "w", encoding="utf-8") as f:
        f.write("\n".join(lines) + "\n")
Step 9 — Print completion summary

After processing all URLs, print the summary table to the terminal (format shown in Output Structure section above). Include:

  • Total URLs attempted
  • Posts successfully saved
  • Total files written (images + PDFs separately)
  • Any URLs that were skipped and the reason
Step 10 — Handle errors gracefully
Error scenarioAction
URL is not an Instagram URLSkip with message: "Skipped — not an Instagram URL: [url]"
Post is private or requires loginSkip with message: "Skipped — post is private or login required: [url]"
CDN fetch returns 403/404Try alternate CDN URL if available; if none, skip slide and note in metadata
Pillow not installedSkip PDF stitching, save slides only, note in summary
Network timeoutRetry once after 5 seconds; if still failing, skip and log
Folder name collisionAppend shortcode suffix to folder name
Rate limiting (429)Wait 10 seconds and retry; log if retry also fails

Show full SKILL.md (548 more words)Show less

Bonus — Downloading Instagram Reels (Video)

This skill covers images and carousel PDFs. For Reels video files, Claude Code cannot download video directly without a third-party tool, because Instagram's video CDN uses signed URLs and additional auth tokens.

Recommended approach for Reels:

Use yt-dlp, a maintained open-source tool:

bash
# Install
pip install yt-dlp

# Download a Reel
yt-dlp "https://www.instagram.com/reel/XXXX/" -o "%(title)s.%(ext)s"

# Download to a specific folder
yt-dlp "https://www.instagram.com/reel/XXXX/" \
  -o "./instagram-downloads/%(uploader)s_%(id)s.%(ext)s"

# Download best quality
yt-dlp -f "bestvideo+bestaudio" "https://www.instagram.com/reel/XXXX/"

Claude can run this command via Bash if the user asks. yt-dlp handles the auth token extraction automatically for public Reels.


Full Script Template

Claude should offer to write this as a standalone script (instagram_downloader.py) that the user can run independently:

python
#!/usr/bin/env python3
"""
Instagram Post Downloader
Fetches high-res images from public Instagram posts and carousels.
Requires: pip install requests Pillow
"""

import os
import re
import sys
import json
import time
import requests
from datetime import datetime, timezone
from pathlib import Path

try:
    from PIL import Image
    PILLOW_AVAILABLE = True
except ImportError:
    PILLOW_AVAILABLE = False
    print("Warning: Pillow not installed. PDF stitching disabled. Run: pip install Pillow")


HEADERS = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
                  "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept-Language": "en-US,en;q=0.9",
    "Referer": "https://www.instagram.com/",
}


def extract_shortcode(url: str) -> str:
    match = re.search(r"instagram\.com/(?:p|reel|tv)/([A-Za-z0-9_-]+)", url)
    if not match:
        raise ValueError(f"Cannot extract shortcode from URL: {url}")
    return match.group(1)


def fetch_post_data(shortcode: str) -> dict:
    """Try multiple endpoints to get post JSON data."""
    # Attempt A: JSON endpoint
    try:
        url = f"https://www.instagram.com/p/{shortcode}/?__a=1&__d=dis"
        r = requests.get(url, headers=HEADERS, timeout=15)
        if r.status_code == 200:
            data = r.json()
            media = (data.get("graphql", {}).get("shortcode_media") or
                     data.get("data", {}).get("shortcode_media"))
            if media:
                return media
    except Exception:
        pass

    # Attempt B: Embed page
    try:
        url = f"https://www.instagram.com/p/{shortcode}/embed/captioned/"
        r = requests.get(url, headers=HEADERS, timeout=15)
        html = r.text
        # Look for JSON blob in script tags
        matches = re.findall(r'window\.__additionalDataLoaded\([^,]+,(\{.+?\})\);', html)
        for blob in matches:
            try:
                data = json.loads(blob)
                media = (data.get("graphql", {}).get("shortcode_media") or
                         data.get("data", {}).get("shortcode_media"))
                if media:
                    return media
            except json.JSONDecodeError:
                continue
    except Exception:
        pass

    return {}


def get_cdn_urls(media: dict) -> list[tuple[str, str]]:
    """Return list of (filename, cdn_url) tuples."""
    results = []
    media_type = media.get("__typename", "")

    if media_type == "GraphSidecar":
        edges = media.get("edge_sidecar_to_children", {}).get("edges", [])
        for i, edge in enumerate(edges, start=1):
            node = edge.get("node", {})
            resources = node.get("display_resources", [])
            url = (max(resources, key=lambda r: r.get("config_width", 0)).get("src")
                   if resources else node.get("display_url", ""))
            if url:
                ext = "jpg" if "jpg" in url.lower() else "webp"
                filename = f"slide_{i:02d}.{ext}"
                results.append((filename, url))
    else:
        resources = media.get("display_resources", [])
        url = (max(resources, key=lambda r: r.get("config_width", 0)).get("src")
               if resources else media.get("display_url", ""))
        if url:
            ext = "jpg" if "jpg" in url.lower() else "webp"
            results.append((f"image.{ext}", url))

    return results


def sanitise_folder_name(caption: str, shortcode: str) -> str:
    truncated = caption[:40] if caption else ""
    cleaned = re.sub(r"[^a-zA-Z0-9 \-]", "", truncated)
    underscored = re.sub(r"[\s\-]+", "_", cleaned).strip("_").lower()
    return underscored if underscored else shortcode


def download_file(url: str, dest_path: str) -> bool:
    r = requests.get(url, headers=HEADERS, stream=True, timeout=30)
    r.raise_for_status()
    with open(dest_path, "wb") as f:
        for chunk in r.iter_content(chunk_size=8192):
            f.write(chunk)
    return True


def stitch_pdf(image_paths: list[str], output_path: str) -> None:
    if not PILLOW_AVAILABLE:
        return
    images = [Image.open(p).convert("RGB") for p in sorted(image_paths)]
    if images:
        images[0].save(output_path, format="PDF", save_all=True,
                       append_images=images[1:], resolution=150.0)


def process_url(post_url: str, base_dir: str, stitch_pdf_flag: bool) -> dict:
    result = {"url": post_url, "status": "ok", "files": [], "error": None}
    try:
        shortcode = extract_shortcode(post_url)
        media = fetch_post_data(shortcode)

        caption = ""
        username = ""
        if media:
            caption_edges = media.get("edge_media_to_caption", {}).get("edges", [])
            caption = caption_edges[0]["node"]["text"] if caption_edges else ""
            owner = media.get("owner", {})
            username = owner.get("username", "")

        folder_name = sanitise_folder_name(caption, shortcode)
        post_dir = os.path.join(base_dir, folder_name)
        if os.path.exists(post_dir):
            post_dir = f"{post_dir}_{shortcode}"
        os.makedirs(post_dir, exist_ok=True)

        cdn_urls = get_cdn_urls(media) if media else []
        if not cdn_urls:
            # Fallback: oEmbed
            oembed_url = f"https://api.instagram.com/oembed/?url={post_url}&omitscript=true"
            r = requests.get(oembed_url, headers=HEADERS, timeout=10)
            if r.status_code == 200:
                thumb = r.json().get("thumbnail_url", "")
                if thumb:
                    cdn_urls = [("image.jpg", thumb)]
                    username = r.json().get("author_name", "")

        downloaded_paths = []
        cdn_map = {}
        for filename, url in cdn_urls:
            dest = os.path.join(post_dir, filename)
            download_file(url, dest)
            downloaded_paths.append(dest)
            cdn_map[filename] = url
            result["files"].append(filename)

        if stitch_pdf_flag and len(downloaded_paths) > 1 and PILLOW_AVAILABLE:
            pdf_path = os.path.join(post_dir, "carousel.pdf")
            stitch_pdf(downloaded_paths, pdf_path)
            result["files"].append("carousel.pdf")

        post_type = "carousel" if len(cdn_urls) > 1 else "single_image"
        write_metadata(post_dir, post_url, shortcode, post_type, caption, username, cdn_map)
        result["files"].append("metadata.txt")

    except Exception as e:
        result["status"] = "error"
        result["error"] = str(e)

    return result


def write_metadata(post_dir, post_url, shortcode, post_type, caption, username, cdn_map):
    lines = [
        f"Post URL:       {post_url}",
        f"Shortcode:      {shortcode}",
        f"Type:           {post_type}",
    ]
    if post_type == "carousel":
        lines.append(f"Slide count:    {len([k for k in cdn_map if 'slide' in k])}")
    lines += [
        f"Caption:        {caption}",
        f"Username:       @{username}",
        f"Fetched at:     {datetime.now(timezone.utc).isoformat()}",
        "CDN URLs:",
    ]
    for fn, url in cdn_map.items():
        lines.append(f"  {fn:<18} {url}")
    with open(os.path.join(post_dir, "metadata.txt"), "w", encoding="utf-8") as f:
        f.write("\n".join(lines) + "\n")


def main(urls: list[str], base_dir: str = "./instagram-downloads", stitch: bool = True):
    os.makedirs(base_dir, exist_ok=True)
    results = []
    for url in urls:
        url = url.strip()
        if not url:
            continue
        print(f"Processing: {url}")
        r = process_url(url, base_dir, stitch)
        results.append(r)
        time.sleep(1)  # polite delay between requests

    # Summary
    ok = [r for r in results if r["status"] == "ok"]
    err = [r for r in results if r["status"] == "error"]
    total_files = sum(len(r["files"]) for r in ok)
    print("\nInstagram Post Downloader — Batch Complete")
    print("==========================================")
    print(f"URLs processed:   {len(results)}")
    print(f"Posts saved:      {len(ok)}")
    print(f"Total files:      {total_files}")
    print(f"Errors:           {len(err)}")
    print(f"Output dir:       {os.path.abspath(base_dir)}\n")
    for r in results:
        if r["status"] == "ok":
            print(f"  OK  {r['url']}")
        else:
            print(f"  ERR {r['url']}  — {r['error']}")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python instagram_downloader.py <url1> [url2] ...")
        sys.exit(1)
    main(sys.argv[1:])

Quality Checks

Before marking the task complete, verify each item:

  • Domain allowlist confirmed — *.cdninstagram.com is added before any fetch attempts
  • All provided URLs validated as Instagram URLs before processing begins
  • CDN URLs are the highest-resolution variants available (largest config_width selected)
  • Folder name is sanitised — no special characters, no spaces, max 40 chars from caption
  • Folder collision handled — shortcode appended if folder already exists
  • Carousel slides numbered sequentially with zero-padding (slide_01, slide_02, ...)
  • PDF includes all slides in correct order (not alphabetical — by slide index)
  • metadata.txt written to every post folder, including full CDN URLs
  • Pillow dependency checked at startup — graceful fallback if not available
  • Batch completion summary printed with file counts and any errors
  • Private post errors caught and reported — not silently skipped
  • Rate limiting handled — at least 1 second delay between requests
  • No credential or cookie storage — skill operates on public posts only

Anti-Patterns

  • Do not attempt to download private posts or content behind a login wall — this skill is for public posts only
  • Do not ignore 429 rate-limit responses — always implement a backoff wait before retrying
  • Do not save all downloads to a single flat folder when processing multiple accounts — use named subfolders per source
  • Do not skip PDF stitching for carousel posts — individual slides delivered without a combined PDF are incomplete output
  • Do not proceed if Instagram returns a login wall — surface the limitation clearly rather than returning an error silently

Example Trigger Phrases

  • "Download this Instagram post for me: https://www.instagram.com/p/ABC123/"
  • "Save that carousel to my downloads folder"
  • "Can you grab all the slides from this Instagram post and make a PDF?"
  • "Download these 5 Instagram posts" [followed by list of URLs]
  • "Archive this IG post before it gets deleted"
  • "I need the full-res images from this carousel"
  • "Download the images from this Instagram URL and stitch them into a PDF"
  • "Batch download these Instagram posts" [followed by URLs]
  • "Save the slides from this Instagram carousel as individual JPEGs"
  • "Get me the high-res version of this Instagram image"

Notes on Instagram's Anti-Scraping Measures

Instagram actively changes its page structure and API endpoints. If all three fetch attempts fail:

  1. The embed page method (/embed/captioned/) is historically the most stable — start there.
  2. CDN URLs expire. Download immediately after fetching — do not store URLs and download later.
  3. Instagram may return a login wall for some posts even if they're technically public. If this happens, the skill cannot proceed without authentication (which is out of scope).
  4. If Instagram returns a 429, wait 10–30 seconds before retrying. Reduce batch size for large lists.

This skill is designed for public posts only. It does not support login, sessions, or private content.


Originally inspired by a skill from Frank and Diana Dovgopol (Write, Prompt, Scale) — adapted and extended for this library.

© mohitagw15856, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/instagram-post-downloader of mohitagw15856/pm-claude-skills.

Open the folder on GitHubat commit 1cbf1f0

Compare with similar skills

Instagram Post Downloader next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Instagram Post Downloader compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Instagram Post Downloader this skillmohitagw15856/pm-claude-skills1.4k—~6.1kAutomated safety check: PassMIT
Threads Carouselitchernetski/threads-carousel-claude-skill110—~3.5kAutomated safety check: PassMIT
Social Media Carouseldanielmeppiel/agentic-sdlc-handbook1581 repos~2.2kAutomated safety check: PassCustom licence
Slide Deck Image GeneratorSpaceZephyr/design-buddy176—~6.5kAutomated safety check: PassNone
Carousel Writer Smsblacktwist/social-media-skills560—~4.1kAutomated safety check: PassMIT
Carousel Writersocial-media-skills/skills134—~1.6kAutomated safety check: PassMIT

Similar skills

  • Threads Carousel

    itchernetski/threads-carousel-claude-skill

    Convert text posts into visual carousel images or presentations for Threads, Instagram, LinkedIn, TikTok, YouTube.

    110 GitHub stars~3.5k tokensUpdated 5 mo ago
    Documents & OfficeAuto-check passed
  • Social Media Carousel

    danielmeppiel/agentic-sdlc-handbook

    Multi-slide carousel design for Instagram, LinkedIn, and Twitter/X with layout rules and hooks.

    158 GitHub starsUsed in 1 repo~2.2k tokens
    Documents & OfficeAuto-check passed
  • Slide Deck Image Generator

    SpaceZephyr/design-buddy

    Turns written content into designed slide images from an outline, merges them into PPTX or PDF, and can borrow styles from a registry of brand design systems.

    176 GitHub stars~6.5k tokensUpdated 3 mo ago
    Documents & OfficeAuto-check passed
  • Carousel Writer Sms

    blacktwist/social-media-skills

    When the user wants to write content for a LinkedIn carousel, Instagram carousel, Facebook carousel, TikTok photo carousel, Pinterest Idea Pin, or any swipeable multi-slide format.

    560 GitHub stars~4.1k tokensUpdated 5 mo ago
    Documents & OfficeAuto-check passed
  • Carousel Writer

    social-media-skills/skills

    A skill your agent uses to write a carousel — a swipeable multi-slide post (Instagram carousel, LinkedIn document/PDF carousel, or TikTok photo carousel) — one of the highest-save, highest-dwell…

    134 GitHub stars~1.6k tokensUpdated 9 days ago
    Documents & OfficeAuto-check passed
  • Paper Deck

    zsyggg/paper-craft-skills

    将论文、技术文章或知识内容制作成高真实感的 AIGC 幻灯片。先做叙事结构和逐页视觉导演,再调用生图模型生成每一页 16:9 slide image,最后合成为 PPTX/PDF。适合论文汇报、组会、公开课、技术分享、商业化研究展示;当用户提到“论文PPT”“AI生成PPT”“不像AI的PPT”“高质感幻灯片”“逐页生图PPT”时使用。

    1.3k GitHub stars~1.4k tokensUpdated 4 mo ago
    Documents & OfficeAuto-check passed

More from mohitagw15856/pm-claude-skills

All 1,348 skills in this repo
  • Car Tco

    mohitagw15856/pm-claude-skills

    Compare the total cost of car ownership across buy-new, buy-used, lease, and keep-your-current-car — depreciation, insurance, maintenance ramp, and fuel over a real horizon, not just the monthly…

    1.4k GitHub stars~1.1k tokensUpdated yesterday
    Auto-check passed
  • Cs Health Scorecard

    mohitagw15856/pm-claude-skills

    Build a customer health scorecard for a specific account. An agent skill from mohitagw15856/pm-claude-skills.

    1.4k GitHub stars~2.4k tokensUpdated yesterday
    Auto-check passed
  • Exit Waterfall

    mohitagw15856/pm-claude-skills

    Compute who gets what at each exit price from a cap table — liquidation preferences, conversion points, and where the founders' share collapses.

    1.4k GitHub stars~1.1k tokensUpdated yesterday
    Auto-check passed
  • Feature Prioritisation

    mohitagw15856/pm-claude-skills

    Apply prioritisation frameworks (RICE, MoSCoW, Kano, ICE, Opportunity Scoring) to rank features and backlog items.

    1.4k GitHub stars~2k tokensUpdated yesterday
    Auto-check passed
  • Fire Number

    mohitagw15856/pm-claude-skills

    Compute a financial-independence (FIRE) target and years-to-reach with every assumption labeled as an assumption — plus a sensitivity table instead of a single false-precision answer.

    1.4k GitHub stars~1.1k tokensUpdated yesterday
    Auto-check passed
  • Freelance Rate

    mohitagw15856/pm-claude-skills

    Derive a freelance day/hourly rate backwards from target income, honest billable utilization, overhead, and the self-employment tax premium — the arithmetic that proves a rate is not salary÷2000.

    1.4k GitHub stars~1.2k tokensUpdated yesterday
    Auto-check passed

Works with

Questions about Instagram Post Downloader

What does Instagram Post Downloader do?

Download and save Instagram posts as high-resolution files. An agent skill from mohitagw15856/pm-claude-skills. Instagram Post Downloader is an agent skill from mohitagw15856/pm-claude-skills. Download and save Instagram posts as high-resolution files.

When should I use Instagram Post Downloader?

Instagram Post Downloader fits situations like: asked to download; archive an Instagram post.

How do I install Instagram Post Downloader in Claude Code?

Run `npx skills add mohitagw15856/pm-claude-skills --skill instagram-post-downloader -a claude-code`. Or copy the skill folder (skills/instagram-post-downloader in mohitagw15856/pm-claude-skills) into .claude/skills/instagram-post-downloader in your project. Claude Code loads it when a task matches its description.

How do I install Instagram Post Downloader in Codex?

Run `npx skills add mohitagw15856/pm-claude-skills --skill instagram-post-downloader -a codex`. Or copy the skill folder (skills/instagram-post-downloader in mohitagw15856/pm-claude-skills) into .agents/skills/instagram-post-downloader in your project. Codex loads it when a task matches its description.

Can I use Instagram Post Downloader in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add mohitagw15856/pm-claude-skills --skill instagram-post-downloader -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/instagram-post-downloader, .gemini/skills/instagram-post-downloader, .github/skills/instagram-post-downloader and .opencode/skills/instagram-post-downloader in your project.

What does Instagram Post Downloader need to run?

Going by SKILL.md and its folder, Instagram Post Downloader needs the command-line tools its instructions call (yt-dlp and pip). Our summary lists: Python 3.

Does Instagram Post Downloader access the network?

SKILL.md names 5 domains. In commands or code: instagram.com, scontent.cdninstagram.com, api.instagram.com, scontent-lax3-1.cdninstagram.com and instagram.fxxx1-1.fbcdn.net; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.

Is Instagram Post Downloader safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Instagram Post Downloader use?

Instagram Post Downloader is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Instagram Post Downloader use?

About 6.1k tokens (SKILL.md is roughly 24k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Instagram Post Downloader?

Skills that share tags, products or a category with Instagram Post Downloader: Threads Carousel (itchernetski/threads-carousel-claude-skill, 110 stars), Social Media Carousel (danielmeppiel/agentic-sdlc-handbook, 158 stars), Slide Deck Image Generator (SpaceZephyr/design-buddy, 176 stars) and Carousel Writer Sms (blacktwist/social-media-skills, 560 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Instagram Post Downloader?

mohitagw15856 (a GitHub user) maintains it in mohitagw15856/pm-claude-skills, which has 1,434 GitHub stars. The repository holds 1,348 skills in this directory. The repository was last updated on October 9, 2026.

Source: mohitagw15856/pm-claude-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.