Osintgram

How to use Osintgram: every command explained

Osintgram is an interactive shell bound to one target username: you launch it with python3 main.py <target>, then type one command at a time at a "Run a command:" prompt. The in-tool list command prints 23 commands, three more than the README documents, and every one of them is in the table below.

16 min readOsintgram team

Osintgram is not a scanner you point at a username and walk away from. main.py takes one required positional argument, builds an API client, prints a banner, and then loops on a prompt until you type quit. Every command applies to that one target, and nothing happens until you type something.

Launch with python3 main.py <target>, then type commands at the Run a command: prompt. The in-tool list shows 23 commands; the README only documents 20. File output is off by default: type FILE=y or JSON=y first, or launch with -f / -j. The four contact-harvesting commands (fwersemail, fwingsemail, fwersnumber, fwingsnumber) issue one API call per follower with no backoff, and they are the ones that get you blocked.

How the shell works

This guide assumes a working clone and a populated config/credentials.ini; if not, start with how to install Osintgram. On Kali the same applies, with a few distro-specific traps.

Always launch from the repo root

src/config.py reads config/credentials.ini as a relative path, so python3 ~/Osintgram/main.py target from your home directory silently finds no credentials. cd into the clone first. Running it from elsewhere is also the documented cause of issue #105, ModuleNotFoundError: No module named 'src.Osintgram'.

On startup the classic backend prints the target banner. That is Logged as <you>. Target: <target> [<numeric id>], plus [PRIVATE PROFILE] when the account is private and either [FOLLOWING] or [NOT FOLLOWING]. The constructor prints it, which is why it appears before the ASCII logo. The HikerAPI banner is shorter: no login line, no follow state. Then you get the prompt: the literal string Run a command: , in yellow.

TAB completion is wired to the command dictionary through gnureadline on Linux and macOS, or pyreadline on Windows, so partial names complete. Anything the dictionary does not recognize prints Unknown command in red; an empty line just prints a blank line. quit and exit both print Goodbye! and leave, and Ctrl-C is trapped to do the same thing.

Run a command: FILE=y
Run a command: JSON=y
Run a command: info
Run a command: followers
Run a command: target
Run a command: quit
What to type, not a captured session: main.py prints the prompt; the rest is your input. TAB completes command names, but not the FILE= and JSON= toggles.

You are not stuck with the username you launched against. The target command prompts Insert new target username: , resolves the new account and re-prints the banner, with no new login. It has one wart: setTarget() appends the target name to the output directory every time it runs, so after a switch your files land in output/<first target>/<second target>/ rather than a sibling folder.

Three ways to launch it

The README documents three launch modes, and they differ in more than syntax.

# 1. interactive shell
python3 main.py <target username>

# 2. one command, then exit
python3 main.py <target username> --command info

# 3. HikerAPI backend, no Instagram login of your own
HIKERAPI_TOKEN=<hikerapi token> python3 main.py <target username> -c info
The three invocations listed in the README, with a real command substituted.

Single-command mode is not just the shell with one turn taken for you. When -c is set, main.py skips printlogo() and the constructor suppresses the Attempt to login... line, so the output is much quieter, and photos stops asking how many to download and takes everything. The loop also breaks after exactly one iteration, so -c FILE=y toggles the flag and exits without running anything. Use -f and -j instead.

The third mode is the one that matters most in 2026. If config.getHikerToken() returns anything (from the hikerapi_token field of credentials.ini or the HIKERAPI_TOKEN environment variable), main.py instantiates HikerCLI instead of the classic Osintgram class, and no Instagram login happens at all. The startup line becomes Connect to HikerAPI.... Treat it as the path still designed to work rather than a guaranteed one: issue #2664 (2026-06-21) reports that branch mishandling alternate user-response formats, and HikerAPI is a paid third party you hand your targets to. Whether the classic username-and-password path authenticates for you at all is a separate question, covered in does Osintgram still work.

FlagWhat it doesNotes
-c, --commandRun one command and exitSuppresses the logo and the login line; photos stops prompting
-f, --fileWrite .txt output for the sessionIdentical to typing FILE=y
-j, --jsonWrite JSON output for the sessionIdentical to typing JSON=y
-o, --outputHelp text says "where to store photos"Actually replaces the whole output base directory, for every file type
-C, --cookiesClear the cached session before startingSame effect as the cache command: config/settings.json is reset to an empty object
The complete flag set, read from the argparse block in main.py. There are exactly five; there is no --file-output.

Every Osintgram command

The README lists 20 commands. The cmdlist() function that the in-tool list calls prints 23. The three it adds are cache, commentdata and target, and none of them appears in the README's command block. doc/COMMANDS.md is further behind still: its header block drops fwersnumber and fwingsnumber (both have sections lower down), and it documents cache, commentdata and target nowhere at all. If you want the authoritative list, type list in the shell or read the commands dictionary in main.py.

