Skip to main content

Python package to simplify the Mist System APIs usage

Reason this release was yanked:

websocket async issue

Project description

MISTAPI - Python Package for Mist API

PyPI version Python versions License: MIT

A comprehensive Python package to interact with the Mist Cloud APIs, built from the official Mist OpenAPI specifications.


Table of Contents


Features

Supported Mist Clouds

Support for all Mist cloud instances worldwide:

  • APAC: api.ac5.mist.com, api.gc5.mist.com, api.gc7.mist.com
  • EMEA: api.eu.mist.com, api.gc3.mist.com, api.ac6.mist.com, api.gc6.mist.com
  • Global: api.mist.com, api.gc1.mist.com, api.ac2.mist.com, api.gc2.mist.com, api.gc4.mist.com

Authentication

  • API token and username/password authentication (with 2FA support)
  • Environment variable configuration (.env file support)
  • HashiCorp Vault integration for secure credential storage
  • System keyring integration (macOS Keychain, Windows Credential Locker, etc.)
  • Interactive CLI prompts for credentials when needed

Core Features

  • Complete API Coverage: Auto-generated from OpenAPI specs
  • Automatic Pagination: Built-in support for paginated responses
  • WebSocket Streaming: Real-time event streaming for devices, clients, and location data
  • Device Diagnostics: High-level utilities for ping, traceroute, ARP, BGP, OSPF, and more
  • Error Handling: Detailed error responses and logging
  • Proxy Support: HTTP/HTTPS proxy configuration
  • Log Sanitization: Automatic redaction of sensitive data in logs

Installation

Basic Installation

# Linux/macOS
python3 -m pip install mistapi

# Windows
py -m pip install mistapi

Upgrade to Latest Version

# Linux/macOS
python3 -m pip install --upgrade mistapi

# Windows
py -m pip install --upgrade mistapi

Installation with uv

uv is a fast Python package manager:

# Install in current project
uv add mistapi

# Or run directly without installing
uv run --with mistapi python my_script.py

Development Installation

# With pip
pip install -e ".[dev]"

# With uv
uv sync

Requirements

  • Python 3.10 or higher
  • Dependencies: requests, python-dotenv, tabulate, deprecation, hvac, keyring, websocket-client

Quick Start

import mistapi

# Initialize session
apisession = mistapi.APISession()

# Authenticate (interactive prompt if credentials not configured)
apisession.login()

# Use the API - Get device models
device_models = mistapi.api.v1.const.device_models.listDeviceModels(apisession)
print(f"Found {len(device_models.data)} device models")

# Interactive organization selection
org_id = mistapi.cli.select_org(apisession)[0]

# Get organization information
org_info = mistapi.api.v1.orgs.orgs.getOrg(apisession, org_id)
print(f"Organization: {org_info.data['name']}")

Configuration

Configuration is optional - you can pass all parameters directly to APISession. However, using an .env file simplifies credential management.

Using Environment File

import mistapi
apisession = mistapi.APISession(env_file="~/.mist_env")

Environment Variables

Create a .env file with your credentials:

MIST_HOST=api.mist.com
MIST_APITOKEN=your_api_token_here

# Alternative to API token
# MIST_USER=your_email@example.com
# MIST_PASSWORD=your_password

# Proxy configuration
# HTTPS_PROXY=http://user:password@myproxy.com:3128

# Logging configuration
# CONSOLE_LOG_LEVEL=20  # 0=Disabled, 10=Debug, 20=Info, 30=Warning, 40=Error, 50=Critical
# LOGGING_LOG_LEVEL=10

Configuration Options

Environment Variable APISession Parameter Type Default Description
MIST_HOST host string None Mist Cloud API endpoint (e.g., api.mist.com)
MIST_APITOKEN apitoken string None API Token for authentication (recommended)
MIST_USER email string None Username/email for authentication
MIST_PASSWORD password string None Password for authentication
MIST_KEYRING_SERVICE keyring_service string None System keyring service name
MIST_VAULT_URL vault_url string None HashiCorp Vault URL
MIST_VAULT_PATH vault_path string None Path to secret in Vault
MIST_VAULT_MOUNT_POINT vault_mount_point string None Vault mount point
MIST_VAULT_TOKEN vault_token string None Vault authentication token
CONSOLE_LOG_LEVEL console_log_level int 20 Console log level (0-50)
LOGGING_LOG_LEVEL logging_log_level int 10 File log level (0-50)
HTTPS_PROXY https_proxy string None HTTP/HTTPS proxy URL
env_file str None Path to .env file

