Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: typos

Table of Contents

Summary

Application Programmable Interface (API) allows users to create custom software solutions to communicate with RouterOS to gather information, adjust the configuration, and manage the router. API closely follows syntax from the command-line interface (CLI). It can be used to create translated or custom configuration tools to aid ease of use in running and managing routers with RouterOS.

API service must be enabled before trying to establish the API connection. By default, API uses TCP:8728 and TCP:8729 (secure). 

API-SSL service is capable of working in two modes - with and without a certificate. In the case no certificate is used in /ip service settings then an anonymous Diffie-Hellman cipher has to be used to establish a connection. If a certificate is in use, a TLS session can be established.

Protocol

Communication with the router is done by sending sentences and receiving one or more sentences in return. A sentence is a sequence of words terminated by zero-length words. Word is part of a sentence encoded in a certain way - encoded length and data. Communication happens by sending sentences to the router and receiving replies to sent sentences. Each sentence sent to the router using API should contain a command as a first word followed by words in no particular order, the end of the sentence is marked by a zero-length word. When the router receives a full sentence (command word, no or more attribute words, and zero-length word) it is evaluated and executed, then a reply is formed and returned.

...

Words are part of a sentence. Each word has to be encoded in a certain way - the length of the word followed by the word content. The length of the word should be given as a count of bytes that are going to be sent.

Length The length of the word is encoded as follows:

Value of length# of bytesEncoding
0 <= len <= 0x7F1len, lowest byte
0x80 <= len <= 0x3FFF2len | 0x8000, two lower bytes
0x4000 <= len <= 0x1FFFFF3len | 0xC00000, three lower bytes
0x200000 <= len <= 0xFFFFFFF4len | 0xE0000000
len >= 0x1000000050xF0 and len as four bytes


  • Each word is encoded as length, followed by that many bytes of content;
  • Words are grouped into sentences. End The end of a sentence is terminated by a zero-length word;
  • The scheme allows encoding of length up to 0x7FFFFFFFFF, only four-byte length is supported;
  • Bytes of len bytes are sent most significant first (network order);
  • If the first byte of the word is >= 0xF8, then it is a reserved control byte. After receiving an unknown control byte API client cannot proceed, because it cannot does not know how to interpret the following bytes;
  • Currently, control bytes are not used;

In general, words can be described like this <<encoded word length><word content>>. Word content can be separated into 5 parts: command word, attribute word, API attribute word. query word, and reply word

Command word

First The first word in the sentence has to be a command followed by attribute words and a zero-length word or terminating word. Name The name of the command word should begin with '/'. Names of commands closely follow CLI, with spaces replaced with '/'. There are Some commands that are specific to API;

Command word structure in the strict order:

  • encoded length
  • content prefix /
  • CLI converted command


API-specific commands:

login
cancel

...

Attribute word

Each command word has its own list of of attribute words depending on content.

Atribute Attribute word structure consists of 5 parts in this order:

  • encoded length
  • content prefix equals sign - =
  • attribute name
  • separating equals sign - =
  • value of an attribute if there is one. It is possible that the attribute does not have a value


Note

...

Value can hold multiple equal signs in the value of an attribute word since the way the word is encoded.

...


Note

Value can be empty.

Examples without encoded length prefix:

...

API attribute word structure is in the strict order:

  • encoded length
  • content prefix with the dot.
  • attribute name
  • name postfixed with equals =sign
  • attribute value

Currently, the only such API attribute is the tag.

Note

If the sentence contain contains an API attribute word tag then each returned sentence in reply from the router to that tagged sentence will be tagged with the same tag.

Query word

Senteces Sentences can have additional query paramteres parameters that restrict their scope. They are explained in detail in separate A detailed explanation is in the query section.

Example of a sentence using query word attributes:

...

  • Query words begin with '?'.
  • Currently, only the print command handles query words.
Warning

Order The order of query words is significant

Reply word

It is only sent only by the router . It is only sent in response to the full sentence send by received from the client.

  • First The first word of reply begins with '!';
  • Each sentence sent generates at least one reply (if a connection does not get terminated);
  • Last The last reply for every sentence is the reply that has the first word !done;
  • Errors and exceptional conditions begin with !trap;
  • Data replies begin with !re
  • If the API connection is closed, RouterOS sends sends !fatal with a reason as a reply and then closes the connection;

API sentences

API sentence is the main object of communication using API.

  • Empty sentences are ignored.
  • Sentence A sentence is processed after receiving zero length word.
  • There is a limit on the number and size of sentences that the client can send before it has logged in.
  • Order of attribute words should not be relied on. As order and count is are changeable by .proplist attribute.
  • Sentence The sentence structure is as follows:
    • First The first word should contain a command word;
    • Should contain zero-length word to terminate the sentence;
    • Can contain none or several attribute words. There is no particular order at in what attribute words has have to be sent in the sentence, order is not important for attribute words;
    • Can contain none or several query words. Order The order of query words in the sentence is important.
