Skip to content

API v1

Read bot data, read the votes for your own bot, and report your server count. REST over HTTPS, JSON both ways.

The full specification as OpenAPI 3.1: /api/v1/openapi.json

Authentication

Create keys in your bot dashboard; you see them exactly once. They belong in an environment variable, not in your source. The two read endpoints also work without a key, at a lower limit.

const response = await fetch('https://topbot.gg/api/v1/bots/155149108183695360', {
  headers: { Authorization: `Bearer ${process.env.TOPBOT_TOKEN}` },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.code}: ${error.message}`);
}

const { data } = await response.json();
console.log(data.name, data.votesTotal);

Limits

Counted per minute and key, or per minute and IP when anonymous. Every response carries the numbers; a 429 adds a Retry-After header.

TierRequests per minute
Without a key20
Tier 1 — standard60
Tier 2 — erhöht240
Tier 3 — partner1200
  • x-ratelimit-limit
  • x-ratelimit-remaining
  • x-ratelimit-reset

Endpoints

GET/api/v1/botsno key needed

List published bots

page
query
limit
query
sort
query
search
query

200 · 422 · 429

GET/api/v1/bots/{id}no key needed

Get one bot

id
path

200 · 404 · 429

GET/api/v1/bots/{id}/votes

Recent votes for your bot

Requires the `votes:read` scope. The key must belong to this bot.

id
path
limit
query
hours
query — Only votes from the last n hours (max 720).

200 · 401 · 403 · 429

GET/api/v1/bots/{id}/check/{userId}

Has this user voted?

Requires the `votes:read` scope. Use this before handing out a reward. Returns 200 with `voted: false` when they have not — that is an answer, not an error.

id
path
userId
path — Discord user ID.

200 · 401 · 403 · 429

POST/api/v1/bots/{id}/stats

Report your server count

Requires the `stats:write` scope. Call this when your shard counts change — once every few minutes is plenty.

id
path

200 · 401 · 403 · 422 · 429

Vote webhooks

We call your URL as soon as someone votes for your bot. Verify the signature before handing out a reward — otherwise anyone who knows your URL can reward themselves.

import { createHmac, timingSafeEqual } from 'node:crypto';

app.post('/topbot', express.raw({ type: 'application/json' }), (request, response) => {
  const timestamp = request.header('x-topbot-timestamp');
  const signature = request.header('x-topbot-signature');
  const body = request.body.toString('utf8');

  // Wiederholte alte Aufrufe abwehren.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return response.sendStatus(400);
  }

  const expected = createHmac('sha256', process.env.TOPBOT_WEBHOOK_SECRET)
    .update(`${timestamp}.${body}`)
    .digest('hex');

  // In konstanter Zeit vergleichen.
  if (
    signature.length !== expected.length ||
    !timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
  ) {
    return response.sendStatus(401);
  }

  const payload = JSON.parse(body);
  if (payload.event === 'vote' && !payload.flagged) {
    grantReward(payload.user);
  }

  response.sendStatus(200);
});

Answer with any 2xx. On 5xx and 429 we retry six times with growing delays (10s, 40s, 90s, 160s, 250s); on other 4xx we do not retry. After 20 finally failed deliveries we switch the webhook off and let you know.

Badges

An SVG you embed in your README or on your site. It follows the viewer’s system setting and is cached for five minutes. Use theme=dark or theme=light to pin one version, compact=1 to drop the label.

votesserversrating
[![Topbot.gg](https://topbot.gg/api/badge/155149108183695360?variant=votes)](https://topbot.gg/en/bots/YOUR-SLUG)