| name | signal-daemon |
| description | Use this skill whenever you need to read Signal messages or send a Signal message via the signal-daemon HTTP API. Triggers include: reading recent Signal messages, querying messages by sender or group, sending a message to self, a phone number, a group, or a Signal username, or any interaction with the local signal-daemon running on port 6000. |
Signal Daemon HTTP API
Overview
A local HTTP daemon runs on http://localhost:6000 and exposes two endpoints:
GET /messages — query received Signal messages from the local SQLite store
GET /send — send a Signal message to self, a phone number, a group, or a username
Timestamps are always in milliseconds since Unix epoch.
Important: Sending to self completes immediately. Sending to anyone else returns a confirm_url that must be shown to the user — the message is not sent until the user opens that URL in their browser and clicks "Send".
Reading Messages
All messages from the last hour
curl "http://localhost:6000/messages?since=$(python3 -c 'import time; print(int(time.time()*1000) - 3600000)')"
All messages from the last 10 minutes
curl "http://localhost:6000/messages?since=$(python3 -c 'import time; print(int(time.time()*1000) - 600000)')"
All messages from the last 24 hours
curl "http://localhost:6000/messages?since=$(python3 -c 'import time; print(int(time.time()*1000) - 86400000)')"
Filter by sender (phone number or display name)
curl "http://localhost:6000/messages?sender=%2B1234567890"
Note: + must be URL-encoded as %2B in phone numbers.
Filter by sender name
curl --get "http://localhost:6000/messages" --data-urlencode "sender=Alice"
Filter by group ID
curl "http://localhost:6000/messages?group=AfL%2Fco87TsyfTv4FqgJfcF6rNWoRkO2CYLybn83tfTU%3D"
Combine filters: sender + time window
curl --get "http://localhost:6000/messages" \
--data-urlencode "sender=Alice" \
--data-urlencode "since=$(python3 -c 'import time; print(int(time.time()*1000) - 3600000)')"
Between two timestamps
curl "http://localhost:6000/messages?since=1774390000000&until=1774399000000"
Response format
{
"count": 2,
"messages": [
{
"id": 1,
"timestamp": 1774397441614,
"source": "+1234567890",
"source_name": "Alice",
"group_id": "AfL/co87TsyfTv4FqgJfcF6rNWoRkO2CYLybn83tfTU=",
"message": "Hello there",
"raw_json": "...",
"received_at": "2026-03-25T02:00:00.000000"
}
]
}
Notes
- Only messages with non-null text are returned (attachments, reactions, and sync events are excluded)
timestamp is when the message was sent (milliseconds)
received_at is when the poller collected it (ISO 8601 UTC string)
group_id is null for direct messages
Sending Messages
The ?to= parameter accepts three kinds of recipient:
| Format | Example | Description |
|---|
| Phone number | +1234567890 | Must start with + and country code |
| Group ID | AfL/co87Ts... | Base64 string from group_id in /messages |
| Signal username | alice.01 | Contains a dot, e.g. name.NN |
Send to self (no confirmation required)
When to matches your own account number, the message is sent immediately.
curl --get "http://localhost:6000/send" \
--data-urlencode "to=+1234567890" \
--data-urlencode "message=Hello from the daemon"
Response format (sent immediately)
{
"ok": true,
"to": "+1234567890",
"message": "Hello from the daemon"
}
Send to another person, group, or username — requires user confirmation
curl --get "http://localhost:6000/send" \
--data-urlencode "to=+1987654321" \
--data-urlencode "message=Hello Alice"
curl --get "http://localhost:6000/send" \
--data-urlencode "to=AfL/co87TsyfTv4FqgJfcF6rNWoRkO2CYLybn83tfTU=" \
--data-urlencode "message=Hello everyone"
curl --get "http://localhost:6000/send" \
--data-urlencode "to=alice.01" \
--data-urlencode "message=Hello Alice"
Response format (confirmation required)
{
"pending": true,
"confirm_url": "http://localhost:7000/confirm?token=...",
"to": "AfL/co87TsyfTv4FqgJfcF6rNWoRkO2CYLybn83tfTU=",
"display_name": "Family Chat",
"message": "Hello everyone",
"note": "Open confirm_url in your browser to approve or deny this message."
}
When you receive a pending: true response, you must present the confirm_url to the user. The message has NOT been sent yet. The user must open the URL in their browser and click "Send" to approve, or "Don't send" to cancel. Do not silently discard the URL.
Notes
display_name is the human-readable name shown on the confirmation page. For groups it is the group name looked up from signal-cli; for phone numbers and usernames it is the to value as given.
- Signal usernames (e.g.
alice.01) are detected by the presence of a dot and a short length. If you pass something that is neither a phone number nor a known group ID and does not match the username heuristic, it will be sent as a username anyway — signal-cli will return an error if it is not valid.
Query Parameter Reference
| Parameter | Endpoint | Type | Description |
|---|
since | /messages | int | Start timestamp in milliseconds (inclusive) |
until | /messages | int | End timestamp in milliseconds (inclusive) |
sender | /messages | string | Filter by phone number (e.g. +1234567890) or display name |
group | /messages | string | Filter by base64 group ID |
to | /send | string | Recipient: phone number, group ID, or Signal username (required) |
message | /send | string | Message text (required) |
All parameters are optional for /messages. to and message are required for /send.
Timestamp Quick Reference
python3 -c 'import time; print(int(time.time()*1000))'
python3 -c 'import time; print(int(time.time()*1000) - 600000)'
python3 -c 'import time; print(int(time.time()*1000) - 3600000)'
python3 -c 'import time; print(int(time.time()*1000) - 86400000)'
python3 -c 'import time; print(int(time.time()*1000) - 604800000)'