Skip to content

Chapter 13 — WebSocket API Reference

The Stolo engine exposes a local JSON-over-WebSocket API on ws://127.0.0.1:4848. The app is its primary client, but administrators can use the API for automation, integration, and headless deployments (e.g. scripting a Pi relay).

The API is local-only: the server binds to loopback and every session must authenticate with the per-boot token.

13.1 Conventions

  • Requests are JSON objects: {"action": "<name>", ...params, "request_id": "req_1"}. The optional request_id is echoed on the response, letting clients correlate replies.
  • Responses are events: {"event": "<name>", ...fields, "request_id": "req_1"}.
  • Errors: {"event": "error", "error": "<message>", "request_id": ...}. Unknown actions return Unknown action: <name>.
  • Hashes are lowercase hex strings (angle brackets tolerated on input).
  • Push events (unsolicited) are delivered to all authenticated clients as they happen.
  • Maximum frame size is 16 MB (attachments travel base64-encoded).
  • Before setup completes, most actions return Setup not complete — please complete setup first. While the service is quiesced (deactivated license), mesh-emitting actions return service_quiesced.

13.2 Authentication

The engine writes a random token to <profile>/ws_token (e.g. ~/.gridx/ws_token) at startup. The first message on any connection must be:

{"action": "authenticate", "token": "<contents of ws_token>", "request_id": "req_0"}

Success: {"event": "authenticated"} — followed by a device_identity_ready push. Failure: {"event": "error", "error": "Invalid token"}. Any other action first returns Not authenticated.

13.3 Identity & profile

Action Parameters Response event
get_identity identityidentity_hash, destination_hash, display_name, team, role, avatar_color, is_setup_complete
get_device_identity device_identitydevice_hash, device_identity_file_path, created_at, is_new
complete_setup display_name (required), team, role, avatar_color setup_complete — creates the identity and starts messaging on first run
set_display_name display_name display_name_set
set_avatar_color avatar_color avatar_color_set
set_role role role_set
install_vendor_cert cert_b64 (base64 msgpack [key_id, sig]) vendor_cert_installedkey_id, replaced

13.4 Presence & service control

Action Parameters Response event
announce display_name (optional) announce_sent — broadcast a presence announce
set_presence presence ("active" or "idle") presence_set
set_service_quiesced quiesced (bool) service_quiesced — mesh-dark mode on/off
network_path_changed reason (optional, ≤64 chars) network_path_ack — triggers a live interface rebuild after a network change
restart_backend Desktop: engine re-execs (clients see backend_restarting). Mobile: returns restart_unavailable (relaunch the app instead).

13.5 Messaging

Action Parameters Response event
send_message destination; content and/or attachment_data (base64) + attachment_type (image|file|audio); optional attachment_name, attachment_duration, delivery_mode (standard|priority|critical) message_sent — includes message_id, initial status
cancel_message message_id message_cancelled (critical messages in queued/retrying only)
send_reaction recipient, target_message_id, emoji reaction result (toggles add/remove)
get_messages peer, limit (50), before messages
get_conversations conversations (with hops, has_path enrichment)
mark_read message_id or peer marked_read
delete_message message_id message_deleted
delete_conversation peer conversation_deleted
get_media message_id media_data — base64 payload + attachment metadata
get_peer_signal peer peer_signal — RSSI/SNR/message count

Status vocabulary (initial responses and message_status_update pushes): sending, sent, path_requested, queued, retrying, delivered, unconfirmed, failed, failed_permanent, cancelled, too_large, invalid_destination.

13.6 Peers & network

Action Parameters Response event
get_peers peers_list — each peer with presence, via_interface, hops, has_path, is_transport, org/vendor fields, signal data
get_network_status network_statustransport_enabled, destination_hash, known_peers, propagation_node, interfaces[], mesh_health, versions
get_discovered_interfaces discovered_interfaces — interfaces published for discovery on the network

13.7 Groups

Action Parameters Response event
create_group name, members[], display_names (optional) group_created
list_groups groups
get_group_members group_id, include_left (optional) group_members
send_group_message group_id, content, delivery_mode group_message_queuedmessage_id, recipient_count
get_group_messages group_id, limit, before group_messages — messages plus per-recipient status rows
add_group_member / remove_group_member group_id, member group_member_added / group_member_removed (owner only)
leave_group group_id group_left
mark_group_read group_id group_marked_read

13.8 GPS

Action Parameters Response event
update_my_gps lat, lon; optional alt, speed, heading, accuracy, timestamp gps_updated
get_all_positions max_age (default 600 s) positions

13.9 Radio, RNode & BLE

