Zum Inhalt springen

API v1

Bot-Daten lesen, die Stimmen für deinen eigenen Bot abfragen und deinen Server-Count melden. REST über HTTPS, JSON hin und zurück.

Die vollständige Spezifikation als OpenAPI 3.1: /api/v1/openapi.json

Anmeldung

Schlüssel legst du im Dashboard deines Bots an und siehst sie genau einmal. Sie gehören in eine Umgebungsvariable, nicht in den Quelltext. Die beiden Lese-Endpunkte funktionieren auch ohne Schlüssel, dann aber mit einem knapperen 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

Gezählt wird je Minute und Schlüssel, anonym je Minute und IP. Jede Antwort trägt die Zahlen mit; ein 429 zusätzlich einen Retry-After-Header.

StufeAnfragen pro Minute
Ohne Schlüssel20
Stufe 1: standard60
Stufe 2: erhöht240
Stufe 3: Partner1200
  • x-ratelimit-limit
  • x-ratelimit-remaining
  • x-ratelimit-reset

Jeder Schlüssel startet auf Stufe 1. Brauchst du mehr, schreib uns kurz, wofür und mit welcher Last du rechnest. Wir heben die Stufe für deinen Schlüssel an. Im Support-Server fragen

Endpunkte

GET/api/v1/botsohne Schlüssel

List published bots

Parameter

pageintegerquery · optional · Standard 1
Page number, starting at 1.
limitintegerquery · optional · Standard 50
How many entries one response returns.Zwischen 1 und 100
sortstringquery · optional · Standard votes_month
Order of the page. The directory itself defaults to votes this month.Erlaubt: votes_month, votes_total, servers, new, name
searchstringquery · optional
Substring match on the bot name. For a query a human typed, use `GET /search`.Höchstens 100 Zeichen

Antworten

