# Discordbot bot directory API

This API lets your bot post its server count (rankings, charts) and check whether a user voted for it.

- Base URL: `https://discordbot.fr/api/v1`
- Format: JSON (`Content-Type: application/json`)
- Markdown version of this page, to give to an AI: https://discordbot.fr/en/bots/api.md

## Authentication

Generate a token on your bot’s edit page (Dashboard → My bots → Edit → API and statistics). It starts with `dbt_` and is shown only once: generating a new one revokes the old one.

Send it in the header `Authorization` (`Bearer ` is accepted but optional):

```http
Authorization: dbt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

The token is secret: keep it server-side (environment variable), never in published code or in a browser. A token only works for its own bot.

## Post statistics

`POST /api/v1/bots/{bot_id}/stats` — token required

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `server_count` | integer | yes | Server count (0 to 50,000,000) |
| `shard_count` | integer | no | Shard count (1 to 100,000) |

Response: `200 { "ok": true }`

Post them on startup, then about every 30 minutes. Limit: 30 requests per minute per token.

```bash
curl -X POST "https://discordbot.fr/api/v1/bots/BOT_ID/stats" \
  -H "Authorization: $DISCORDBOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"server_count": 1234, "shard_count": 1}'
```

### discord.js (Node 18+)

```js
async function postStats(client) {
  const res = await fetch(`https://discordbot.fr/api/v1/bots/${client.user.id}/stats`, {
    method: "POST",
    headers: { Authorization: process.env.DISCORDBOT_TOKEN, "Content-Type": "application/json" },
    body: JSON.stringify({ server_count: client.guilds.cache.size, shard_count: client.shard?.count ?? 1 }),
  });
  if (!res.ok) console.warn("discordbot.fr stats:", res.status, await res.text());
}

client.once("ready", () => {
  postStats(client);
  setInterval(() => postStats(client), 30 * 60 * 1000);
});
```

With several shards, post the total from a single shard (for example the sum of `client.shard.fetchClientValues("guilds.cache.size")` on shard 0), otherwise each shard overwrites the others’ count.

### discord.py

```python
import os, aiohttp
from discord.ext import tasks

@tasks.loop(minutes=30)
async def post_stats():
    async with aiohttp.ClientSession() as session:
        await session.post(
            f"https://discordbot.fr/api/v1/bots/{bot.user.id}/stats",
            headers={"Authorization": os.environ["DISCORDBOT_TOKEN"]},
            json={"server_count": len(bot.guilds), "shard_count": bot.shard_count or 1},
        )

@post_stats.before_loop
async def before_post_stats():
    await bot.wait_until_ready()

# in setup_hook or on_ready: post_stats.start()
```

## Check a vote

`GET /api/v1/bots/{bot_id}/check?userId={user_id}` — token required

Tells whether the user voted in the last 12 hours (one vote every 12 h). Limit: 120 requests per minute per token.

```json
{ "voted": 1, "nextVoteAt": "2026-01-01T12:00:00.000Z" }
```

`voted` is `1` or `0`; `nextVoteAt` is when the user can vote again, or `null`.

## Vote webhook

Set a webhook URL (public HTTPS) on the edit page: on every vote we send a JSON `POST`. If you set a secret, it is sent as is in the `Authorization` header: check it before trusting the request. The “Test the webhook” button sends a `test` call.

```json
{ "bot": "BOT_ID", "user": "USER_ID", "type": "upvote", "isWeekend": false, "query": "" }
```

`type` is `upvote` or `test`. Answer with a 2xx code within 5 seconds; redirects are not followed and failed calls are not retried.

## Public endpoints

No token, CORS open, published bots only.

- `GET /api/v1/bots/{bot_id}` — bot profile (cached 60 s):

```json
{
  "id": "BOT_ID",
  "name": "My bot",
  "avatar_url": "https://cdn.discordapp.com/avatars/...",
  "short_description": "...",
  "tags": ["moderation"],
  "server_count": 1234,
  "shard_count": 1,
  "votes_total": 56,
  "votes_month": 7,
  "url": "https://discordbot.fr/bot/BOT_ID"
}
```

- `GET /api/v1/bots/{bot_id}/history?days=90` — daily history, `days` from 1 to 730 (cached 5 min, 120 requests per minute per IP):

```json
{ "id": "BOT_ID", "days": 90, "points": [{ "day": "2026-01-01", "server_count": 1234, "shard_count": 1, "votes": 3 }] }
```

## Errors

Errors return `{ "error": "code" }`.

| HTTP | Code | Meaning |
| --- | --- | --- |
| 400 | `invalid_fields` | Invalid field, listed in `fields` |
| 401 | `invalid_token` | Missing or revoked token |
| 403 | `token_mismatch` | The token belongs to another bot |
| 404 | `not_found` | Unknown or unpublished bot |
| 429 | `rate_limited` | Too many requests, retry in a minute |
| 503 | `directory_unavailable` | Directory temporarily unavailable |
