Bot logs
The Bot tab of your application can ask the running bot for its recent log lines, so you can see what happened without logging in to the machine it runs on. This page is the protocol a bot implements to answer.
How it works
- A developer (owner, or team developer and above) presses Request logs on the Bot tab and picks a time window and a line count.
- GameVox sends your bot a gateway dispatch named
GAMEVOX_LOG_REQUEST. It goes only to the live gateway sessions of that same application. - Your bot collects the lines of that window and uploads them with one REST call. The portal shows them as soon as they arrive.
- If nothing arrives within two minutes the portal says the bot did not answer. A bot that does not know the event ignores it, so nothing breaks for libraries that have never heard of it.
The gateway event
An ordinary dispatch (opcode 0) with t set to GAMEVOX_LOG_REQUEST. It carries a sequence number like any other dispatch and is replayed on resume.
{
"op": 0,
"s": 4821,
"t": "GAMEVOX_LOG_REQUEST",
"d": {
"request_id": "6f0c2d1e-8a54-4b7e-9c3d-2a1f5e7b9d40",
"since": "2026-10-30T11:45:00Z",
"max_lines": 2000,
"nonce": "3f9a0c...e1b7",
"expires_at": "2026-10-30T12:02:00Z"
}
} request_id: identifies the request; part of the upload URL.since: RFC 3339 time. Send lines at or after it.max_lines: the most lines wanted (50 to 20,000). Send the newest ones if you have more.nonce: a one-time secret for this request. Send it back with the upload; it proves the upload comes from the bot that received the request.expires_at: RFC 3339 time after which the upload is refused (two minutes after the request).
Uploading the lines
Answer with one request, authenticated like every other bot call (Authorization: Bot <token>). JSON is the normal form:
POST /api/v10/applications/@me/log-requests/6f0c2d1e-8a54-4b7e-9c3d-2a1f5e7b9d40
Authorization: Bot <token>
Content-Type: application/json
{
"nonce": "3f9a0c...e1b7",
"lines": [
{ "ts": "2026-10-30T11:58:02.114Z", "level": "info", "msg": "synced 12 guilds" },
{ "ts": "2026-10-30T11:58:09.870Z", "level": "error", "msg": "poll failed: timeout" }
]
}
HTTP/1.1 204 No Content Each line is {ts, level, msg}: ts an RFC 3339 time, level one of debug, info, warn, error, and msg the text. A bare string is accepted in place of an object.
Plain text also works: send Content-Type: text/plain with one line per row and the nonce in an X-GameVox-Log-Nonce header. Lines sent that way carry no timestamp or level (GameVox guesses a level from the words in the line). The header also works with JSON; the body field is only a convenience.
Limits and rules
- The body may be at most 2 MiB and a request keeps at most 20,000 lines. Past the line limit the newest lines are kept; a larger body is refused with 413.
- A request can be answered once. A second upload gets 409.
- Only the application the request was made for can answer it. Another application’s request id looks like an unknown id (404).
- The upload must arrive before
expires_at(410 after). - The nonce must match (403 otherwise).
- Requests are limited to one every 30 seconds and 30 per day per application. Bundles are kept for 7 days and then deleted.
- Log text is shown as plain text only. Control characters are stripped and each line is cut at 4,000 characters.
Errors
- 400: the body is not valid JSON, or the nonce is missing.
- 403: the nonce does not match.
- 404: no such request for your application.
- 409: the request was already answered.
- 410: the request expired.
- 413: the body is larger than 2 MiB.
- 429: your bot is rate limited like on any other route.
Remove secrets before you send
Logs leave your machine, so remove what should not leave it: your bot token and any other credential, Authorization headers, passwords in URLs, email addresses, and the IP addresses of your users. GameVox first-party bots do all of this before uploading.
GameVox also redacts on arrival (tokens, Authorization values, key=value pairs whose key names a secret, passwords in URLs, long hex strings and email addresses) and tells the viewer how many values it hid. That is a safety net, not a reason to skip your own redaction: it cannot know what is private in your messages.
Example
Most libraries hand you events they do not model through a raw hook: on_socket_raw_receive in discord.py, the raw event in discord.js. Keep the last few thousand lines in a ring buffer, and when the event arrives, send the part that was asked for.
# discord.py: keep recent lines, answer GAMEVOX_LOG_REQUEST
import collections, json, logging
from datetime import datetime, timezone
RING = collections.deque(maxlen=5000)
class Ring(logging.Handler):
def emit(self, record):
RING.append({"ts": datetime.fromtimestamp(record.created, timezone.utc).isoformat(),
"level": record.levelname.lower(),
"msg": redact(self.format(record))})
logging.getLogger().addHandler(Ring())
@client.event
async def on_socket_raw_receive(msg):
packet = json.loads(msg)
if packet.get("t") != "GAMEVOX_LOG_REQUEST":
return
d = packet["d"]
lines = [l for l in RING if l["ts"] >= d["since"]][-d["max_lines"]:]
await session.post(
f"https://app-api.gamevox.com/api/v10/applications/@me/log-requests/{d['request_id']}",
headers={"Authorization": f"Bot {TOKEN}"},
json={"nonce": d["nonce"], "lines": lines}) Compatibility
The event is new and optional. Bots written against Discord libraries keep working unchanged, because a dispatch a library does not know is ignored. The GameVox first-party bots (League of Legends, VALORANT, Deadlock, Valheim, WARDOGS, WoW guild, Tickets and Tibia) support it through the shared bot library.