run script via REST API

Hi,
I already created a script, works fine if I run the script from WinBox.
Now I’m trying to run the script via REST API (https-url). www-ssl (with certificate), api, api-ssl activated, firewall rule created.

https://remote:xxxx@192.168.1.88/rest/system/resource => works fine, get infos from my switch

https://remote:xxx192.168.1.88/rest/system/script/run/myscript => doesn’t work, response: {“detail”:“no such command prefix”,“error”:400,“message”:“Bad Request”}
What’s wrong with my syntax?

Tanks in advance

You need to use the “POST” method to run the script.

The only way Ive found to specify the script to run is by the number from the

/system/script print
Flags: I - invalid
 0   name="ScriptName" owner="admin" policy=read,write,test dont-require-permissions=no last-started=2025-02-18 14:26:22 run-count=885 source=

to run the script

 curl -k -u username:password -H "Content-Type: application/json"  -X POST https://192.168.1.1/rest/system/script/run -d '{"number": "0"}'

.id?
https://gist.github.com/elico/9110bc2a7eab12b9e65a1c1b3e4f8c69


{“.id”: “ScriptName”}
should work

Cool, that works well

curl -k -u username:password -H "Content-Type: application/json"  -X POST https://192.168.1.1/rest/system/script/run -d '{".id": "ScriptName"}' -w ", %{http_code}"
[], 200

Thanks!

Hey jaclaz-

The {“.id”: “ScriptName”} thing works but I’m not sure I understand why.

The “.id” property is an array reference like “*3”, not a string like “ScriptName”. Does it somehow do a lookup on the “name” property?

It shouldn’t work like that… but some stuff does vary by command. It might just map both “.id” to “numbers=”* as the “$1”/unnamed argument to /system/script/run and let that command sort it out. (* “numbers” is .id’s cousin, and weird too, sometimes it can be a name not number too). Now, I’d avoid using name in .id, since that’s not consistent with some/most calls. And seems a bug fix away from not working in future.

If you’re using curl, few options to a “lookup by name”:

  1. If familiar with jq, it’s likely better to a GET to find all ids, pipe that to jq to filter the one you want, and pipe again to a 2nd curl with the id

  2. There is the .query syntax offered by REST API on a POST ending in /print. It’s still two REST calls, one to find .id by name (adding .query “stack” to JSON in 1st POST), the 2nd to /system/script/run. This thread has some example of using .query: http://forum.mikrotik.com/t/how-using-query-stack-in-rest-api/173298/1 & links to others with more .query examples

  3. Lookup the .id on RouterOS using “/system/script/print show-id”. That won’t change unless you delete the script. One call and easy.

Thanks for the information.

I’m only using Curl here as a quick proof of concept. My bash scripts are too fragile for production :wink:

The closest I’ve found for documentation of this is from https://help.mikrotik.com/docs/spaces/ROS/pages/47579162/REST+API

If table contains named parameters, then name instread of ID can be used, for example, get ether1:

$ curl -k -u admin: https://10.155.101.214/rest/interface/ether1

>

I would definitely recommend not using undocumented features in production  scripts. The methods you suggest are much more robust.

All “APIs” in RouterOS really go down some fixed schema. And the CLI has a tool /console/inspect that allow you kinda see the scheme, which AFAIK guides what REST accepts. FWIW, if you want do a lot with REST API, I publish the “schema” that’s usable in tools like Postman here: https://tikoci.github.io/restraml with a discussion of how to use it with Postman and VSCode here: http://forum.mikrotik.com/t/rest-api-schema-for-postman-more/169502/1

So /console/inspect does tell you want numbers is defined to & for script/run it seems to include names. If you want to get into the weeds, you can kinda see what might be going on – still kinda odd REST takes name as .id however:

> /console/inspect request=syntax  path=system,script,run



Columns: TYPE, SYMBOL, SYMBOL-TYPE, NESTED, NONORM, TEXT
TYPE SYMBOL SYMBOL-TYPE NESTED NONORM TEXT
syntax collection 0 yes
syntax explanation 1 no Item number
syntax use-script-permissions explanation 1 no


