Self-hosted Instagram data API. Free alternative to Apify's Instagram Scraper ($1.50 / 1,000 results, 266K users).
Deploy once. Scrape profiles, posts, reels, hashtags, and follower graphs at zero per-request cost.
A FastAPI REST service wrapping aiograpi, the async Instagram Private API client. Full OpenAPI docs at /docs once running.
What you can pull:
- User profiles, follower / following lists
- Posts, reels, stories, highlights, IGTV
- Hashtag and location media feeds
- Direct messages, comments, likes
- Music tracks, explore page
- Account insights (business accounts)
cp .env.example .env
# Edit .env: set API_KEY to a strong secret
docker compose upService starts on http://localhost:8000. OpenAPI docs at http://localhost:8000/docs.
pip install .
cp .env.example .env
uvicorn aiograpi_rest.main:app --reload- Click Deploy on Railway above
- Set
API_KEYin Railway's environment variables panel - Set
AIOGRAPI_REST_DB_PATH=/data/db.jsonand mount a volume at/data
All endpoints require a valid Instagram session ID passed as X-Session-ID header.
Step 1 — Get a session ID:
curl -X POST http://localhost:8000/auth/login \
-H "X-API-Key: your-api-key" \
-F "username=your_instagram_username" \
-F "password=your_instagram_password"
# Returns: "XXXXXXXXXXXXXXXXX" ← your session IDStep 2 — Use it:
curl http://localhost:8000/user/by/username?username=natgeo \
-H "X-API-Key: your-api-key" \
-H "X-Session-ID: XXXXXXXXXXXXXXXXX"Session IDs are stored in db.json (path controlled by AIOGRAPI_REST_DB_PATH). They persist across restarts.
| Variable | Default | Description |
|---|---|---|
API_KEY |
(empty) | Require X-API-Key header on all requests. Leave blank for local/trusted-network use. |
AIOGRAPI_REST_DB_PATH |
./db.json |
Path to session storage (TinyDB JSON). Mount a volume here in Docker. |
RATE_LIMIT_REQUESTS |
60 |
Requests per IP per window. 0 disables limiting. |
RATE_LIMIT_WINDOW |
60 |
Rate limit window in seconds. |
DEBUG |
false |
Set true to expose /docs and /redoc when API_KEY is set. Never enable on a public endpoint without also setting API_KEY. |
Session IDs are accepted only via X-Session-ID header or cookie. Query-string transport was removed — query params appear in server access logs, proxy logs, browser history, and Referer headers.
When API_KEY is set, every request must include X-API-Key: <value>. Generate a strong key:
python3 -c "import secrets; print(secrets.token_hex(32))"If a session ID is compromised:
- Revoke it: Instagram → Settings → Security → Active sessions → terminate the session
- Purge local storage: delete
db.json(or the specific row wheresessionidmatches) - Re-authenticate:
POST /auth/loginto generate a new session ID
New users get clean session IDs on every POST /auth/login call. The old session is not automatically invalidated server-side — always revoke at step 1 if compromise is suspected.
API_KEYset to a randomly generated 32+ character secretDEBUG=false(default)- Running behind HTTPS (Railway, Render, and nginx all handle TLS termination)
AIOGRAPI_REST_DB_PATHpoints to a persistent volume, not the container filesystem- Rate limiting tuned for your expected request volume
| Apify | instagram-api-free | |
|---|---|---|
| Cost | $1.50 / 1,000 results | $0 (self-hosted) |
| Data ownership | Apify's servers | Your infrastructure |
| Rate limits | Apify's quotas | Your Instagram account's limits |
| Customization | Limited | Full source access |
| Setup time | 2 min | 5 min |
Built on subzeroid/aiograpi-rest (MIT).
Changes in this fork:
- Removed session ID query-param transport (credential exposure via server logs)
- Removed HikerAPI referral link from Swagger docs
- Added
API_KEYservice-level authentication - Added per-IP rate limiting middleware (
RATE_LIMIT_REQUESTS/RATE_LIMIT_WINDOW) - Added
DEBUGflag with explicit dev vs. production behavior - Added
.env.example - Added Railway one-click deploy (
railway.json) - Rewrote README
MIT — see LICENSE