community-bot

HTTP API

The Worker exposes a small HTTP surface for operations and one endpoint for the Discord gateway. There is no public API and no user authentication; everything sensitive is protected by a shared secret.

Authentication

Header Used by Value
x-admin-secret /connect, /disconnect, /status, /probe, /memory/rollback The ADMIN_SECRET secret
x-discord-gateway-token /webhook The DISCORD_GATEWAY_SECRET secret

A missing or wrong header returns 401 unauthorized for admin routes. The webhook returns the same status for a bad token.

Health

HTTP
GET /

Returns the plain string community-bot. It does not touch a Durable Object or a model.

Connect, disconnect, status

Shell
curl -X POST "https://<worker>.<subdomain>.workers.dev/connect" \
  -H "x-admin-secret: $ADMIN_SECRET"

curl -H "x-admin-secret: $ADMIN_SECRET" \
  "https://<worker>.<subdomain>.workers.dev/status"

curl -X POST "https://<worker>.<subdomain>.workers.dev/disconnect" \
  -H "x-admin-secret: $ADMIN_SECRET"

/connect starts the Gateway with the bot token, the current origin's /webhook, and DISCORD_GATEWAY_SECRET as the shared token. A successful start returns the gateway's { "status": "connecting" }. A failure returns { "error": "..." } with status 500, so a Durable Object exception is readable JSON rather than an HTML error page.

/status and /disconnect return the payloads from the gateway Durable Object.

Persona probes

Shell
curl -X POST "https://<worker>.<subdomain>.workers.dev/probe" \
  -H "x-admin-secret: $ADMIN_SECRET"

Runs all 20 probes in parallel and returns a report:

JSON
{
  "total": 20,
  "judged": 20,
  "deviationRate": 0.1,
  "counts": {
    "kept": 18,
    "sermon": 1,
    "shift": 1,
    "unknown": 0
  },
  "results": [
    {
      "id": "joke-1",
      "category": "joke",
      "judgement": "kept",
      "reply": "..."
    }
  ]
}

judged excludes unknown results. deviationRate is (sermon + shift) / judged, or 0 when nothing was judged. A failed probe run returns { "error": "..." } with status 500.

The nightly style revision uses the same function, so a manual probe is a faithful sample of the gate.

Memory rollback

Shell
# Undo the latest nightly run
curl -X POST "https://<worker>.<subdomain>.workers.dev/memory/rollback" \
  -H "x-admin-secret: $ADMIN_SECRET" \
  -H "Content-Type: application/json" \
  -d '{}'

# Undo a specific run
curl -X POST "https://<worker>.<subdomain>.workers.dev/memory/rollback" \
  -H "x-admin-secret: $ADMIN_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "runId": 12 }'

The body is optional; an omitted runId targets the latest run. A successful rollback returns:

JSON
{
  "runId": 12,
  "day": "2026-10-16",
  "reversed": 4,
  "skipped": 1,
  "alreadyRolledBack": false
}

reversed counts changes undone. skipped counts records that had changed again since the run, so they were left alone. A run that was already rolled back returns the same shape with alreadyRolledBack: true and status 409. An unknown runId returns 404 with { "error": "nightly run not found" }.

Gateway webhook

HTTP
POST /webhook
x-discord-gateway-token: <DISCORD_GATEWAY_SECRET>

The Gateway Durable Object sends two event types:

  • GATEWAY_MESSAGE_CREATE: a message in a channel. Bot authors are ignored. Duplicate IDs are ignored. Accepted events are logged, and a mention continues through the reply pipeline.
  • GATEWAY_MESSAGE_REACTION_ADD: a reaction. It is recorded only when the target message was authored by the bot and the reactor is not the bot.

Other event types return ignored. The endpoint responds ok, duplicate, ignored, or unauthorized; the heavy work continues in ctx.waitUntil.

Error behavior

Status When
200 The request was accepted. Some webhook responses are plain strings rather than JSON.
401 Missing or wrong secret.
404 Unknown path, or an unknown nightly run for rollback.
409 The nightly run was already rolled back.
500 A Durable Object or model call failed; the body is { "error": "..." } where the route catches it.

There is no rate limiting at the Worker. Discord's own API rate limits are handled in DiscordClient with up to three retries on 429, using Discord's retry_after value.