Authentication

The login() function must be called to authenticate. The package supports multiple authentication methods.

1. Interactive Authentication

If credentials are not configured, you'll be prompted interactively:

Cloud Selection:

----------------------------- Mist Cloud Selection -----------------------------

0) APAC 01 (host: api.ac5.mist.com)
1) EMEA 01 (host: api.eu.mist.com)
2) Global 01 (host: api.mist.com)
...

Select a Cloud (0 to 10, or q to exit):

Credential Prompt:

--------------------------- Login/Pwd authentication ---------------------------

Login: user@example.com
Password: 
[  INFO   ] Authentication successful!

Two Factor Authentication code required: 123456
[  INFO   ] 2FA authentication succeeded

-------------------------------- Authenticated ---------------------------------
Welcome Thomas Munzer!

2. Environment File Authentication

import mistapi

apisession = mistapi.APISession(env_file="~/.mist_env")
apisession.login()

# Output:
# -------------------------------- Authenticated ---------------------------------
# Welcome Thomas Munzer!

3. HashiCorp Vault Authentication

import mistapi

apisession = mistapi.APISession(
    vault_url="https://vault.mycompany.com:8200",
    vault_path="secret/data/mist/credentials",
    vault_token="s.xxxxxxx"
)
apisession.login()

4. System Keyring Authentication

import mistapi

apisession = mistapi.APISession(keyring_service="my_mist_service")
apisession.login()

Note: The keyring must contain: MIST_HOST, MIST_APITOKEN (or MIST_USER and MIST_PASSWORD)

5. Direct Parameter Authentication

import mistapi

apisession = mistapi.APISession(
    host="api.mist.com",
    apitoken="your_token_here"
)
apisession.login()

API Requests Usage

Basic API Calls

# Get device models (constants)
response = mistapi.api.v1.const.device_models.listDeviceModels(apisession)
print(f"Status: {response.status_code}")
print(f"Data: {len(response.data)} models")

# Get organization information
org_info = mistapi.api.v1.orgs.orgs.getOrg(apisession, org_id)
print(f"Organization: {org_info.data['name']}")

# Get organization statistics
org_stats = mistapi.api.v1.orgs.stats.getOrgStats(apisession, org_id)
print(f"Organization has {org_stats.data['num_sites']} sites")

# Search for devices
devices = mistapi.api.v1.orgs.devices.searchOrgDevices(apisession, org_id, type="ap")
print(f"Found {len(devices.data['results'])} access points")

Error Handling

# Check response status
response = mistapi.api.v1.orgs.orgs.listOrgs(apisession)
if response.status_code == 200:
    print(f"Success: {len(response.data)} organizations")
else:
    print(f"Error {response.status_code}: {response.data}")

# Exception handling
try:
    org_info = mistapi.api.v1.orgs.orgs.getOrg(apisession, "invalid-org-id")
except Exception as e:
    print(f"API Error: {e}")

Log Sanitization

The package automatically sanitizes sensitive data in logs:

import logging
from mistapi.__logger import LogSanitizer

# Configure logging
LOG_FILE = "./app.log"
logging.basicConfig(filename=LOG_FILE, filemode="w")
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)

# Add sanitization filter
LOGGER.addFilter(LogSanitizer())

# Sensitive data is automatically redacted
LOGGER.debug({"user": "john", "apitoken": "secret123", "password": "pass456"})
# Output: {"user": "john", "apitoken": "****", "password": "****"}

Getting Help

# Get detailed help on any API function
help(mistapi.api.v1.orgs.stats.getOrgStats)

CLI Helper Functions

Interactive functions for selecting organizations and sites.

Organization Selection

# Select single organization
org_id = mistapi.cli.select_org(apisession)[0]

# Select multiple organizations
org_ids = mistapi.cli.select_org(apisession, allow_many=True)

Output:

Available organizations:
0) Acme Corp (id: 203d3d02-xxxx-xxxx-xxxx-76896a3330f4)
1) Demo Lab (id: 6374a757-xxxx-xxxx-xxxx-361e45b2d4ac)

Select an Org (0 to 1, or q to exit): 0

Site Selection

# Select site within an organization
site_id = mistapi.cli.select_site(apisession, org_id=org_id)[0]

Output:

Available sites:
0) Headquarters (id: f5fcbee5-xxxx-xxxx-xxxx-1619ede87879)
1) Branch Office (id: a8b2c3d4-xxxx-xxxx-xxxx-987654321abc)