Note

Zero-length word terminates the sentence. If it is not provided router will not start to evaluate sent words and will consider all the input as part of the same sentence.

...

Note: that each command and response ends with an empty word.

Login method post-v6.43:

/login
=name=admin
=password=
 !done


  • Now the client sends a username and password in the first message.
  • Password is sent in plain text.
  • in case of an error, the reply contains =message=error message.
  • In case of a successful login, the client can start to issue commands.

...

  • It is possible to run several commands simultaneously, without waiting for the previous one to complete. If the API client is doing this and needs to differentiate command responses, it can use the 'tag' API parameter in command sentences.
  • If you include the 'tag' parameter with a non-empty value in the command sentence, then the 'tag' parameter with exactly the same value will be included in all responses generated by this command.
  • If you do not include the 'tag' parameter or it's its value is empty, then all responses for this command will not have a '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 interrupted and in the usual case generate '!trap' and '!done' responses
    • please note that /cancel is separate command and can have it's its own unique '.tag' parameter, that is not related to '=tag' argument of this command
  • listen
    • listen command is avaliable available where console print command is available, but it does not have the expected effect everywhere (i.e. may not work)
    • "!re" sentences are generated as something changes in a particular item list
    • when an item is deleted or dissapears disappears in any other way, the '!re' sentence includes the 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 an 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 a list contains duplicate entries, handling of such entries is not defined.
        • if propery a property 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 the print command, even those that have slow access time (such as file contents and perfomance performance counters). Thus the use of of .proplist is encouraged. Omission The omission of .proplist may have a high perfomance -performance penalty if the "=detail=" argument is set.

Queries

The print command accepts query words that limit the set of returned sentences. This feature is available since RouterOS 3.21. 

  • Query words begin Query words begin with '?'.
  • Order The order of query words is significant. Query A query is evaluated starting from the first word.
  • Query A query is evaluated for each item in the list. If the query succeeds, the item is processed, if a query fails, the item is ignored.
  • Query A query is evaluated using a stack of boolean values. Initially, the stack contains an infinite amount of 'true' values. At the end of the evaluation, if the stack contains at least one 'false' value, the query fails.
  • Query words operate according to the following rules:
Query
Desciption
Description
?namepushes 'true' if an item has a value of property name, 'false' if it does not.
?-namepushes 'true' if an item does not have a value of property name, 'false' otherwise.
?name=x
?=name=x
pushes 'true' if the property name has a value equal to x, 'false' otherwise.
?<name=xpushes 'true' if the property name has a value less than x, 'false' otherwise.
?>name=xpushes 'true' if the property name has a value greater than x, 'false' otherwise.
?#operationsapplies operations to the values in the stack.
  • operation string is evaluated from left to right.
  • the sequence of decimal digits followed by any other character or end of the word is interpreted as a stack index. top value has an index 0.
  • an index that is followed by a character pushes a copy of the value at that index.
  • an index that is followed by the end of the word replaces all values with the value at that index.
  • ! character replaces the top value with the opposite.
  • & pops two values and pushes the result of logical 'and' operation.
  • | pops two values and pushes the result of logical 'or' operation.
  • . after an index does nothing.
  • . after another character pushes a copy of the top value.

...


Warning

Regular expressions are not supported in API, so do not try to send a query with the ~ symbol


Examples:

  • Get all ethernet and VLAN interfaces:
/interface/print
?type=ether
?type=vlan
?#|
  • Get all routes that have a non-empty comment:
/ip/route/print
?>comment=

OID

The print command can return OID values for properties that are available in SNMP. This feature appeared in 3.23 version. 

In the In console, OID values can be seen by running the 'print oid' command. In API, these properties have a name that ends with ".oid", and can be retrieved by adding their name to the value of '.proplist'. An example:


/system/resource/print
=.proplist=uptime,cpu-load,uptime.oid,cpu-load.oid
 !re
=uptime=01:22:53
=cpu-load=0
=uptime.oid=.1.3.6.1.2.1.1.3.0
=cpu-load.oid=.1.3.6.1.2.1.25.3.3.1.2.1

 !done


!trap

When for some reason API sentence fails trap is sent in return accompanied with by a message attribute and on some occasions category argument.

message

When an API sentence fails, some generic message or message from the used internal process is return returned to give more details about the failure

Code Block
languagetext
themeFadeToGrey
<<< /ip/address/add 
<<< =address=192.168.88.1 
<<< =interface=asdf

...

 <<< 

>>> !trap 
>>> =category=1 
>>> =message=input does not match any value of interface

...


category

if it is a general error, it is categorized and the error category is returned. possible values for this attribute are

  • 0 - missing item or command
  • 1 - argument value failure
  • 2 - execution of command interrupted
  • 3 - scripting related failure
  • 4 - a general failure
  • 5 - API related failure
  • 6 - TTY related failure
  • 7 - value generated with :return command

