A reverse proxy for the Discord API, deployed as a Cloudflare Worker. Adds authentication, token management, snowflake validation, and server-specific business logic endpoints on top of the standard Discord API. Original motivation was for my Google Sheets to be able to call the Discord API, without my requests being rejected from Discord, because they would detect Google's IP addresses.
- Reverse proxy - Forwards any request to
https://discord.com/api/v10with automatic token injection - Dual token support - Switches between bot and user tokens based on the endpoint or an explicit header
- Snowflake validation - Validates Discord IDs in URL paths before forwarding, returning Discord-compatible error responses
- Rate limit interception - Reformats 429 responses into a consistent JSON envelope
- Custom endpoints - Server-specific business logic that processes Discord data server-side
- OpenAPI spec - Auto-generated via
@hono/zod-openapiwith Swagger UI
| Component | Technology |
|---|---|
| Runtime | Cloudflare Workers |
| Framework | Hono + @hono/zod-openapi |
| Language | TypeScript (strict mode) |
| Validation | Zod |
| Testing | Vitest + @cloudflare/vitest-pool-workers |
| Package Manager | Bun |
bun installCreate a .dev.vars file in the project root with your secrets:
DISCORD_TOKEN_BOT=your-bot-token
DISCORD_TOKEN_USER=your-user-token
AUTH_KEY=your-api-keyCaution
I advise to use an alt account for the user token to avoid any future risks for your main account of being banned by Discord.
bun run lint # Type check (tsc --noEmit)
bun run test # Run all tests
bun run dev # Start local dev server via WranglerRequests flow through a layered middleware pipeline before reaching route handlers:
Request
|
v
[Rate Limit Interceptor] Reformats 429 responses (post-processing)
|
v
[Auth Middleware] Validates x-auth-key or Authorization header
|
v
[Discord Context] Selects bot/user token and User-Agent
|
v
[Snowflake Validator] Validates Discord IDs in URL path segments
|
v
[Custom Routes] /custom/* - Business logic endpoints
|
v
[Proxy Forwarder] /* - Catch-all to discord.com/api/v10
Note
In simple terms, we can add custom endpoints even under /custom, if nothing matches, then we forward it to the official API.
The proxy selects which Discord token to use based on:
x-proxy-context: userheader - Forces user tokenx-proxy-context: botheader - Forces bot token- Path heuristic (default) - Paths containing
/guildsuse user token; everything else uses bot token
User token requests also receive a browser-like User-Agent header.
src/
index.ts App factory and middleware sieve
types.ts Shared type definitions (Bindings, DiscordUser)
global.d.ts Build-time constants (BUILD_HASH, BUILD_TIMESTAMP)
middleware/
auth.ts API key authentication
discord-context.ts Token selection and User-Agent injection
snowflake-validator.ts Discord ID format validation
routes/
proxy.ts Catch-all Discord API reverse proxy
custom.ts Custom business logic route tree
custom/
chillzone/events/
kindness-cascade/ Kindness Cascade tallying module (see its own README)
test/
env.d.ts Cloudflare test type augmentation
middleware/ Unit tests for each middleware
routes/ Integration tests for proxy and custom routes
custom/chillzone/events/
kindness-cascade/ Classifier, tallier, formatter, and handler tests
Tallies submissions for the ChillZone server's Kindness Cascade event. Fetches all messages from a channel, classifies each one, and returns ranked leaderboards.
GET /custom/chillzone/events/kindness-cascade?guildId={id}&channelId={id}
GET /custom/chillzone/events/kindness-cascade?guildId={id}&channelId={id}&formattedMessage=true
See src/custom/chillzone/events/kindness-cascade/README.md for full documentation.
GET /custom/chillzone/events/cupids-inbox
Returns { "tally": 0 }. Placeholder for now. Not yet imported from my private project.
bun run test # Run all 88 tests across 10 suites
bun run test -- --ui # Open Vitest UITests use @cloudflare/vitest-pool-workers to run in a Workers-compatible runtime. Discord API calls are mocked at the fetch level.
Deployment is handled by a three-stage CI/CD pipeline:
- Push to
main—ci.yamlruns linting and tests. Dependabot dependency bumps are auto-merged directly toproduction. - Pre-production review —
pre-production-review.yamlgenerates a change report and requests reviewer approval via thestatus/approvedlabel. - Deploy —
deploy.yamltriggers on push toproduction, uploading and promoting the Worker via Wrangler's gradual deployment (wrangler versions upload→wrangler versions deploy).
Set these in your repository settings under Settings → Secrets and variables → Actions:
| Secret | Where to get it |
|---|---|
CLOUDFLARE_API_TOKEN |
Cloudflare Dashboard → My Profile → API Tokens — create a token with the Edit Cloudflare Workers template |
CLOUDFLARE_ACCOUNT_ID |
Cloudflare Dashboard → select your account → copy the Account ID from the right sidebar on the overview page |
CUSTOM_DOMAIN |
Your Cloudflare Workers custom domain (e.g. api.example.com). Injected into wrangler.jsonc at deploy time via the __CUSTOM_DOMAIN__ placeholder |
PAT |
GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens — needs Contents: Read and write and Pull requests: Read and write permissions for this repo. Required because pushes made with the default GITHUB_TOKEN do not trigger downstream workflows |