Pure-Python client library for Mikrotik RouterOS API
Project description
routeros-py
A pure-Python client library for Mikrotik RouterOS devices using the RouterOS API binary protocol.
Python port of the Go library github.com/go-routeros/routeros/v3.
Features
- Python type casting — parse RouterOS string values to
int,bool,float,timedelta, and more - Key/value dictionary — every sentence exposes a
.mapdict for fast field access - Source address / port binding — bind the client socket to a specific local interface
- TLS/SSL encryption — connect via
ssl.SSLContext(port 8729) - Logging support — integrates with Python's standard
loggingmodule - Synchronous mode — simple blocking
run()calls - Asynchronous mode — tagged concurrent requests via a background thread
- Listener / streaming — subscribe to real-time device events via a
Queue - Context manager — use
with dial(...) as c:for automatic cleanup - RouterOS version detection — auto-detects pre-6.43 (MD5) vs post-6.43 (cleartext) auth
- API schema discovery — walk the full RouterOS API tree and save it as a structured JSON schema
Installation
# With uv (recommended)
uv add routeros-py
# With pip
pip install routeros-py
Requirements: Python ≥ 3.10
Quick Start
import routeros
# Connect and run a command
with routeros.dial("192.168.1.1:8728", "admin", "") as c:
reply = c.run("/ip/address/print")
for sentence in reply.re:
print(sentence.map["address"], "→", sentence.map["interface"])
Usage Examples
Synchronous (default)
import routeros
with routeros.dial("192.168.1.1:8728", "admin", "secret") as c:
# Print all IP addresses
reply = c.run("/ip/address/print")
for s in reply.re:
print(s.map)
# Filter by interface
reply = c.run("/ip/address/print", "?interface=ether1")
for s in reply.re:
print(s.map["address"])
Asynchronous (concurrent requests)
import threading
import routeros
with routeros.dial("192.168.1.1:8728", "admin", "") as c:
c.async_start()
results = []
def fetch(cmd):
r = c.run(cmd)
results.append(r)
t1 = threading.Thread(target=fetch, args=("/ip/address/print",))
t2 = threading.Thread(target=fetch, args=("/interface/print",))
t1.start(); t2.start()
t1.join(); t2.join()
for r in results:
for s in r.re:
print(s.map)
Listener (streaming events)
import routeros
with routeros.dial("192.168.1.1:8728", "admin", "") as c:
listener = c.listen("/ip/firewall/address-list/listen")
for sentence in listener: # blocks until stream ends
print("Event:", sentence.map)
# Or cancel after a timeout:
# listener.cancel()
TLS / SSL
import ssl
import routeros
ctx = ssl.create_default_context()
ctx.load_verify_locations("ca.pem") # or ctx.check_hostname = False
with routeros.dial_tls("192.168.1.1:8729", "admin", "", tls_context=ctx) as c:
reply = c.run("/system/identity/print")
print(reply.re[0].map["name"])
Source Address / Port Binding
import routeros
with routeros.dial(
"192.168.1.1:8728", "admin", "",
source_address=("10.0.0.5", 0) # host, port (0 = ephemeral)
) as c:
...
Type Casting
import routeros
from routeros import cast, cast_optional, typed_map
from datetime import timedelta
with routeros.dial("192.168.1.1:8728", "admin", "") as c:
reply = c.run("/interface/print")
for s in reply.re:
mtu = cast(s.map["mtu"], int) # → 1500
running = cast(s.map["running"], bool) # → True / False
row = typed_map(s, {
"name": str,
"mtu": int,
"running": bool,
"rx-byte": int,
})
print(row)
API Schema Discovery
Discover the full RouterOS API tree and save it as a structured JSON schema.
The output format mirrors the MikroTik REST-API inspect.json — a nested tree
of _type: "dir", "cmd", and "arg" nodes with parameter descriptions.
import routeros
with routeros.dial("192.168.1.1:8728", "admin", "secret") as c:
schema = routeros.discover_api(c)
print(schema["version"]) # e.g. "7.20.8"
print(schema["endpoint_count"]) # e.g. 541
# Navigate the nested tree
ip_addr = schema["tree"]["ip"]["address"]
print(ip_addr["_type"]) # "dir"
add_cmd = ip_addr["add"]
print(add_cmd["_type"]) # "cmd"
print(add_cmd["address"]) # {"_type": "arg", "desc": "A.B.C.D ..."}
The schema is saved to data/mikrotik/api/routeros_<version>.json automatically
and reloaded from disk on subsequent calls (no re-discovery needed).
# Load a previously saved schema without a live connection
schema = routeros.load_api_schema("7.20.8")
if schema:
print(schema["tree"]["ip"]["firewall"])
Schema JSON structure
{
"version": "7.20.8",
"generated_at": "2026-03-21T14:00:00+00:00",
"endpoint_count": 541,
"tree": {
"ip": {
"_type": "dir",
"address": {
"_type": "dir",
"add": {
"_type": "cmd",
"address": {"_type": "arg", "desc": "A.B.C.D (IP address)"},
"interface": {"_type": "arg", "desc": "string value"},
"disabled": {"_type": "arg"}
},
"print": {
"_type": "cmd",
"brief": {"_type": "arg"},
"detail": {"_type": "arg"},
"count-only": {"_type": "arg"},
"without-paging":{"_type": "arg"}
},
"remove": {
"_type": "cmd",
"numbers": {"_type": "arg", "desc": "see documentation"}
}
}
}
}
}
Discovery strategies
-
REST-API
inspect.json(preferred) — if pre-downloaded MikroTik REST-API files exist indata/mikrotik/rest-api/<version>/, they are loaded and merged (inspect.json+extra/inspect.json) for the richest result. Covers RouterOS 7.9 – 7.22+. -
Binary-API BFS (fallback) — walks the live router's
/console/inspectendpoint with comma-separated path components, queryingrequest=syntaxper command to obtain parameter names and descriptions.
Force re-discovery
# Ignore the cached file and re-discover from the live device
schema = routeros.discover_api(c, force=True)
Custom output directory
from pathlib import Path
schema = routeros.discover_api(c, data_dir=Path("/tmp/routeros-schemas"))
Logging
import logging
import routeros
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger("routeros") # library logger
with routeros.dial("192.168.1.1:8728", "admin", "", logger=log) as c:
c.run("/system/resource/print")
API Reference
See docs/api-reference.md for the full API.
| Symbol | Description |
|---|---|
dial(address, user, password, ...) |
Connect via plain TCP |
dial_tls(address, user, password, ...) |
Connect via TLS/SSL |
Client.run(*words) |
Send command, wait for reply |
Client.run_args(words) |
Same with list argument |
Client.async_start() |
Enable async / concurrent mode |
Client.listen(*words) |
Start streaming listener |
Reply.re |
List of !re sentences |
Reply.done |
Final !done sentence |
ListenReply |
Iterable streaming reply |
Sentence.map |
dict[str, str] of all fields |
cast(value, type) |
Cast a single RouterOS value |
cast_optional(value, type) |
Cast, returning None for "" |
typed_map(sentence, schema) |
Cast all fields from a schema dict |
discover_api(client, ...) |
Walk & cache the full RouterOS API schema |
load_api_schema(version, ...) |
Load a previously saved schema from disk |
Development
Requirements
- Python ≥ 3.10
- uv
Setup
git clone https://github.com/Butterfly-Student/routeros-py
cd routeros-py/routeros
uv sync
Running Tests
# Unit tests only (no device needed)
uv run pytest tests/unit/ -v
# With coverage
uv run pytest tests/unit/ --cov=routeros --cov-report=term-missing
# Integration tests (requires .env with device credentials)
cp .env.example .env
# Edit .env with your RouterOS host/credentials
uv run pytest tests/integration/ -v -m integration
# All tests
uv run pytest
Docker (RouterOS test environment)
cd docker
cp .env.example .env # edit if needed
docker compose up -d
# Wait ~60 s for RouterOS to boot, then run integration tests
uv run pytest tests/integration/ -v
Environment Variables
Copy .env.example to .env and fill in your values:
ROUTEROS_HOST=192.168.1.1
ROUTEROS_PORT=8728
ROUTEROS_USER=admin
ROUTEROS_PASSWORD=
ROUTEROS_TLS_PORT=8729
ROUTEROS_TLS_VERIFY=false
License
MIT — see LICENSE.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file routeros_py-1.1.0.tar.gz.
File metadata
- Download URL: routeros_py-1.1.0.tar.gz
- Upload date:
- Size: 103.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ee16bd297bb073bd48348b87d66df9c3d9e4988717dc5d8b1e1abc5d92cd868
|
|
| MD5 |
7bf2ea127af0c63b171b0f5332f71c70
|
|
| BLAKE2b-256 |
ea10068231fc2f65816756ab8769aeaf35b5625fa76c83ae8ab91e75d29b463e
|
File details
Details for the file routeros_py-1.1.0-py3-none-any.whl.
File metadata
- Download URL: routeros_py-1.1.0-py3-none-any.whl
- Upload date:
- Size: 32.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7bcc070c2b765a032bfe52b6b9828489b0e5ec6aca356b1b100aa085b4edaf1e
|
|
| MD5 |
26923ad2ac5300cd4be841836f1ec324
|
|
| BLAKE2b-256 |
de075019502ba730412faf9c07c223f4a4b60820cfac3f0ec2f54325b516b821
|