Saves are the cheapest content idea you never use.
You save Instagram posts thinking you'll come back to them. You don't. They pile up in collections you never open again, and every "this would make a great Reel" idea dies the moment you swipe to the next post.
This guide builds a pipeline that pulls your saves into Notion twice a day on its own, then turns them into platform-ready content ideas on demand. By the end, your saves stop being dead weight and start being your content calendar.
Three pieces, one pipeline.
You're building three things that work together. The Python daemon does the dumb work on a schedule. The two Notion databases hold raw saves and polished ideas separately. The slash command is the creative layer you run when you're ready to think about content.
sync.py
A Python script that pulls saved posts from Instagram and writes new ones to Notion. Runs twice a day on its own.
Two databases
Instagram Saves holds the raw research feed. Content Ideas holds the reframed, ready-to-shoot stuff.
Slash command
Reads unprocessed saves, reframes them for your audience, writes the approved ones to Content Ideas.
The flow
Scheduler fires sync.py at 9 AM and 9 PM. New saves land in Notion. You run /instagram-sync ideate when ready.
What you need before you start.
Five things. If you have all five, the next 40 minutes go fast.
- Claude Code installed from claude.ai/claude-code.
- A Mac. The scheduler is Mac-specific. Linux users can adapt with cron.
- Python 3.9 or newer. Run python3 --version to check. brew install python3 to upgrade.
- A Notion account with an internal integration token.
- An Instagram account with saved posts.
To get your Notion integration token, go to notion.so/profile/integrations, click New integration, name it Instagram Saves Sync , check Read content, Insert content, and Update content. Save and copy the token that starts with ntn_ . Hold onto it.
Then in your terminal, create your project folder. Every file lives here.
mkdir instagram-saves-engine cd instagram-saves-engine
Paste this into Claude Code first.
Open Claude Code in your fresh project folder. Paste the prompt below. This sets the full scope before you ask Claude to generate anything specific. Everything in the next steps assumes Claude has this context.
Build an Instagram Saves → Notion sync system with two parts: Part 1: A Python background script that authenticates to Instagram's web API using browser session cookies, fetches all saved posts (with collection names), and syncs new ones into a Notion database. Each save gets stored with its author, caption, URL, content type (Post/Reel/Carousel), and which Instagram collection it came from. A state file prevents duplicates. The script runs automatically twice a day via macOS launchd. Part 2: A Claude Code skill that reads unprocessed saves from Notion and turns them into content ideas. For each save, it reframes the original concept for an audience of [your niche]. Generates 3 hook variations, a structured outline (Hook → Key Points → CTA), and platform-specific breakdowns for Instagram, TikTok, and YouTube. Presents the ideas for review, then saves approved ones to a separate Content Ideas Notion database and marks the original saves as processed. Goal: a zero-friction pipeline. Save something on Instagram, and twice a day it flows into Notion. Then on demand, those saves get transformed into actionable content ideas ready for production.
Paste into a new Claude Code project as your opening message. Replace [your niche] with who you make content for.
Build the two Notion databases.
Two databases. The first captures raw saves as they come in. The second holds the polished content ideas you actually shoot. Don't combine them. The whole point of the split is keeping your content calendar clean while saves accumulate in the background.
I want to create two Notion databases for an Instagram saves pipeline. Walk me through creating each one in Notion and list every property name and type I need to add. Database 1: Instagram Saves - Name (title) - URL (URL) - Type (select: Post, Reel, Carousel, IGTV) - Author (text) - Status (select: New, Reviewed, Used) - Media ID (text) - Saved (date) - Caption (text) - Collection (select) Database 2: Content Ideas - Name (title) - Platform (multi-select: Instagram, TikTok, YouTube) - Format (select: Carousel, Reel, Short Video, Long-form Video) - Status (select: Not started, In progress, Done) - Hook Options (text) - Angle (text) - Priority (select: High, Medium, Low) - Week Of (date) - Pillar (select: your content pillars here) After I create them, tell me how to find the database IDs from the Notion URL.
Paste into Claude Code. Replace "your content pillars here" with your actual pillars before pasting.
Once both databases exist, grab the ID from each URL. It's the 32-character string between your workspace name and the ?v= . Copy both. You'll need them twice in the next steps.
Then connect your integration to each database. Open the database, click the three-dot menu in the top right, choose Connect to, pick your integration. Do this for both databases or the sync will fail with a permissions error.
Get your Instagram session.
Instagram has no public API for saved posts. You authenticate the same way your browser already does, using the session cookies it sends with every request.
Open Chrome, sign in to instagram.com. Press Cmd+Option+I to open DevTools. Click the Application tab. In the left sidebar, expand Cookies and click https://www.instagram.com .
Find these three values and copy each one: sessionid , csrftoken , and ds_user_id .
Treat these like a password. Anyone with your sessionid can access your account. Never commit them to git. They live in a local config file that never leaves your machine.
Heads up that these cookies rotate every few weeks. When your sync starts failing, come back to this step and grab fresh values. The slash command you're building has a built-in refresh action that walks you through it.
Generate sync.py.
You're not writing Python. Claude is. This prompt produces the full sync script, a requirements file, an example config, and a gitignore.
Build a Python script called sync.py that pulls my Instagram saved posts and writes new ones to a Notion database. Inputs (from config.json in the same directory): - ig_session_id, ig_csrftoken, ig_user_id (Instagram web cookies) - notion_token (Notion integration token starting with ntn_) - notion_database_id (my Instagram Saves database ID) - collections_filter (optional list of collection names to sync, e.g. ["Content Ideas", "Inspiration"]) Instagram API calls (mimic the web client, not the mobile app): - Headers: desktop Chrome User-Agent, X-IG-App-ID: 936619743392459 - Cookies: sessionid, csrftoken, ds_user_id - Validate session: GET https://www.instagram.com/api/v1/accounts/edit/web_form_data/ - Fetch saves: GET https://www.instagram.com/api/v1/feed/saved/posts/?count=50, paginate with next_max_id, sleep 1 second between pages - Fetch collection names: GET https://www.instagram.com/api/v1/collections/list/ with collection_types=["ALL_MEDIA_AUTO_COLLECTION","PRODUCT_AUTO_COLLECTION","MEDIA"] Dedup logic: - Maintain state.json with a list of already-synced media IDs - Skip any post whose pk is already in the list - Only add IDs to state.json after successful Notion writes Notion writes (use the official notion-client Python SDK): - One page per new post with these properties: Name (title = @author/code), URL, Type (select), Author (text), Status (select = "New"), Media ID (text), Saved (date = now UTC), Caption (text, truncated to 1900 chars), Collection (select if filter is set) - URL format: https://instagram.com/reel/CODE/ for reels, /p/CODE/ for everything else Logging: - Log to both stdout and sync.log in the same directory - Final line: Sync complete: N new | N skipped | N total | N errors Error handling: - Per-post exceptions: log and continue, do not crash the run - Failed IDs must NOT be added to state.json (so they retry next run) - Invalid session: exit immediately with a clear message to refresh cookies Also generate: - requirements.txt (requests>=2.31.0, notion-client>=2.2.0) - config.example.json (template with placeholder strings) - .gitignore (excludes config.json, state.json, sync.log, .venv/)
Paste into Claude Code from your project folder.
Why not use the popular instagrapi library? It targets Instagram's mobile API and gets flagged faster. The web API approach mimics what your browser already sends, which is lower risk for a personal script on your own account.
Once Claude generates the files, set up your Python environment.
python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt cp config.example.json config.json
Open config.json and fill in every placeholder. Cookie values go in the three ig_ fields. Your ntn_ integration token goes in notion_token . The Saves database ID goes in notion_database_id . Add a collections_filter array with the collection names you want to pull from, or remove the key to pull all saves.
Run it twice a day on its own.
Run sync.py once first to confirm it works. A clean run prints Sync complete: N new | N skipped | N total | N errors , and rows appear in your Instagram Saves database with Status = New. If your first run pulls way more saves than you expected, add a collections_filter to config.json listing only the collections you actually want. Pulling everything floods the pipeline.
Once that's working, set up the scheduler. launchd is the Mac-native scheduler. It starts on boot, logs cleanly, and is how Apple wants background processes to run. This job fires sync.py at 9 AM and 9 PM every day, even when you're not at your desk.
Generate a macOS launchd plist file called com.myname.instagram-saves-sync.plist that runs .venv/bin/python3 sync.py from my project directory at 9:00 AM and 9:00 PM every day. Use absolute paths throughout. Capture stdout to launchd-stdout.log and stderr to launchd-stderr.log in the project directory. My project directory is: [paste your absolute path here] After writing the file, give me the two bash commands to install it: 1. Copy to ~/Library/LaunchAgents/ 2. Load with launchctl
Paste into Claude Code. Replace the absolute path placeholder before sending.
Run the two commands Claude gives you. Then verify the job is registered with launchctl list | grep instagram-saves . If you see an entry, you're set. If your scheduler stops working after a macOS update, reload it with launchctl load ~/Library/LaunchAgents/com.myname.instagram-saves-sync.plist .
Build the /instagram-sync slash command.
This is where the engine gets your voice. The slash command reads unprocessed saves, reframes each one for your audience, generates hooks and outlines, and writes the approved ideas to your Content Ideas database. Customize every placeholder in the prompt below before pasting.
Create a slash command file at .claude/commands/instagram-sync.md. The command handles six actions based on what I ask: 1. Run a sync now: Run .venv/bin/python3 sync.py from my project directory at [your absolute path]. Then automatically trigger the Ideate pipeline if any saves have Status = "New." 2. Ideate from saves (6-step pipeline): Step 1: Query the Instagram Saves Notion database (ID: [paste your Saves DB ID]) for entries with Status = "New." Fetch Caption, Author, Type, URL, and Collection. If more than 10 saves are pending, process in batches of 10 with user review between batches. Step 2: For each save, generate 1 content idea: - Reframe for this audience: [your niche] - Assign a content pillar based on the save's Instagram collection. Collection-to-pillar mapping: [your mapping] - Write 3 hook options: Curiosity-driven, Value-driven, Emotional - Write a structured outline: HOOK, 3-4 KEY TALKING POINTS, CTA - Write platform breakdowns: Instagram (Carousel or Reel), TikTok (30-60s script), YouTube (only if the topic has long-form depth) - Set Priority: High for core-collection saves or timely topics, Medium for strong audience overlap, Low for tangential inspiration Step 3: Present all ideas with title, pillar, priority, platforms, hook options, outline, and platform breakdowns. Ask: approve all, select by number, skip all, or request modifications. Step 4: For each approved idea, create a page in the Content Ideas database (ID: [paste your Ideas DB ID]) with properties: Name, Platform, Format, Status = "Not started", Angle, Hook Options (all 3 joined by " | "), Priority, Week Of (current Monday), Pillar. Page body: angle, full outline, Instagram section, TikTok section, YouTube section (if applicable). Step 5: Update each processed save's Status: "Used" if approved, "Reviewed" if skipped. Step 6: Print a SAVES IDEATION COMPLETE summary with saves processed, ideas generated, ideas saved to Notion, the saved titles, and recommended next commands. 3. Check sync status: Read state.json and the last 20 lines of sync.log. Report last sync time, total synced, recent errors. 4. Check scheduler status: Run launchctl list | grep instagram-saves. 5. Refresh session: Walk me through getting fresh sessionid, csrftoken, ds_user_id from Chrome DevTools (instagram.com > Application > Cookies), updating config.json, running a test sync. 6. View recent saves: Query the Instagram Saves database sorted by Saved date, newest first.
Paste into Claude Code from your project folder. Replace every bracketed placeholder.
The point of splitting sync.py from the slash command is the split between dumb and smart work. The sync runs unattended on a schedule. The ideation runs on demand, when you're actually ready to think about content.
Open Claude Code in your project folder and run /instagram-sync ideate . Claude queries the Saves database for Status = New rows, reframes each save for your audience, presents the ideas with hooks and platform breakdowns, writes the approved ones to your Content Ideas database, and flips each processed save to Used or Reviewed.
Avoid these.
- Skipping the collections filter. If you have 800 saves, pulling all of them on the first run will flood Notion and bury the saves that actually matter. Add a collections_filter from day one.
- Forgetting to connect the Notion integration to both databases. The sync will fail with a permissions error and you'll waste 20 minutes debugging the script.
- Committing config.json. Your session cookies are in there. Confirm .gitignore includes config.json, state.json, sync.log, and .venv/ before your first commit.
- Processing daily instead of weekly. Batching saves into a weekly ideation session sharpens the output and lowers the cognitive load. The scheduler keeps running. You don't have to.
- Deleting state.json. It's the only thing preventing duplicate Notion pages. Back it up. If it disappears, the next sync re-creates every previous save as a new row.