200
A page of bots.
405
This path exists, but not for that method. See `Allow`.
422
Invalid query parameters.
429
Rate limit exceeded. Wait the seconds given in `Retry-After`.
500
Something broke on our side. Retrying later is reasonable.
Beispielantwort
{
  "data": [
    {
      "id": "155149108183695360",
      "name": "Dyno",
      "slugs": {
        "de": "dyno",
        "en": "dyno"
      },
      "shortDescription": {
        "de": "Automod gegen Spam und Linkwerbung, dazu Warnungen, Timeouts und ein Protokoll.",
        "en": "Automod against spam and link ads, plus warnings, timeouts and an audit log."
      },
      "prefix": "?",
      "pricing": "freemium",
      "iconUrl": "https://bilder.topbot.gg/bots/155149108183695360/icon.webp",
      "bannerUrl": null,
      "inviteUrl": "https://discord.com/oauth2/authorize?client_id=155149108183695360&scope=bot",
      "supportServerUrl": "https://discord.gg/dyno",
      "websiteUrl": "https://dyno.gg",
      "categories": [
        "moderation",
        "security"
      ],
      "tags": [
        "automod",
        "logging"
      ],
      "owners": [
        "155149108183695360"
      ],
      "serverCount": 11768028,
      "shardCount": 512,
      "votesTotal": 905,
      "votesMonth": 84,
      "rating": {
        "average": 3.5,
        "count": 12
      },
      "isVerified": true,
      "publishedAt": "2026-08-10T19:14:15.268Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 39,
    "pages": 1
  }
}
GET/api/v1/bots/{id}ohne Schlüssel

Get one bot

Parameter

idstringpath · Pflicht
Discord application ID of the bot, the number from its invite URL.

Antworten

200
The bot.
404
No published bot with that application ID.
405
This path exists, but not for that method. See `Allow`.
429
Rate limit exceeded. Wait the seconds given in `Retry-After`.
500
Something broke on our side. Retrying later is reasonable.
Beispielantwort
{
  "data": {
    "id": "155149108183695360",
    "name": "Dyno",
    "slugs": {
      "de": "dyno",
      "en": "dyno"
    },
    "shortDescription": {
      "de": "Automod gegen Spam und Linkwerbung, dazu Warnungen, Timeouts und ein Protokoll.",
      "en": "Automod against spam and link ads, plus warnings, timeouts and an audit log."
    },
    "prefix": "?",
    "pricing": "freemium",
    "iconUrl": "https://bilder.topbot.gg/bots/155149108183695360/icon.webp",
    "bannerUrl": null,
    "inviteUrl": "https://discord.com/oauth2/authorize?client_id=155149108183695360&scope=bot",
    "supportServerUrl": "https://discord.gg/dyno",
    "websiteUrl": "https://dyno.gg",
    "categories": [
      "moderation",
      "security"
    ],
    "tags": [
      "automod",
      "logging"
    ],
    "owners": [
      "155149108183695360"
    ],
    "serverCount": 11768028,
    "shardCount": 512,
    "votesTotal": 905,
    "votesMonth": 84,
    "rating": {
      "average": 3.5,
      "count": 12
    },
    "isVerified": true,
    "publishedAt": "2026-08-10T19:14:15.268Z"
  }
}
GET/api/v1/bots/{id}/reviewsohne Schlüssel

Published reviews for a bot

The same reviews shown on the public bot page, most helpful first. No key required.

Parameter

idstringpath · Pflicht
Discord application ID of the bot, the number from its invite URL.
pageintegerquery · optional · Standard 1
Page number, starting at 1.
limitintegerquery · optional · Standard 25
How many reviews one page returns.Zwischen 1 und 100

Antworten

200
A page of reviews.
404
No published bot with that application ID.
405
This path exists, but not for that method. See `Allow`.
429
Rate limit exceeded. Wait the seconds given in `Retry-After`.
500
Something broke on our side. Retrying later is reasonable.
Beispielantwort
{
  "data": [
    {
      "id": "4f6d9a2c-1f0e-4a5e-9b3d-2c8a7e1b5d40",
      "rating": 5,
      "body": "Läuft seit Monaten ohne Ausfall, die Einrichtung war in wenigen Minuten erledigt.",
      "user": "624317230955626507",
      "username": "thespen",
      "helpfulCount": 3,
      "developerReply": {
        "body": "Danke! Die nächste Version bringt Vorlagen für Automod-Regeln.",
        "repliedAt": "2026-09-02T10:12:00.000Z"
      },
      "createdAt": "2026-08-30T18:22:41.000Z",
      "updatedAt": "2026-08-30T18:22:41.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 25,
    "total": 12,
    "pages": 1
  }
}
GET/api/v1/bots/{id}/historyohne Schlüssel

Server count history for a bot

Daily server counts, from the same source as the `serverCount` field on the bot: the numbers you report through `POST /bots/{id}/stats` if you report any, otherwise Discord's own count from the App Directory. `source` says which one it is. A bot with neither gets an empty list. One point per day, oldest first, collected nightly. No key required.

Parameter

idstringpath · Pflicht
Discord application ID of the bot, the number from its invite URL.
daysintegerquery · optional · Standard 30
Window in days. Only these four values exist; each one is its own cache entry.Erlaubt: 7, 30, 90, 365

Antworten

200
The history, oldest first.
404
No published bot with that application ID.
405
This path exists, but not for that method. See `Allow`.
422
Invalid query parameters.
429
Rate limit exceeded. Wait the seconds given in `Retry-After`.
500
Something broke on our side. Retrying later is reasonable.
Beispielantwort
{
  "data": [
    {
      "date": "2026-09-14",
      "servers": 11762004
    },
    {
      "date": "2026-09-15",
      "servers": 11768028
    }
  ],
  "days": 30,
  "source": "self"
}
GET/api/v1/bots/{id}/votes

Recent votes for your bot

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

Parameter

idstringpath · Pflicht
Discord application ID of the bot, the number from its invite URL.
limitintegerquery · optional · Standard 50
How many entries one response returns.Zwischen 1 und 100
hoursintegerquery · optional
Only votes from the last n hours (max 720).Zwischen 1 und 720

Antworten

200
The most recent votes, newest first.
401
Missing or invalid API key.
403
The key lacks the required scope, or belongs to a different bot.
405
This path exists, but not for that method. See `Allow`.
429
Rate limit exceeded. Wait the seconds given in `Retry-After`.
500
Something broke on our side. Retrying later is reasonable.
Beispielantwort
{
  "data": [
    {
      "user": "624317230955626507",
      "username": "thespen",
      "votedAt": "2026-09-16T07:41:02.104Z",
      "flagged": false
    }
  ]
}
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, and that is an answer, not an error.

Parameter

idstringpath · Pflicht
Discord application ID of the bot, the number from its invite URL.
userIdstringpath · Pflicht
Discord user ID.

Antworten

200
Vote status within the 12-hour cooldown window.
401
Missing or invalid API key.
403
The key lacks the required scope, or belongs to a different bot.
405
This path exists, but not for that method. See `Allow`.
429
Rate limit exceeded. Wait the seconds given in `Retry-After`.
500
Something broke on our side. Retrying later is reasonable.
Beispielantwort
{
  "data": {
    "voted": true,
    "votedAt": "2026-09-16T07:41:02.104Z",
    "expiresAt": "2026-09-16T19:41:02.104Z"
  }
}
GET/api/v1/bots/{id}/premium/{userId}

Does this user have premium?

Requires the `premium:read` scope. Use this before unlocking a premium feature. Returns 200 with `active: false` when they do not subscribe, and that is an answer, not an error.

Parameter

idstringpath · Pflicht
Discord application ID of the bot, the number from its invite URL.
userIdstringpath · Pflicht
Discord user ID.

Antworten

200
Premium status, including the tier and the paid-until date.
401
Missing or invalid API key.
403
The key lacks the required scope, or belongs to a different bot.
405
This path exists, but not for that method. See `Allow`.
429
Rate limit exceeded. Wait the seconds given in `Retry-After`.
500
Something broke on our side. Retrying later is reasonable.
Beispielantwort
{
  "data": {
    "active": true,
    "tierId": "e0b4a1c8-3f6d-4f0a-9f2e-7c1b5d8a4e32",
    "tierName": "Pro",
    "interval": "month",
    "guildId": null,
    "expiresAt": "2026-10-14T09:00:00.000Z",
    "cancelAtPeriodEnd": false
  }
}
GET/api/v1/bots/{id}/premium/guild/{guildId}

Does this server have premium?

Requires the `premium:read` scope. The check for guild-scoped tiers: ask with the server ID instead of the user ID. Returns 200 with `active: false` when it does not subscribe.

Parameter

idstringpath · Pflicht
Discord application ID of the bot, the number from its invite URL.
guildIdstringpath · Pflicht
Discord server (guild) ID.

Antworten

200
Premium status of the server, including the paid-until date.
401
Missing or invalid API key.
403
The key lacks the required scope, or belongs to a different bot.
405
This path exists, but not for that method. See `Allow`.
429
Rate limit exceeded. Wait the seconds given in `Retry-After`.
500
Something broke on our side. Retrying later is reasonable.
Beispielantwort
{
  "data": {
    "active": true,
    "tierId": "a1c2e3f4-5b6d-4e7f-8a9b-0c1d2e3f4a5b",
    "tierName": "Server Pro",
    "interval": "month",
    "guildId": "1538175049536901261",
    "expiresAt": "2026-10-14T09:00:00.000Z",
    "cancelAtPeriodEnd": false
  }
}
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.

Parameter

idstringpath · Pflicht
Discord application ID of the bot, the number from its invite URL.

Body

serverCount
integer
Pflicht · ≥ 0
shardCount
integer
optional · ≥ 1
{
  "serverCount": 6520,
  "shardCount": 4
}

Antworten

200
The stored values.
401
Missing or invalid API key.
403
The key lacks the required scope, or belongs to a different bot.
405
This path exists, but not for that method. See `Allow`.
422
Invalid body.
429
Rate limit exceeded. Wait the seconds given in `Retry-After`.
500
Something broke on our side. Retrying later is reasonable.
Beispielantwort
{
  "data": {
    "serverCount": 6520,
    "shardCount": 4
  }
}

Fehler

Jeder Fehler hat denselben Aufbau. `code` ist stabil und maschinenlesbar, `message` ist englischer Klartext für den Menschen im Log. Nur bei `validation_failed` kommt `details` dazu, ein Eintrag je abgelehntem Feld.

{
  "error": {
    "code": "validation_failed",
    "message": "One or more query parameters are invalid.",
    "details": [
      {
        "field": "limit",
        "message": "Too big: expected number to be <=100"
      }
    ]
  }
}
CodeStatusWann
unauthorized401Schlüssel fehlt, ist ungültig oder wurde zurückgezogen.
forbidden403Dem Schlüssel fehlt der nötige Scope, oder er gehört zu einem anderen Bot.
not_found404Kein veröffentlichter Bot mit dieser Application-ID.
method_not_allowed405Die Adresse gibt es, diese Methode nicht. `Allow` nennt die erlaubten.
validation_failed422Parameter oder Body sind ungültig. `details` nennt Feld und Grund.
rate_limited429Zu viele Anfragen im Zeitfenster.
internal_error500Fehler auf unserer Seite. Ein späterer Versuch ist sinnvoll.

405, 429 und 500 kann jeder Endpunkt beantworten, auch dort, wo ein Aufruf aussieht, als könnte er nicht fehlschlagen. Bei 429 sagt `Retry-After`, wie viele Sekunden zu warten sind.

Webhooks

Wir rufen deine Adresse auf, sobald jemand für deinen Bot gestimmt hat, ein Premium-Abo beginnt oder endet (event: premium) oder jemand den Bot unterstützt (event: donation). Prüfe die Signatur, bevor du eine Belohnung ausschüttest oder etwas freischaltest. Sonst kann jeder, der deine Adresse kennt, sich selbst beschenken.

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');

  // Reject replayed calls.
  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');

  // Compare in constant time.
  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);
});

