Skip to main content

Route index

The file​

The index is the routes/index.json file of the repository: plain JSON any language can read. An entry looks like this:

{
"method": "PATCH",
"path": "/channels/{channel_id}",
"name": "Update channel",
"source": "discord",
"auth": ["bot"],
"major": "channel_id",
"global": true,
"model": "unknown",
"coverage": "exercised",
"notes": ["Changing a channel's name or topic sits behind a sub-limit of its own, ..."]
}
  • major: the parameter that gives each value its own counter: channel_id, guild_id, webhook_id, or webhook_id+webhook_token. Empty when every call shares one counter.
  • global: false for routes exempt from the bot's global limit.
  • model: token_bucket, fixed_window, or unknown until a run has seen it.
  • family: routes Discord counts together, in a single bucket.
  • source: discord for Discord's OpenAPI specification, userdoccers for routes only Discord Userdoccers documents, such as POST /guilds/{guild_id}/members-search.
  • coverage: whether the engine exercises the route, and if not, why (in notes).
  • notes: sub-limits and other special cases.

From Go​

The routes package matches a request to its route:

import "github.com/FCAgreatgoals/bucketmap/routes"

r, ok := routes.Match("PATCH", "/api/v10/channels/123/messages/456")
// r.Path "/channels/{channel_id}/messages/{message_id}"
// r.MajorValue(path) "123"

Its documentation is on pkg.go.dev.

Two ways a bucket refills​

X-RateLimit-Reset-After is the time until the bucket is full again, which means two different things depending on the bucket:

  • Fixed window (fixed_window): the bucket refills all at once, when the window ends. From one request to the next, now + Reset-After stays put, and Reset-After shrinks as the bucket drains.
  • Token bucket (token_bucket): the bucket refills one request at a time. From one request to the next, now + Reset-After moves forward by one request's worth, and Reset-After grows as the bucket drains.

Two back-to-back requests on the same bucket are enough to tell them apart, far below any limit. The engine sends a few such pairs on purpose.

Keeping the index current​

The repository's index workflow rebuilds the index every week from the latest sources, and fails when they describe a route the index does not know yet. To rebuild it by hand:

git clone --depth 1 https://github.com/discord/discord-api-spec /tmp/spec
git clone --depth 1 https://github.com/discord-userdoccers/discord-userdoccers /tmp/userdoccers
bucketmap index -spec /tmp/spec/specs/openapi.json -userdoccers /tmp/userdoccers/pages

bucketmap index options: -spec (Discord's openapi.json), -userdoccers (the pages directory of Discord Userdoccers), -annotations (routes/annotations.json by default) and -out (routes/index.json by default).

What the sources do not say lives in routes/annotations.json: bucket models, families, notes, and why a route is left out. The build fails on any route that is neither exercised nor explained.