Command examples

/system/package/getall

/system/package/getall

 !re
=.id=*5802
=disabled=no
=name=routeros-x86
=version=3.0beta2
=build-time=oct/18/2006 16:24:41
=scheduled=

 !re
=.id=*5805
=disabled=no
=name=system
=version=3.0beta2
=build-time=oct/18/2006 17:20:46
=scheduled=

... more !re sentences ...
 !re
=.id=*5902
=disabled=no
=name=advanced-tools
=version=3.0beta2
=build-time=oct/18/2006 17:20:49
=scheduled=

 !done

/user/active/listen

/user/active/listen

 !re
=.id=*68
=radius=no
=when=oct/24/2006 08:40:42
=name=admin
=address=0.0.0.0
=via=console

 !re
=.id=*68
=.dead=yes

... more !re sentences ...

/cancel, simultaneous commands

/login

 !done
=ret=856780b7411eefd3abadee2058c149a3

/login
=name=admin
=response=005062f7a5ef124d34675bf3e81f56c556

 !done
-- first start listening for interface changes (tag is 2)
/interface/listen
.tag=2
-- disable interface (tag is 3)
/interface/set
=disabled=yes
=.id=ether1
.tag=3
-- this is done for disable command (tag 3)
 !done
.tag=3

-- enable interface (tag is 4)
/interface/set
=disabled=no
=.id=ether1
.tag=4
-- this update is generated by a change made by the first set command (tag 3)
 !re
=.id=*1
=disabled=yes
=dynamic=no
=running=no
=name=ether1
=mtu=1500
=type=ether
.tag=2

-- this is done for enable command (tag 4)
 !done
.tag=4

-- get interface list (tag is 5)
/interface/getall
.tag=5

-- this update is generated by a change made by the second set command (tag 4)
 !re
=.id=*1
=disabled=no
=dynamic=no
=running=yes
=name=ether1
=mtu=1500
=type=ether
.tag=2

-- these are replies to getall command (tag 5)
 !re
=.id=*1
=disabled=no
=dynamic=no
=running=yes
=name=ether1
=mtu=1500
=type=ether
.tag=5

 !re
=.id=*2
=disabled=no
=dynamic=no
=running=yes
=name=ether2
=mtu=1500
=type=ether
.tag=5

-- here interface getall ends (tag 5)
 !done
.tag=5

-- stop listening - request to cancel command with tag 2, cancel itself uses tag 7
/cancel
=tag=2
.tag=7

-- listen command is interrupted (tag 2)
 !trap
=category=2
=message=interrupted
.tag=2

-- cancel command is finished (tag 7)
 !done
.tag=7

-- listen command is finished (tag 2)
 !done
.tag=2

Example client

...

A simple API client in

...

Python3

...

Example output:

Code Block
languagetext
themeFadeToGrey
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

API implementations in different languages, provided by different sources. They are not ordered in any particular order.

Code Block
languagepy
#!/usr/bin/python

import sys, posix, time, md5, binascii, socket, select, ssl

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", "=name=" + username,
"=password=" + pwd]):
if repl == '!trap':
return False
elif '=ret' in attrs.keys():
#for repl, attrs in self.talk(["/login"]):
chal = binascii.unhexlify(attrs['=ret'])
md = md5.new()
md.update('\x00')
md.update(pwd)
md.update(chal)
for repl2, attrs2 in self.talk(["/login", "=name=" + username,
"=response=00" + binascii.hexlify(md.digest())]):
if repl2 == '!trap':
return False
return True

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 open_socket(dst, port, secure=False):
s = None
res = socket.getaddrinfo(dst, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
af, socktype, proto, canonname, sockaddr = res[0]
skt = socket.socket(af, socktype, proto)
if secure:
s = ssl.wrap_socket(skt, ssl_version=ssl.PROTOCOL_TLSv1_2, ciphers="ADH-AES128-SHA256") #ADH-AES128-SHA256
else:
s = skt
s.connect(sockaddr)
return s

def main():
s = None
dst = sys.argv[1]
user = "admin"
passw = ""
secure = False
port = 0

# use default username and pasword if not specified
if len(sys.argv) == 4:
user = sys.argv[2]
passw = sys.argv[3]
elif len(sys.argv) == 3:
user = sys.argv[2]

if (port==0):
port = 8729 if secure else 8728

s = open_socket(dst, port, secure)
if s is None:
print ('could not open socket')
sys.exit(1)

apiros = ApiRos(s);
if not apiros.login(user, passw):
return

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__':
if len(sys.argv) == 1:
print "Usage: %s IP [user] [pass] [--secure]" % str(sys.argv[0])
else:
main()
Code Block
languagetext
themeFadeToGrey
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

API implementations in different languages, provided by different sources. They are not ordered in any particular order.

in the Wiki

on the MikroTik Forum

External sources