Feature Request : OpenAPI for REST API

Hi

It is glad to have REST API for new RouterOS beta v7.

We are trying to implement REST API automation for RouterOS V7 but we found there is a lot of effort to research API usage to map with command interface.

I was wondering if we can have OpenAPI specification json document to speed up development for user.



Chang

Did you ever find one OpenAPI or swagger doc for the REST API? Has someone already built one already?

+1. Also looking for this. Any news yet?

Maybe this is useful? You can get the list by calling:
http://ros_adrese/webfig/list

Thanks @mrz – that’s useful. More so for pragmatically creating skins (since /webfig/list seems to match values that go in the a “skin” file).

But it’s a bit away from being usable in REST tools like Postman, etc…

Still helpful however: the files referenced by the /webfig/list actually list all the attributes, which is useful. But the JG file output seems to use the “Webfig” names (make sense given context), but doesn’t seem to contain anywhere the equivalent CLI/REST name (e.g. “DHCP Client” vs. “dhcp-client”) so any transformation need to map those, somehow. That seems like a big project.

If this helps anyone, I wrote some JavaScript code to parse the output – since /webfig/list doesn’t seem to be modern JSON (but similar to the “webfig skin JSON” format) you need eval() to parse the output. I don’t do anything with it, but you can at least view it more reasonably. OR, perhaps collect some from various versions to compare the schema deltas release to release.





function webfiglist(ip) {
  if (typeof window === "object" && !ip) {
    ip = (new URL(window.location.href)).host
  }
  return new Promise((done) => {
    fetch(`http://${ip}/webfig/list`)
      .then((req) => req.text())
      .then((txt) => {
        var results = {};
        /* this is critical... 
               the /webfig/list is not valid JSON document, it's a JS "fragment" 
               ...so we need eval() to "convert it" to a variable to then return */
        eval(`results = [${txt}]`);
        done(results);
      });
  });
}

function webfigschemas(ip) {
    if (typeof window === "object" && !ip) {
        ip = (new URL(window.location.href)).host
   }  
   return webfiglist(ip).then((list) =>
    Promise.all(
      list
        .filter((i) => i.unique)
        .map((i) => {
          return new Promise((done) => {
            let file = i.name
            fetch(`http://${ip}/webfig/${file}`)
              .then((req) => req.text())
              .then((txt) => {
                /* same eval() trick as the webfiglist, except return a "tuple" with [filename, data] */
                var results
                eval(`results = ${txt}`)
                done([ file, results ])
              })
          })
        })
    )
  )
}


webfiglist().then(console.log)
webfigschemas().then(console.log)

// NODE.JS save to file
// let ip = "192.168.88.1"
// const fs = require('fs');
// webfigschemas(ip).then(d => fs.writeFileSync("./webfig-list-schema.json", JSON.stringify(d)))

If you paste that into JavaScript console from a browser “Inspect” option while on webfig page, it should show the “schema data” in the console output. If you use nodeJS, you should be able pass an ip address to the webfiglist(“192.168.88.1”) to get it from any RouterOS device (might be possible in browser, but cross-site scripting checks would need tweaks).

Another useful tool could be /console/inspect.
You can request completion, the list of child menu commands and parameters

Interesting, are inspect request=child and other related features documented anywhere?

It’s an easter egg :slight_smile:

Except now we can find them :wink:

It’s not critical for my needs, but I wrote some code to pull out all the “chicks” ( request=child) in the /console/inspect.

It uses recursion with a ROS function to walk the tree of “child” and returns a ROS ::array (& shoves it into a :global variable called “$ast” too). Surprisingly this DOES NOT get ANY stack overflow or memory limits, at least in v7.6. In fact, after walking the tree, even on a little cAP ac, there are 4329 unique “cmd”, “args”, and “path” branches, with a few leafs each.

:global ast [:toarray ""]

:global mkast do={
    :global mkast
    :global ast
    :local path "" 
    :if ([:typeof $1] ~ "str|array") do={ :set path $1 }
    :local pchild [/console/inspect as-value request=child path=$path]
    :foreach k,v in=$pchild do={
        :if (($v->"type") = "child") do={
            :local astkey ""
            :local arrpath [:toarray $path]
            :foreach part in=$arrpath do={
                :set astkey "$astkey/$part"
            }
            :set ($ast->$astkey->($v->"name")) $v
            :put "Processing: $astkey $($v->"name") $($v->"node-type")"
            :local newpath "$($path),$($v->"name")"
    		# TODO use [/console/inspect as-value request=syntax path=$path]
            [$mkast $newpath]
        }
    }
    return $ast
}

# & this call start the recursion 
:put [$mkast]

$ast will also contain the schema as an nested array, reflecting the parent/child relationships… so can use like this:

 :put ($ast->"/ip/address")
 
add=.id=*2;name=add;node-type=cmd;type=child;comment=.id=*3;name=comment;node-type
=cmd;type=child;disable=.id=*4;name=disable;node-type=cmd;type=child;edit=.id=*5;n
ame=edit;node-type=cmd;type=child;enable=.id=*6;name=enable;node-type=cmd;type=chi
ld;export=.id=*7;name=export;node-type=cmd;type=child;find=.id=*8;name=find;node-t
ype=cmd;type=child;get=.id=*9;name=get;node-type=cmd;type=child;print=.id=*a;name=
print;node-type=cmd;type=child;remove=.id=*b;name=remove;node-type=cmd;type=child;
reset=.id=*c;name=reset;node-type=cmd;type=child;set=.id=*d;name=set;node-type=cmd
;type=child

Another example, this is one part of the output $ast array above in YAML-ized style output:

