API
Application Programming Interface (API) lets users create custom software solutions to communicate with RouterOS for information gathering, adjusting configuration, and managing the router. The API closely follows the syntax of the command-line interface (CLI). You can use it to create translated or custom configuration tools that make it easier to run and manage RouterOS routers.
The API service must be enabled before you try to establish a connection — enable it with /ip/service set api disabled=no. By default, the API uses TCP ports 8728 and 8729 (secure).
The API-SSL service can operate in two modes: with or without a certificate. Without a certificate configured for the service, the service offers only anonymous Diffie-Hellman ciphers, which current TLS clients do not accept, so the connection fails with a handshake error. With a certificate configured for the service, the client can establish a regular TLS session. For the service settings, see the ip/service CLI reference.
Protocol
You communicate with the router by sending sentences and receiving one or more sentences in return. A sentence is a sequence of words terminated by a zero-length word. A word is part of a sentence encoded in a certain way: encoded length followed by data. When the router receives a full sentence (command word, one or more attribute words, and a zero-length word), it evaluates and executes the sentence and then forms and returns a reply.
API sentences
Sentence is the main object of communication with the API.
- Empty sentences are ignored.
- 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.
- Do not rely on the order of attribute words, because the order and count can be changed by the
.proplistattribute.
The sentence structure is as follows:
- The first word must contain a command word.
- The sentence must contain a zero-length word to terminate it.
- The sentence can contain zero or more attribute words. The order of attribute words does not matter.
- The sentence can contain zero or more query words. The order of query words is important.
If a zero-length word is not provided, the router will not start evaluating the sent words and will consider all the following input as part of the same sentence.
API words
- Words are grouped into sentences. A zero-length word terminates a sentence.
- Each word is encoded as a length followed by that many bytes of content.
- The encoding supports lengths up to 0xFFFFFFFF; the five-byte form (
0xF0+ four bytes) encodes the largest lengths. - len bytes are sent most significant first (network order).
- If the first byte of the word is >= 0xF8, it is a reserved control byte. After receiving an unknown control byte, an API client cannot proceed because it does not know how to interpret the following bytes.
- Currently, control bytes are not used.
The length of the word is encoded as follows:
| Value of length | # of bytes | Encoding |
|---|---|---|
0 <= len <= 0x7F | 1 | len, lowest byte |
0x80 <= len <= 0x3FFF | 2 | len |
0x4000 <= len <= 0x1FFFFF | 3 | len |
0x200000 <= len <= 0xFFFFFFF | 4 | len |
len >= 0x10000000 | 5 | 0xF0 and len as four bytes |
In general, words can be described like this <encoded-word-length><word-content>.
Word content can be separated into five parts: command word, attribute word, API attribute word, query word, and reply word
Command word
The first word in the sentence must be a command, followed by attribute words and a zero-length or terminating word. The name of the command word must begin with a slash (/). Names of commands closely follow the CLI. Spaces between path objects are not supported; they must be replaced with slashes (/).
Some commands are specific to API:
login- used for login process to provide login credentials.cancel- used to cancel currently running command.
Command word structure in the strict order:
- encoded length
- content prefix /
- CLI converted command
A few examples of command word content:
/login
/user/active/listen
/interface/vlan/remove
/system/reboot
Attribute word
Each command word has its list of attribute words depending on content.
The attribute word structure consists of five parts in this order:
- encoded length
- content is prefixed with equals (
=) character - attribute name
- equals (
=) character as a name and value separator - attribute value if there is one.
Some attribute examples (excluding encoded length prefix):
=address=10.0.0.1
=disable-running-check=yes
The value can contain equal (=) symbols:
=name=iu=c3Eeg
Value can be empty:
=comment=
Keep in mind that the order of attribute words and API parameters is not important and should not be relied on.
API attribute word
API attribute word structure is in the strict order:
- encoded length
- content is prefixed with the dot (
.) character - attribute name
- equals (
=) character as a name and value separator - attribute value
Currently, the only such API attribute is the tag.
Query word
Sentences can have additional query parameters that restrict their scope. See the query section for details.
- Query words begin with
?. - Currently, only the
printcommand handles query words.
Example of a sentence using query word attributes:
/interface/print
?type=ether
?type=
?#|!
The order of query words is significant
Reply word
It is only sent by the router in response to the full sentence received from the client.
- The first word of reply begins with
!. - Each sentence sent generates at least one reply (if a connection does not get terminated).
- 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. - Replies of commands which do not have any data to reply with, begin with
!empty. - If the API connection must be closed, RouterOS sends a
!fatalwith a reason in a description and then closes the connection.
Initial login
The login command supports two authentication methods: the plain-text method and the legacy challenge-response method of older RouterOS versions.
Plain-text password:
/login
=name=admin
=password=
The router replies with !done if authentication is successful. The password is sent in plain text. After a successful login, the client can start issuing commands.
If the credentials are wrong or the account cannot use the API, the router replies with an error:
!trap
=message=invalid name or password (6)
!done
Legacy challenge-response, used by older RouterOS versions:
When the router answers a login with =ret=<challenge> instead of a plain !done, it expects the legacy method. A login sentence without a password is answered with a challenge, for example:
/login
!done
=ret=93b438ec9b80057c06dd9fe67d56aa9a
The client answers in the second message with the user name and the challenge response: the two characters 00 followed by the lowercase hex MD5 digest of the 0x00 byte, the password and the received challenge.
/login
=name=admin
=response=00e134102a9d330dd7b1849fedfea3cb57
!done
Current RouterOS still answers a login sentence without a password with a challenge, but it rejects the legacy response with invalid user name or password. Use the plain-text method.
Closing the TCP connection ends the API session.
Tags
The API allows running several commands simultaneously without waiting for the previous one to complete. To tell the replies apart, add the API attribute word .tag=<value> to the command sentence; every reply for that sentence then carries the same .tag=<value> word.
If you omit the .tag word or leave its value empty, replies for the command carry no tag.
Note the distinction between the tag and an argument named tag: the tag is an API attribute word (.tag=), while a command argument such as /cancel's =tag= is a regular attribute word (=name=value).
Command description
-
/cancel- Optional argument:
=tag=<tag of command to cancel>; without it, all running commands are cancelled. - Does not cancel itself.
- All canceled commands are interrupted and usually generate
!trapand!doneresponses. - Note that
/cancelis a separate command and can have its own unique.tagparameter that is not related to the command's=tagargument.
- Optional argument:
-
listen- The
listencommand is available wherever the CLIprintcommand is available, but it may not work everywhere. !resentences are generated when something changes in a particular item list.- When an item is deleted or disappears, the
!resentence includes the boolean value=.dead=true. - This command does not terminate on its own; use the
/cancelcommand to stop it.
- The
-
getall- The
getallcommand is available wherever the CLIprintcommand is available (getallis an alias forprint). - Replies contain a
=.id=<item internal number>property.
- The
-
print- The API
printcommand differs from the CLI counterpart in the following ways:- The
whereargument is not supported; items can be filtered using query words. - The
.proplistargument is a comma-separated list of property names to include in returned items.- Returned items may have additional properties.
- The order of returned properties is not significant and cannot be relied on.
- If the list contains duplicate entries, handling of those duplicates is undefined.
- If a property appears in
.proplistbut is absent from an item, that item does not have the property value (?nameevaluates to false for that item). - If
.proplistis absent, all properties are included as requested by theprintcommand, even those with slow access time (such as file contents and performance counters). Therefore, use of.proplistis encouraged. Omitting.proplistmay incur a high performance penalty if the=detail=argument is set.
- The
- The API
Queries
The print and getall commands accept query words that limit the set of returned sentences.
- Query words begin with
?. - The order of query words is significant. A query is evaluated starting from the first word.
- 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.
- A query is evaluated using a stack of boolean values. Initially, the stack contains an infinite amount of
truevalues. At the end of the evaluation, if the stack contains at least onefalsevalue, the query fails. - Query words operate according to the following rules:
| Query | Description |
|---|---|
?name | pushes true if an item has a value of property name, false if it does not. |
?-name | pushes true if an item does not have a value of property name, false otherwise. |
?name=x | pushes true if the property name has a value equal to x, false otherwise. |
?<name=x | pushes true if the property name has a value less than x, false otherwise. |
?>name=x | pushes true if the property name has a value greater than x, false otherwise. |
?#operations | applies operations to the values in the stack.
|
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 (equivalent to CLI command
/interface/print where type=ether || type=vlan):
/interface/print
?type=ether
?type=
?#|
- Get all routes that have a non-empty comment (equivalent to CLI command
/ip/route/print where comment):
/ip/route/print
?>comment=
- Get all routes that do not have distance greater than 1 and gateway equal to 172.16.1.1 (equivalent to CLI command
/ip/route/print where !(distance>1 && gateway=172.16.1.1)):
/ip/route/print
?>distance=1
?gateway=172.16.1.1
?#&!
OID
The print command can return OID values for properties that are available in SNMP.
In the CLI, OID values can be seen by running the print oid command. In the API, properties that have an OID expose it through a property whose name ends with .oid; add those names to the value of .proplist to retrieve them. An example:
/system/resource/print
=.proplist=uptime,cpu-frequency,uptime.oid,cpu-frequency.oid
The router sends a reply:
!re
=uptime=16m44s
=cpu-frequency=864
=uptime.oid=.1.3.6.1.2.1.1.3.0
=cpu-frequency.oid=.1.3.6.1.4.1.14988.1.1.3.14.0
!done
Not every SNMP property is exposed through the API. The properties that have an OID are the ones the router shows with the print oid command; a property without an OID, such as cpu-load for the system resources, has no .oid counterpart.
!trap
When an API sentence fails, the router returns a trap sentence. The trap carries a message attribute with details about the failure; some traps also carry a category attribute with the error category:
- 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
For example, an unknown command returns a trap with =category=0:
/foobar/print
Router reply with:
!trap
=category=0
=message=no such command or directory (foobar)
!trap
=message=no such command prefix
!done
A wrong argument value returns a trap with only a message:
/ip/address/add
=address=192.168.88.1
=interface=asdf
Router reply with:
!trap
=message=input does not match any value of
!done
Modify existing items
Like its CLI counterpart, the API has a set command that accepts an item's ID and parameters to set. The value of the .id attribute can be the internal number (for example *1) or, for items that have a name, the name (for example ether1).
The only exception is that the API does not accept queries directly with the set command. For example, the CLI command to set the MTU on all ethernet interfaces:
/interface set [find where type=ether] mtu=1500
To achieve the same with API, you first need to run a print query to get IDs, and only then execute the set command.
/interface/print
=.proplist=.id
?type=ether
Router reply with:
!re
=.id=*1
!re
=.id=*2
Now you need to loop through returned IDs and send the set command for each:
/interface/set
=.id=*1
=mtu=1500
/interface/set
=.id=*2
=mtu=1500
The router answers a successful set with a single !done (no data), and with !trap if the item was not found.
Command examples
listen notifications
Start listening for changes in the interface list:
/interface/listen
The router sends an !re sentence whenever an item appears or changes. When an item is removed, the !re sentence carries the boolean =.dead=true:
!re
=.id=*1
=name=ether1
=disabled=false
=running=true
!re
=.id=*1
=.dead=true
Stop listening with /cancel.
/cancel, simultaneous commands
Start listening for interface changes (tag is 2):
/interface/listen
.tag=2
Send a command to disable the interface (tag is 3):
/interface/set
=disabled=true
=.id=ether1
.tag=3
The router replies with !done for the disable command (executed with tag 3); the change also generates an update for the listen command (tag 2):
!re
=.id=*1
=disabled=true
=dynamic=false
=running=false
=name=ether1
=mtu=1500
=type=ether
.tag=2
!done
.tag=3
Enable the interface (tag is 4):
/interface/set
=disabled=false
=.id=ether1
.tag=4
The change generates another update for the listen command, followed by done for the enable command:
!re
=.id=*1
=disabled=false
=dynamic=false
=running=true
=name=ether1
=mtu=1500
=type=ether
.tag=2
!done
.tag=4
Send a command to get the interface list (tag is 5):
/interface/getall
.tag=5
The client receives replies to the getall command (executed with tag 5):
!re
=.id=*1
=disabled=false
=dynamic=false
=running=true
=name=ether1
=mtu=1500
=type=ether
.tag=5
!re
=.id=*2
=disabled=false
=dynamic=false
=running=true
=name=ether2
=mtu=1500
=type=ether
.tag=5
!done
.tag=5
Stop listening — request cancelling the 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 that supports both login methods and prints the whole exchange on the terminal.