query-ts
A command-line tool for querying the Tailscale API. List and filter devices, users, groups, services, and ACLs using a small, glob-friendly query language, and render the results as a table, JSON, YAML, or plain text.
Features
- Query devices (default), users, groups, services, and ACLs.
- A simple filter DSL with glob matching, tag matching,
AND/OR, parentheses, and negation. - Human-friendly coloured tables by default, plus
--json,--yaml, and--plainoutput for scripting. - Authenticates with either a Tailscale API key or OAuth2 client credentials (tokens fetched and cached automatically).
Installation
Install with Homebrew:
brew install 1stvamp/tap/query-ts
Or with snap:
sudo snap install query-ts
Or from PyPI with your tool of choice:
pip install query-ts
# or, to install as an isolated tool:
uv tool install query-ts
# or:
pipx install query-ts
This installs a query-ts command and requires Python 3.11+.
To run from a checkout instead (the project is managed with uv):
uv run query-ts --help
Authentication
Provide either an API key or OAuth2 client credentials, not both. One of the two is required; the CLI exits with an error if no credentials are given.
API key
export TAILSCALE_API_KEY="tskey-api-..."
# or pass it inline with -k/--api-key
OAuth2 client credentials
Both the ID and secret are required together:
export TAILSCALE_OAUTH_CLIENT_ID="..."
export TAILSCALE_OAUTH_CLIENT_SECRET="..."
# or pass --oauth-client-id and --oauth-client-secret
An access token is obtained via the client_credentials grant and cached for
its lifetime, refreshing automatically before it expires.
Tailnet
By default the authenticated user's own tailnet (-) is used. Override it with
-n/--tailnet or the TAILSCALE_TAILNET environment variable.
Usage
query-ts [QUERY] [OPTIONS]
QUERY is an optional filter expression. Without it, all resources of the
selected type are listed.
# All devices, as a table
query-ts
# Devices whose name starts with "web-"
query-ts 'web-*'
# Production-tagged devices that aren't deprecated
query-ts 'env:production -tag:deprecated'
# Users matching a pattern
query-ts --users 'admin*'
# JSON output for two name patterns
query-ts --json 'web-* OR api-*'
# Comma-separated device names (implies plain output)
query-ts --comma 'env:prod'
# List ACL groups
query-ts --type=groups
Query language
Patterns are glob-style (fnmatch)
and matched case-insensitively against resource names.
Name patterns
| Pattern | Matches |
|---|---|
web-* |
names starting with web- |
*.example.com |
FQDNs ending in .example.com |
Tag patterns
A word containing a colon is treated as a tag filter. The prefix:suffix form
expands to the tag prefix-suffix:
| Pattern | Matches tag |
|---|---|
tag:env-prod |
env-prod |
env:prod |
env-prod (same as above) |
env:prod* |
env-prod, env-production, etc. (glob on the suffix) |
Logical operators
| Expression | Meaning |
|---|---|
web AND env:prod |
both conditions (AND is the default) |
web env:prod |
implicit AND (same as above) |
web OR env:prod |
either condition |
(web OR api) env:prod |
parentheses for precedence |
AND and OR are case-insensitive.
Negation
A leading - negates a term:
query-ts '-web-*' # exclude names matching web-*
query-ts '-tag:env-prod' # exclude tagged resources
A query that begins with - (such as a negation) would otherwise be mistaken
for a command-line option. Use -- to stop option parsing and pass it as the
query:
query-ts -- -web-* # exclude names matching web-*
Options
Resource type
| Flag | Description |
|---|---|
--devices |
Query devices (default) |
--users |
Query users |
--groups |
Query ACL groups |
--services |
Query Tailscale Services |
--acl |
Query ACL rules |
-t, --type TYPE |
Resource type: devices, users, groups, services, acl |
Output format
| Flag | Description |
|---|---|
-j, --json |
Output as JSON |
-y, --yaml |
Output as YAML |
-p, --plain |
Output as plain text (newline-separated names) |
-f, --format FORMAT |
Output format: table, json, yaml, plain |
The default format is a coloured table.
Plain-text output
| Flag | Description |
|---|---|
-s, --separator SEP |
Field separator for plain output (default: newline) |
-c, --comma |
Use a comma separator; selects plain output unless a format is set explicitly |
--field FIELD |
Field to extract in plain output (e.g. name, id) |
These options only affect plain output. When --field is omitted, a default
is chosen per resource type (hostname for devices, login for users).
Connection & authentication
| Flag | Description |
|---|---|
-k, --api-key KEY |
Tailscale API key (env: TAILSCALE_API_KEY) |
--oauth-client-id ID |
OAuth2 client ID (env: TAILSCALE_OAUTH_CLIENT_ID) |
--oauth-client-secret SECRET |
OAuth2 client secret (env: TAILSCALE_OAUTH_CLIENT_SECRET) |
-n, --tailnet NET |
Tailnet name, or - for the authenticated user's tailnet (env: TAILSCALE_TAILNET) |
Display
| Flag | Description |
|---|---|
--color / --no-color |
Force or disable colour output (auto-detected by default) |
--show COLS |
Comma-separated table columns to always show in full (e.g. --show last-seen,tags) |
--online-window MINUTES |
Minutes since last seen to still count a device as online (default: 5; env: QUERY_TS_ONLINE_WINDOW) |
--version |
Show the version and exit |
-h, --help |
Show help and exit |
Responsive tables
The default table output adapts to your terminal width. When space is tight it
degrades gracefully: list columns (IP addresses, tags, members) wrap onto
multiple lines first, then the lowest-priority columns are dropped, then long
values are shortened (emails lose their domain, timestamps drop the time).
Hostname and Tags are never dropped.
For devices, Last Seen is hidden by default and the online state is shown as a
coloured dot in the Online column. Use --show last-seen to add the timestamp
column, or name any columns you always want kept in full, e.g.
--show ip,tags,last-seen. Columns passed to --show are exempt from the
automatic dropping and shortening.
A device counts as online if it holds a live connection to the Tailscale control
server, or if it was last seen within the --online-window (default 5 minutes).
The Tailscale API has no plain online flag, and idle-but-reachable devices (phones
in particular) drop the control connection, so widen the window if such devices
read as offline, e.g. --online-window 15.
Development
# Install dependencies (including dev tools)
uv sync
# Run the test suite
uv run pytest tests/ -v
Tests use respx to mock the Tailscale
HTTP API, so no real credentials or network access are required.
License
Metadata
Release files for query-ts 1.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| query_ts-1.1.2.tar.gz | 57.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| query_ts-1.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 77.9 kB
Release files / query_ts-1.1.2.tar.gz
| Download URL | query_ts-1.1.2.tar.gz |
|---|---|
| Size | 57.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a3ab96a68c7b991730d0d886ebe959df52b631096d84dc5ac422975000019937
|
|
BLAKE2b-256 checksum How to use checksums |
e5b41c1bc224de639fc743c3a1c695db7a1421957d1b0dc0f85e8a8b5b4a7187
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 19, 2026.
Transparency logRelease files / query_ts-1.1.2-py3-none-any.whl
| Download URL | query_ts-1.1.2-py3-none-any.whl |
|---|---|
| Size | 20.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ac3431bf78c8e3f8941088d9eabc61708d400766e77e882c8b9a267641b816f5
|
|
BLAKE2b-256 checksum How to use checksums |
520ab25748af160b0280f57e5ee0a3cd4d14e3890303351fbd1e7ba81df62b04
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 19, 2026.
Transparency log