“SwOS has no API; it can only be managed from the browser.” That is what the official documentation says, and it is true… half the time. It applies to MikroTik smart switches running SwOS — the CRS3xx series and similar. Because when you open the web interface of a MikroTik switch from the CRS3xx series running SwOS, that page is not generated by the switch: it is static JavaScript that, behind the scenes, reads and writes a handful of files on the device via HTTP. Those files are the API. Undocumented, a bit rough, but it is there — and with it, you can configure VLANs, read counters, and automate backups, all from the command line.

In this article, we open that channel manually, decode the format byte by byte, configure a VLAN via POST, and compare the raw API with what the same switch exposes via SNMP. At the end, we release dojo.swos, a small Python library that encapsulates everything.

The “b-files”: the API you did not know you had

The SwOS web UI communicates with the switch using files with the .b extension, served over HTTP with Digest authentication (no HTTPS). A GET reads, a POST writes. The first one to look at is sys.b:

$ curl --digest -u admin: http://192.0.2.10/sys.b
{upt:0x0000d32c,cip:0x3c000d0a,mac:'e48d8c000001',brd:'…',
 sid:'…',id:'53574f53',ver:'322e3136',temp:0x00000027,
 fan1:0x00000b6d,allp:0x03ff,ip:0x0158090a, … }

It looks like JSON but is not. The encoding rules are few, and once you understand them, everything opens up:

Element How it is written Example Meaning
Number hexadecimal 0x.., even length 0x00000027 39 (°C)
String ASCII hex pairs, single quotes '53574f53' SWOS
Port bitmask a number, bit 0 = port 1 0x03ff ports 1–10
Array square brackets [0x02,0x07] [2, 7]
IP address integer little-endian 0x0158090a 10.9.88.1

Let us apply them to sys.b: id:'53574f53' is the hexadecimal string for “SWOS” (the identity); ver:'322e3136' is “2.16”; temp:0x00000027 is 39 °C; fan1:0x00000b6d is 2925 RPM for the fan; allp:0x03ff is the bitmask for the ten ports. And the two addresses: cip:0x3c000d0a, read backwards byte by byte, is the IP the switch obtained via DHCP; ip:0x0158090a is the static fallback IP. Little-endian is the trap that catches everyone the first time.

Reading port status

link.b is the link status. Port names arrive as hexadecimal strings, flags as bitmasks:

$ curl --digest -u admin: http://192.0.2.10/link.b
{en:0x03ff, lnk:0x0001, …
 nm:['506f727431','506f727432', … '5346502b31','5346502b32']}

en:0x03ff: all ten ports enabled. lnk:0x0001: only bit 0 is set, so link is active only on port 1 (the only cable connected). And nm is the array of names: 506f727431 → “Port1”, … 5346502b31 → “SFP+1”. No MIB, no library: just curl and the table above.

Configuring a VLAN… via POST

Here SwOS surprises you: the same URL you read in GET you write in POST. The VLAN table is in vlan.b. It is empty by default:

$ curl --digest -u admin: http://192.0.2.10/vlan.b
[]

We create VLAN 10 on ports 2 and 3. Member ports are a bitmask: port 2 = bit 1, port 3 = bit 2 → 0x0006. We post it:

$ curl --digest -u admin: -X POST \
    --data-binary "[{vid:0x000a,mbr:0x0006}]" \
    http://192.0.2.10/vlan.b

$ curl --digest -u admin: http://192.0.2.10/vlan.b
[{nm:'',mbr:0x0006,vid:0x000a,piso:0x00,lrn:0x01,mrr:0x00,igmp:0x00}]

