{
"path": "/projects/jasper",
"site": "at://did:plc:ofrbh253gwicbkc5nktqepol/site.standard.publication/3mfyq5mpohw25",
"tags": [
"atproto",
"tooling"
],
"$type": "site.standard.document",
"title": "Jasper",
"description": "Convert Instagram data exports into posts, stories, and videos on Grain or Spark while preserving original timestamps.",
"publishedAt": "2026-04-16T00:00:00.000Z",
"textContent": "Jasper imports your Instagram photos to Grain or Spark while preserving original timestamps. Your memories appear with their original dates, not the import date.\n\nThe name follows ATProto import tools using mineral names — a nod to the pattern established by Malachite. Jasper is a red-orange quartz, fitting for something that preserves photographic memories.\n\nWhat it does\n\n- Preserves timestamps — Photos appear with their original Instagram dates\n- Multiple targets — Import to Grain or Spark\n- Stories and videos — Instagram stories and videos import to Spark (Grain doesn't support them)\n- Handles all export formats — Works with 2022, 2023, 2024, and 2025 Instagram exports\n- Gallery-based (Grain) — Photos are organised into Grain galleries you choose or create\n- Multi-image posts (Spark) — Carousel posts import as a single Spark post with up to 12 images\n- Skips duplicates — Already-imported posts are detected and skipped\n- Dry run mode — Preview what would be imported before committing\n- OAuth authentication — Secure login via your existing AT Protocol identity\n- Web interface — Import from your browser at jasper.croft.click\n\nInstallation\n\nUsage\n\nInteractive Mode\n\nRun without arguments for guided prompts:\n\nJasper will prompt you to select a target platform (Grain or Spark) and, for Grain, select or create a gallery before importing.\n\nCommand Line\n\nAuthentication\n\nOAuth (Recommended)\n\nOAuth uses a loopback callback server on port 8766. Your browser opens to your PDS's authorisation screen, you approve, and the session is saved automatically. Subsequent imports use the stored session — no re-authentication needed.\n\nApp Password\n\nIf OAuth isn't available, you can use an app password:\n\nGenerate an app password at bsky.app/settings/app-passwords.\n\nGetting Your Instagram Export\n\n1. Open Instagram (app or web browser)\n2. Go to your profile\n3. Tap the menu (☰) → Settings → Accounts Centre\n4. Select \"Your information and permissions\"\n5. Choose \"Export your information\"\n6. Select \"Export to device\"\n7. Choose what to include (or select \"All information\")\n8. Select JSON format\n9. Choose media quality (High recommended)\n10. Enter your password to confirm\n11. Wait for the email notification (can take hours to days)\n12. Download the ZIP file when ready\n\nJasper will locate posts_1.json and stories_1.json automatically, handling all export format variations.\n\nTarget Platforms\n\nJasper supports two AT Protocol platforms for importing. Each has different capabilities:\n\n| Feature | Grain | Spark |\n| ----------------- | ------------------------- | -------------------- |\n| Photos | ✅ | ✅ |\n| Videos | ❌ | ✅ |\n| Stories | ❌ | ✅ |\n| Galleries | ✅ | ❌ |\n| Multi-image posts | ❌ (one photo per record) | ✅ (up to 12 images) |\n| Alt text | Optional | Required |\n| Max image size | 1 MB | 5 MB |\n\nChoose the target that fits your content. Use --target grain or --target spark (default: grain).\n\nData Model\n\nGrain\n\nJasper creates three types of records:\n\n1. social.grain.photo — The image blob with aspect ratio and timestamp\n2. social.grain.gallery — A container you create or select for organising photos\n3. social.grain.gallery.item — Links each photo to your chosen gallery with position\n\nPhotos must be linked to a gallery to display properly on grain.social. Each record holds one photo.\n\nSpark\n\nJasper creates two types of records:\n\n1. so.sprk.feed.post — A post with a media union of images or video\n2. so.sprk.story.post — A story with a media union of images or video\n\nSpark uses a media union — each post has either so.sprk.media.images (up to 12 images) or so.sprk.media.video. Carousel Instagram posts become a single Spark post with multiple images. Videos are uploaded as blobs and attached with so.sprk.media.video.\n\nWhat Gets Imported\n\nImported to Grain:\n\n- ✅ Photos (JPEG, PNG, WebP, GIF)\n- ✅ Original timestamps\n- ✅ Captions (as alt text)\n- ✅ Carousel posts (each photo as a separate record)\n\nImported to Spark:\n\n- ✅ Photos (JPEG, PNG, WebP, GIF)\n- ✅ Videos (MP4, MOV)\n- ✅ Original timestamps\n- ✅ Captions (as alt text)\n- ✅ Carousel posts (up to 12 images per post)\n- ✅ Stories (image and video)\n\nNot imported:\n\n- ❌ Reels\n- ❌ Stories to Grain (not supported)\n- ❌ Videos to Grain (not supported)\n\nOptions\n\n| Option | Description |\n| --------------------- | ---------------------------------------------------- |\n| -i, --input <path> | Path to Instagram export ZIP or directory |\n| --target <platform> | Target platform: grain or spark (default: grain) |\n| --dry-run | Preview posts without importing |\n| --limit <N> | Import at most N posts |\n| --reverse | Process newest posts first (default: oldest first) |\n| --alt <text> | Override alt text for all photos (default: captions) |\n| -v, --verbose | Enable debug logging |\n| -q, --quiet | Suppress non-essential output |\n| -y, --yes | Skip confirmation prompts |\n| --oauth-login | Sign in via OAuth |\n| --logout [DID] | Sign out (removes stored session) |\n| --list-sessions | List stored OAuth sessions |\n\nDaily Limits for Large Exports\n\nLarge Instagram exports (hundreds of photos) should be split across multiple days to avoid hitting PDS blob upload quotas. Jasper supports resumable imports with daily limits:\n\n| Option | Description |\n| ------------------- | ---------------------------------------------- |\n| --daily-limit <N> | Maximum posts to import per day (default: 100) |\n| --resume | Resume previous import session |\n| --list-imports | List pending import sessions |\n| --clear-imports | Clear all saved import state |\n\nWhen the daily limit is reached, Jasper saves your progress and prompts you to continue the next day. Run jasper --resume to continue importing.\n\nWeb Interface\n\nJasper has a browser-based interface at jasper.croft.click — no installation required. The web app runs entirely in your browser: your Instagram export is parsed locally, and photos are uploaded directly to your PDS.\n\nThe web app uses @atproto/oauth-client-browser for authentication. No data passes through any intermediate server.\n\nSource: packages/jasper-web in the monorepo.\n\nData Storage\n\nAll data stays on your machine:\n\n| Location | Content |\n| ---------------------- | ----------------------------------- |\n| ~/.jasper/oauth.json | OAuth session tokens |\n| ~/.jasper/imports/ | Import state for resumable sessions |\n| ~/.jasper/logs/ | Debug log files |\n\nNo data is sent to any server except your chosen Grain or Spark account.\n\nOAuth Scope\n\nJasper requests minimal permissions based on target platform:\n\nGrain:\n\nSpark:\n\nThis follows ATProto's granular permission model — no broad transition:generic scope.\n\nDevelopment\n\nBuild from source:\n\nRun in dev mode:\n\nBuild the web app:\n\nRequirements\n\n- Node.js 18+\n- A Grain or Spark account (use your existing AT Protocol identity)\n\nLicense\n\nAGPL-3.0-only.\n\nContact\n\nEwan Croft — contact@ewancroft.uk",
"canonicalUrl": "https://docs.ewancroft.uk/projects/jasper"
}