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:

Mar 2026 Edit "Retired" as a one-off and built per-version. It was not actually validate OAS3. Some tools like Postman could load it, but not all. See Feature Request : OpenAPI for REST API - #17 by Amm0 for new version of OpenAPI 3 schema.

Wow! Thank you nice work!

OpenAPI 2.0 schema "Retired"

OAS2 was published for most RouterOS versions in past, but new version will only have OpenAPI 3 schema. See Feature Request : OpenAPI for REST API - #17 by Amm0 below

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:

RouterOS Schema Tools

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

So you can more easily see changes between versions...

Come a long way with the /console/inspect. In fact, between restraml schemas+diff (using request=syntax & request=child), which has RAML and OpenAPI schemas, and an RouterOS LSP for syntax colors/errors/completion (using request=completion & request=highlight) — using most what /console/inspect has to offer.

While the LSP gets pretty far with inspect's completion and highlight data. RouterOS LSP does gets limited since I have not been able to figure out, reliably, what the "current working directory" is when using any /console/inspect request=... input="some code" things. The LSP's view of RouterOS script is only "highlight" tokens associated with text position in an LSP client editor (VSCode, nvim, etc). But Inspect's tokenizer knows definitively the current path= (in the ip,address sense) since it resolve subshells like [find] and other syntax which inherent some path perfectly. While in some cases path could be inferred like /ip/address/add... where it's "fully qualified". This limited the possibility to a request=syntax in the LSP to offer editor the "signature" (i.e. CLI's F1 help).

So if you had a new "easter eggs" for this cwd problem, be good to know. The full set of issues where /console/inspect is limited is here:

With the added question/mystery... /console/inspect request=error is one I not been able to figure out what it does. Any new clues? It's always nil regardless of what is provided (both valid or all sorts of invalid syntax)

:put [:typeof [/console/inspect request=error input=":put [/system/identity/get name]"]]
# nil
:put [:typeof [/console/inspect request=error input="/somebadcommand"]]                                
# nil

But guessing it does something, just don't know what. Right now it's riddle in within in the easter egg inspection.

Finally, years after topic was opened, there is a proper OpenAPI 3.0 schema for REST API using our friend /console/inspect.

https://tikoci.github.io/restraml/7.22.1/openapi.json (routeros.npk only)
https://tikoci.github.io/restraml/7.22.1/extra/openapi.json (with all extra-packages for CHR, so most of them)

Only versions newer than 7.22.1 will have OpenAPI 3 schemes, RAML remains for older versions, and still be built. All new beta/rc and releases will include OpenAPI, so you can change the URL above to match versions (within ~24 hours of a new public RouterOS build).

RAML really only works in Postman, and slowly. Modern tools want OpenAPI format. Now OpenAPI 3 schema is still big (11MB, 5000+ methods)... so it takes most visual tools a while to render still. CLI / CI validation using the OAS3 schema works better, since it not 5000 x x UI controls to track/draw.

NEW REST API Explorer Webpage

There is also new Scalar-based web viewer of the schema.

Screenshot 2026-03-28 at 8.29.51 AM
Screenshot 2026-03-28 at 8.21.58 AM

"Test Request" requires CORS Proxy

You cannot "run" anything since RouterOS does not support "CORS" in REST API. You can setup a "CORS Proxy" to allow using the "Test Request" control. Search forum, you can use ngnix, Traefik, or Caddy server - all should work with the new "API Explorer". I tested Traefik as CORS Proxy, and webpage works with the requests.

Doc Links Included

The OpenAPI 3 version adds doc links to help.mikrotik.com in selected spots. It just contains links and it pretty conservative in matching them, so should generally be right... but many elements do have some doc page that could be links. This is a work in progress.

The doc links here from a SQLite database after converting the help.mikrotik.com's "monthly" PDF/HTML offline to FTS5-enabled SQLite tables, breaking out fragments, "callout", and attribute tables.

The database used is actual built in tikoci/rosetta, which is also an MCP Server that allows LLM agents can more easily consume MikrotTik's help.mikrotik.com docs. See GitHub - tikoci/rosetta: MCP Server with RouterOS docs + commands + products + changelogs, using SQLite-as-RAG, sourced from MikroTik · GitHub for details.

NOTE OpenAPI schema generation does not use an LLM for the docs. Rather it shares the same database as the MCP Server. The doc links come the table generated by Python scripts that parse Confluence HTML into SQLite.

How REST schemas are made?

For every RouterOS build, the following process essentially calls /console/inspect with ~50K request. The exact process was captured by CoPilot in GraphViz (which forum supports via [graphviz] BBtags).

How a RouterOS OpenAPI Schema Is Made How a RouterOS OpenAPI Schema Is Made cluster_trigger ⏰ 1 · VERSION DETECTION Daily at 4 AM UTC cluster_infra 🖥️ 2 · SPIN UP VIRTUAL ROUTER GitHub Actions Runner + QEMU/KVM cluster_crawl 🔍 3 · CRAWL THE ENTIRE API TREE rest2raml.js (Bun runtime) cluster_loop 🔄 Recursive Tree Walk Every command, every argument cluster_openapi ⚙️ 4 · GENERATE OpenAPI 3.0 deep-inspect.ts (Bun runtime) cluster_enrich 📚 5 · ENRICH WITH DOCUMENTATION enrich-openapi.ts + Rosetta SQLite DB cluster_publish 🚀 6 · PUBLISH TO GITHUB PAGES cron 🕐 auto.yaml Cron Trigger channels Query 4 MikroTik Channels stable · testing · development · long-term upgrade.mikrotik.com/routeros/NEWESTa7.* cron->channels check Version already built? channels->check skip ✓ Skip check->skip openapi.json exists dispatch Dispatch Build Workflow check->dispatch Missing qemu_install Install QEMU apt install qemu-system-x86 qemu-utils dispatch->qemu_install kvm Enable KVM Hardware acceleration GitHub runners have /dev/kvm qemu_install->kvm dl_chr Download CHR Disk Image chr-{ver}.vdi.zip download.mikrotik.com → cdn.mikrotik.com kvm->dl_chr convert Convert VDI → QCOW2 qemu-img convert -f vdi -O qcow2 dl_chr->convert launch Launch RouterOS in QEMU 256 MB RAM · virtio disk · user-mode net Port 9180→80 (REST) · Port 9122→22 (SSH) convert->launch wait ⏳ Wait for REST API up to 5 min launch->wait ready ✅ RouterOS REST API Ready http://localhost:9180/rest wait->ready curl succeeds start_crawl Start at Root Path POST /rest/console/inspect {request: "child", path: ""} ready->start_crawl fetch Fetch Children {request: "child", path: "ip,address,..."} Returns: name + node-type (dir│cmd│arg) start_crawl->fetch checktype Node Type? fetch->checktype recurse 📁 Directory Recurse deeper checktype->recurse dir syntax ⚡ Command / Argument {request: "syntax"} Returns type info: "0..65535" · "IP address" "string, max length 45" checktype->syntax cmd / arg recurse->fetch ~2000+ paths syntax->fetch inspect_json 💾 inspect.json Complete API tree ~5000 nodes syntax->inspect_json raml_out 📄 schema.raml RAML 1.0 schema syntax->raml_out crashwarn ⚠️ Skip Crash Paths where · do · else · rule command · on-error crashwarn->recurse parse_tree Parse Inspect Tree Map RouterOS commands → REST operations inspect_json->parse_tree raml_validate ✔️ Validate RAML 1.0 node validraml.cjs webapi-parser raml_out->raml_validate raml_validate->parse_tree map_ops Map Commands → HTTP Methods get → GET  · set → PATCH add → PUT  · remove → DELETE parse_tree->map_ops parse_types Parse Type Descriptions "0..4294967295" → integer min/max "IP address" → string format:ipv4 "time interval" → string (duration) map_ops->parse_types openapi_raw 📋 openapi.json OpenAPI 3.0 schema ~1800 paths · ~3400 params parse_types->openapi_raw openapi_validate ✔️ Validate OpenAPI 3.0 bun validate-openapi.ts swagger-parser openapi_raw->openapi_validate rosetta_dl Download Rosetta SQLite DB ros-help.db.gz Parsed from help.mikrotik.com Official RouterOS manual pages openapi_validate->rosetta_dl match_paths Match API Paths → Manual Pages /ip/address → "IP Address" page /interface/bridge → "Bridge" page Multi-strategy: path · title · abbreviation rosetta_dl->match_paths add_docs Add externalDocs Links Each operation gets a link to help.mikrotik.com documentation match_paths->add_docs add_descs Enrich Property Descriptions Merge manual descriptions with RouterOS syntax type info add_docs->add_descs enriched 📋 openapi.json ✨ Enriched with docs links ~40% operations linked ~36% args described add_descs->enriched commit Git Commit to main docs/{version}/ schema.raml · inspect.json · openapi.json enriched->commit push Push with Retry + Rebase Handles concurrent builds (base + extra packages in parallel) commit->push pages 🌐 GitHub Pages tikoci.github.io/restraml Interactive schema explorer Diff tool · Command lookup push->pages

And that whole process essentially ends up back in GitHub here: https://github.com/tikoci/restraml/tree/main/docs which is "raw" downloads for all the schemas produced.
(GitHub Pages serves same directory as normal HTTPS that download link and API Explorer use)

OpenAPI 3 and other schemas are also downloadable from the main "Schema Download" page on my TIKOCI site:

https://tikoci.github.io/restraml/