CLI reference
Synopsis
insta-dl [-h] [-V] [-v]
[--backend {hiker,aiograpi}]
[--dest DEST]
[--hiker-token HIKER_TOKEN]
[--login LOGIN] [--password PASSWORD] [--session SESSION]
[--fast-update]
[--latest-stamps LATEST_STAMPS]
[--comments]
[--stories] [--highlights]
[--post-filter EXPR]
targets [targets ...]
Targets
Positional. One or more, mixed forms allowed.
| Form |
Meaning |
Example |
<username> |
Download all posts by a profile |
instagram |
#<tag> |
Download recent posts for a hashtag (quote it!) |
'#sunset' |
post:<shortcode> |
Download one post / reel by shortcode |
post:DXZlTiKEpxw |
https://[www.]instagram.com/p/<code>/ |
Same, by URL |
https://www.instagram.com/p/DXZlTiKEpxw/ |
https://[www.]instagram.com/reel/<code>/ |
Same, for reels |
https://instagram.com/reel/DXZlTiKEpxw/ |
https://[www.]instagram.com/tv/<code>/ |
Same, for IGTV |
https://instagram.com/tv/DXZlTiKEpxw/ |
https://[www.]instagram.com/<username>/ |
Same as bare <username> |
https://instagram.com/instagram/ |
URL parsing is case-insensitive, anchored on the host (evil.com/instagram.com/... is rejected), and ignores ?query and #fragment. Stories-by-URL (https://instagram.com/stories/...) is rejected with an explicit message — use --stories <username> instead.
Options
Backend selection
| Flag |
Default |
Description |
--backend {hiker,aiograpi} |
hiker |
Which transport to use. See Backends. |
hiker backend
| Flag |
Source |
Description |
--hiker-token TOKEN |
env HIKERAPI_TOKEN |
Required. Get one at hikerapi.com. |
aiograpi backend (in development)
| Flag |
Description |
--login USER |
Instagram username. |
--password PASS |
Instagram password. |
--session PATH |
Session-file path. Created on first successful login, reused after. |
Output
| Flag |
Default |
Description |
--dest DIR |
. |
Where files are written. Subdirectories <dest>/<username>/, <dest>/#<tag>/ are created automatically. |
Incremental updates
| Flag |
Description |
--fast-update |
Stop iterating posts at the first one already on disk. Skip already-downloaded resource files (uses .exists() check). |
--latest-stamps FILE |
INI file recording the newest taken_at per profile. Used as a cutoff when --fast-update is set; updated at the end of each run. |
| Flag |
Description |
--comments |
Save a <post>_comments.json sidecar per post, streamed incrementally. |
--stories |
Also download the user's stories (active 24h slot). Lands in <dest>/<username>/stories/. |
--highlights |
Also download the user's saved highlights. Lands in <dest>/<username>/highlights/<id>_<title>/. |
--post-filter EXPR |
Restricted-AST predicate evaluated against each post; posts where the expression is falsy are skipped. See Post filter expressions below. |
--dry-run |
Walk the iteration as if downloading, but skip CDN fetches and sidecar writes. Logs [dry-run] would download → <filename> per resource. Useful for sanity-checking --post-filter expressions. Metadata API calls still happen because filters are evaluated against full Post records. |
--no-progress |
Suppress the per-download progress bar even on a TTY. (Non-TTY destinations like CI logs auto-suppress regardless.) |
Logging
| Flag |
Description |
-v, --verbose |
DEBUG-level logging. Shows HTTP requests, redirect chains, schema-drift warnings. |
-q, --quiet |
Warnings and errors only. Also suppresses the per-download progress bar. Mutually exclusive with -v. |
A summary line is emitted at the end of every profile / hashtag run:
profile instagram: downloaded 8 · filtered 12 · already on disk 4
Counts:
- downloaded — posts where at least one resource was newly written (or, with --dry-run, would be).
- filtered — posts excluded by --post-filter.
- already on disk — posts where every resource was already present and --fast-update skipped them.
Help
| Flag |
Description |
-h, --help |
Print usage and exit. |
-V, --version |
Print version and exit. |
Post filter expressions
--post-filter EXPR compiles a Python-like expression once and runs it against each post; a falsy result skips the post. Only a restricted AST is accepted — no attribute access, subscription, calls, or lambdas — so misuse fails fast rather than silently pulling everything.
Names available inside the expression:
| Name |
Type |
Value |
likes |
int |
post.like_count or 0 if unknown |
comments |
int |
post.comment_count or 0 if unknown |
caption |
str |
Post caption, empty string if none |
code |
str |
Shortcode (e.g. DXZlTiKEpxw) |
username |
str |
post.owner_username |
location |
str |
Location name, empty string if none |
taken_at |
datetime |
Post timestamp (use year / month / day for comparisons) |
year, month, day |
int |
Convenience extracts of taken_at |
is_video, is_photo, is_album |
bool |
Media-type flags |
Examples:
insta-dl --post-filter 'likes > 1000 and is_video' instagram
insta-dl --post-filter "'sunset' in caption" '#photography'
insta-dl --post-filter 'year == 2026 and month >= 4' instagram
insta-dl --post-filter "username in ('instagram', 'meta')" '#tech'
Shell completions
insta-dl is compatible with argcomplete. Enable once per shell:
# bash / zsh — one-off
eval "$(register-python-argcomplete insta-dl)"
# bash — persistent
activate-global-python-argcomplete --user
# zsh — add to ~/.zshrc
autoload -U bashcompinit && bashcompinit
eval "$(register-python-argcomplete insta-dl)"
After activation: insta-dl --<TAB> completes flag names.
Exit codes
| Code |
Meaning |
0 |
Success |
130 |
Interrupted with Ctrl-C |
| Other |
Uncaught exception (with traceback) — file an issue |
Environment variables
| Variable |
Used by |
Description |
HIKERAPI_TOKEN |
hiker backend |
Default token source if --hiker-token not given. |
Output layout
<dest>/<username>/
2026-04-21_16-04-15_DXZlTiKEpxw.mp4 # media
2026-04-21_16-04-15_DXZlTiKEpxw.json # metadata sidecar
2026-04-21_16-04-15_DXZlTiKEpxw_comments.json # with --comments
2026-04-21_16-04-15_DXZlTiKEpxw_1.jpg # carousel children get _1, _2, ...
stories/
2026-04-21_18-30-00_178290.mp4 # with --stories
highlights/
17991_Travel/ # with --highlights
2025-10-12_19-20-30_4011.jpg
<dest>/#<tag>/
2026-04-22_10-15-00_ABCDE.jpg # hashtag downloads
File mtime matches the post's taken_at. Path components from the API (username, owner, hashtag, highlight title) are sanitized: path separators replaced, .. and reserved Windows names neutered, bidi/zero-width characters stripped, length capped at 200.
Sidecar JSON shape
{
"pk": "3880296624023575664",
"code": "DXZlTiKEpxw",
"media_type": "video",
"taken_at": "2026-04-21T16:04:15+00:00",
"owner_pk": "25025320",
"owner_username": "instagram",
"caption": "...",
"like_count": 181649,
"comment_count": 3517,
"resources": [
{
"url": "https://scontent.cdninstagram.com/.../...mp4",
"is_video": true,
"width": null,
"height": null
}
],
"location_name": null,
"location_lat": null,
"location_lng": null
}
URLs in the sidecar are stripped of query parameters — signed CDN tokens (oh=, oe=, _nc_sid) are not persisted to disk.