> /console/inspect request=syntax  path=system,script,run,number



Columns: TYPE, SYMBOL, SYMBOL-TYPE, NESTED, NONORM, TEXT
TYPE SYMBOL SYMBOL-TYPE NESTED NONORM TEXT
syntax ItemName definition 0 no
syntax definition 1 no _YAML | rancher | > lsbridge > | > PIANO > | …

So looks to be .id or name,… So maybe REST API must map .id to the generic if there is not an “.id”?

But leave it @jaclaz to actually try using .id with a name. Since REST API does just map into the native API, and from API it also takes a name from /system/script in a quick test. From a pure API design POV, it really should fail if you explicitly use .id IMO.

The net is “number” will take a name, which might be better than .id… — which is strange sounding, but “number” is in the schema it seems. In RouterOS [7.16+] fetch syntax (to itself), that works:

/tool/fetch user=$routerlogin password=$routerpasswd http-method=post url="http://localhost/rest/system/script/run" http-header="Content-Type: application/json" http-data=[:serialize to=json {"number"="$myscriptname"}] output=user-with-headers

“Syntactic Sugar”?
“Undocumented Feature”?
“Magic”?

Very interesting rabbit hole to go down. Thanks for that.

Now I’ll go back to wondering about the implications of http://forum.mikrotik.com/t/v7-18rc-testing-is-released/182052/1 :wink:

Nothing on the REST API. Mikrotik also has a low-level protocol called just “API”.

FWIW, they have not changed either REST API or API protocol in years (commands used do change, not the protocol itself). So that’s not a regular thing. And like I said, I don’t see any effect in REST API from the change in low-level API.

In fact, if I had to guess, they might have added !empty to improve REST API performance in some cases (since REST API likely just “wraps” the low-level based on debug logs). But I actually don’t know why !empty become a thing. But it really should not affect REST API.

To assuage fears… the “restraml” schema site above, runs 50K REST API to generate the data. So if something wholesale was broken in REST, that almost certainly catch it. But it 99% runs without issue on each build Mikrotik releases. And time it hasn’t, it wasn’t REST API at issue.

Basically “number” is the “syntactic sugar”, so in /bin/sh terms, essentially, it gets mapped to the $1 in a CLI command. Since “run” takes a name as $1, the APIs following same. Or at least that’s what I’ve seen. It’s the “.id” part that’s weird here. “number” should work POST request that show it in schema or /console/inspect - but it will follow how that particular command treats the generic “number” (which again can be name or .id, or at CLI, “print #”)

Only for the record, jaclaz has no idea about “why” it works :open_mouth: , my logic has only been that it didn’t seem possible to me that the only way to reference a script would have been enumerating it with print as such mechanism would have been too prone to possible errors (think of a complex environment where scripts are locally created and deleted by scheduler or whatever) and some other way had to exist, and looked around to find how other people used the API to run a script.
rextended has more or less consumed his keyboard typing how you should never use numbers to make reference to anything in ROS, and it is very good advice, it seemed to me impossible that it was the only way in this case.
Now, why it is seemingly not well (or at all) documented is the real mistery to me.

When you leave the standard [get] [set] [print] [remove] operators in POST (or at CLI too), some of the “rules” are broken. For example, “run” is not a standard command, it’s specialized to /system/script - kinda like a function. While things from get/set/remove are attributes (/properties), more strictly follow rules. Command like “run” are freed from such shackles, and can take what they want.

Basically a REST POST will follow the CLI. And “run” takes a name, so same can be done with REST. That part isn’t weird. I just would have expected it needing to be in { “number”: “myscript” } — I generally trust /console/inspect at being definitive to what JSON is needed REST POST. But it doesn’t mean more isn’t mapped…

But another way to look at it… is .id is just a specific form of schema’s , so .id get mapped into that. And in case of “run”, a can be name, but REST API is merely providing some value to run, which REST mapped to value of {“.id”: …}