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.
| Stufe | Anfragen pro Minute |
|---|
| Ohne Schlüssel | 20 |
| Stufe 1: standard | 60 |
| Stufe 2: erhöht | 240 |
| Stufe 3: Partner | 1200 |
x-ratelimit-limitx-ratelimit-remainingx-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/searchohne Schlüssel
Search bots
The same search the site uses: typo tolerance, synonyms (moderation/mod/automod, musik/music, …) and German↔English matching included. Use this instead of `GET /bots?search=` when a human typed the query.
Parameter
- qstringquery · Pflicht
- The search query, as a human typed it. Typos are tolerated.Höchstens 100 Zeichen
- limitintegerquery · optional · Standard 20
- How many hits to return.Zwischen 1 und 50
- langstringquery · optional · Standard en
- Controls synonyms, ranking and the language of `slug` in the hits.Erlaubt: de, en
Antworten
- 200
- Matching bots, best first.
- 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",
"shortDescription": "Automod against spam and link ads, plus warnings, timeouts and an audit log.",
"slug": "dyno",
"iconUrl": "https://bilder.topbot.gg/bots/155149108183695360/icon.webp",
"categories": [
"moderation"
],
"votesMonth": 84,
"votesTotal": 905,
"serverCount": 11768028,
"rating": {
"average": 3.5,
"count": 12
},
"isVerified": true
}
],
"total": 1,
"processingTimeMs": 4
}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"
}
]
}
}| Code | Status | Wann |
|---|
| unauthorized | 401 | Schlüssel fehlt, ist ungültig oder wurde zurückgezogen. |
| forbidden | 403 | Dem Schlüssel fehlt der nötige Scope, oder er gehört zu einem anderen Bot. |
| not_found | 404 | Kein veröffentlichter Bot mit dieser Application-ID. |
| method_not_allowed | 405 | Die Adresse gibt es, diese Methode nicht. `Allow` nennt die erlaubten. |
| validation_failed | 422 | Parameter oder Body sind ungültig. `details` nennt Feld und Grund. |
| rate_limited | 429 | Zu viele Anfragen im Zeitfenster. |
| internal_error | 500 | Fehler 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.
[](https://topbot.gg/de/bots/YOUR-SLUG)
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.
[](https://topbot.gg/de/bots/YOUR-SLUG)