Manual:API
Summary
This document describes the operation of MikroTik RouterOS API for RouterOS3. The API (application programming interface) is a way to create your own versions of Winbox. This guide will help you make simplified, or translated control applications for RouterOS v3.
API uses port 8728 which is disabled by default. To enable API use followin command:
/ip service enable api
Protocol
- Protocol stream is formatted as a sequence of words.
- Each word is encoded as length, followed by that many bytes of content.
- Words are grouped into sentences. End of sentence is terminated by zero length word.
- Length is encoded as follows:
Value of length | Number of bytes | Encoding |
---|---|---|
0 <= len <= 0x7F | 1 | len, lowest byte |
0x80 <= len <= 0x3FFF | 2 | len | 0x8000, two lower bytes |
0x4000 <= len <= 0x1FFFFF | 3 | len | 0xC00000, three lower bytes |
0x200000 <= len <= 0xFFFFFFF | 4 | len | 0xE0000000 |
len >= 0x10000000 | 5 | 0xF0 and len as four bytes |
- Although this scheme allows encoding of length up to 0x7FFFFFFFF, only four byte length is supported.
- Bytes of len are sent most significant first (network order).
- If first byte of word is >= 0xF8, then it is a reserved control byte. After receiving unknown control byte API client cannot proceed, because it cannot know how to interpret following bytes.
- Currently control bytes are not used.
Short description of API sentences
- Empty sentences are ignored.
- Sentences are processed after receiving terminating zero length word.
- There is a limit on number and size of sentences client can send before it has logged in.
- Commands
- First word is name of command. Examples:
/login /ip/address/getall /user/active/listen /interface/vlan/remove /system/reboot
- Names of commands closely follow console, with spaces replaced by '/'. There are commands that are specific to API, like getall or login.
- Name of command should begin with '/'.
- Next, command arguments can be specified. Examples:
=address=10.0.0.1 =name=iu=c3Eeg =disable-running-check=yes
- Command argument should begin with '=' followed by name of argument, followed by another '=', followed by value of argument.
- There are API specific arguments, such as .id. Names of API specific arguments begin with dot.
- Argument value can be empty and can contain '='.
- Command sentence can have parameters that are specific to and processed by API protocol. These parameters should begin with '.' followed by name of parameter, followed by '=', followed by value of parameter.
- Currently the only such parameter is 'tag'.
- Order of arguments and API parameters is not important and cannot be relied on
- Commands can have additional query parameters that restrict their scope. They are explained in detail in separate section. Exapmle:
/interface/print ?type=ether ?type=vlan ?#|!
- Query words begin with '?'.
- Order of query words is significant.
- Currently only 'print' command handles query words.
- First word of reply begins with '!'.
- Each command generates at least one reply (if connection does not get terminated).
- Last reply for every command is reply that has first word !done.
- Errors and exceptional conditions begin with !trap.
- Data replies begin with !re
- If API connection is closed, RouterOS sends !fatal with reason as reply and then closes the connection.
Initial login
- First, clients sends /login command.
- Note that each command and response ends with an empty word.
- Reply contains =ret=challenge argument.
- Client sends second /login command, with =name=username and =response=response.
- In case of error, reply contains =ret=error message.
- In case of successful login client can start to issue commands.
Tags
- It is possible to run several commands simultaneously, without waiting for previous one to complete. If API client is doing this and needs to differentiate command responses, it can use 'tag' API parameter in command sentences.
- If you include 'tag' parameter with non-empty value in command sentence, then 'tag' parameter with exactly the same value will be included in all responses generated by this command.
- If you do not include 'tag' parameter or it's value is empty, then all responses for this command will not have 'tag' parameter.
Command description
- /cancel
- optional argument: =tag=tag of command to cancel, without it cancels all running commands
- does not cancel itself
- all canceled commands are interruped and in the usual case generate '!trap' and '!done' responses
- please note that /cancel is separate command and can have it's own unique '.tag' parameter, that is not related to '=tag' argument of this command
- listen
- listen command is avaliable where console print command is available, but it does not have expected effect everywhere (i.e. may not work)
- !re sentences are generated as something changes in particular item list
- when item is deleted or dissapears in any other way, the '!re' sentence includes value '=.dead=yes'
- This command does not terminate. To terminate it use /cancel command.
- getall
- getall command is available where console print command is available. Since version 3.21 getall is an alias for print.
- replies contain =.id=Item internal number property.
- print
- API print command differs from the console counterpart in the following ways:
- where argument is not supported. Items can be filtered using query words (see below).
- .proplist argument is a comma separated list of property names that should be included for the returned items.
- returned items may have additional properties.
- order of returned properties is not defined.
- if list contains duplicate entries, handling of such entries is not defined.
- if propery is present in .proplist, but absent from the item, then that item does not have this property value (?name will evaluate to false for that item).
- if .proplist is absent, all properties are included as requested by print command, even those that have slow access time (such as file contents and perfomance counters). Thus use of .proplist is encouraged. Omission of .proplist may have high perfomance penalty if =detail= argument is set.
- API print command differs from the console counterpart in the following ways:
Queries
print command accepts query words that limit set of returned items. This feature appeared in the 3.21 version.
- Query words begin with '?'.
- Order of query words is significant. Query is evaluated starting from the first word.
- Query is evaluated for each item in the list. If query succeeds, item is processed, if query fails, item is ignored.
- Query is evaluated using a stack of boolean values. Initially stack contains infinite amount of 'true' values. At the end of evaluation, if stack contains at least one 'false' value, query fails.
- Query words operate according to the following rules:
?name | pushes 'true' if item has value of property name, 'false' if it does not. |
?-name | pushes 'true' if item does not have value of property name, 'false' otherwise. |
?name=x ?=name=x |
pushes 'true' if property name has value equal to x, 'false' otherwise. |
?<name=x | pushes 'true' if property name has value less than x, 'false' otherwise. |
?>name=x | pushes 'true' if property name has value greater than x, 'false' otherwise. |
?#operations | applies operations to the values in the stack.
|
Examples:
- Get all ethernet and VLAN interfaces:
/interface/print ?type=ether ?type=vlan ?#|
- Get all routes that have non-empty comment:
/ip/route/print ?>comment=
OID
print command can return OID values for properties that are available in SNMP. This feature appeared in 3.23 version.
In console, OID values can be seen by running 'print oid' command. In API, these properties have name that ends with ".oid", and can be retrieved by adding their name to the value of '.proplist'. An example:
Command examples
/system/package/getall
Template:Bapi Template:Apic Template:Apic Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis |- | ... more !re sentences ... |- Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Eapi
/user/active/listen
Template:Bapi Template:Apic Template:Apic Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis |- | ... more !re sentences ... |- Template:Eapi
/cancel, simultaneous commands
Template:Bapi Template:Apic Template:Apic Template:Apis Template:Apis Template:Apis Template:Apic Template:Apic Template:Apic Template:Apic Template:Apis Template:Apis Template:Apih Template:Apic Template:Apic Template:Apic Template:Apih Template:Apic Template:Apic Template:Apic Template:Apic Template:Apic Template:Apih Template:Apis Template:Apis Template:Apis Template:Apih Template:Apic Template:Apic Template:Apic Template:Apic Template:Apic Template:Apih Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apih Template:Apis Template:Apis Template:Apis Template:Apih Template:Apic Template:Apic Template:Apic Template:Apih Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apih Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apih Template:Apis Template:Apis Template:Apis Template:Apih Template:Apic Template:Apic Template:Apic Template:Apic Template:Apih Template:Apis Template:Apis Template:Apis Template:Apis Template:Apis Template:Apih Template:Apis Template:Apis Template:Apis Template:Apih Template:Apis Template:Apis Template:Apis Template:Eapi
Example client
- this is simple API client in Python2
- example for Python3
- usage: api.py ip-address username password
- after that type words from keyboard, terminating them with newline
- Since empty word terminates sentence, you should press enter twice after last word before sentence will be sent to router.
#!/usr/bin/python import sys, posix, time, md5, binascii, socket, select class ApiRos: "Routeros api" def __init__(self, sk): self.sk = sk self.currenttag = 0 def login(self, username, pwd): for repl, attrs in self.talk(["/login"]): chal = binascii.unhexlify(attrs['=ret']) md = md5.new() md.update('\x00') md.update(pwd) md.update(chal) self.talk(["/login", "=name=" + username, "=response=00" + binascii.hexlify(md.digest())]) def talk(self, words): if self.writeSentence(words) == 0: return r = [] while 1: i = self.readSentence(); if len(i) == 0: continue reply = i[0] attrs = {} for w in i[1:]: j = w.find('=', 1) if (j == -1): attrs[w] = '' else: attrs[w[:j]] = w[j+1:] r.append((reply, attrs)) if reply == '!done': return r def writeSentence(self, words): ret = 0 for w in words: self.writeWord(w) ret += 1 self.writeWord('') return ret def readSentence(self): r = [] while 1: w = self.readWord() if w == '': return r r.append(w) def writeWord(self, w): print "<<< " + w self.writeLen(len(w)) self.writeStr(w) def readWord(self): ret = self.readStr(self.readLen()) print ">>> " + ret return ret def writeLen(self, l): if l < 0x80: self.writeStr(chr(l)) elif l < 0x4000: l |= 0x8000 self.writeStr(chr((l >> 8) & 0xFF)) self.writeStr(chr(l & 0xFF)) elif l < 0x200000: l |= 0xC00000 self.writeStr(chr((l >> 16) & 0xFF)) self.writeStr(chr((l >> 8) & 0xFF)) self.writeStr(chr(l & 0xFF)) elif l < 0x10000000: l |= 0xE0000000 self.writeStr(chr((l >> 24) & 0xFF)) self.writeStr(chr((l >> 16) & 0xFF)) self.writeStr(chr((l >> 8) & 0xFF)) self.writeStr(chr(l & 0xFF)) else: self.writeStr(chr(0xF0)) self.writeStr(chr((l >> 24) & 0xFF)) self.writeStr(chr((l >> 16) & 0xFF)) self.writeStr(chr((l >> 8) & 0xFF)) self.writeStr(chr(l & 0xFF)) def readLen(self): c = ord(self.readStr(1)) if (c & 0x80) == 0x00: pass elif (c & 0xC0) == 0x80: c &= ~0xC0 c <<= 8 c += ord(self.readStr(1)) elif (c & 0xE0) == 0xC0: c &= ~0xE0 c <<= 8 c += ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) elif (c & 0xF0) == 0xE0: c &= ~0xF0 c <<= 8 c += ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) elif (c & 0xF8) == 0xF0: c = ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) c <<= 8 c += ord(self.readStr(1)) return c def writeStr(self, str): n = 0; while n < len(str): r = self.sk.send(str[n:]) if r == 0: raise RuntimeError, "connection closed by remote end" n += r def readStr(self, length): ret = '' while len(ret) < length: s = self.sk.recv(length - len(ret)) if s == '': raise RuntimeError, "connection closed by remote end" ret += s return ret def main(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((sys.argv[1], 8728)) apiros = ApiRos(s); apiros.login(sys.argv[2], sys.argv[3]); inputsentence = [] while 1: r = select.select([s, sys.stdin], [], [], None) if s in r[0]: # something to read in socket, read sentence x = apiros.readSentence() if sys.stdin in r[0]: # read line from input and strip off newline l = sys.stdin.readline() l = l[:-1] # if empty line, send sentence and start with new # otherwise append to input sentence if l == '': apiros.writeSentence(inputsentence) inputsentence = [] else: inputsentence.append(l) if __name__ == '__main__': main()
Example run:
debian@localhost:~/api-test$ ./api.py 10.0.0.1 admin '' <<< /login <<< >>> !done >>> =ret=93b438ec9b80057c06dd9fe67d56aa9a >>> <<< /login <<< =name=admin <<< =response=00e134102a9d330dd7b1849fedfea3cb57 <<< >>> !done >>> /user/getall <<< /user/getall <<< >>> !re >>> =.id=*1 >>> =disabled=no >>> =name=admin >>> =group=full >>> =address=0.0.0.0/0 >>> =netmask=0.0.0.0 >>> >>> !done >>>
See also
API examples in the Wiki
API examples on the MikroTik Forum
- in Perl by Hugh
- in Delphi by Rodolfo
- in Delphi #2 by Chupaka
- in Java by MikroTik
- in NodeJS by Trakkasure