The switch accepted the write and returned the complete entry, revealing all fields: vid (VLAN id), mbr (member ports), nm (name), piso (isolation), lrn (learning), mrr (mirror), igmp (snooping). Two valuable details discovered by testing:

  • SwOS performs a key-based merge: you can post only the fields you change. A POST of {id:'...'} on sys.b changes the identity and leaves everything else intact.
  • The configuration is immediate and live. A wrong VLAN or isolation setting on the port you are administering from will cut you off. Always test on ports without a cable, and remember Actions → Save Config: the write modifies the current configuration, but SwOS does not make it persistent across reboots until you save.

Monitoring: raw counters

!stats.b is a goldmine: for each port, arrays of received/transmitted bytes and packets, unicast/multicast/broadcast, even the histogram by frame size (p64, p128, …) and frames with incorrect FCS. All in hexadecimal, all per-port. It is the raw material for a homemade traffic graph, without depending on anything other than curl.

The other side: SNMP

The same switch, if you enable SNMP (which is also done with a POST to snmp.b), exposes its data in a standard and readable format:

$ snmpwalk -v2c -c public 192.0.2.10 1.3.6.1.2.1.1
sysDescr.0     = STRING: CRS3xx SwOS v2.16
sysName.0      = STRING: SWOS
sysObjectID.0  = enterprises.14988   (MikroTik)

$ snmpwalk -v2c -c public 192.0.2.10 ifOperStatus
ifOperStatus.1 = up(1)
ifOperStatus.2 = down(2)   …

These are the same facts as in the b-files, but dressed in MIBs: sysDescr contains the model and version (which in b-files were brd+ver in hexadecimal), ifOperStatus.1 = up is bit 0 of lnk:0x0001, ifInOctets are the same bytes as rb in !stats.b. Two sides, one single switch:

b-file (hidden API) SNMP
Read yes (raw, hexadecimal) yes (standard, readable)
Write/config yes (VLAN, ports, SNMP…) no (read-only)
Coverage everything the GUI sees (temp, fans, PoE…) system, interfaces, bridge
Tools curl + a parser any NMS (Prometheus, LibreNMS, ntopng)
Standard no, reverse-engineering yes (RFC)

But be careful about what SNMP does not give you, verified manually on a real switch. The SwOS SNMP stack implements the basic BRIDGE-MIB (MAC table, port map) but not the Q-BRIDGE-MIB for VLANs (1.3.6.1.2.1.17.7 → «No Such Object»): even if you create a VLAN via b-file, it remains invisible in SNMP. There is not even the POWER-ETHERNET-MIB (1.3.6.1.2.1.105): PoE and power consumption are not exposed via standard SNMP. What is present, in the MikroTik enterprise MIB, is the temperature (…14988.1.1.3.11.0, in tenths of a degree) and DDM monitoring for SFPs. Moral: VLANs, PoE, fans, and PSU voltages are read (and written) only from b-files; SNMP covers system, interfaces, traffic, MAC, and temperature.

A map of ready-to-use OIDs

If you want to hook the switch up to Prometheus, LibreNMS, or a simple snmpget in cron, these are the branches that SwOS actually exposes (default community public, SNMP v2c):

Item OID Example
Description (model+version) 1.3.6.1.2.1.1.1.0 CRS3xx SwOS v2.16
Identity 1.3.6.1.2.1.1.5.0 SWOS
Uptime 1.3.6.1.2.1.1.3.0 timeticks
Port name 1.3.6.1.2.1.2.2.1.2.N Port1
Port status (up/down) 1.3.6.1.2.1.2.2.1.8.N up(1)
Port MAC 1.3.6.1.2.1.2.2.1.6.N hex
Byte rx/tx (32 bit) …2.2.1.10.N / .16.N counter
Byte rx/tx (64 bit) 1.3.6.1.2.1.31.1.1.1.6.N / .10.N counter64
Speed (Mbps) 1.3.6.1.2.1.31.1.1.1.15.N 1000

Here are the non-standard branches, in the MikroTik enterprise MIB (1.3.6.1.4.1.14988) — the things a generic NMS won’t look for on its own but that are worth adding:

