CLI
The official command line interface for the VPNDetection API.
Getting Started
No account is needed to start: without a key a lookup answers ip and is_vpn, up to 1000 a day per source address. A free key adds Tor and relay detection, where the address is and the AS that announces it, and every paid plan answers every field.
macOS
brew trust vpndetection-io/tap
brew tap vpndetection-io/tap
brew install vpndetectionHomebrew won't load a formula from a third-party tap until you trust it. Skip that first line and it fails with invalid syntax in tap!, which is misleading: the formula is fine. brew trust arrived in Homebrew 7, so run brew update first if it comes back as an unknown command.
The CLI needs macOS 13 Ventura or later. 1.2.0 is the last release that runs on macOS 12 Monterey.
Debian / Ubuntu
Install from our apt repository, which keeps the CLI up to date with apt upgrade:
echo "deb [trusted=yes] https://apt.vpndetection.io/ /" | sudo tee /etc/apt/sources.list.d/vpndetection.list
sudo apt update && sudo apt install vpndetectionOr install a single .deb without the repository:
curl -Ls https://github.com/vpndetection-io/cli/releases/latest/download/deb.sh | shWindows
choco install vpndetectionOr install for the current user, which needs no admin rights:
iwr -useb https://github.com/vpndetection-io/cli/releases/latest/download/windows.ps1 | iexDocker
docker run --rm ghcr.io/vpndetection-io/cli 146.70.116.130Using go install
go install github.com/vpndetection-io/cli@latestThe binary is named after the module, so rename it to vpndetection if you want the examples below to read the same.
Using curl / wget
Binaries are published for 23 platform and architecture pairs on the releases page. Pick yours:
# Linux amd64; for Windows use ".zip" instead of ".tar.gz"
curl -LO https://github.com/vpndetection-io/cli/releases/download/v1.11.1/vpndetection_1.11.1_linux_amd64.tar.gz
tar -xzf vpndetection_1.11.1_linux_amd64.tar.gz
sudo mv vpndetection /usr/local/bin/macOS has a one-line installer that picks the right architecture for you:
curl -Ls https://github.com/vpndetection-io/cli/releases/latest/download/macos.sh | shNote that the binaries are not code-signed, so macOS Gatekeeper will ask before running one the first time, and Windows SmartScreen may warn.
From source
git clone https://github.com/vpndetection-io/cli
cd cli
./scripts/build.shThe result lands in build/. Go 1.26 or newer.
Quick Start
Default help message
Invoking the CLI with nothing shows what it can do:
$ vpndetection
Usage: vpndetection <ip | cidr | range | file>... [<opts>]
vpndetection <cmd> [<opts>] [<args>]
...Login
You can use the CLI without an account, but a key widens the answer and raises your allowance. vpndetection login opens your browser, you confirm a short code, and you pick which of your API keys this machine should hold:
$ vpndetection login
opened your browser to finish signing in.
https://app.vpndetection.io/device
and confirm this code: MA8A-T2WQ
waiting for you to approve...
stored key mk_9************************************Etjx in session "default", now activeNothing is typed or pasted here, so the key never reaches your shell history. If no browser opens, the URL is printed and you can open it anywhere, including on your phone - which is what makes this work over SSH.
vpndetection signup is the same thing with the sign-up page first, so a new account and a working CLI are one step.
For a CI job, set VPNDETECTION_API_KEY in the job's environment from your CI's secret store instead. Every command reads it, so nothing is stored on the runner, and the key never sits on a command line, where every account on the machine can read it from the process list:
$ export VPNDETECTION_API_KEY=...
$ vpndetection 146.70.116.130vpndetection login --paste prompts for one without opening a browser, and vpndetection logout signs this machine out, which ends the authorization rather than only deleting the local file.
My IP
$ vpndetection myip
ip 146.70.116.130
is_bogon false
is_vpn trueThis is the address our edge observed, so behind a proxy or a VPN it reports the exit you left through.
Any IP
$ vpndetection 146.70.116.130
ip 146.70.116.130
is_bogon false
is_vpn true
is_hosting true
is_relay false
is_tor false
is_cdn false
vpn.provider mullvad
vpn.last_seen 2026-09-12
hosting.provider m247
hosting.confidence highPrivate, reserved and documentation addresses are answered locally without a request, so they cost nothing and work offline.
Piping
Addresses can come from standard input, and from arguments, at the same time:
$ cat addresses.txt | vpndetection
$ vpndetection 1.1.1.1 8.8.8.0/24 9.9.9.1-9.9.9.9 addresses.txtAnything resolving to more than one address prints JSON keyed by address; a single address prints the readable block. That follows the resolved count, so 10.0.0.1/32 is one and 10.0.0.0/30 is four.
Field filter
$ vpndetection 146.70.116.130 -f is_vpn
is_vpn true
$ vpndetection 146.70.116.130 -f is_vpn,vpn.provider
is_vpn true
vpn.provider mullvad
$ vpndetection 146.70.116.130 -f vpn # the whole objectIt applies to every format. JSON, JSON Lines and YAML keep the answer's nesting, and leave out a field the answer does not carry:
$ vpndetection bulk 146.70.116.130 1.1.1.1 --jsonl -f ip,is_vpn,vpn.provider
{"ip":"146.70.116.130","is_vpn":true,"vpn":{"provider":"mullvad"}}
{"ip":"1.1.1.1","is_vpn":false,"vpn":{}}Bulk
bulk always prints the machine format, whatever the count, which is what a script wants:
$ vpndetection bulk 8.8.8.0/24 --csv > answers.csv
$ vpndetection bulk addresses.txt --jsonl | jq -r 'select(.is_vpn) | .ip'Bulk runs stream: nothing is held in memory beyond one batch, so a /8 is a long command rather than an impossible one, and output starts immediately. With no arguments at a terminal it reads addresses you type, one per line, ending at a blank line.
Output formats are --pretty, --json, --jsonl, --csv and --yaml.
Exit status
A lookup exits 1 when any address could not be answered, and 0 when every one was. Whatever went wrong - a key the API refused, a spent allowance, the API out of reach - that address gets its error in the output, in place of its answer, and every other address is answered as usual. A script that can live with gaps adds || true and finds them in the output:
$ vpndetection bulk addresses.txt --jsonl > answers.jsonl || true
$ jq -r 'select(.error) | .ip' answers.jsonlSessions
Credentials are stored in named sessions, so one machine can hold several organizations' keys and switch between them without logging in again:
$ vpndetection login --session work
$ vpndetection login --session acme --base-url https://api.example.com
$ vpndetection session list
NAME KEY API LAST USED
acme mk_4***************mnop https://api.example.com never
* default mk_1***************abcd default 2h ago
work mk_9***************wxyz default never
$ vpndetection session use work
$ vpndetection --session work 146.70.116.130 # one command, without switching--base-url points a session at another deployment of the API; a session without one talks to https://api.vpndetection.io.
A key can also come from --key or the VPNDETECTION_API_KEY environment variable, in that order of precedence. The config file is written 0600 inside a 0700 directory.
Plan and usage
$ vpndetection whoami
key mk_1************************abcd
from session default
api https://api.vpndetection.io
org 85bb51e4-2eb6-4a31-8e4d-02ba8b98fe61
plan max
fields max tier
used 412,908 of 5,000,000
[### ] 8.3%
hard limit none; requests above the quota are billed as overage
resets 2026-10-04T07:00:00Z (in 20d)Also spelled entitlement. Usage counts against the anniversary of your subscription, not the calendar month and not the billing period, and it is the same number a lookup is gated on.
Databases
The same classifications, published as files you host yourself. Access is granted by contract and needs a key carrying the db.download scope.
$ vpndetection db list
ID NAME LICENSE STANDING TERM
vpn_ip_v1 VPN IP standard licensed renews 2027-01-04
$ vpndetection db metadata vpn_ip_v1
$ vpndetection db download vpn_ip_v1
$ vpndetection db download cdn_ip_v1 --format mmdb cdn.mmdb
$ curl -fL "$(vpndetection db url vpn_ip_v1)" -o vpn_ip_v1.csv.gzDownloads are verified against the published sha256 and land through a .part file, so an interrupted transfer never leaves a truncated file that reads as a whole database.
Auto-Completion
Auto-completion is supported for at least the following shells:
bash
zsh
fishIt completes commands, flags, the values flags take, field names, and the session names this machine actually holds.
NOTE: it may work for other shells as well, because the implementation is in Go and is not shell-specific.
Installation
Installing auto-completions is as simple as running one command, which covers bash, zsh and fish:
vpndetection completion installStart a new shell afterwards to pick it up.
If you want to customize the installation (for example when the auto-installation does not work as expected), you can request the completion line for each shell instead:
# get bash completion script
vpndetection completion bash
# get zsh completion script
vpndetection completion zsh
# get fish completion script
vpndetection completion fishvpndetection completion uninstall removes it again.
Shell not listed?
If your shell is not listed here, you can open an issue.
Note that as long as the COMP_LINE environment variable is provided to the binary itself, it will output completion results. So if your shell provides a way to pass COMP_LINE on auto-completion attempts to a binary, then have your shell do that with the vpndetection binary itself.
Data
How much you get back per lookup depends on the plan behind your key. See the plans for what each includes. All examples in this document use a key with everything enabled.
A field your plan does not include is missing from the answer, not false. is_tor absent means we did not check; is_tor: false means we checked and it is not a Tor node. The readable output prints only what you were served; CSV writes an empty cell for absent and the literal false for false. --show-absent renders the lot.
Caching
Answers are cached on disk, so looking the same address up twice costs one request rather than two.
vpndetection cache info
vpndetection cache clear
vpndetection --nocache 146.70.116.130
vpndetection config list
vpndetection config cache=disable cache_ttl=24hEntries are partitioned by credential, because which fields an answer carries depends on the plan behind the key - one key's answers are never served to another's. Clearing empties every partition.
Color Output
Disabling color output
The CLI respects either the --nocolor flag or the NO_COLOR environment variable to disable color output.
Color on Windows
To enable color support for the Windows command prompt, run the following to enable Console Virtual Terminal Sequences:
REG ADD HKCU\CONSOLE /f /v VirtualTerminalLevel /t REG_DWORD /d 1You can disable this by running the following:
REG DELETE HKCU\CONSOLE /f /v VirtualTerminalLevel