API Reference

Every public developer endpoint. Base URL: https://api.discordium.org/api/v1. Authentication is always Authorization: Bearer <api token>. A machine-readable OpenAPI reference is at https://api.discordium.org/api/docs.

Report bot statistics

POST/bots/:id/stats

Scope: stats:write. The token must belong to the bot named by :id. Limit: 1/minute per bot. Each request is a complete snapshot: omitted optional fields are cleared, not preserved.

{
  "serverCount": 12482,
  "shardCount": 12
}

serverCount: required integer, 0–10,000,000. shardCount: optional integer, 0–10,000. Answers 200 with { serverCount, shardCount, updatedAt }. Full guide: Statistics.

Check a vote

GET/webhooks/votes/check?userId=<discord-user-id>

Scope: votes:read, bot tokens only. Answers { voted, votedAt, nextVoteAt }. voted is true only while the vote is inside the active 12-hour window; otherwise all three are false/null.

POST/webhooks/votes/check

Batch form: body { "userIds": ["…"] }, 1–50 Discord IDs, answers { "votes": { "<userId>": { voted, votedAt, nextVoteAt } } }. Limit for both forms: 120 checked users/minute per bot (single = 1, batch = unique IDs). Full guide: Vote Checking.

Read your own vote data

GET/webhooks/votes?startDate=&endDate=&limit=

Scope: votes:read. Your listing's recent votes, newest first (default 50, max 100 per request; both date filters optional). Each row: { id, userId, username, discriminator, avatar, votedAt, metadata }. discriminator is always "" (Discord retired discriminators; the field remains for compatibility) and metadata is always {}.

GET/webhooks/stats

Scope: votes:read. Aggregates for your listing: { dailyVotes (30 days), totalVotes, totalUniqueVoters, lastVote, topVoters }.

Manage the vote webhook from code

The dashboard (Bot → Integrations) is the recommended way to configure webhooks, but the same operations exist as API endpoints for programmatic setups:

POST/webhooks/register

Scope: webhooks:write (tokens holding only the older votes:read continue to work). Body: { "webhookUrl": "https://…", "secret": "…", "events": ["vote.created"], "isActive": true }. Creates or replaces your listing's webhook. This is a complete statement, so omitting secret clears a stored one. Re-registering also clears an auto-disable.

POST/webhooks/test

Same scope. Body: { "webhookUrl": "https://…" }. Sends one test delivery (test: true) through the production delivery path and reports the result.

Webhook endpoints must be publicly reachable. URLs pointing at private networks, localhost or internal hostnames are rejected.

Rate limits

EndpointTypeRequiredDescription
POST /bots/:id/stats1/min-Per bot. Faster reports answer 429 and change nothing.
votes/check (both forms)120 users/min-Per bot; single check = 1, batch = unique IDs. Use batches for volume.
everythingper-IP-A general per-IP request limit also applies across the whole API.

Limits are keyed to your listing, not your token, so regenerating a token never resets a window. A refused request answers 429 with a Retry-After header saying how many seconds to wait.

Errors

Every error body carries a stable, machine-readable code alongside the human-readable message. Branch on code: message may be reworded at any time, so never parse it.

{
  "statusCode": 429,
  "error": "HttpException",
  "code": "RATE_LIMITED",
  "message": "Vote-check rate limit exceeded (120 checked users per minute); retry in 42s",
  "retryAfterSeconds": 42,
  "path": "/api/v1/webhooks/votes/check",
  "timestamp": "2026-08-24T12:00:00.000Z"
}
CodeTypeRequiredDescription
VALIDATION_FAILED400-A body field or parameter is malformed or outside its documented range.
WEBHOOK_URL_INVALID400-The webhook target is not a valid http(s) URL.
WEBHOOK_URL_NOT_PUBLIC400-The webhook target points at a private network or internal host.
INVALID_TOKEN401-No token, or a token the API does not recognize.
MISSING_SCOPE401-A valid token that lacks the scope this endpoint requires.
FORBIDDEN403-A valid credential for the wrong thing: another bot's ID, or a non-bot token.
NOT_FOUND404-The listing does not exist.
RATE_LIMITED429-Over a rate limit. Comes with a Retry-After header and retryAfterSeconds in the body.
INTERNAL_ERROR500-Something went wrong on Discordium's side. Safe to retry with backoff.

429 responses additionally carry a Retry-After header (seconds) and the same number as retryAfterSeconds in the body. Wait that long, then retry.

Discordium API Reference | Discordium