Skip to content

Signal CLI API Reference

Signal API — a secure communication device projecting a holographic signal wave.

Sanctum runs three separate Signal interfaces. They share a name and a purpose but almost nothing else. Confusing them is the most common source of 404 errors in the stack, because “signal-cli on port 8080” and “signal-cli on port 18081” are not the same service, the same protocol, or the same API. They are three strangers with the same surname who happen to live in the same building.

InterfacePortProtocolContainer / ProcessAPI Style
signal-cli native8080JSON-RPCHomebrew binary (PID on Mac)POST /api/v1/rpc
signal-cli-rest-api18081RESTDocker: signal-cli-rest-api/v1/..., /v2/...
signal-yoda18082RESTDocker: signal-yoda/v1/..., /v2/...

The Homebrew-installed signal-cli 0.14.2 binary running as a daemon with --http 127.0.0.1:8080. This is OpenClaw’s primary bridge — Yoda sends and receives messages through this interface.

Process: signal-cli -a +1XXXXXXXXXX daemon --http 127.0.0.1:8080 --no-receive-stdout

MethodPathDescription
POST/api/v1/rpcJSON-RPC 2.0 endpoint (the only real endpoint)
GET/api/v1/eventsServer-Sent Events stream for incoming messages
GET/api/v1/checkHealth check

Everything else returns 404. There is no /v1/about. There is no /api/v1/accounts. Those paths belong to the Docker wrapper, not this interface.

Send a message:

Terminal window
curl -s -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"send","params":{"recipient":["+1XXXXXXXXXX"],"message":"Hello from Sanctum"},"id":1}' \
http://127.0.0.1:8080/api/v1/rpc

Get version:

Terminal window
curl -s -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"version","id":2}' \
http://127.0.0.1:8080/api/v1/rpc
# Returns: {"jsonrpc":"2.0","result":{"version":"0.14.2"},"id":2}

List groups:

Terminal window
curl -s -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"listGroups","id":3}' \
http://127.0.0.1:8080/api/v1/rpc

Check if a number is registered:

Terminal window
curl -s -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"getUserStatus","params":{"recipient":["+1XXXXXXXXXX"]},"id":4}' \
http://127.0.0.1:8080/api/v1/rpc

Key methods (camelCase, passed as "method" in the JSON-RPC request): send, sendReceipt, listGroups, updateGroup, getUserStatus, version, sendSyncRequest, listContacts, block, unblock, startLink, finishLink, subscribeReceive, unsubscribeReceive.

Full method list: signal-cli-jsonrpc(5) man page.

The bbernhard/signal-cli-rest-api Docker wrapper. Provides a proper REST API with versioned endpoints. Runs its own signal-cli instance internally with a separate data directory (data-rest-api/).

Container: signal-cli-rest-api on 127.0.0.1:18081

MethodPathDescription
GET/v1/aboutVersion info, capabilities, mode
GET/v1/accountsList registered accounts
POST/v2/sendSend a message (v2 — supports quotes, mentions)
GET/v1/groups/{number}List groups for an account
GET/v1/receive/{number}Receive pending messages
Terminal window
# Check version
curl -s http://127.0.0.1:18081/v1/about
# Returns: {"versions":["v1","v2"],"build":2,"mode":"json-rpc","version":"0.98",...}
# Send a message
curl -s -X POST -H "Content-Type: application/json" \
-d '{"message":"Hello","number":"+1XXXXXXXXXX","recipients":["+1XXXXXXXXXX"]}' \
http://127.0.0.1:18082/v2/send

Identical to the REST API above but with a separate data directory (data-yoda/). This is the Yoda-specific Signal identity used by the agent council. Same image, same API, different account.

Container: signal-yoda on 127.0.0.1:18082

Both Docker containers are defined in ~/.openclaw/signal-cli/compose.yaml:

services:
signal-cli-rest-api:
image: bbernhard/signal-cli-rest-api:latest
container_name: signal-cli-rest-api
restart: unless-stopped
environment:
- MODE=json-rpc
ports:
- "127.0.0.1:18081:8080"
- "127.0.0.1:6001:6001"
volumes:
- ./data-rest-api:/home/.local/share/signal-cli
signal-cli-yoda:
image: bbernhard/signal-cli-rest-api:latest
container_name: signal-yoda
restart: unless-stopped
environment:
- MODE=json-rpc
ports:
- "127.0.0.1:18082:8080"
- "127.0.0.1:6002:6001"
volumes:
- ./data-yoda:/home/.local/share/signal-cli

OpenClaw connects to signal-cli via the native CLI binary, not through any HTTP API. The relevant config in openclaw.json:

{
"channels": {
"signal": {
"accounts": {
"yoda": {
"name": "Yoda Signal",
"enabled": true,
"account": "+1XXXXXXXXXX",
"cliPath": "/opt/homebrew/bin/signal-cli"
}
},
"enabled": true
}
}
}

This means OpenClaw messaging works even when the HTTP/REST interfaces are down. The CLI binary talks directly to the signal-cli daemon via its internal socket, not via HTTP. The HTTP API is for external tooling, health checks, and regression tests — not for the primary message flow.

Terminal window
# Native JSON-RPC (should return version)
curl -s -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"version","id":1}' \
http://127.0.0.1:8080/api/v1/rpc
# REST API container 1 (should return about info)
curl -s http://127.0.0.1:18081/v1/about
# REST API container 2 / Yoda (should return about info)
curl -s http://127.0.0.1:18082/v1/about
# Docker container status (both should be "running")
docker inspect --format '{{.State.Status}}' signal-cli-rest-api signal-yoda

The signal-health.sh script provides military-grade monitoring of all Signal interfaces with auto-recovery capabilities.

Location: ~/.sanctum/scripts/signal-health.sh

Terminal window
# Check-only mode (human-readable)
~/.sanctum/scripts/signal-health.sh
# Auto-fix mode (restarts failed components)
~/.sanctum/scripts/signal-health.sh --fix
# Machine-readable JSON output
~/.sanctum/scripts/signal-health.sh --json
# Silent mode (log only, no stdout)
~/.sanctum/scripts/signal-health.sh --fix --quiet
ComponentCheckAuto-Fix Action
Native daemon (:8080)JSON-RPC version callRestart via launchctl or direct launch
signal-cli-rest-api (:18081)/v1/accounts returns expected accountdocker restart signal-cli-rest-api
signal-yoda (:18082)/v1/accounts returns Yoda numberdocker restart signal-yoda
OpenClaw bridge (:1977)Gateway listening + node bridge connected to :8080Report only (manual)
CodeMeaning
0All components healthy
1One or more components were down but auto-recovered
2Manual intervention required

A LaunchAgent runs the health check every 5 minutes with --fix --quiet:

Plist: ~/Library/LaunchAgents/com.sanctum.signal-health.plist

Terminal window
# Check status
launchctl list | grep signal-health
# Reload after edits
launchctl unload ~/Library/LaunchAgents/com.sanctum.signal-health.plist
launchctl load ~/Library/LaunchAgents/com.sanctum.signal-health.plist

All health check results are appended to ~/.sanctum/logs/signal-health.log. LaunchAgent stdout/stderr go to ~/.sanctum/logs/signal-health-stdout.log and signal-health-stderr.log.