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 and game listing tokens. Answers { voted, votedAt, nextVoteAt }. voted is true only while the vote is inside the active window (12 hours for bots, 24 for game servers); otherwise all three are false/null. Minecraft listings may send playerName instead of userId.

POST/webhooks/votes/check

Batch form: body { "userIds": ["…"] }, 1–50 Discord IDs, answers { "votes": { "<userId>": { voted, votedAt, nextVoteAt } } }, or { "playerNames": ["…"] } on a Minecraft listing. Limit for both forms: 120 checked users/minute per listing (single = 1, batch = unique entries). 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, votedAt, metadata }. metadata is {}, except on a Minecraft vote that named a player: { "playerName": "…" }.

GET/webhooks/stats

Scope: votes:read. Aggregates for your listing: { dailyVotes (30 days), totalVotes, totalUniqueVoters, lastVote, topVoters }. Each topVoters entry is { user_id, vote_count } — names and avatars are not returned; resolve them from the id through Discord. Both this endpoint and /webhooks/votes share a limit of 30 requests per minute per listing, separate from the vote-check budget.

Manage the vote webhook from code

The dashboard (your listing's Integrations tab) 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 listing; single check = 1, batch = unique entries. 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.