CommandWhat it returnsStatus and notes
addrsGPS locations tagged in the target's postsUsually empty. Counts only posts with lat and lng, then reverse-geocodes each one through Nominatim
cacheWipes the cached session fileLocal only, no network call. Prints Cache Cleared. or Settings.json don't exist.; on HikerAPI it just says Cache is already empty.
captionsCaptions from the target's postsIts JSON export writes to the wrong filename (see below)
commentdataEvery comment on every post, with author id and usernameUndocumented in the README. Its JSON export is malformed
commentsTotal number of comments across the postsWalks the entire feed to count
followersFollower list: id, username, full nameNo throttle handler: a rate limit here surfaces as a raw traceback
followingsAccounts the target followsSame missing handler as followers
fwersemailPublic emails published by the target's followersOne extra API call per follower. The main throttle magnet
fwingsemailPublic emails of accounts the target followsSame one-call-per-user shape
fwersnumberPublic phone numbers of the target's followersSame shape, plus a misspelled JSON filename
fwingsnumberPublic phone numbers of accounts the target followsSame shape
hashtagsHashtags the target usesWhole-feed pass
infoProfile metadata as bracketed labelsOne of the few commands that skips the private-profile guard. Ignores FILE=y: it only ever writes JSON
likesTotal number of likes across the postsWhole-feed pass
mediatypeHow many posts are photos versus videosWhole-feed pass
photodesAlt-text descriptions of the photosDead. It calls the retired ?__a=1 web endpoint; the HikerAPI path replies Instagram has disabled this functionality.
photosDownloads posts as .jpg into the output folderPrompts for a count; takes everything under -c. Enumerates the whole feed before applying the limit
propicDownloads the profile pictureAlso skips the private-profile guard
storiesDownloads currently active stories as .jpg or .mp4KeyError: 'media_count' is reported when the reel payload lacks the key
taggedUsers the target tagged in their own postsNo explicit guard, but it reads the feed, so it still needs feed access
targetSwitches to a new target without restartingUndocumented in the README. Nests the output directory on every switch
wcommentedUsers who commented on the posts, ranked by countWhole feed plus a comment fetch per post, slow on active accounts
wtaggedUsers who tagged the target, ranked by countReads the tagged-in feed; its pagination loop switches to the target feed
Descriptions paraphrase the strings cmdlist() prints. Status is read from main.py, src/Osintgram.py and src/hikercli.py plus the public issue tracker; we did not run the CLI against a live Instagram account.

Four more dictionary entries are not data commands: list and help print that same list, quit and exit leave. Two inputs, FILE=y/n and JSON=y/n, are handled outside the dictionary altogether, which is why TAB completion never offers them.

One caveat applies to the whole table: nothing returns data if the backend cannot authenticate. Every row assumes you got past login, with either a session the classic client accepts or a HikerAPI token. The commands are not what breaks first in 2026.

Reading the info output

info is the command you will run first and the one most likely to mislead you. It calls the private endpoint users/{user_id}/full_detail_info/ and reads content['user_detail']['user'], then prints a fixed sequence of bracketed labels:

  • [ID], [FULL NAME], [BIOGRAPHY]: always printed
  • [FOLLOWED], [FOLLOW]: always printed, and not what they look like
  • [BUSINESS ACCOUNT], then [BUSINESS CATEGORY] only when the account is a business and has not hidden its category
  • [VERIFIED ACCOUNT], then [EMAIL] only when a public email is set
  • [HD PROFILE PIC]: always printed, as a URL
  • [FB PAGE], [WHATSAPP NUMBER], [CITY], [ADDRESS STREET], [CONTACT PHONE NUMBER]: each printed only when the field is populated

The [FOLLOWED] / [FOLLOW] trap

[FOLLOWED] is the follower count and [FOLLOW] is the following count. The labels read like the opposite of what they mean. The JSON export settles it: the same two values are written under the keys edge_followed_by and edge_follow, the old web-API names for followers and following. Take the counts from the JSON export if you are transcribing them.

The HikerAPI backend prints an extra [MEDIA] line with the post count that the classic path never shows, so the same command yields a different field list per backend. And when the underlying call raises, the handler prints Oops... <target> non exist, please enter a valid username. and exits with code 2, a message you should not trust. Issue #1020 shows Instagram returning a non-JSON error body and the tool reporting it as a missing user.

Saving output, and the filenames that collide

Nothing is written to disk unless you ask. Type FILE=y for text output and JSON=y for JSON at any point in the session; the tool confirms with Write to file: enabled and Export to JSON: enabled, and FILE=n / JSON=n turn them back off. Launching with -f or -j sets the same booleans before the first command.

On current master, results land in a per-target subdirectory. On the 1.3 release tag they landed in a flat output/, which is why older guides show a different layout. The .txt file holds the raw PrettyTable ASCII table (file.write(str(t))), not CSV, so do not expect to open it in a spreadsheet.

output/
|-- dont_delete_this_folder.txt
`-- <target>/
    |-- <target>_followers.txt
    |-- <target>_followers.json
    |-- <target>_propic.jpg
    |-- <target>_<photo id>.jpg
    `-- <target>_<story id>.mp4
