tscli is a fast, single-binary CLI for the Tailscale HTTP API.
From your terminal you can manage devices, users, auth keys, webhooks, posture integrations, tailnet-wide settings, and even hit raw endpoints when the SDK hasnβt caught up yet.
Authenticate and run your first command:
export TAILSCALE_API_KEY=tskey-xxx
export TAILSCALE_TAILNET=example.com
tscli list devicesCreate a reusable, preauthorized ephemeral auth key:
tscli create key \
--type authkey \
--description "ci-runner" \
--expiry 24h \
--reusable \
--ephemeral \
--preauthorizedSet up a named profile for multi-tailnet use:
tscli config profiles upsert _lbr_sandbox --api-key tskey-abc123
tscli config profiles set-active _lbr_sandbox
tscli config profiles listExplore available commands:
tscli --help
tscli get --help
tscli create key --help| Area | What you can do |
|---|---|
| Devices | List, get, (de)authorize, rename, force IPv4, enable subnet routes, expire, set / delete posture attributes |
| Keys | List & get existing keys; create auth-keys or OAuth clients (with full scope/tag validation) |
| Users | List (filter by type / role), get, suspend / restore / approve, manage invites |
| Tailnet settings | Get & patch booleans + key-expiry with a single command (tscli set settings β¦) |
| Policy file (ACL) | Fetch as raw HUJSON or canonical JSON |
| Webhooks | List, get, delete, create (generic / Slack) with subscription & provider validation |
| Posture integrations | List, get, create, patch existing integrations |
| Invites | List / delete device- or user-invites |
| Contacts | Get & update contact emails |
| Debug switch | --debug or TSCLI_DEBUG=1 prints full HTTP requests / responses to stderr |
| Config precedence | flags β env β ~/.tscli.yaml (or local ./.tscli.yaml) |
brew tap jaxxstorm/tap
brew install tscli # upgrades via βbrew upgradeβscoop bucket add jaxxstorm https://github.com/jaxxstorm/scoop-bucket.git
scoop install tsclinix shell github:jaxxstorm/tscliPre-built archives for macOS, Linux, Windows (x86-64 / arm64) are published on every release:
# install the newest tscli (Linux/macOS, amd64/arm64)
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case $ARCH in
x86_64) ARCH=amd64 ;;
aarch64|arm64) ARCH=arm64 ;;
esac
curl -sSL "$(curl -sSL \
https://api.github.com/repos/jaxxstorm/tscli/releases/latest \
| grep -oE "https.*tscli_.*_${OS}_${ARCH}\.tar\.gz" \
| head -n1)" \
| sudo tar -xz -C /usr/local/bin tscligo install github.com/jaxxstorm/tscli@latestAfter any method, confirm:
tscli --versionThe primary docs live in the in-repo Docsify site under docs/.
- Site entrypoint:
docs/index.html - Start page:
docs/README.md - Generated command docs:
docs/commands/
Most useful pages:
docs/getting-started.md: install, first command, and flagsdocs/configuration.md: config keys, profiles, and precedencedocs/authentication.md: API-key auth methods and security guidancedocs/command-reference.md: generated command reference and workflow
Docs workflows:
make docs-generate # regenerate docs/commands from Cobra
make docs-check # fail if generated docs are stale/missing
make docs-serve # serve docs locally with docsify| Option | Flag / Env var | YAML key | Default |
|---|---|---|---|
| Tailscale API key | --api-key, -k / TAILSCALE_API_KEY |
api-key |
β |
| Tailnet name | --tailnet, -n / TAILSCALE_TAILNET |
tailnet |
- |
| Active profile | β | active-tailnet |
β |
| Profile list | β | tailnets |
[] |
# ~/.tscli.yaml
output: pretty # other options are: human, json or yaml
active-tailnet: _lbr_sandbox
tailnets:
- name: _lbr_sandbox
api-key: tskey-abc123
- name: example.com
api-key: tskey-def456
# Legacy keys are still supported for backward compatibility
tailnet: example.com
api-key: tskey-def456Profile management commands:
tscli config profiles list
tscli config profiles upsert _lbr_sandbox --api-key tskey-abc123
tscli config profiles set-active _lbr_sandbox
tscli config profiles delete example.comtscli <noun> <verb> [flags]
Canonical verb taxonomy:
createcreates resourcessetupdates or mutates resourcesgetfetches a single resourcelistfetches collectionsdeleteremoves resources
Compatibility aliases remain available for legacy paths such as get dns ns, set dns prefs, set dns splitdns, and get webhook webhook.
# Get a single resource
tscli get device --device node-abc123
# List a collection
tscli list users
# Update settings
tscli set settings --devices-approval=true
# Delete a resource
tscli delete key --key key-id-k, --api-key string Tailscale API key
-n, --tailnet string Tailnet (default "-")
-d, --debug Dump raw HTTP traffic to stderr
| API Area / Action | Status | tscli Command |
|---|---|---|
| Devices | ||
| List tailnet devices | β | tscli list devices |
| Get a device | β | tscli get device --device <device> |
| Delete a device | β | tscli delete device --device <device> |
| Expire a device key | β | tscli set device expiry --device <device> |
| List device routes | β | tscli list routes --device <device> |
| Set device routes | β | tscli set device routes --device <device> --route <cidr> |
| Authorize / de-authorize device | β | tscli set device authorization --device <device> --approve=<bool> |
| Set device name | β | tscli set device name --device <device> --name <hostname> |
| Set device tags | β | tscli set device tags --device <device> --tag tag:<tag> |
| Update a device key | β | tscli set device key --device <device> --disable-expiry|--enable-expiry |
| Set device IPv4 address | β | tscli set device ip --device <device> --ip <ip> |
| Get posture attributes | β | tscli get device posture --device <device> |
| Set custom posture attributes | β | tscli set device posture --device <device> --key custom:x --value <v> |
| Delete custom posture attributes | β | tscli delete device posture --device <device> --key custom:x |
| Batch update posture attributes | β | tscli set device attributes --file <payload.json> |
| Policy File | ||
| Get policy file | β | tscli get policy [--json] |
| Set policy file | β | tscli set policy --file <acl.hujson> |
| Preview rule matches | β | tscli get policy preview --type user|ipport --value β¦ [--current|--file F] |
| Validate / test policy | β | tscli get policy validate --file <policy.json> |
| Keys | ||
| List tailnet keys | β | tscli list keys |
| Create auth-key / OAuth client | β | tscli create key --type authkey --oauthclient β¦ |
| Get key | β | tscli get key --key <id> |
| Delete / revoke key | β | tscli delete key --key <key-id> |
| Update key metadata/scopes | β | tscli set key --key <id> --body '{"description":"new"}' |
| Create a token | β | tscli create token --client-id <oauth-client-id> --client-secret <oauth-client-secret> |
| DNS | ||
| List DNS nameservers | β | tscli list nameservers |
| Get DNS configuration | β | tscli get dns configuration |
| Set DNS configuration | β | tscli set dns configuration --body '<json>' |
| Set DNS nameservers | β | tscli set dns nameservers --nameserver <ip> β¦ |
| Get DNS preferences | β | tscli get dns preferences |
| Set DNS preferences | β | tscli set dns preferences --magicdns=<bool> |
| List DNS search paths | β | tscli get dns searchpaths |
| Set DNS search paths | β | tscli set dns searchpaths --searchpath <domain> β¦ |
| Get split-DNS map | β | tscli get dns split-dns |
| Update split-DNS | β | tscli set dns split-dns --entry <d>=<ip> β¦ |
| Set split-DNS | β | tscli set dns split-dns --replace --entry <d>=<ip> |
| Logging | ||
| List configuration audit logs | β | tscli list logs config --start <t> [--end <t>] |
| List network flow logs | β | tscli list logs network --start <t> [--end <t>] |
| Get log-streaming status | β | `tscli get logs stream --type {configuration |
| Get log-streaming configuration | β | `tscli get logs stream --type {configuration |
| Set log-streaming configuration | β | `tscli set logs stream --type {configuration |
| Disable log-streaming | β | `tscli delete logs stream --type {configuration |
| Create or get AWS external id. | β | tscli get logs aws |
| Validate external ID integraton with IAM role trust policy | β | tscli get logs aws validate --external-id <id> --role-arn <arn> |
| Users | ||
| List users | β | tscli list users [--type β¦] [--role β¦] |
| Get a user | β | tscli get user --user <id> |
| Update user role | β | tscli set user role --user <id> --role <role> |
| Approve / suspend / restore user | β | tscli set user access --user <id> --approve|--suspend|--restore |
| Delete a user | β | tscli delete user --user <id> |
| User Invites | ||
| List user invites | β | tscli list invites user [--state β¦] |
| Create user invite | β | tscli create invite user --email <email> [--role <role>] |
| Get a user invite | β | tscli get user invite --id <invite-id> |
| Delete a user invite | β | tscli delete user invite --id <invite-id> |
| Resend user invite | β | tscli set user invite --id <invite-id> --resend |
| Device Invites | ||
| List device invites | β | tscli list invites device --device <device> |
| Create device invite | β | tscli create invite device --device <device> --email <email> |
| Get a device invite | β | tscli get device invite --id <invite-id> |
| Delete a device invite | β | tscli delete device invite --id <invite-id> |
| Resend / accept device invite | β | tscli set device invite --id <invite-id> --status <resend|accept> |
| Posture Integrations | ||
| List integrations | β | tscli list posture-integrations |
| Create integration | β | tscli create posture-integration --provider <p> β¦ |
| Get integration | β | tscli get posture-integration --id <id> |
| Update integration | β | tscli set posture-integration --id <id> β¦ |
| Delete integration | β | tscli delete posture-integration --id <id> |
| Contacts | ||
| Get contacts | β | tscli get contacts |
| Update contact | β | tscli set contacts --contact <id> --email <e@x> |
| Resend verification | β | tscli set contact --type <type> --resend |
| Webhooks | ||
| List webhooks | β | tscli list webhooks |
| Create webhook | β | tscli create webhook --url <endpoint> β¦ |
| Get webhook | β | tscli get webhook --id <id> |
| Update webhook subscriptions | β | tscli set webhook --id <id> --subscription <event> |
| Delete webhook | β | tscli delete webhook --id <id> |
| Test webhook | β | tscli set webhook test --id <id> |
| Rotate webhook secret | β | tscli set webhook --id <id> --rotate |
| Services | ||
| List services | β | tscli list services |
| Get service | β | tscli get service --service <name> |
| List service devices | β | tscli list services devices --service <name> |
| Get service approval | β | tscli get service approval --service <name> --device <id> |
| Update service | β | tscli set service --service <name> --body '<json>' |
| Set service approval | β | tscli set service approval --service <name> --device <id> --approved=true |
| Delete service | β | tscli delete service --service <name> |
| Tailnet Settings | ||
| Get tailnet settings | β | tscli get settings |
| Update tailnet settings | β | tscli set settings --devices-approval β¦ |
# Approve a waiting device
tscli set device authorization --device node-abc123 --approve
# Rotate an auth-key that expires in 30 days
tscli create key --description "CI" --expiry 720h | jq .key
# Create a reusable preauthorized ephemeral auth-key
tscli create key \
--type authkey \
--description "short-lived runner key" \
--expiry 24h \
--reusable \
--ephemeral \
--preauthorized
# Create Slack webhook for device deletions
tscli create webhook \
--url https://hooks.slack.com/services/T000/B000/XXXXX \
--provider slack \
--subscription nodeDeletedtscli delete devices accepts --include and --exclude partial-match filters. Use --include pattern (repeatable) to limit deletions to names containing the pattern, or --exclude pattern to skip matching devices. These flags are mutually exclusive so that a single filter axis controls the selection.
git clone https://github.com/jaxxstorm/tscli
cd tscli
TAILSCALE_API_KEY=tskey-β¦ TAILSCALE_TAILNET=example.com go run ./cmd/tscli list devicesTests & lint:
make test-unit
make test-integration
make testDocumentation workflows:
make docs-generate
make docs-check
make docs-serveGenerate a CLI/OpenAPI coverage-gap report:
make coverage-gaps
# writes:
# coverage/coverage-gaps.json
# coverage/coverage-gaps.mdtscli pins a Tailscale OpenAPI snapshot for deterministic contract checks:
- Schema:
pkg/contract/openapi/tailscale-v2-openapi.yaml - Metadata:
pkg/contract/openapi/snapshot-metadata.yaml - Command mapping:
pkg/contract/openapi/command-operation-map.yaml
Refresh the snapshot (requires network access), then re-run tests and report generation:
curl -sS "https://api.tailscale.com/api/v2?outputOpenapiSchema=true" \
> pkg/contract/openapi/tailscale-v2-openapi.yaml
shasum -a 256 pkg/contract/openapi/tailscale-v2-openapi.yaml
make test
make coverage-gapsMIT β see LICENSE.