Zrzur Developers

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

  1. PUT /applications/{application_id}/commands with your commands.
  2. Open wss://events.zrzur.com/?version=1&format=json.
  3. Send { "type": "Authenticate", "token": "YOUR_BOT_TOKEN", "intents": 33 }.
  4. Every 20 seconds send { "type": "Ping", "data": 1 }.
  5. For each packet: if type is Bulk, handle each item of v.
  6. On InteractionCreate, within 3 seconds, POST /interactions/{interaction_id}/{token}/callback.
  7. 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

AddressWhat
GET /openapi.jsonEvery REST route, as OpenAPI 3
GET /gateway/schemaEvery gateway event, as JSON Schema
GET /Addresses and limits of this server

Generate a client from them in your own language.

Next