Install
openclaw skills install zulipInteract with Zulip chat platform via REST API and Python client. Use when you need to read messages from streams/topics, send messages to channels or users, manage DM conversations, list users, or integrate with Zulip organizations for team communication workflows.
openclaw skills install zulipInteract with Zulip chat platform for team communication.
pip install zulip
Create ~/.config/zulip/zuliprc:
[api]
email=bot@example.zulipchat.com
key=YOUR_API_KEY_HERE
site=https://example.zulipchat.com
Get credentials from Zulip admin panel (Settings → Bots).
python scripts/zulip_client.py streams
The scripts/zulip_client.py provides common operations:
List streams:
python scripts/zulip_client.py streams
python scripts/zulip_client.py streams --json
Read messages:
# Recent stream messages (by name)
python scripts/zulip_client.py messages --stream "General" --num 10
# By stream ID (more reliable, use 'streams' to find IDs)
python scripts/zulip_client.py messages --stream-id 42 --num 10
# Specific topic
python scripts/zulip_client.py messages --stream "General" --topic "Updates"
# Private messages
python scripts/zulip_client.py messages --type private --num 5
# Mentions
python scripts/zulip_client.py messages --type mentioned
Note: Stream names may have descriptions that look like part of the name. Use --stream-id for unambiguous identification.
Send messages:
# To stream
python scripts/zulip_client.py send --type stream --to "General" --topic "Updates" --content "Hello!"
# Private message (user_id)
python scripts/zulip_client.py send --type private --to 123 --content "Hi there"
List users:
python scripts/zulip_client.py users
python scripts/zulip_client.py users --json
import zulip
client = zulip.Client(config_file="~/.config/zulip/zuliprc")
# Read messages
result = client.get_messages({
"anchor": "newest",
"num_before": 10,
"num_after": 0,
"narrow": [{"operator": "stream", "operand": "General"}]
})
# Send to stream
client.send_message({
"type": "stream",
"to": "General",
"topic": "Updates",
"content": "Message text"
})
# Send DM
client.send_message({
"type": "private",
"to": [user_id],
"content": "Private message"
})
# List streams
curl -u "bot@example.com:KEY" https://example.zulipchat.com/api/v1/streams
# Get messages
curl -u "bot@example.com:KEY" -G \
"https://example.zulipchat.com/api/v1/messages" \
--data-urlencode 'anchor=newest' \
--data-urlencode 'num_before=20' \
--data-urlencode 'num_after=0' \
--data-urlencode 'narrow=[{"operator":"stream","operand":"General"}]'
# Send message
curl -X POST "https://example.zulipchat.com/api/v1/messages" \
-u "bot@example.com:KEY" \
--data-urlencode 'type=stream' \
--data-urlencode 'to=General' \
--data-urlencode 'topic=Updates' \
--data-urlencode 'content=Hello!'
def get_latest_messages(client, stream_name, last_seen_id=None):
narrow = [{"operator": "stream", "operand": stream_name}]
if last_seen_id:
# Get only messages after last seen
request = {
"anchor": last_seen_id,
"num_before": 0,
"num_after": 100,
"narrow": narrow
}
else:
# Get recent messages
request = {
"anchor": "newest",
"num_before": 20,
"num_after": 0,
"narrow": narrow
}
result = client.get_messages(request)
return result["messages"]
def reply_to_message(client, original_message, reply_text):
"""Reply in the same stream/topic as original message."""
client.send_message({
"type": "stream",
"to": original_message["display_recipient"],
"topic": original_message["subject"],
"content": reply_text
})
def search_messages(client, keyword, stream=None):
narrow = [{"operator": "search", "operand": keyword}]
if stream:
narrow.append({"operator": "stream", "operand": stream})
result = client.get_messages({
"anchor": "newest",
"num_before": 50,
"num_after": 0,
"narrow": narrow
})
return result["messages"]
def get_user_id(client, email):
"""Find user_id by email address."""
result = client.get_members()
for user in result["members"]:
if user["email"] == email:
return user["user_id"]
return None
Zulip uses Markdown:
**text***text*`code````language\ncode\n```> quoted text@**Full Name**#**stream-name**[text](url)with open("file.pdf", "rb") as f:
result = client.upload_file(f)
file_url = result["uri"]
# Share in message
client.send_message({
"type": "stream",
"to": "General",
"topic": "Files",
"content": f"Check out [this file]({file_url})"
})
# Add reaction
client.add_reaction({
"message_id": 123,
"emoji_name": "thumbs_up"
})
# Remove reaction
client.remove_reaction({
"message_id": 123,
"emoji_name": "thumbs_up"
})
See references/api-quick-reference.md for complete API documentation, endpoints, and examples.
Config file not found:
~/.config/zulip/zuliprc exists with correct formatAuthentication failed:
Empty messages array:
client.get_subscriptions() to check subscriptionsRate limit errors:
Retry-After header on 429 responses