Select a Site (0 to 1, or q to exit): 0

Pagination Support

Get Next Page

# Get first page
response = mistapi.api.v1.orgs.clients.searchOrgClientsEvents(
    apisession, org_id, duration="1d"
)
print(f"First page: {len(response.data['results'])} results")

# Get next page
if response.next:
    response_2 = mistapi.get_next(apisession, response)
    print(f"Second page: {len(response_2.data['results'])} results")

Get All Pages Automatically

# Get all pages with a single call
response = mistapi.api.v1.orgs.clients.searchOrgClientsEvents(
    apisession, org_id, duration="1d"
)
print(f"First page: {len(response.data['results'])} results")

# Retrieve all remaining pages
all_data = mistapi.get_all(apisession, response)
print(f"Total results across all pages: {len(all_data)}")

Examples

Comprehensive examples are available in the Mist Library repository.

Device Management

# List all devices in an organization
devices = mistapi.api.v1.orgs.devices.listOrgDevices(apisession, org_id)

# Get specific device details
device = mistapi.api.v1.orgs.devices.getOrgDevice(
    apisession, org_id, device_id
)

# Update device configuration
update_data = {"name": "New Device Name"}
result = mistapi.api.v1.orgs.devices.updateOrgDevice(
    apisession, device.org_id, device.id, body=update_data
)

Site Management

# Create a new site
site_data = {
    "name": "New Branch Office",
    "country_code": "US",
    "timezone": "America/New_York"
}
new_site = mistapi.api.v1.orgs.sites.createOrgSite(
    apisession, org_id, body=site_data
)

# Get site statistics
site_stats = mistapi.api.v1.sites.stats.getSiteStats(apisession, new_site.id)

Client Analytics

# Search for wireless clients
clients = mistapi.api.v1.orgs.clients.searchOrgWirelessClients(
    apisession, org_id, 
    duration="1d",
    limit=100
)

# Get client events
events = mistapi.api.v1.orgs.clients.searchOrgClientsEvents(
    apisession, org_id,
    duration="1h",
    client_mac="aabbccddeeff"
)

WebSocket Streaming

