Skip to content

Commands ​

Every command the engine has, grouped by what it works on. Each is called as POST /api/invoke/<command> with a JSON object of arguments, and answers 200 with its result or 422 with an EngineError. How to authenticate and what the statuses mean is in the API overview.

Conventions ​

  • Argument names are camelCase (jobId, nodeId). An argument a command does not know, or a required one left out, is refused with command.args_invalid; every command that takes arguments can fail with it. A command without arguments does not read the body.
  • Objects passed as an argument — config, request, document, library, emulator, profile — use the engine's own field names, mostly snake_case (timeout_ms). Inside them a field the engine does not know is ignored, so a misspelled optional field quietly keeps its default. Only feedback_send's form refuses unknown fields.
  • Optional arguments and fields may be left out or sent as null; the tables give their defaults.
  • Results are JSON. "null" means the command has nothing to return.
  • Addresses written IP:port take a numeric address and a port (127.0.0.1:9000, [::1]:9000); a host name there is refused. Where a table says IP:port or host:port, a host name works too: it is looked up when the command runs, and its IPv4 address is used when it has one (so localhost:9000 is 127.0.0.1:9000).
  • Jobs: a command marked Starts a job returns a JobInfo; the work goes on until it ends or is stopped with job_stop. See jobs.
  • Paths in results are on the machine where the engine runs — on a server, inside its data folder; download them with /api/files.

The examples use this shell function:

bash
SERVER=http://127.0.0.1:1430
TOKEN=$(cat token.txt)
invoke() {
  curl -sS -X POST "$SERVER/api/invoke/$1" \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    --data "${2:-}"
}

Application ​

app_info ​

What the engine is and where it runs. No arguments.

Result

FieldTypeMeaning
versionstringSignal Lab's version, 1.0.0
modestringdesktop or server
secrets_writablebooleanWhether secret_set and secret_delete can work here: false on a server
data_dirstringThe data folder, on the machine the engine runs on
osstringwindows, linux…
archstringx86_64, aarch64…

get_host_info ​

The machine's name and the address it would send from. No arguments.

Result: { "local_ip": string, "hostname": string }. local_ip is the IPv4 address the system picks for traffic to the internet (found without sending anything), or 127.0.0.1 when there is none. hostname is the computer's name, or localhost when the system does not say.

firewall_status ​

Whether the system's firewall lets other machines reach this program. Only Windows has a per-program firewall to read; elsewhere applies is false, and of the rest only program is filled in. No arguments.

Result

FieldTypeMeaning
appliesbooleanThere is a per-program firewall here (Windows)
programstringThe program the rules are about
enabledbooleanThe firewall is on for the network the machine is on now
networksstring[]The kinds of network the machine is on: domain, private, public
allowedbooleanAn inbound rule lets UDP in for this program on the current network
blockedbooleanAn inbound rule blocks this program on the current network; it wins over any allow rule
rulesnumberInbound rules for this program, of any kind

Errors: firewall.failed.

firewall_allow ​

Lets other machines reach Signal Lab: the system shows its own administrator prompt, then the program's inbound rules (a block rule included) are replaced by one allow rule each for Signal Lab and the signallab command line next to it. Desktop app on Windows only; a server refuses, since nobody is at its screen to answer the prompt.

ArgumentTypeRequiredMeaning
publicbooleanyesAllow on public networks too, not only private and domain ones

Result: the new firewall_status.

Errors: firewall.server (on a server), firewall.unsupported (not Windows), firewall.declined (the prompt was answered No), firewall.failed.

feedback_send ​

Sends a message to Signal Lab's developers, through the studio's hub, which mails it to them.

ArgumentTypeRequiredMeaning
formobjectyesThe message, below. Unknown fields are refused
Field of formTypeDefaultMeaning
messagestring—What happened; required, at most 20 000 characters
emailstringnoneWhere an answer may go
metaobject of strings{}What the app says about itself (version, os, arch, mode, lang, screen)
screenshots{ name, data }[][]Images, data in base64; at most 6, 8 MiB each
logs{ name, text }[][]Text files; at most 4, 2 MiB each

Everything together is at most 15 MiB.

Result: { "id": string }, the reference the developers get.

Errors: feedback.message_required, feedback.message_too_long, feedback.too_many_files, feedback.file_too_large, feedback.too_large, feedback.invalid, the hub's refusals (feedback.email_invalid, feedback.file_type, feedback.rate_limited, feedback.disabled, feedback.send_failed, feedback.failed), and the network's transport.*.

bash
invoke app_info
# {"version":"1.0.0","mode":"server","secrets_writable":false,"data_dir":"/data","os":"linux","arch":"x86_64"}

Jobs ​

jobs_list ​

The running jobs, oldest first. No arguments.

Result: JobInfo[].

job_stop ​

Stops one job at once: its sockets close, its relay, server or connection goes. A stopped job sends no job://ended; a stopped run saves no report.

ArgumentTypeRequiredMeaning
idnumberyesThe job's id

Result: true when a job with that id was running, false otherwise.

jobs_stop_all ​

Stops every running job, whoever started it. No arguments.

Result: null.

bash
invoke jobs_list
# [{"id":3,"kind":"osc-monitor","label":"OSC monitor 0.0.0.0:9000","params":{"bind":"0.0.0.0:9000"},"started_ms":1759600000000}]
invoke job_stop '{"id":3}'
# true

Experiments and runs ​