Output layout on master. The -o flag replaces the "output" base, not just the image path.

The naming is where it gets messy. Three commands write JSON under a filename that does not match the command you typed, one of those overwrites a previous result, and two more break the pattern their own way.

Command with JSON=yFile it actually writesProblem
captions<target>_followings.jsonOverwrites the file followings wrote. Copy-paste bug in get_captions
photodes<target>_descriptions.jsonName does not match the command
fwersnumber<target>_fwerssnumber.jsonDouble "s", and the key inside the object is followings_phone_numbers
commentdata<target>_comment_data.jsonHand-written with a trailing comma and no separators between objects, so json.load() fails on it
info<target>_info.json onlyThere is no .txt writer at all; FILE=y does nothing here
Read from the writer blocks in src/Osintgram.py on master.

Do not run captions after followings in the same session

With JSON=y enabled, captions writes to <target>_followings.json. If you dumped the following list first, running captions replaces it with caption data and there is no prompt, no backup and no warning. Export captions in a separate session, or rename the followings dump first.

The commands that get you throttled

The four contact-harvesting commands share one architecture. get_fwersemail() first paginates the entire follower list, printing a running Catched N followers email counter. Only then does it ask Do you want to get all emails? y/n: . Then, for every follower it collected, it issues a separate user_info call and keeps the record only if a public email is set.

Two consequences follow. Your limit is applied after the full enumeration, so answering "n" and asking for 200 emails still walks the whole follower list first. And the limit only counts matches, so a target whose followers rarely publish an email gets enumerated almost end to end regardless. There is no sleep, no jitter and no backoff in those loops; issue #657 is a community patch that adds one.

Asking for fewer results does not make it cheaper

The follower list is enumerated in full before the prompt appears, and one profile call is issued per follower afterwards. On the classic backend that is request volume Instagram counts against you; on HikerAPI the same loop calls user_by_id_v2 once per user, so it is request volume you pay for. Neither backend gives you a cheap sample of a large follower list.

What a block looks like depends on the command. The four contact commands catch ClientThrottledError and print Error: Instagram blocked the requests. Please wait a few minutes before you try again. followers and followings have no such handler, so the same condition escapes as a raw traceback ending in urllib.error.HTTPError: HTTP Error 429: Too Many Requests, the shape reported in issue #394.

The handler does not save your data either. In issue #366 a user reached Catched 41643 followers email before the block, and the recovery path then crashed with TypeError: string indices must be integers, discarding every collected record. Issue #342 reports being cut off around 35,000 followers on a six-figure target. Those are the only hard numbers that exist: no reliable wall-clock threshold has been published, and any guide that gives you one in minutes is guessing.

photos, comments, likes, mediatype, hashtags, wcommented and commentdata all paginate the entire feed, and the last two fetch comments per post on top of that. They rarely trigger a block, but on an account with thousands of posts they are much slower than they look.

Commands that return nothing

A red Sorry! No results found :-( is not an error. It means the request succeeded and the target has no data of that kind. Most commands can emit it.

addrs is the usual suspect. It only counts posts where the location object carries both a latitude and a longitude, and geotagged posts have been rare since Instagram retired the photo map. Whatever does exist is then reverse-geocoded through Nominatim, which is itself rate-limited.

photodes is a different case: it is not empty, it is dead. It still fetches https://www.instagram.com/<target>/?__a=1 and reaches into graphql.user.edge_owner_to_timeline_media, an unauthenticated endpoint that no longer returns that JSON. On the HikerAPI backend the function has been reduced to a single line that prints Instagram has disabled this functionality. Treat it as removed.

stories returns nothing when there is nothing live, which is normal, but it also has a fragile parser: issue #1258 shows it aborting with KeyError: 'media_count' when the reel payload does not include that key.

Private targets, briefly

If the banner shows [PRIVATE PROFILE] alongside [NOT FOLLOWING], almost every command in the table stops before it makes a request. A single guard checks whether the account is private and unfollowed, prints Impossible to execute command: user has private profile, and offers Do you want send a follow request? [Y/N]: . The HikerAPI backend is blunter still: private alone blocks, with no follow-request prompt. No flag changes that. The full picture (which commands survive and what Instagram still exposes publicly) is in does Osintgram work on private accounts.

Doing the same job without the CLI

The command set is genuinely broad. What makes Osintgram awkward is everything around it: one target per session, a credentials file, throttling with no backoff, filenames that overwrite each other, and a login path that fails for many people before any of this matters. If you are running the CLI for real research, keep sessions short and export one dataset at a time.

If you only want the report, a hosted lookup skips the whole apparatus: there is no repo to clone, no Instagram account of your own to risk, and no rate-limit loop to babysit. It covers public profiles only, the same ceiling the CLI has.

Frequently asked questions

Osintgram is an independent OSINT tool and is not affiliated with Instagram or Meta. These guides describe publicly documented open-source software and public-data research only. Use OSINT techniques lawfully, on subjects you are authorized to investigate, and never to harass or surveil private individuals.