/zerotier/edit:
    number:
      .id: *2
      name: number
      node-type: arg
      type: child
    value-name:
      .id: *3
      name: value-name
      node-type: arg
      type: child
  /zerotier/enable:
    numbers:
      .id: *2
      name: numbers
      node-type: arg
      type: child

I don’t do it here, but adding another call to “/console/inspect request=syntax …” for each child with same path would get a description (e.g. type of “explanation”). With that you’d have all the info for a REST schema. My thought is RAML might be easier to generate from ROS script, since it uses “YAML format”, not JSON, and YAML is easier to generate in ROS script. I think the keys from my code are close to the REST POST things, so that be easiest to model into RAML. And I believe some tools can use or convert from the RAML schema format to swagger / OpenAPI.

But /console/inspect does seem to have the info for a REST schema, now converting is still more work.

One note is /console/inspect seems to know about ALL of the packages (e.g. “extra-packages”), even if not installed – so you’ll see child entries for calea, iot, etc. even if not available.

Great info, thanks! It seems that the metadata proxy-repository is pretty coherent and well worth exploring for building a command tree for automatic integration. I concur regarding RAML.

Took another look at this today, thought it be easy to get the “explanation”.

While “request=child” works with issue, some “request=syntax” (e.g. “path=ip,address,add,interface”, see below), cause the entire terminal to terminate (“Console has crashed; please log in again.”) – most things seem to work with request=syntax.

[user@router] > /console/inspect request=syntax path=ip,address,add
Columns: TYPE, SYMBOL, SYMBOL-TYPE, NESTED, NONORM, TEXT
TYPE    SYMBOL     SYMBOL-TYPE  NESTED  NONORM  TEXT                                   
syntax             collection        0  yes                                            
syntax  address    explanation       1  no      Local IP address                       
syntax  broadcast  explanation       1  no      Broadcast address                      
syntax  comment    explanation       1  no      Short description of the item          
syntax  copy-from  explanation       1  no      Item number                            
syntax  disabled   explanation       1  no      Defines whether item is ignored or used
syntax  interface  explanation       1  no      Interface name                         
syntax  netmask    explanation       1  no      Network mask                           
syntax  network    explanation       1  no      Network prefix                         
[user@router] > /console/inspect request=syntax path=ip,address,add,address
Columns: TYPE, SYMBOL, SYMBOL-TYPE, NESTED, NONORM, TEXT
TYPE    SYMBOL   SYMBOL-TYPE  NESTED  NONORM  TEXT                   
syntax  Address  definition        0  no      A.B.C.D    (IP address)
[user@router] > /console/inspect request=syntax path=ip,address,add,interface

Console has crashed; please log in again.

Even wrapping it a “:do {} on-error={}” does NOT catch the crash so that didn’t work to avoid the issue.

:do {
   /console/inspect request=syntax path=ip,address,add,interface
} on-error={:put "got error"}

While that would not be critical for schema per-se, it does have a description to set, and there is some “collection” node-type that likely indicates an possible array (instead of just string) as JSON param.


FWIW you can call the same /console/inspect via REST API too. But the same params in REST "{ “request”: “syntax”, “path”: “ip,address,add,interface” } while don’t “crash” it timeouts with no data (empty JSON array).

In fact, here the RAML for just the /console/inspect:

#%RAML 1.0
title: ROS.RAML sample
version: 7.6
protocols: [HTTPS]
mediaType: [application/json]
securitySchemes:
  basic:
    description: |
      Mikrotik REST API only supports Basic Authentication, secured by HTTPS
    type: Basic Authentication
securedBy: [basic]
baseUri: https://{host}:{port}/rest
baseUriParameters:
  host:
    description: RouterOS device IP or host name
    default: "192.168.88.1"
  port:
    description: RouterOS https port to use
    default: "443"
documentation:
  - title: RouterOS RAML Schema
    content: |
      Schema is generated using `/console/inspect` on a RouterOS devices and
      interpreted into a schema based on the rules in
      [Mikrotik REST documentation](https://help.mikrotik.com)
  - title: Demo Only
    content: We just try a few commands 

/console:
  /inspect:
    post:
      description: Inspects the RouterOS AST
      body:
        application/json:
          type: object
          properties:
            .proplist?:
              type: string
              description: List of properties to return (see RouterOS docs)
            .query?:
              type: string
              description: List of properties to return (see RouterOS docs)
            path?:
              type: string
              description: Comma-seperated string of RouterOS path
              example: 
            input?:
              type: string
            request:
              type: string
              enum: [self|child|completion|highlight|syntax|error]
          example:
              path: "ip,address,add,interface"
              request: syntax
      responses:
        200:
          body:
            application/json:
              type: array

I kinda forgot about this one. But I do have a JavaScript implementation that using /system/console via REST, and generate a RAML 1.0 scheme.

See https://forum.mikrotik.com/viewtopic.php?t=199476 for RAML-based alternative.

edit: I noticed this was the beta forum, so moved the RAML approach to new thread in Scripting topic

Here is an OpenAPI schema I generated from above RAML schema.

OpenAPI 3.0 / swagger schema
https://tikoci.github.io/restraml/routeros-openapi3.json

It may have some calls that aren’t allowed, and other things may not have translated perfectly. But it loads:

Wow! Thank you nice work!

I recently automated building the schema files at GitHub, including OpenAPI 2.0 (OAS2). So newer (and older) versions of the RAML and OpenAPI schemas are available at:

https://tikoci.github.io/restraml

The same page has a nifty “diff” tool, to compare RouterOS versions.

So you can more easily see changes between versions…
screen-diff-dark.png