These commands take and return an experiment document (Experiment): the JSON the editor saves and exports, with version, name, params, profiles, profile, seed, cookies, nodes and edges. Its nodes are in nodes; its parameters, profiles and templates in data. A document is at most 4 MiB (file.too_large). To run an experiment and wait for its result, use POST /api/run rather than experiment_start.

experiment_load ​

The working experiment: experiment.json in the data folder — on a server, the one its interface shows. When there is none, the starter experiment. Older document versions are migrated. No arguments.

Result: Experiment.

Errors: file.io, file.json_invalid (with the file's path, line and column), file.too_large, doc.version_unsupported and the other doc.* checks.

experiment_save ​

Replaces the working experiment, experiment.json in the data folder. It is written to a temporary file first, so a failed write leaves the previous one.

WARNING

On a server this is the document every browser's editor works on.

ArgumentTypeRequiredMeaning
documentExperimentyesThe document

Result: string, the path written.

Errors: doc.*, the document's size checks (param.*, params.too_many, profile.*, profiles.too_many, seed.range), file.too_large, file.io.

experiment_parse ​

Reads an experiment from JSON text, as Open JSON… does. Versions 1 to 8 are migrated to version 9, the current one; a file from before version 8 opens with cookies off, so it runs as it did. A byte-order mark is skipped.

ArgumentTypeRequiredMeaning
textstringyesThe file's text

Result: Experiment.

Errors: file.json_invalid (line, column), file.too_large, doc.version_unsupported, doc.*, the size checks of experiment_save.

experiment_export ​

Writes a snapshot of a document to exports/experiment-<ms>-<16 hex digits>.json in the data folder. Every export is a new file.

ArgumentTypeRequiredMeaning
documentExperimentyesThe document

Result: string, the path written.

Errors: those of experiment_save.

experiment_validate ​

Checks that a document would run with its active profile (or its defaults): the graph, every field, parameters, and that every secret it names is stored. A blocking problem is the error. On success, it says which of the other profiles would fail, so you know before switching.

ArgumentTypeRequiredMeaning
documentExperimentyesThe document
overridesobject of stringsnoParameter values for this check only, as Run with… gives them

Result: { "profile": string or null, "error": EngineError }[] — each other profile that would not validate (null: the defaults, without a profile). An empty list means every profile is fine.

Errors: any validation code (doc.*, graph.*, node.*, param.*, profile.*, template.*, loop.*…), run.override_unknown (an override for a parameter the document does not have), secret.missing, secret.store, secret.unsupported.

experiment_resolve ​

One node with its templates filled in, as the editor's preview shows it: the active profile's values and the variable values you give. Secrets are shown as ••••, never their values. Names that have no value stay as written and are listed.

ArgumentTypeRequiredMeaning
documentExperimentyesThe document
nodeIdstringyesThe node
varsobjectyesVariable values to use, by name; {} for none

Result: { "node": node, "missing": string[] }.

Errors: node.not_found, template.*, secret.store, secret.unsupported.

experiment_send_node ​

Send now: performs one node on its own, through the same code a run uses. An action is sent; a wait listens from now on until it matches or times out. A WebSocket send or Wait for WebSocket node opens the connection its WebSocket connect node describes. Nothing is sent with cookies, and the run's Impairment and Emulator nodes are not opened.

ArgumentTypeRequiredMeaning
documentExperimentyesThe document; its active profile gives the parameter values
nodeIdstringyesAn action or a wait
varsobjectyesVariable values the node's templates read; {} for none

Result

FieldTypeMeaning
detailstringWhat happened, in English
responseHttpResponse or nullAn HTTP node's response
varsobjectWhat the step set: a wait's reply, or what the Extract value nodes after a request take from its response

Secret values are masked in all of it.

Errors: node.not_found, run.not_an_action (not an action or a wait), ws.connection_unknown, secret.missing, template.*, and whatever the step fails with: transport.*, wait.timeout, check.*…

experiment_start ​

Starts a run, as Run experiment does, and returns at once. Its steps arrive as experiment://step events, its end as experiment://ended, and its report is saved under runs/ in the data folder. Waits, emulators, impairment relays and MQTT subscriptions open before the first step, so a port that is taken fails here. A run longer than 300 s fails with run.timeout. Starts a job (experiment).

ArgumentTypeRequiredMeaning
documentExperimentyesThe document
overridesobject of stringsnoParameter values for this run only
seednumbernoThe run's seed, 0 to 9007199254740991; default: the document's, else a new one

Result: JobInfo, params.name the experiment's name.

Errors: everything experiment_validate reports, seed.range, transport.address_in_use and the other bind failures, emulator.*, impair.*, node.params_only (a listen address or an MQTT wait's broker or topic that is not fixed when the run starts), and the mqtt_connect errors of a broker an MQTT wait cannot reach.

experiment_runs ​

Runs read back from their reports in runs/, newest first. A report that cannot be read is left out.

ArgumentTypeRequiredMeaning
namestringnoOnly runs of the experiment with this exact name
limitnumbernoAt most this many; default 50, at most 500

Result: run summaries:

FieldTypeMeaning
namestringThe report's file name, run-<ms>-<job>.json: what experiment_compare takes
experimentstringThe experiment's name
started_ms, ended_msnumberMilliseconds since 1970
outcomestringpassed or failed
seednumberThe run's seed
profilestring or nullIts profile
loadsobject[]Each load step: node, sent, rps, p95_ms, error_rate, held (every threshold held)

Errors: file.io.

experiment_compare ​

Two runs side by side, load step by load step, as the timeline's Compare shows them. Steps are matched by node id.

ArgumentTypeRequiredMeaning
astringyesThe earlier run's report file name
bstringyesThe later run's report file name

Result: { "a": summary, "b": summary, "steps": [...] }, each step with node, missing_in (a or b, when only one run has it), metrics, sent ([a, b]), thresholds_a and thresholds_b (each threshold as { metric, op, value, actual, held }). metrics lists nine metrics, each as { metric, a, b, change, percent, worse }: metric is p50_ms, p90_ms, p95_ms, p99_ms, mean_ms, max_ms, error_rate, rps or missed; change is b − a; percent the change in % of a (null when a is 0); worse that it moved the wrong way — higher, or lower for rps — by 5 % or more, or from 0 to anything. A step only one run has is never worse. See load.

Errors: runs.name_invalid (anything but a report's file name, no folders), runs.not_found, file.json_invalid, file.io.

bash
invoke experiment_validate "$(jq '{document: .}' experiment.json)"
# []

Secrets ​

Secret values are used by experiments as {{secret.NAME}} and never leave the engine: no command returns one. Where they are kept depends on where the engine runs:

WhereStoreSetting and removing
Desktop app, WindowsWindows Credential ManagerYes
Desktop app, LinuxNonesecret.unsupported
ServerSIGNALLAB_SECRET_<NAME>, or the file <NAME> in --secrets-dir (default /run/secrets/signallab)No: secret.read_only

A name starts with a Latin letter or _, goes on with Latin letters, digits and _, and has at most 128 characters (secret.name_invalid).

secret_status ​

Which of the given names have a stored value.

ArgumentTypeRequiredMeaning
namesstring[]yesThe names to look up

Result: an object, name → true (stored) or false.

Errors: secret.name_invalid (on a server), secret.store, secret.too_large (a server's file over 16 KiB), secret.unsupported.

secret_set ​

Stores a value under a name, replacing the one there. Desktop app only.

ArgumentTypeRequiredMeaning
namestringyesThe name
valuestringyesNot empty; at most 16 KiB

Result: null.

Errors: secret.read_only (on a server), secret.unsupported, secret.name_invalid, secret.empty, secret.too_large, secret.store.

secret_delete ​

Removes a stored value. Removing one that is not stored is not an error. Desktop app only.

ArgumentTypeRequiredMeaning
namestringyesThe name

Result: null.

Errors: secret.read_only (on a server), secret.unsupported, secret.name_invalid, secret.store.

bash
invoke secret_status '{"names":["API_TOKEN","MQTT_PASSWORD"]}'
# {"API_TOKEN":true,"MQTT_PASSWORD":false}

OSC ​

See OSC for the screen these commands serve.

osc_send ​

Sends one OSC message in one UDP datagram, from a fresh socket.

ArgumentTypeRequiredMeaning
targetstringyesIP:port or host:port to send to; a name is looked up, its IPv4 address taken when it has one
addressstringyesThe OSC address, /mixer/fader/1; it starts with /
argsOscArg[]yesThe arguments; [] for none

Result: number, the bytes sent.

Errors: node.osc_address (no leading /; field address), transport.target_invalid (no port, or neither form), transport.dns (the name does not resolve), transport.*.

osc_monitor_start ​

Listens for OSC on a UDP port and decodes every packet. Each one arrives as an osc://message event. Starts a job (osc-monitor, params.bind).

ArgumentTypeRequiredMeaning
bindstringyesIP:port to listen on: 0.0.0.0:9000 every network card, 127.0.0.1:9000 this machine only

Result: JobInfo.

Errors: node.bind_invalid, transport.address_in_use, transport.address_unavailable, transport.denied, wait.bind_failed. The job ends with wait.receive_failed if the socket can no longer receive.

osc_generator_start ​

Sends a stream of OSC messages whose single argument follows a waveform. Progress arrives as osc://gen-tick. Starts a job (osc-gen, params.target, params.address).

ArgumentTypeRequiredMeaning
configobjectyesBelow
Field of configTypeDefaultMeaning
targetstring—IP:port or host:port to send to; a name is looked up once, when the job starts
addressstring—The OSC address; it starts with /
ratenumber—Messages per second, held between 0.1 and 5000
waveformstring—sine, triangle, saw (falling: max to min, then back at once), ramp (rising: min to max, then back at once), square, random or constant (max)
freqnumber—Cycles of the waveform per second
min, maxnumber—The value's range
as_intbooleanfalseRound and send an int instead of a float
duration_snumber0Stop after this many seconds; 0 runs until stopped

Result: JobInfo.

Errors: node.osc_address, transport.target_invalid, transport.dns, transport.*. The job ends with a transport.* error if a send fails.

bash
invoke osc_send '{"target":"127.0.0.1:9000","address":"/cue/go","args":[{"type":"int","value":1}]}'
# 16
invoke osc_monitor_start '{"bind":"0.0.0.0:9000"}'

HTTP and cookies ​

See HTTP.

http_request ​

Sends one HTTP request and returns the response. A request that gets no response — refused, timed out, a name that does not resolve, a certificate that is not trusted — is not an error of the command: the response says so in error and cause.

ArgumentTypeRequiredMeaning
requestHttpRequestyesThe request
cookiesbooleannoSend the HTTP screen's cookie jar and keep what the answer sets; default false

Result: HttpResponse.

Errors: http.client_failed (the request could not even be prepared).

http_burst_start ​

Sends one request many times, several at once, and measures it. Without a rate, each worker sends again as soon as it has an answer; with one, requests start on a fixed schedule however slow the answers are, and a request that waited more than 50 ms past its moment for a free worker is skipped and counted as missed. Progress arrives as http://burst-progress ten times a second. Starts a job (http-burst, params.method, params.url, and params.rate when paced).

ArgumentTypeRequiredMeaning
configobjectyesThe fields of HttpRequest and the ones below, in one object
Field of configTypeDefaultMeaning
concurrencynumber—At most this many in flight, held between 1 and 512
totalnumber0Stop after this many requests; 0: no count
duration_snumber0Stop after this many seconds; 0: no time limit
ratenumber0Requests started per second, 0.1 to 100 000; 0: as fast as answers come
cookiesbooleanfalseUse the HTTP screen's cookie jar

With neither total nor duration_s, the burst runs until stopped.

Result: JobInfo.

Errors: http.rate_invalid, http.duration_invalid, http.client_failed.

http_cookies ​

The HTTP screen's cookie jar: every cookie that has not expired. On a server there is one jar for every page and script. No arguments.

Result: cookies, each with name, value, domain, host_only (no Domain attribute: only the host that set it gets it back), path, expires (Unix seconds, null for a session cookie), secure, http_only and same_site (string or null).

http_cookies_clear ​

Empties the HTTP screen's cookie jar. No arguments.

Result: null.

bash
invoke http_request '{"request":{"method":"GET","url":"http://127.0.0.1:8080/health","headers":[["Accept","application/json"]],"body":null,"timeout_ms":5000}}' \
  | jq '{status, latency_ms, body}'

WebSocket ​

See WebSocket. A connection that ws_connect opens is a job; the others name it by jobId.

ws_connect ​

Opens a WebSocket and keeps it open. What arrives and what is sent comes as ws://messages every 100 ms; the connection's state as ws://state. Starts a job (websocket, params.url).

ArgumentTypeRequiredMeaning
configWsConfigyesWhere and how to connect

Result: JobInfo.

Errors: ws.url_invalid, ws.header_invalid, ws.protocol_invalid, ws.handshake_status (the server answered the upgrade with another status), ws.subprotocol_refused, ws.handshake_failed, transport.*.

ws_send ​

Sends one message on an open connection.

ArgumentTypeRequiredMeaning
jobIdnumberyesThe connection's job
messageobjectyes{ "text": "…" } for a text message, or { "hex": "de ad be ef" } for a binary one; exactly one of them

Result: number, the bytes sent.

Errors: ws.not_connected, ws.payload_required (neither or both), hex.invalid (an empty hex too), node.too_long (over 16 MiB, field payload; nothing is sent and the connection stays open), ws.closed, transport.* (transport.timeout when the server stopped reading for 10 s).

ws_close ​

Closes a connection with a close handshake and waits up to 2 s for the server's answer; the job then ends. Once a connection has ended, its job is gone and closing it is ws.not_connected.

ArgumentTypeRequiredMeaning
jobIdnumberyesThe connection's job
codenumberno1000, or 3000 to 4999 for an application's own; default 1000
reasonstringnoAt most 123 bytes; default empty

Result: { "code", "reason", "by", "error" } — by is client, server or lost; code is 1005 when the close carried none and 1006 when there was no close frame.

Errors: ws.close_code, node.too_long, ws.not_connected.

ws_exchange ​

One exchange without a job: connect, send a message if given, wait for an answer if asked to, close.

ArgumentTypeRequiredMeaning
configWsConfigyesWhere and how to connect
messageobjectno{ "text" } or { "hex" }, as for ws_send
expectobjectnoWhat to wait for: mode (any, contains, regex, hex; default any), pattern (default empty), timeout_ms (default 2000)

With expect and no message, the first matching message after connecting counts — a greeting.

Result: { "handshake", "sent", "reply", "closed" } — handshake is { url, peer, local, protocol, ms }; sent the bytes sent or null; reply{ kind, text, hex, bytes, json, ms } or null (json: a text answer parsed, else null; ms: since the send, or since connecting when nothing was sent); closed as ws_close returns.

Errors: those of ws_connect and ws_send, wait.timeout (with ms, unmatched and target), regex.invalid and hex.invalid (a pattern that does not parse).

bash
invoke ws_exchange '{"config":{"url":"ws://127.0.0.1:9001/"},"message":{"text":"{\"type\":\"ping\"}"},"expect":{"mode":"contains","pattern":"pong"}}' \
  | jq .reply.text

MQTT ​

MQTT 3.1.1 over plain TCP, QoS 0, 1 and 2. See MQTT.

mqtt_connect ​

Connects to a broker and keeps the connection. The command returns once the broker has accepted the connection (CONNACK), so a wrong password or a closed port is its error. Messages arrive as mqtt://messages every 100 ms; state changes as mqtt://state; completed QoS 1/2 publishes and unsubscribes as mqtt://ack. Starts a job (mqtt, params.broker, params.client).

ArgumentTypeRequiredMeaning
configMqttConfigyesThe broker and how to connect

Result: JobInfo.

Errors: mqtt.client_id_required, transport.* (refused, unreachable, dns, timeout after 6 s), mqtt.no_answer (no CONNACK within 6 s), mqtt.protocol, mqtt.refused_protocol, mqtt.refused_client_id, mqtt.refused_unavailable, mqtt.refused_credentials, mqtt.refused_not_authorized, mqtt.refused.

mqtt_publish ​

Publishes on an open connection.

ArgumentTypeRequiredMeaning
jobIdnumberyesThe connection's job
topicstringyesThe topic: not empty, and no + or #
payloadstringyesThe payload, sent as UTF-8
qosnumberyes0, 1 or 2 (above 2 is sent as 2)
retainbooleanyesAsk the broker to keep it; an empty payload with retain clears a retained value

Result: null. A QoS 1 or 2 publish is confirmed later by mqtt://ack.

Errors: node.topic_wildcard (field topic) and mqtt.topic_required (field topic), as for mqtt_publish_once — the command refuses them before it looks for the connection; mqtt.not_connected.

mqtt_subscribe ​

Subscribes an open connection to filters. What the broker grants arrives as mqtt://state with state: "subscribed".

ArgumentTypeRequiredMeaning
jobIdnumberyesThe connection's job
filters{ filter, qos }[]yesAt least one; qos defaults to 0. + and # are wildcards

Result: null.

Errors: mqtt.filter_required, mqtt.not_connected.

mqtt_unsubscribe ​

Unsubscribes an open connection from filters.

ArgumentTypeRequiredMeaning
jobIdnumberyesThe connection's job
filtersstring[]yesAt least one

Result: null. The broker's answer arrives as mqtt://ack with kind: "unsubscribed".

Errors: mqtt.filter_required, mqtt.not_connected.

mqtt_publish_once ​

Connects, publishes one message, waits for the acknowledgement its QoS asks for (up to 6 s), disconnects. It brings its own connection, under a client id of its own — the first 12 characters of client_id, -o and a number — so it never knocks a live connection with that id off the broker.

ArgumentTypeRequiredMeaning
configMqttConfigyesThe broker; subscribe is not used
topicstringyesNot empty and without + or #
payloadstringyesSent as UTF-8
qosnumberyes0, 1 or 2 (above 2 is sent as 2)
retainbooleanyesAsk the broker to keep it

Result: string, a summary written by Signal Lab: <topic> → <broker> · <bytes> B · qos<n>, with retained when retained.

Errors: mqtt.topic_required, node.topic_wildcard, and those of mqtt_connect but mqtt.client_id_required: an empty client_id is accepted here.

bash
invoke mqtt_publish_once '{"config":{"host":"127.0.0.1","port":1883,"client_id":"lab"},"topic":"lab/lamp/set","payload":"ON","qos":1,"retain":false}'
# "lab/lamp/set → 127.0.0.1:1883 · 2 B · qos1"

Broadcast, multicast and discovery ​

See broadcast and discovery.

DANGER

Broadcast and a sweep reach every host of a network segment. Send only on networks you are responsible for.

broadcast_send ​

Sends one datagram to each target, once.

ArgumentTypeRequiredMeaning
configobjectyesBelow
Field of configTypeDefaultMeaning
modestring—list, broadcast, multicast or sweep
targetstring—By mode, below
portnumber0The port, for sweep only
payloadPayload—What each datagram carries
bindstringanyThe local IP:port it is sent from; empty or null: 0.0.0.0:0 ([::]:0 when every target is IPv6)
ttlnumber1IP TTL, or the multicast hop limit; 1 to 255
multicast_loopbooleantrueMulticast comes back to this machine too
rate, count, duration_snumber0For broadcast_beacon_start only
modetarget
listIP:port or host:port entries separated by commas, semicolons or new lines (not by spaces); a name is looked up, its IPv4 address taken when it has one
broadcast255.255.255.255:port, or an address ending in .255 with its port
multicastA group from 224.0.0.0 to 239.255.255.255 with its port
sweepA CIDR block, 192.0.2.0/24: every usable host on port; at most 1024 hosts, so /22 or narrower

Result

FieldTypeMeaning
targetsnumberDestinations
packets, bytesnumberWhat went out
errorsnumberDatagrams that could not be sent
resolvedstring[]The first 8 destinations
summarystringThe payload in one line
errorEngineErrorWhy the first failed datagram failed; left out when none did

Errors: broadcast.target_required, broadcast.not_broadcast, broadcast.ipv6, broadcast.not_multicast, broadcast.sweep_port, broadcast.cidr_invalid, broadcast.prefix_invalid, broadcast.sweep_too_large, node.osc_address (an OSC address must start with /), hex.empty, hex.invalid, node.bind_invalid, socket.option_failed, transport.target_invalid, transport.dns, bind failures.

broadcast_beacon_start ​

Sends the same round — one datagram per target — again and again. Its counters arrive as broadcast://emit-stat every 250 ms. After more than 32 failed sends with none sent at all, it stops with the reason. Starts a job (beacon, params.mode, params.target, params.targets, params.rate).

ArgumentTypeRequiredMeaning
configobjectyesAs for broadcast_send, with the three below
Field of configTypeDefaultMeaning
ratenumber—Rounds per second; above 0, and rounds × targets at most 50 000 datagrams per second
countnumber0Stop after this many rounds; 0: no count
duration_snumber0Stop after this many seconds; 0: until stopped

Result: JobInfo.

Errors: those of broadcast_send, broadcast.rate_invalid, broadcast.rate_limit.

discovery_start ​

Listens on a UDP port, keeps a list of every peer that sends something, and can answer probes as a device would. The peers arrive as broadcast://peers every 400 ms. Starts a job (discovery, params.bind, params.groups, params.joined).

ArgumentTypeRequiredMeaning
configobjectyesBelow
Field of configTypeDefaultMeaning
bindstring—IP:port to listen on
groupsstring[][]Multicast groups to join (IPv4)
interfacestringanyThe local IPv4 address to join the groups on
reusebooleantrueShare the port with a program already listening on it (SO_REUSEADDR)
respondbooleanfalseAnswer what arrives
responsePayloadnoneThe answer; needed with respond
respond_delay_msnumber0Wait this long before answering
match_containsstringnoneAnswer only datagrams whose text contains this

At most 512 peers are listed; later ones are not added.

Result: JobInfo.

Errors: node.bind_invalid, broadcast.port_shared (the port is taken and reuse is off), broadcast.interface_invalid, broadcast.not_multicast, broadcast.join_failed, broadcast.reply_missing, node.osc_address, hex.*, bind failures. The job ends with wait.receive_failed if the socket can no longer receive.

bash
invoke broadcast_send '{"config":{"mode":"list","target":"127.0.0.1:9000, 127.0.0.1:9001","payload":{"kind":"text","text":"PING"}}}' \
  | jq '{packets, errors}'

Impairment ​

A relay between a client and its server that delays, drops, duplicates, corrupts, reorders or throttles what passes, over UDP or TCP. See impairment.

netsim_start ​

Starts a relay: what arrives on listen goes on to target, and the answers come back the same way, both impaired by the profile. Its counters arrive as netsim://stat every 250 ms. Starts a job (netsim, params.listen, params.target, and params.protocol for TCP).

ArgumentTypeRequiredMeaning
configobjectyesBelow
Field of configTypeDefaultMeaning
listenstring—IP:port the relay listens on; point the client here
targetstring—IP:port of the real server, or host:port — a host name is looked up once, when the relay starts
profileImpairProfile—What to do to the traffic
seednumbernewThe draws' seed: the same seed and the same traffic give the same drops
protocolstringudpudp (datagrams) or tcp (streams)

Result: JobInfo.

Errors: node.range (a value of the profile outside its range, with min, max and the field), node.too_long, node.bind_invalid, transport.target_invalid, transport.dns (a target name that cannot be found), bind failures. The job ends with wait.receive_failed if a socket can no longer receive.

netsim_set_profile ​

A running relay impairs with another profile from now on, without closing its sockets.

ArgumentTypeRequiredMeaning
jobIdnumberyesThe relay's job
profileImpairProfileyesThe new profile

Result: null.

Errors: netsim.not_running, node.range, node.too_long.

bash
invoke netsim_start '{"config":{"listen":"127.0.0.1:9010","target":"127.0.0.1:9000","profile":{"latency_ms":80,"jitter_ms":20,"loss":0.02}}}'

Storm and scanner ​

DANGER

A storm loads a target as hard as you ask, and a scan probes every port of a range. Aim them only at hosts you are responsible for.

storm_start ​

Sends a steady load of UDP datagrams or TCP connections to one target. Its counters arrive as storm://stat every 250 ms. Starts a job (storm, params.protocol, params.target, params.rate).

ArgumentTypeRequiredMeaning
configobjectyesBelow
Field of configTypeDefaultMeaning
targetstring—IP:port or host:port; a name is looked up once, when the job starts
protocolstring—udp: datagrams; tcp: a connection per unit that writes the payload and closes (each connect may take 500 ms)
sizenumber—Payload bytes, held between 1 and 65 507
ratenumber—Units per second, on a schedule: unit n is due n / rate seconds after the start, and each wake sends what is due (at most 256; a schedule further behind skips the older units); 0 sends as fast as it can
duration_snumber0Stop after this many seconds; 0: until stopped

Result: JobInfo.

Errors: transport.target_invalid, transport.dns. Failed sends are counted in the events, not reported as errors.

scan_start ​

Tries a TCP connection to every port of a range and reports the open ones, with what the service says first when asked. Open ports arrive as scan://open, progress as scan://progress. Starts a job (scan, params.host, params.from, params.to).

ArgumentTypeRequiredMeaning
configobjectyesBelow
Field of configTypeDefaultMeaning
hoststring—A host name or an address
port_start, port_endnumber—The range, both included; given the wrong way round, they are swapped
concurrencynumber256Attempts at once, 1 to 1024
timeout_msnumber600Per port, 50 to 10 000
grab_bannerbooleanfalseRead up to 256 bytes the service sends within 400 ms of connecting

Result: JobInfo.

Errors: scan.host_required.

bash
invoke scan_start '{"config":{"host":"127.0.0.1","port_start":8000,"port_end":9100,"grab_banner":true}}'

Inspector ​

The Inspector records what the tools send and receive, as frames, while capture is armed. On a server there is one Inspector for every page and script. See the Inspector.

inspect_set_enabled ​

Arms or disarms capture. While disarmed nothing is recorded.

ArgumentTypeRequiredMeaning
enabledbooleanyesArm (true) or disarm

Result: CaptureStats.

inspect_stats ​

The capture's counters. No arguments.

Result: CaptureStats.

inspect_snapshot ​

The newest frames, oldest first.

ArgumentTypeRequiredMeaning
limitnumberyesHow many, 1 to 8192

Result: Frame[], without their bytes (see inspect_payload).

inspect_clear ​

Empties the capture and its counters. No arguments.

Result: CaptureStats.

inspect_export ​

Writes every frame held to capture-<ms>.jsonl or capture-<ms>.txt in the data folder. In jsonl, each line is a frame with the bytes it keeps in data, base64; txt is for reading, with a hex dump of each frame.

ArgumentTypeRequiredMeaning
formatstringyestxt; anything else writes jsonl

Result: string, the path written.

Errors: inspect.empty, file.io.

inspect_payload ​

The bytes a frame keeps, past the 1 KiB preview its batch carried.

ArgumentTypeRequiredMeaning
seqnumberyesThe frame's number

Result: { "seq", "bytes", "kept", "dump", "hex" } — bytes the frame's size, kept how many of them are kept (up to 256 KiB), dump every row as offset hex |ascii|, hex the plain hex a replay sends.

Errors: inspect.frame_gone (newer frames took its place), inspect.no_payload (only its size was recorded).

bash
invoke inspect_set_enabled '{"enabled":true}'
invoke inspect_snapshot '{"limit":20}' | jq '.[] | {seq, proto, dir, summary}'

Signal library ​

The library is signals.json in the data folder. It is only storage: a signal is sent with the command of its transport (osc_send, broadcast_send, http_request, mqtt_publish or mqtt_publish_once). See signals and files.

signals_load ​

Reads the library. When the file does not exist, the starter set is written first. No arguments.

Result: { "path": string, "library": library, "seeded": boolean } — seeded is true when the starter set was just written. The library is { "version", "signals": [...], "folders": [...] }: version 2 (a version 1 file comes back as it is), folders left out when there are none. Each signal has id, name, group (its folder, "A/B"; empty for none), note and body.

Errors: signals.json_invalid (with path, line, column; the file is never replaced), file.io.

signals_save ​

Replaces the whole library file, through a temporary file in the same folder. A file that exists and does not read as a library is left as it is.

ArgumentTypeRequiredMeaning
libraryobjectyes{ version, signals, folders } as signals_load returns it

Result: string, the path written.

Errors: signals.json_invalid (the file now on disk does not read, with path, line, column; nothing is written), signals.encode, file.io.

A signal's body by its transport:

transportFields
osctarget, address, args (OscArg[])
udptarget, payload: { "kind": "text", "text" } or { "kind": "hex", "hex" }
httprequest (HttpRequest)
mqttbroker (host:port), topic, payload, qos, retain
bash
invoke signals_load | jq '.library.signals[] | {name, transport: .body.transport}'

Emulators ​

An emulator is Signal Lab playing the other side: an HTTP API, an OSC, UDP or TCP device, an MQTT broker. Its document — name, bind, protocol, the protocol's rules and an optional outage — is described in emulators. The library is emulators.json in the data folder.

emulators_load ​

Reads the emulator library. When the file does not exist, the starter set is written first. No arguments.

Result: { "path", "library": { "version": 1, "emulators": [{ "id", "note", "emulator" }] }, "seeded" }.

Errors: emulators.json_invalid (with path, line, column; never replaced), file.io.

emulators_save ​

Replaces the whole emulator library, through a temporary file.

ArgumentTypeRequiredMeaning
libraryobjectyes{ version, emulators } as emulators_load returns it

Result: string, the path written.

Errors: emulators.encode, file.io.

emulator_check ​

Whether an emulator would start: everything emulator_start checks before it binds.

ArgumentTypeRequiredMeaning
emulatorobjectyesThe emulator document
paramsobject of stringsnoValues its templates read as parameters

Result: null when it would start.

Errors: emulator.*; node.* for a field missing, out of range, too long or malformed (node.required, node.range, node.too_long, node.bind_invalid, node.target_invalid, node.method_invalid…); param.unknown, template.*, osc.pattern_*, regex.invalid, hex.invalid. Each has rule, retained or response in params when the problem is in one of them.

emulator_start ​

Starts an emulator as a job of its own. Its socket is open when the command returns. What it receives and answers arrives as emulator://activity every 200 ms when something changed. Starts a job (emulator, params.name, params.protocol, params.local, and params.source when source is given).

ArgumentTypeRequiredMeaning
emulatorobjectyesThe emulator document
paramsobject of stringsnoValues its templates read as parameters
seednumbernoIts seed, 0 to 9007199254740991; default: a new one
sourcestringnoThe library entry it comes from, kept on the job as params.source

Result: JobInfo.

Errors: those of emulator_check, seed.range, transport.address_in_use and the other bind failures.

emulator_exchanges ​

What a running emulator received and answered. It keeps the last 500 exchanges.

ArgumentTypeRequiredMeaning
jobIdnumberyesThe emulator's job
afternumbernoOnly exchanges numbered above this; default 0
limitnumbernoAt most this many, 1 to 500; default 500

Result

FieldTypeMeaning
job_idnumberThe job
name, protocol, localstringThe emulator, its protocol and the address it listens on
countsobjecttotal, unmatched (no rule took it), failed, down (arrived while it was down: its outage or emulator_down), hits (per rule), and missed (MQTT: messages a client too far behind did not get; left out while 0)
forcedstringunavailable, reset or timeout while it is taken down; left out otherwise
exchangesobject[]Each: seq, ts, from, request, rule (1-based; left out when none took it), reply, status, fault, ms, error, frame, down, and data (the request as templates read it)

Errors: emulator.not_running.

emulator_down ​

Takes a running emulator down until it is brought up, whatever its outage schedule says, or brings it back. While down, an HTTP emulator meets each request with fault, a TCP device and an MQTT broker drop their connections and refuse new ones, and OSC and UDP devices answer nothing.

ArgumentTypeRequiredMeaning
jobIdnumberyesThe emulator's job
downbooleanyesDown (true) or up
faultstringnoWhat HTTP requests meet: unavailable (503, without Retry-After: when it comes back is not known), reset (the connection closes), timeout (no answer); default unavailable

Result: null.

Errors: emulator.not_running.

bash
invoke emulator_exchanges '{"jobId":5,"after":0}' | jq '.counts, (.exchanges[] | {request, rule, status})'

Shared types ​

JobInfo ​

What a command that starts a job returns, and what jobs_list lists: id, kind, label (English, for logs), params (the values the label names; left out when none) and started_ms. See jobs.

OscArg ​

One OSC argument, its type and its value:

typevalueOSC tag
int32-bit integeri
floatnumber, sent as 32-bit floatf
strstrings
long64-bit integerh
doublenumber, 64-bitd
booltrue or falseT or F
blobarray of bytes, [222, 173]b
nilnone: { "type": "nil" }N

HttpRequest ​

FieldTypeDefaultMeaning
methodstring—GET, POST…
urlstring—http:// or https://
headers[name, value][][]Request headers
bodystring or nullnullThe body
timeout_msnumber10000For the whole exchange
authobjectnone{ "scheme": "basic", "username", "password" }, { "scheme": "digest", "username", "password" } or { "scheme": "bearer", "token" }

Up to 10 redirects are followed. Credentials and cookies typed for one host never go on to another. A Digest request answers the server's 401 challenge and sends again.

HttpResponse ​

FieldTypeMeaning
okbooleanA 2xx status
status, status_textnumber, stringThe status; 0 and empty without a response
latency_msnumberUntil the whole body arrived
headers[name, value][]Response headers
bodystringThe body as text, at most 256 KiB
body_bytesnumberThe body's full size
truncatedbooleanbody was cut at 256 KiB
errorstring or nullWhy there was no response, every layer of the cause
causestring or nullWhat kind of failure: refused, timeout, dns, unreachable, reset, address_in_use, address_unavailable, denied, tls, target_invalid, failed — the same as the transport.* codes
digestobjectA Digest request that met a 401 only; left out otherwise. challenged: the challenge was answered and the request sent again. error: why it could not be, an EngineError (http.digest_not_offered, http.digest_unsupported, http.digest_invalid, http.digest_other_origin) or null

Payload ​

What a broadcast or discovery datagram carries: { "kind": "osc", "address", "args" }, { "kind": "text", "text" } (sent as is, no terminating zero) or { "kind": "hex", "hex" } (de ad be ef, deadbeef, 0xDE,0xAD — anything but hex digits is ignored).

MqttConfig ​

FieldTypeDefaultMeaning
hoststring—The broker's name or address
portnumber—Usually 1883
client_idstring—Not empty; another connection with the same id is knocked off by the broker
username, passwordstringemptyusername is sent when not empty; password only along with a username
keep_alive_snumber60Pings go at half of it; 0: none
clean_sessionbooleantrueThe CONNECT flag
willobject or nullnull{ topic, payload, qos, retain }, published by the broker if the connection is lost
subscribe{ filter, qos }[][]Subscribed as soon as the connection is up

WsConfig ​

FieldTypeDefaultMeaning
urlstring—ws:// or wss:// (wss:// trusts what the system trusts for HTTPS)
headers[name, value][][]Sent with the upgrade request
protocolsstring[][]Subprotocols to offer, in order of preference
timeout_msnumber10000For the connection, TLS and the upgrade together

Messages are at most 16 MiB either way.

ImpairProfile ​

Every field is optional; what is left out does nothing. Probabilities are 0 to 1.

FieldRangeMeaningUDPTCP
nameat most 60 charactersA label for the timeline and the reportyesyes
latency_ms0 to 60 000Delay added to everythingyesyes
jitter_ms0 to 60 000Up to this much more, drawn each timeyesyes
loss0 to 1A datagram is droppedyes—
duplicate0 to 1A datagram is sent twiceyes—
corrupt0 to 1One bit of a datagram is flippedyes—
reorder0 to 1A datagram is held back so later ones overtake ityes—
rate_kbps0, or 8 to 10 000 000Bandwidth limit, kilobits per second; 0: noneyesyes
burst_start0 to 1A datagram starts a burst of lossesyes—
burst_length1 to 1000Datagrams a burst lasts on average (needed with burst_start)yes—
offlinetrue or falseNothing gets throughyesyes
reset0 to 1A chunk of a stream resets its connection—yes
stall0 to 1A chunk of a stream leaves its connection half-open—yes

Frame and CaptureStats ​

A Frame is one captured packet, request or message:

FieldMeaning
seqIts number, rising
tsWhen, milliseconds since 1970
protoosc, udp, tcp, http, mqtt, ws…
dirtx (sent) or rx (received)
sourceThe tool that captured it: osc-monitor, broadcast, netsim…
job_idIts job, or null
local, remoteThe addresses: IP:port of this side and of the other (a URL or a broker for HTTP, WebSocket and MQTT). A relayed frame's local is the address the relay listens on and its remote where the frame was going; the leg ends its verdict (· client→target, · target→client)
bytesIts size
summaryOne line
detailA decode over several lines, or null
hexA hex dump of the first 1 KiB, or null
verdictWhat became of it — dropped, sampled, a status — or null
keptOf bytes, how many are kept (up to 256 KiB); 0 when only the size was recorded
publishAn MQTT publish only: { broker, topic, qos, retain, text } — the broker as host:port, and whether the kept bytes, which are the message's payload, are UTF-8 text. Absent for every other frame

CaptureStats: enabled, total (frames recorded), bytes, skipped (recorded but never sent to the interface), buffered (frames held), capacity (8192), held (payload bytes held) and held_limit (64 MiB). The oldest frames give way past either limit.