The package provides a WebSocket client for real-time event streaming from the Mist API (wss://{host}/api-ws/v1/stream). Authentication is handled automatically using the same session credentials (API token or login/password).

Connection Parameters

All channel classes accept the following optional keyword arguments to control the WebSocket keep-alive behaviour:

Parameter Type Default Description
ping_interval int 30 Seconds between automatic ping frames. Set to 0 to disable pings.
ping_timeout int 10 Seconds to wait for a pong response before treating the connection as dead.
ws = mistapi.websockets.sites.DeviceStatsEvents(
    apisession,
    site_ids=["<site_id>"],
    ping_interval=60,   # ping every 60 s
    ping_timeout=20,    # wait up to 20 s for pong
)
ws.connect()

Callbacks

Method Signature Description
ws.on_open(cb) cb() Called when the connection is established
ws.on_message(cb) cb(data: dict) Called for every incoming message
ws.on_error(cb) cb(error: Exception) Called on WebSocket errors
ws.on_close(cb) cb(status_code: int, msg: str) Called when the connection closes
ws.ready() -> bool | None Returns True if the connection is open and ready

Available Channels

Organization Channels

Class Channel Description
mistapi.websockets.orgs.InsightsEvents /orgs/{org_id}/insights/summary Real-time insights events for an organization
mistapi.websockets.orgs.MxEdgesStatsEvents /orgs/{org_id}/stats/mxedges Real-time MX edges stats for an organization
mistapi.websockets.orgs.MxEdgesUpgradesEvents /orgs/{org_id}/mxedges Real-time MX edges upgrades events for an organization

Site Channels

Class Channel Description
mistapi.websockets.sites.ClientsStatsEvents /sites/{site_id}/stats/clients Real-time clients stats for a site
mistapi.websockets.sites.DeviceCmdEvents /sites/{site_id}/devices/{device_id}/cmd Real-time device command events for a site
mistapi.websockets.sites.DeviceStatsEvents /sites/{site_id}/stats/devices Real-time device stats for a site
mistapi.websockets.sites.DeviceUpgradesEvents /sites/{site_id}/devices Real-time device upgrades events for a site
mistapi.websockets.sites.MxEdgesStatsEvents /sites/{site_id}/stats/mxedges Real-time MX edges stats for a site
mistapi.websockets.sites.PcapEvents /sites/{site_id}/pcap Real-time PCAP events for a site

Location Channels

Class Channel Description
mistapi.websockets.location.BleAssetsEvents /sites/{site_id}/stats/maps/{map_id}/assets Real-time BLE assets location events
mistapi.websockets.location.ConnectedClientsEvents /sites/{site_id}/stats/maps/{map_id}/clients Real-time connected clients location events
mistapi.websockets.location.SdkClientsEvents /sites/{site_id}/stats/maps/{map_id}/sdkclients Real-time SDK clients location events
mistapi.websockets.location.UnconnectedClientsEvents /sites/{site_id}/stats/maps/{map_id}/unconnected_clients Real-time unconnected clients location events
mistapi.websockets.location.DiscoveredBleAssetsEvents /sites/{site_id}/stats/maps/{map_id}/discovered_assets Real-time discovered BLE assets location events

Session Channels

Class Channel Description
mistapi.websockets.session.SessionWithUrl Custom URL Connect to a custom WebSocket channel URL

Usage Patterns

Callback style (recommended)

connect() defaults to run_in_background=True and returns immediately. The WebSocket runs in a daemon thread, so your program must stay alive (e.g., with input() or an event loop). Messages are delivered to the registered callback in the background thread.

import mistapi

apisession = mistapi.APISession(env_file="~/.mist_env")
apisession.login()

ws = mistapi.websockets.sites.DeviceStatsEvents(apisession, site_ids=["<site_id>"])
ws.on_message(lambda data: print(data))
ws.connect()                    # non-blocking

input("Press Enter to stop")
ws.disconnect()

Generator style

Iterate over incoming messages as a blocking generator. Useful when you want to process messages sequentially in a loop.

ws = mistapi.websockets.sites.DeviceStatsEvents(apisession, site_ids=["<site_id>"])
ws.connect(run_in_background=True)

for msg in ws.receive():        # blocks, yields each message as a dict
    print(msg)
    if some_condition:
        ws.disconnect()         # stops the generator cleanly

Blocking style

connect(run_in_background=False) blocks the calling thread until the connection closes. Useful for simple scripts.

ws = mistapi.websockets.sites.DeviceStatsEvents(apisession, site_ids=["<site_id>"])
ws.on_message(lambda data: print(data))
ws.connect(run_in_background=False)  # blocks until disconnected

Context manager

disconnect() is called automatically on exit, even if an exception is raised.

import time

with mistapi.websockets.sites.DeviceStatsEvents(apisession, site_ids=["<site_id>"]) as ws:
    ws.on_message(lambda data: print(data))
    ws.connect()
    time.sleep(60)
# ws.disconnect() called automatically here

Device Utilities

mistapi.device_utils provides high-level utilities for running diagnostic commands on Mist-managed devices. Each function triggers a REST API call and streams the results back via WebSocket. The library handles the connection plumbing — you just call the function and get back a UtilResponse object.

Supported Devices

Module Device Type Functions
device_utils.ap Mist Access Points ping, traceroute, retrieveArpTable
device_utils.ex Juniper EX Switches ping, monitorTraffic, retrieveArpTable, retrieveBgpSummary, retrieveDhcpLeases, releaseDhcpLeases, retrieveMacTable, clearMacTable, clearLearnedMac, clearBpduError, clearDot1xSessions, clearHitCount, bouncePort, cableTest
device_utils.srx Juniper SRX Firewalls ping, monitorTraffic, retrieveArpTable, retrieveBgpSummary, retrieveDhcpLeases, releaseDhcpLeases, showDatabase, showNeighbors, showInterfaces, bouncePort, retrieveRoutes
device_utils.ssr Juniper SSR Routers ping, retrieveArpTable, retrieveBgpSummary, retrieveDhcpLeases, releaseDhcpLeases, showDatabase, showNeighbors, showInterfaces, bouncePort, retrieveRoutes, showServicePath

Device Utilities Usage

from mistapi.device_utils import ap, ex

# Ping from an AP
result = ap.ping(apisession, site_id, device_id, host="8.8.8.8")
print(result.ws_data)

# Retrieve ARP table from a switch
result = ex.retrieveArpTable(apisession, site_id, device_id)
print(result.ws_data)

# With real-time callback
def handle(msg):
    print("got:", msg)

result = ex.cableTest(apisession, site_id, device_id, port="ge-0/0/0", on_message=handle)

UtilResponse Object

All device utility functions return a UtilResponse object:

Attribute Type Description
trigger_api_response APIResponse The initial REST API response that triggered the device command. Contains status_code, data, and headers from the trigger request.
ws_required bool True if the command required a WebSocket connection to stream results (most diagnostic commands do). False if the REST response alone was sufficient.
ws_data list[str] Parsed result data extracted from the WebSocket stream. Each entry is a processed output line from the device (e.g., a line of ping output or an ARP table row).
ws_raw_events list[str] Raw, unprocessed WebSocket event payloads as received from the Mist API. Useful for debugging or custom parsing.

Enums

  • ap.TracerouteProtocolICMP, UDP (for ap.traceroute())
  • srx.Node / ssr.NodeNODE0, NODE1 (for dual-node devices)

Development and Testing

Development Setup

# Clone the repository
git clone https://github.com/tmunzer/mistapi_python.git
cd mistapi_python

# With pip
pip install -e ".[dev]"

# With uv
uv sync

Running Tests

# Run all tests
pytest
# or with uv
uv run pytest

# Run with coverage report
pytest --cov=src/mistapi --cov-report=html

# Run specific test file
pytest tests/unit/test_api_session.py

# Run linting
ruff check src/
# or with uv
uv run ruff check src/

Package Structure

src/mistapi/
├── __init__.py           # Main package exports (lazy-loads api, cli, utils, websockets)
├── __api_session.py      # Session management and authentication
├── __api_request.py      # HTTP request handling
├── __api_response.py     # Response parsing and pagination
├── __logger.py           # Logging and sanitization
├── __pagination.py       # Pagination utilities
├── cli.py                # Interactive CLI functions
├── __models/             # Data models
│   ├── __init__.py
│   └── privilege.py
├── api/v1/               # Auto-generated API endpoints
│   ├── const/            # Constants and enums
│   ├── orgs/             # Organization-level APIs
│   ├── sites/            # Site-level APIs
│   ├── login/            # Authentication APIs
│   └── utils/            # Utility functions
├── device_utils/         # Device utility implementations
│   ├── ap.py             # Access Point utilities
│   ├── ex.py             # EX Switch utilities
│   ├── srx.py            # SRX Firewall utilities
│   ├── ssr.py            # Session Smart Router utilities
│   └── ...               # Function-based modules (arp, bgp, dhcp, etc.)
└── websockets/           # Real-time WebSocket streaming
    ├── __ws_client.py    # Base WebSocket client
    ├── orgs.py           # Organization-level channels
    ├── sites.py          # Site-level channels
    ├── location.py       # Location/map channels
    └── session.py        # Custom URL session channel

Contributing

Contributions are welcome! Please follow these guidelines:

How to Contribute

  1. Fork the repository
  2. Create a feature branch
    git checkout -b feature/amazing-feature
    
  3. Commit your changes
    git commit -m 'Add amazing feature'
    
  4. Push to the branch
    git push origin feature/amazing-feature
    
  5. Open a Pull Request

Development Guidelines

  • Write tests for new features
  • Ensure all tests pass before submitting PR
  • Follow existing code style and conventions
  • Update documentation as needed
  • Add entries to CHANGELOG.md for significant changes

License

MIT License

Copyright (c) 2023 Thomas Munzer

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Links

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mistapi-0.61.0.tar.gz (5.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mistapi-0.61.0-py3-none-any.whl (317.5 kB view details)

Uploaded Python 3

File details

Details for the file mistapi-0.61.0.tar.gz.

File metadata

  • Download URL: mistapi-0.61.0.tar.gz
  • Upload date:
  • Size: 5.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for mistapi-0.61.0.tar.gz
Algorithm Hash digest
SHA256 e952c9d617963d1fc8f7ff859cda7e855fb29569baf5698fb63718b270700445
MD5 be9d65632e94231593545ecbe279d3bf
BLAKE2b-256 4ed8ce59df6ea67fc28972cdcc9cc768fa8a8253b64b0aa8575ada5622afc27e

See more details on using hashes here.

File details

Details for the file mistapi-0.61.0-py3-none-any.whl.

File metadata

  • Download URL: mistapi-0.61.0-py3-none-any.whl
  • Upload date:
  • Size: 317.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for mistapi-0.61.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b3e84fcfd886230b1857665ba256aead781fb66fbf562cb19a3cdc2ddf55b0ae
MD5 326bfefa7389bac2bc4a761ab2388cbe
BLAKE2b-256 49cf3041f90a660d02aacd2c8c6db2d365d9540194ff980486ae69447cb09274

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page