Antworte mit einem beliebigen 2xx. Bei 5xx und 429 wiederholen wir sechsmal mit wachsendem Abstand (10 s, 40 s, 90 s, 160 s, 250 s), bei anderen 4xx gar nicht. Nach 20 endgültig gescheiterten Zustellungen schalten wir den Webhook ab und sagen dir Bescheid.

Badges

Ein SVG, das du in deine README oder auf deine Seite einbindest. Es folgt der Systemeinstellung des Betrachters und wird fünf Minuten lang gecacht. Mit theme=dark oder theme=light legst du eine Fassung fest, mit compact=1 fällt die Beschriftung weg.

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

Widget

Die einbettbare Bot-Karte: Icon, Zahlen und Vote-Knopf, live aus dem Verzeichnis. Parameter: theme=dark|light und lang=de|en. Fertigen Code mit deiner Application-ID findest du im Dashboard.

<iframe src="https://topbot.gg/api/widget/155149108183695360?theme=dark&lang=de"
  width="420" height="140" style="border:0"
  title="Topbot.gg" loading="lazy"></iframe>

Serververlauf

Die Serverzahl deines Bots im Zeitverlauf als einbettbares Bild, täglich erfasst. Sie kommt aus derselben Quelle wie das Feld serverCount: deine eigenen Meldungen, sonst Discords Zählung aus dem App Directory. Parameter: range=7|30|90|365 und theme=auto|dark|light. Die Rohdaten liefert GET /bots/{id}/history, das Feld source nennt die Quelle.

Serververlauf
[![Topbot.gg](https://topbot.gg/api/chart/155149108183695360?range=30)](https://topbot.gg/de/bots/YOUR-SLUG)