Item OID Note
Temperature …14988.1.1.3.11.0 in tenths of a degree: 380 = 38.0 °C
Serial number …14988.1.1.7.3.0 string
SwOS version …14988.1.1.7.4.0 2.16
SFP diagnostics (DDM) …14988.1.1.19.1.1 temp, voltage, Tx/Rx power of the module

And the absences, stated frankly because they matter: no CPU load (SwOS does not implement the HOST-RESOURCES-MIB: 1.3.6.1.2.1.25 → «No Such Object» — on RouterOS, however, hrProcessorLoad is available), no VLANs, no PoE, no fan or PSU voltages via SNMP. For that data, and to write anything, you go back to b-files. We hope a future version of SwOS will expand SNMP coverage — in the meantime, you complete it from the other side.

The practical rule: SNMP to monitor (you can hook it up to a metrics system in five minutes), b-files to configure (it is the only way to write without opening the browser).

The library: dojo.swos

Decoding hexadecimal by hand is instructive once; the second time it becomes work. So we wrote dojo.swos, the first brick of the dojo.* libraries: it encapsulates the b-file format, Digest auth, and little-endian, and gives you ready-made objects. It exists in two twin versions, Python and PHP — same logic, same methods.

from dojo.swos import SwOS

sw = SwOS("192.0.2.10")              # admin / password vuota (default SwOS)

s = sw.system()
print(s["identity"], s["version"], s["temp_c"], "°C")
#  SWOS 2.16 39 °C

for p in sw.ports():
    print(p["name"], "UP" if p["link"] else "down")
#  Port1 UP / Port2 down / …

# configurare una VLAN (occhio alla porta di gestione!)
sw.set_vlans([{"vid": 10, "ports": [2, 3], "name": "lab"}])

# e la verifica incrociata: scrivi col b-file, rileggi via SNMP
sw.set_snmp(location="sala-rack")
# snmpget … sysLocation.0 = STRING: sala-rack

The library covers reading system, ports, VLANs, RSTP, SNMP, MAC table, and counters, and the secure writing of identity, SNMP, and VLANs. A raw get()/post() remains available for any .b. In PHP it is identical, with the namespace Dojo\Swos:

use Dojo\Swos\SwOS;

$sw = new SwOS("192.0.2.10");
echo $sw->system()["identity"];      // -> SWOS
$sw->setVlans([["vid" => 10, "ports" => [2, 3]]]);

It is installed from the dojo index: each one has its personal address (you find it on the library page or in your student area), so no one can pass off a package with the same name dojo-*.

# Python
pip install --index-url https://mikrotikdojo.com/pypi/<il-tuo-indirizzo>/simple/ dojo-swos

# PHP
composer config repositories.dojo composer https://mikrotikdojo.com/composer/<il-tuo-indirizzo>/
composer require dojo/swos

MIT License. The library page shows the two versions (Python and PHP) with SHA-256 fingerprint, timestamp, and changelog.

If instead you need to script a router with RouterOS, and not a SwOS switch, there is the cousin library dojo.ros, which speaks the RouterOS API (binary or REST) from Python and PHP.

Two things to know before starting

  • SwOS credentials are separate from RouterOS. On dual-boot switches, the password you use in RouterOS does not apply to SwOS: the SwOS default is user admin with a blank password. The Digest auth realm is the model name. (The first thing to change on a real network.)
  • Accessing SwOS is protected by device-mode. On a CRS3xx dual-boot, switching from RouterOS to SwOS goes through /system routerboard settings set boot-os=swos, but if the device-mode has the lock routerboard engaged, the switch is refused until you unlock it with a physical confirmation. It is the same mechanism that protects containers and disk formatting.

An honest note: all of this is reverse-engineering. MikroTik could change the b-file format between versions without notice, and we hope that an official and documented API for SwOS will eventually arrive. In the meantime, the switch already has everything needed to automate it: you just had to know where to look.