Without a library
Everything a bot does is plain HTTPS and WebSocket. This page shows the calls, so you can write a bot in any language.
You will need
An HTTP client and a WebSocket client. curl is enough for the first steps. YOUR_BOT_TOKEN stands for your token.
Make a request
curl "https://api.zrzur.com/users/@me" -H "x-bot-token: YOUR_BOT_TOKEN"_id in the answer is your bot's id, and also its application id.
Bodies are JSON, with content-type: application/json. Answers are JSON, or empty with 204.
Send a message
curl -X POST "https://api.zrzur.com/channels/CHANNEL_ID/messages" \
-H "x-bot-token: YOUR_BOT_TOKEN" \
-H "content-type: application/json" \
-d '{ "content": "Hello!" }'Upload a file
curl -X POST "https://cdn.zrzur.com/attachments" \
-H "x-bot-token: YOUR_BOT_TOKEN" \
-F "[email protected];type=text/csv"The answer is { "id": "FILE_ID" }. Send it in attachments of a message.
The whole loop
PUT /applications/{application_id}/commandswith your commands.- Open
wss://events.zrzur.com/?version=1&format=json. - Send
{ "type": "Authenticate", "token": "YOUR_BOT_TOKEN", "intents": 33 }. - Every 20 seconds send
{ "type": "Ping", "data": 1 }. - For each packet: if
typeisBulk, handle each item ofv. - On
InteractionCreate, within 3 seconds,POST /interactions/{interaction_id}/{token}/callback. - When the socket closes, wait and start again at step 2.
Node
Node 22 has fetch and WebSocket built in. This is a complete bot in one file:
// Hello World without any library: Node 22+, fetch and WebSocket only.
//
// ZRZUR_BOT_TOKEN=YOUR_BOT_TOKEN node hello-raw.mjs
const token = process.env.ZRZUR_BOT_TOKEN;
const api = process.env.ZRZUR_API_URL ?? "https://api.zrzur.com";
const ws = process.env.ZRZUR_WS_URL ?? "wss://events.zrzur.com";
if (!token) {
console.error("Set ZRZUR_BOT_TOKEN first.");
process.exit(2);
}
/** An interaction's token is in its path: it must never reach a log */
const masked = (path) =>
path.replace(/(\/interactions\/[^/]+\/)[^/]+/, "$1***");
async function rest(method, path, body) {
const response = await fetch(api + path, {
method,
headers: {
"x-bot-token": token,
...(body ? { "content-type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok)
throw new Error(
`${method} ${masked(path)}: ${response.status} ${await response.text()}`,
);
return response.status === 204 ? null : response.json();
}
// 1. Who am I? A bot's application id is its user id.
const me = await rest("GET", "/users/@me");
// 2. Register the global /hello command (PUT replaces the whole list).
await rest("PUT", `/applications/${me._id}/commands`, [
{ name: "hello", description: "Say hello" },
]);
// 3. Connect to the gateway and answer interactions.
const socket = new WebSocket(`${ws}/?version=1&format=json`);
// intents 33 = SERVERS (1) + INTERACTIONS (32): commands, nothing else
socket.addEventListener("open", () =>
socket.send(JSON.stringify({ type: "Authenticate", token, intents: 33 })),
);
async function handle(event) {
if (event.type === "Ready") console.log(`ready as ${me.username}`);
if (
event.type === "InteractionCreate" &&
event.interaction.command?.name === "hello"
) {
const { _id, token: interactionToken } = event.interaction;
// answer within 3 seconds
await rest("POST", `/interactions/${_id}/${interactionToken}/callback`, {
type: "Message",
data: { content: "Hello from Zrzur!" },
});
}
}
socket.addEventListener("message", ({ data }) => {
const event = JSON.parse(data);
// several events can arrive together in one Bulk packet
for (const item of event.type === "Bulk" ? event.v : [event])
handle(item).catch(console.error);
});
// keep the connection alive
setInterval(
() => socket.send(JSON.stringify({ type: "Ping", data: Date.now() })),
20_000,
);
The complete bot adds rate limits, retries, reconnects and saved data.
Python
Python needs the websockets package (pip install websockets). The standard library is enough for HTTP.
import asyncio, json, os, urllib.request
import websockets
TOKEN = os.environ["ZRZUR_BOT_TOKEN"]
API = "https://api.zrzur.com"
def rest(method, path, body=None):
request = urllib.request.Request(
API + path,
method=method,
data=json.dumps(body).encode() if body is not None else None,
headers={"x-bot-token": TOKEN, "content-type": "application/json"},
)
with urllib.request.urlopen(request) as response:
data = response.read()
return json.loads(data) if data else None
async def main():
me = rest("GET", "/users/@me")
rest("PUT", f"/applications/{me['_id']}/commands",
[{"name": "hello", "description": "Say hello"}])
async with websockets.connect("wss://events.zrzur.com/?version=1&format=json") as socket:
await socket.send(json.dumps({"type": "Authenticate", "token": TOKEN, "intents": 33}))
async def ping():
while True:
await asyncio.sleep(20)
await socket.send(json.dumps({"type": "Ping", "data": 1}))
asyncio.create_task(ping())
async for raw in socket:
packet = json.loads(raw)
for event in packet["v"] if packet["type"] == "Bulk" else [packet]:
if event["type"] == "Ready":
print("ready")
if event["type"] == "InteractionCreate":
i = event["interaction"]
rest("POST", f"/interactions/{i['_id']}/{i['token']}/callback",
{"type": "Message", "data": {"content": "Hello from Python!"}})
asyncio.run(main())This short version does not reconnect. Add the loop from Gateway.
Machine-readable descriptions
| Address | What |
|---|---|
GET /openapi.json | Every REST route, as OpenAPI 3 |
GET /gateway/schema | Every gateway event, as JSON Schema |
GET / | Addresses and limits of this server |
Generate a client from them in your own language.