Action Parameters Response event
get_radio_config interface (optional) radio_config
set_radio_region region (us|eu|au|asia) radio_region_set (applies on restart)
set_radio_params interface (optional); frequency, bandwidth, spreadingfactor, codingrate, txpower radio_params_set (restart required)
scan_all_radios all_radios_scan_result — USB + BLE radios found
ble_scan timeout (default 8 s) ble_scan_result — devices with is_rnode flags
ble_switch_config target (name or MAC) ble_config_switched — sets the radio port to ble://<target>
ble_enable / ble_disable / ble_start_pairing port (optional) ble_enabled / ble_disabled / ble_pairing_started (with PIN when available)

13.10 Interfaces & engine configuration

Action Parameters Response event
add_interface name, type (TCPClientInterface|TCPServerInterface|BackboneInterface|RNodeInterface|AutoInterface) + type-specific params (TCP client: target_host, target_port; TCP server: listen_port, optional listen_ip; Backbone: listen_on+port or remote+target_port; RNode: port + radio params) interface_added (restart to activate)
remove_interface name interface_removed
rename_interface old_name, new_name interface_renamed
set_interface_enabled name, enabled interface_enabled_set
set_interface_mode interface, mode (full|gateway|access_point|roaming|boundary) interface_mode_set
get_ifac_config / set_ifac_config / remove_ifac interface or apply_all; network_name, passphrase, ifac_size (8–512) ifac_config / ifac_config_set / ifac_removed
get_discovery_config / set_discovery_config name + discovery keys (discoverable, discovery_name, reachable_on, announce_interval, discovery_stamp_value, discovery_encrypt, publish_ifac, bootstrap_only, latitude, longitude, height) discovery_config / discovery_config_set
get_reticulum_config reticulum_configenable_transport, network_identity, interface_discovery_sources, primary_publisher_org_id, strict_mode
set_reticulum_config enable_transport (bool), network_identity (path or "") reticulum_config_set

13.11 Trust, quarantine, blocking & organizations

Action Parameters Response event
list_quarantined limit (100), before quarantined
accept_from_quarantine message_id, trust_target (null|"sender"|"organization") quarantine_accepted — message replayed into the inbox
deny_from_quarantine message_id quarantine_denied
deny_and_block_from_quarantine message_id, duration_seconds or until (optional) quarantine_denied_and_blocked
list_trusted_peers / remove_trusted_peer — / source_hash trusted_peers_list / trusted_peer_removed
block_peer source_hash; duration_seconds (relative) or until (absolute); omit both = permanent peer_blocked (or error identity_unknown)
unblock_peer identity_hash peer_unblocked
list_blocked blocked_peers_list
set_strict_mode enabled strict_mode_set
create_org name, mode (generate|import_hash|import_keyset), hash, keyset_b64, overwrite org_created
list_orgs / delete_org — / org_id, delete_keyset_file orgs / org_deleted
set_primary_publisher org_id (or null to clear) primary_publisher_set (restart required)
set_org_trusted org_id, trusted org_trust_set

13.12 Push events

Delivered asynchronously to all authenticated clients:

Event Fired when
device_identity_ready On authentication / after setup — device_hash, is_new
new_message / message_received An inbound message is delivered — sender, content, attachment metadata, signal data
message_status_update Any outbound message changes status — message_id, status, optional rtt, retry_count, via_interface
reaction_update A reaction is added/removed on a message
peer_discovered A new peer announce is heard — full peer record
peer_presence_changed A peer's presence tier changes (2 active / 1 idle / 0 offline)
peer_gps_updated A peer shares a position
peer_org_verified / peer_vendor_verified A peer's org membership / vendor certificate verifies
quarantine_added / quarantine_purged A message is quarantined / expired entries are purged
mesh_health Mesh health transitions (healthy / degraded, with reasons)
interface_*, group_* Interface config changes; group lifecycle (created, invite received, message received/sent, member added/removed/left, renamed, recipient status)
backend_restarting The engine is about to restart

13.13 Example session

import asyncio, json, pathlib, websockets

async def main():
    token = (pathlib.Path.home() / ".gridx" / "ws_token").read_text().strip()
    async with websockets.connect("ws://127.0.0.1:4848") as ws:
        await ws.send(json.dumps({"action": "authenticate", "token": token, "request_id": "r0"}))
        print(await ws.recv())  # {"event": "authenticated", ...}

        await ws.send(json.dumps({"action": "get_network_status", "request_id": "r1"}))
        print(await ws.recv())

        await ws.send(json.dumps({
            "action": "send_message", "request_id": "r2",
            "destination": "<peer destination hash>",
            "content": "Radio check", "delivery_mode": "priority",
        }))
        # Watch for message_sent, then message_status_update pushes.
        while True:
            print(await ws.recv())

asyncio.run(main())

A ready-made interactive client ships with the engine as backend/test_client.py.