Jasper
Jasper imports your Instagram photos to Grain or Spark while preserving original timestamps. Your memories appear with their original dates, not the import date.
The 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.
What it does
- Preserves timestamps — Photos appear with their original Instagram dates
- Multiple targets — Import to Grain or Spark
- Stories and videos — Instagram stories and videos import to Spark (Grain doesn't support them)
- Handles all export formats — Works with 2022, 2023, 2024, and 2025 Instagram exports
- Gallery-based (Grain) — Photos are organised into Grain galleries you choose or create
- Multi-image posts (Spark) — Carousel posts import as a single Spark post with up to 12 images
- Skips duplicates — Already-imported posts are detected and skipped
- Dry run mode — Preview what would be imported before committing
- OAuth authentication — Secure login via your existing AT Protocol identity
- Web interface — Import from your browser at jasper.croft.click
Installation
Usage
Interactive Mode
Run without arguments for guided prompts:
Jasper will prompt you to select a target platform (Grain or Spark) and, for Grain, select or create a gallery before importing.
Command Line
Authentication
OAuth (Recommended)
OAuth 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.
App Password
If OAuth isn't available, you can use an app password:
Generate an app password at bsky.app/settings/app-passwords.
Getting Your Instagram Export
- Open Instagram (app or web browser)
- Go to your profile
- Tap the menu (☰) → Settings → Accounts Centre
- Select "Your information and permissions"
- Choose "Export your information"
- Select "Export to device"
- Choose what to include (or select "All information")
- Select JSON format
- Choose media quality (High recommended)
- Enter your password to confirm
- Wait for the email notification (can take hours to days)
- Download the ZIP file when ready
Jasper will locate posts_1.json and stories_1.json automatically, handling all export format variations.
Target Platforms
Jasper supports two AT Protocol platforms for importing. Each has different capabilities:
| Feature | Grain | Spark |
|---|---|---|
| Photos | ✅ | ✅ |
| Videos | ❌ | ✅ |
| Stories | ❌ | ✅ |
| Galleries | ✅ | ❌ |
| Multi-image posts | ❌ (one photo per record) | ✅ (up to 12 images) |
| Alt text | Optional | Required |
| Max image size | 1 MB | 5 MB |
Choose the target that fits your content. Use --target grain or --target spark (default: grain).
Data Model
Grain
Jasper creates three types of records:
- social.grain.photo — The image blob with aspect ratio and timestamp
- social.grain.gallery — A container you create or select for organising photos
- social.grain.gallery.item — Links each photo to your chosen gallery with position
Photos must be linked to a gallery to display properly on grain.social. Each record holds one photo.
Spark
Jasper creates two types of records:
- so.sprk.feed.post — A post with a media union of images or video
- so.sprk.story.post — A story with a media union of images or video
Spark 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.
What Gets Imported
Imported to Grain:
- ✅ Photos (JPEG, PNG, WebP, GIF)
- ✅ Original timestamps
- ✅ Captions (as alt text)
- ✅ Carousel posts (each photo as a separate record)
Imported to Spark:
- ✅ Photos (JPEG, PNG, WebP, GIF)
- ✅ Videos (MP4, MOV)
- ✅ Original timestamps
- ✅ Captions (as alt text)
- ✅ Carousel posts (up to 12 images per post)
- ✅ Stories (image and video)
Not imported:
- ❌ Reels
- ❌ Stories to Grain (not supported)
- ❌ Videos to Grain (not supported)
Options
| Option | Description |
|---|---|
| -i, --input | Path to Instagram export ZIP or directory |
| --target | Target platform: grain or spark (default: grain) |
| --dry-run | Preview posts without importing |
| --limit | Import at most N posts |
| --reverse | Process newest posts first (default: oldest first) |
| --alt | Override alt text for all photos (default: captions) |
| -v, --verbose | Enable debug logging |
| -q, --quiet | Suppress non-essential output |
| -y, --yes | Skip confirmation prompts |
| --oauth-login | Sign in via OAuth |
| --logout [DID] | Sign out (removes stored session) |
| --list-sessions | List stored OAuth sessions |
Daily Limits for Large Exports
Large 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:
| Option | Description |
|---|---|
| --daily-limit | Maximum posts to import per day (default: 100) |
| --resume | Resume previous import session |
| --list-imports | List pending import sessions |
| --clear-imports | Clear all saved import state |
When the daily limit is reached, Jasper saves your progress and prompts you to continue the next day. Run jasper --resume to continue importing.
Web Interface
Jasper 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.
The web app uses @atproto/oauth-client-browser for authentication. No data passes through any intermediate server.
Source: packages/jasper-web in the monorepo.
Data Storage
All data stays on your machine:
| Location | Content |
|---|---|
| ~/.jasper/oauth.json | OAuth session tokens |
| ~/.jasper/imports/ | Import state for resumable sessions |
| ~/.jasper/logs/ | Debug log files |
No data is sent to any server except your chosen Grain or Spark account.
OAuth Scope
Jasper requests minimal permissions based on target platform:
Grain:
Spark:
This follows ATProto's granular permission model — no broad transition:generic scope.
Development
Build from source:
Run in dev mode:
Build the web app:
Requirements
- Node.js 18+
- A Grain or Spark account (use your existing AT Protocol identity)
License
AGPL-3.0-only.
Contact
Ewan Croft — contact@ewancroft.uk
Discussion in the ATmosphere