# Developer Platform

Welcome to your team’s developer platform

<h2 align="center">DTOP Developer Hub</h2>

<p align="center">Intégrez DiscordTop à vos bots, sites et CMS. Vérifiez les votes, déclenchez des récompenses et automatisez votre écosystème en quelques requêtes API.</p>

<p align="center"><a href="/spaces/Z1O8haSztScRfu0fy1yo/pages/PbYb0GukRhiS4qCHdRal" class="button primary">Commencer en 5 minutes</a> <a href="/spaces/lAtlbGhScSjx0xV8Uf9W" class="button secondary">Voir la référence API</a></p>

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><h4><i class="fa-leaf">:leaf:</i></h4></td><td><strong>Démarrage rapide</strong></td><td><a href="/spaces/Z1O8haSztScRfu0fy1yo/pages/PbYb0GukRhiS4qCHdRal">/spaces/Z1O8haSztScRfu0fy1yo/pages/PbYb0GukRhiS4qCHdRal</a></td><td><a href="/files/LHIOZGoI6dPF36TuRaxH">/files/LHIOZGoI6dPF36TuRaxH</a></td></tr><tr><td><h4><i class="fa-server">:server:</i></h4></td><td>Intégrations &#x26; exemples</td><td><a href="/spaces/Z1O8haSztScRfu0fy1yo/pages/iPZg7zwlcR7qIIUQnGCb">/spaces/Z1O8haSztScRfu0fy1yo/pages/iPZg7zwlcR7qIIUQnGCb</a></td><td><a href="/files/IaUMODRBBSBL8eUaPX09">/files/IaUMODRBBSBL8eUaPX09</a></td></tr><tr><td><h4><i class="fa-terminal">:terminal:</i></h4></td><td><strong>API reference</strong></td><td><a href="/spaces/lAtlbGhScSjx0xV8Uf9W">/spaces/lAtlbGhScSjx0xV8Uf9W</a></td><td><a href="/files/ebMoLMpZFyLqpzByuzrk">/files/ebMoLMpZFyLqpzByuzrk</a></td></tr><tr><td>Changelog &#x26; roadmap</td><td>Suivez les nouveautés de l’API DiscordTop, les changements de version et les fonctionnalités à venir.</td><td><a href="/spaces/0dfGqEOSy4lLEVkcRrSJ">/spaces/0dfGqEOSy4lLEVkcRrSJ</a></td><td></td></tr></tbody></table>

{% columns %}
{% column %}

### Commencez en 5 minutes

Mettre en place votre première vérification de vote avec l’API DiscordTop ne devrait prendre que quelques minutes.\
Avec des endpoints clairs, des exemples prêts à l’emploi et une authentification ultra simple, vous serez opérationnel très rapidement.

Aucune prise de tête, aucune complexité : juste votre premier appel réussi, tout de suite.

<a href="/spaces/Z1O8haSztScRfu0fy1yo/pages/PbYb0GukRhiS4qCHdRal" class="button primary" data-icon="rocket-launch">Démarrer</a> <a href="/spaces/lAtlbGhScSjx0xV8Uf9W" class="button secondary" data-icon="terminal">API reference</a>
{% endcolumn %}

{% column %}
{% code title="index.js" overflow="wrap" %}

```javascript
// Vérifier si un utilisateur a voté via son ID Discord

const res = await fetch(
  "https://api.discordtop.net/v7/vote-check?discord_id=1234567890"
);

const data = await res.json();

console.log(data);
// → { has_voted: true, next_vote_at: "...", cooldown_remaining_seconds: ... }

```

{% endcode %}
{% endcolumn %}
{% endcolumns %}

<h3 align="center">En savoir plus sur la plateforme développeur</h3>

<p align="center">Lisez les guides, suivez des tutoriels pas à pas et découvrez comment intégrer DiscordTop<br>à votre bot, votre site web, votre CMS ou votre serveur de jeu.</p>

<p align="center">Que vous souhaitiez créer un système de récompenses, automatiser la gestion des votes<br>ou connecter votre communauté à DiscordTop, tout est documenté.</p>

<p align="center"><a href="/spaces/Sltq0HGzeq5kc2ytOaGE" class="button primary" data-icon="book-open">Guides</a> <a href="/spaces/Z1O8haSztScRfu0fy1yo" class="button secondary" data-icon="book">Documentation</a></p>

<h2 align="center">Rejoignez la communauté des développeurs DiscordTop</h2>

<p align="center">Rejoignez notre serveur Discord pour poser vos questions, partager vos intégrations<br>ou découvrir ce que les autres membres créent avec l’API DiscordTop.</p>

<p align="center">Que vous gériez un petit serveur ou une grande communauté, vous n’êtes plus seul.</p>


# Bienvenue !

Bienvenue sur le **DiscordTop Developer Hub** !

\
Vous trouverez ici toute la documentation nécessaire pour connecter votre bot, votre site web, votre CMS ou vos outils internes à DiscordTop.

Que vous souhaitiez :

* vérifier si un utilisateur a voté,
* déclencher une récompense automatiquement,
* lier un projet externe (Minecraft, site web, etc.),
* automatiser vos systèmes communautaires,

… tout est ici, documenté de manière claire et rapide à intégrer.

### 🚀 Commencez dès maintenant

Découvrez les ressources essentielles pour prendre en main l’API DiscordTop !

<table data-view="cards"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td>Démarrage rapide</td><td>Configurez votre clé API et effectuez votre <strong>premier appel</strong> à l’endpoint <code>/v7/check-vote</code> en quelques minutes.</td></tr><tr><td>Bases de l'intégration</td><td>Apprenez les concepts fondamentaux : token API, identifiants (<code>discord_id</code>, <code>external_id</code>), cooldown, bonnes pratiques et sécurité.</td></tr><tr><td>Références API</td><td>Consultez la documentation complète du endpoint <code>/v7/check-vote</code><br>(accès, paramètres, exemples, codes d’erreur, logique de cooldown, etc.).</td></tr></tbody></table>

### Et ensuite ?

À mesure que l’écosystème DiscordTop évolue, vous pourrez retrouver ici :

* Les nouveautés API
* Le futur SDK officiel `@discordtop/sdk`
* Les webhooks (vote\_received, reward\_granted…)
* Les guides avancés (bots Discord, Azuriom, serveurs Minecraft…)


# Démarrage rapide

Bienvenue dans le guide de démarrage rapide de l’API DiscordTop.

En moins de 5 minutes, vous allez :

1. Obtenir votre clé API
2. Faire votre premier appel à l’endpoint `/v7/vote-check`
3. Comprendre comment interpréter la réponse
4. Intégrer la logique dans votre bot, CMS ou site web

{% hint style="warning" %}
Il est nécessaire d'avoir la permission `ADMINISTRATOR` sur le serveur Discord visés !
{% endhint %}

### 🧩 Obtenir votre clé API

Votre clé API est **liée à votre serveur Discord**.\
Elle permet d’authentifier vos requêtes et doit rester secrète.

Pour récupérer votre clé API :

1. Rendez-vous sur le tableau de bord DiscordTop
2. Allez dans **Développeur & API**
3. Cliquez sur **Générer ma clé API**
4. Copiez-la et stockez-la dans une variable d’environnement

<div data-full-width="false"><figure><img src="/files/W3n3lLuGHgCK0nkKNSqa" alt=""><figcaption></figcaption></figure> <figure><img src="/files/vNqWnDuusDDj2HEi0kSg" alt=""><figcaption></figcaption></figure></div>

Stockez votre clé API dans une variable d'environnement, par exemple :&#x20;

```ini
DISCORDTOP_API_TOKEN="dtop_xxxxxxxxxxxxxxxxxxxxxxxxxx"
```

{% hint style="info" %}
Votre clé API est stocké par serveur, donc l'ensemble des membres de votre serveur possédant la permission ADMINISTRATOR aura accès à la clé API !
{% endhint %}

{% hint style="info" %}
En cas de vol de votre clé, vous pouvez regénéré une nouvelle clé depuis votre tableau de bord !
{% endhint %}

{% hint style="danger" %}
⚠️ **Ne partagez jamais votre clé API publiquement.**
{% endhint %}

{% hint style="danger" %}
⚠️ **Ne l’envoyez jamais dans votre front-end (seulement côté serveur).**
{% endhint %}

### 🔌 Faire votre premier appel

L’endpoint principal est :

```bash
GET https://api.discordtop.net/v7/vote-check
```

Vous pouvez identifier un utilisateur de deux façons :

#### 👉 Avec son **discord\_id**

(identifiant Discord, recommandé si vous avez un bot où si vous utilisez l'oAuth2)

#### 👉 Avec un **external\_id**

(pseudo Minecraft, identifiant de site, ID interne, etc.)

### Exemple : vérifier un vote via `discord_id`

```sh
curl -X GET "https://api.discordtop.net/v7/vote-check?discord_id=123456789012345678" \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Accept-Language: fr"
```

#### Exemple en JavaScript (Node)

```js
const res = await fetch(
  "https://api.discordtop.net/v7/vote-check?discord_id=123456789012345678",
  {
    method: "GET",
    headers: {
      Authorization: `Bearer ${process.env.DISCORDTOP_API_TOKEN}`,
      "Accept-Language": "fr", // optionnel
    },
  }
);

// Exemple de gestion d'erreur
if (res.status === 429) {
  const body = await res.json();
  console.log("Rate limited, retry after", body.retry_after, "seconds");
  process.exit(1);
}

const data = await res.json();
console.log(data);
/*
  {
    has_voted: true,
    cooldown_remaining_seconds: 0
  }
*/
```

### 📥Réponse

Exemple de réponse complète :

```json
{
  "ok": true,
  "guild_id": "1071463831638900836",
  "has_voted": true,
  "last_vote_at": "2025-11-27T17:57:33.017+01:00",
  "next_vote_at": "2025-11-27T18:57:33.017+01:00",
  "cooldown_remaining_seconds": 3579.103
}
```

{% hint style="info" %}
Référez-vous à API Référence afin d'avoir des détails sur la réponse
{% endhint %}

#### Résumé logique :

* `has_voted = true` → **L’utilisateur a voté cette dernière heure**
* `has_voted = false` → **L’utilisateur n'a pas voté sur les 60 dernières minutes (1h)**

#### 🧪 Exemple d’intégration simple

Bot Discord (JavaScript)

```js
if (!data.has_voted) {
  await interaction.reply("🎉 Merci pour ton vote ! Voici ta récompense.");
} else {
  const minutes = Math.ceil(data.cooldown_remaining_seconds / 60);
  await interaction.reply(`⏳ Tu pourras revoter dans ${minutes} minutes.`);
}
```

### Exemple : vérifier un vote via `external_id`

```sh
curl -X GET "https://api.discordtop.net/v7/vote-check?external_id=PlayerNameForExemple" \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Accept-Language: fr"
```

**Cas typiques d’utilisation :**

* vérifier les votes d’un joueur Minecraft (pseudo)
* vérifier les votes d’un compte d’un site web (ID utilisateur)
* synchroniser un système interne à votre projet

{% hint style="warning" %}
Attention, l'utilisation du external\_id nécessite un lien de vote personnalisé afin que les données puissent être utilisées ! Ce système n'est pas compatible avec le vote depuis le bot DTOP !
{% endhint %}

Exemple de lien de vote :&#x20;

```bash
https://discordtop.net/guild/SERVER_ID/vote?external_id=YOUR_EXTERNAL_ID
```

Vous trouverez plus de détail dans la section dédié.


# Integrations

GitBook integrations allow you to connect your GitBook spaces to some of your favorite platforms and services. You can install integrations into your GitBook page from the *Integrations* menu in the top left.

<figure><img src="https://gitbookio.github.io/onboarding-template-images/integrations-hero.png" alt=""><figcaption></figcaption></figure>

### Types of integrations

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th></tr></thead><tbody><tr><td><strong>Analytics</strong></td><td>Track analytics from your docs</td><td><a href="https://www.gitbook.com/integrations#analytics">https://www.gitbook.com/integrations#analytics</a></td><td></td><td></td></tr><tr><td><strong>Support</strong></td><td>Add support widgets to your docs</td><td><a href="https://www.gitbook.com/integrations#support">https://www.gitbook.com/integrations#support</a></td><td></td><td></td></tr><tr><td><strong>Interactive</strong></td><td>Add extra functionality to your docs</td><td><a href="https://www.gitbook.com/integrations#interactive">https://www.gitbook.com/integrations#interactive</a></td><td></td><td></td></tr><tr><td><strong>Visitor Authentication</strong></td><td>Protect your docs and require sign-in</td><td><a href="https://www.gitbook.com/integrations#visitor-authentication">https://www.gitbook.com/integrations#visitor-authentication</a></td><td></td><td></td></tr></tbody></table>


# DiscordJS

Intégrer la vérification des votes dans un bot Discord (JS)

Dans cet exemple, on crée une commande `/vote` qui :

1. Envoie un message avec :
   * un bouton **“Voter sur DiscordTop”** → ouvre la page de vote,
   * un bouton **“Vérifier mon vote”** → appelle l’API DiscordTop.
2. Si le vote est validé par l’API, **vous pouvez appliquer vos propres récompenses** :
   * donner un rôle,
   * ajouter de l’XP,
   * ouvrir l’accès à un salon privé, etc.

***

### 1. Pré-requis

* Node.js **18+** (pour avoir `fetch` intégré)
* Un bot Discord configuré (token)
* Un **token API DiscordTop** (`api_token`) associé à votre serveur - [Comment trouvé ma clé ?](/api-reference/concepts-cles/authentification)

Packages :

```bash
npm install discord.js dotenv
```

***

### 2. Configuration du projet

Créez un fichier `.env` à la racine :

```env
DISCORD_TOKEN=VOTRE_TOKEN_BOT
DISCORD_CLIENT_ID=ID_CLIENT_DE_VOTRE_BOT
DISCORD_GUILD_ID=ID_DU_SERVEUR_DE_TEST
DTOP_API_TOKEN=VOTRE_CLE_API_DISCORDTOP
DTOP_GUILD_ID=ID_DU_SERVEUR_SUR_DTOP
```

> `DTOP_GUILD_ID` est l’ID du serveur tel qu’il apparaît sur DiscordTop (en général, c’est le même que l’ID Discord).

***

### 3. Enregistrer la commande `/vote`

Créez un fichier `deploy-commands.js` :

{% code overflow="wrap" %}

```js
import 'dotenv/config';
import { REST, Routes, SlashCommandBuilder } from 'discord.js';

const commands = [
  new SlashCommandBuilder()
    .setName('vote')
    .setDescription('Obtenir le lien de vote DiscordTop et vérifier votre vote.')
    .toJSON(),
];

const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);

async function main() {
  try {
    console.log('🔁 Mise à jour des commandes (guild)…');
    await rest.put(
      Routes.applicationGuildCommands(
        process.env.DISCORD_CLIENT_ID,
        process.env.DISCORD_GUILD_ID
      ),
      { body: commands }
    );
    console.log('✅ Commandes enregistrées avec succès.');
  } catch (error) {
    console.error('❌ Erreur lors de l’enregistrement des commandes :', error);
  }
}

main();
```

{% endcode %}

Lancer une fois :

```bash
node deploy-commands.js
```

***

### 4. Bot de base avec `/vote` + boutons

Créez un fichier `index.js` :

```js
import 'dotenv/config';
import {
  Client,
  GatewayIntentBits,
  Partials,
  ButtonStyle,
  ActionRowBuilder,
  ButtonBuilder,
  Events,
} from 'discord.js';

const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
  partials: [Partials.GuildMember],
});

// URL de vote DTOP (adapter si besoin)
function getDiscordTopVoteUrl() {
  const guildId = process.env.DTOP_GUILD_ID;
  return `https://discordtop.net/guild/${guildId}/vote`;
}

// Appel à l’API DTOP pour vérifier le vote
async function checkVoteOnDiscordTop(userId) {
  const apiToken = process.env.DTOP_API_TOKEN;
  const url = new URL('https://api.discordtop.net/v7/vote-check');
  
  url.searchParams.set('discord_id', userId);

  // Vous pouvez aussi ajouter &locale=fr si vous voulez forcer la langue
  // url.searchParams.set('locale', 'fr');

  const res = await fetch(url.toString(), {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${apiToken}`,
      'Accept-Language': 'fr-FR',
    },
  });

  // On retourne la réponse brute + le JSON
  let body = null;
  try {
    body = await res.json();
  } catch {
    body = null;
  }

  return { status: res.status, body };
}

client.once(Events.ClientReady, (c) => {
  console.log(`✅ Connecté en tant que ${c.user.tag}`);
});

client.on(Events.InteractionCreate, async (interaction) => {
  // Commande /vote
  if (interaction.isChatInputCommand() && interaction.commandName === 'vote') {
    const voteUrl = getDiscordTopVoteUrl();

    const row = new ActionRowBuilder().addComponents(
      new ButtonBuilder()
        .setLabel('Voter sur DiscordTop')
        .setStyle(ButtonStyle.Link)
        .setURL(voteUrl),
      new ButtonBuilder()
        .setCustomId('dtop-check-vote')
        .setLabel('Vérifier mon vote')
        .setStyle(ButtonStyle.Primary)
    );

    await interaction.reply({
      content:
        'Merci de soutenir le serveur en votant sur DiscordTop !\nCliquez sur le bouton ci-dessous pour voter, puis utilisez **“Vérifier mon vote”** pour recevoir vos récompenses.',
      components: [row],
      ephemeral: true,
    });

    return;
  }

  // Bouton "Vérifier mon vote"
  if (interaction.isButton() && interaction.customId === 'dtop-check-vote') {
    await interaction.deferReply({ ephemeral: true });

    const userId = interaction.user.id;

    try {
      const { status, body } = await checkVoteOnDiscordTop(userId);

      // Gestion des statuts principaux
      if (status === 200) {
        const hasVoted = body?.has_voted ?? false;

        if (!hasVoted) {
          return interaction.editReply(
            "Il semble que vous n'ayez pas encore voté pour ce serveur. Essayez de voter puis réessayez dans quelques secondes."
          );
        }

        // 👉 C’est ici que vous appliquez VOS récompenses :
        // - donner un rôle
        // - ajouter de l’XP
        // - débloquer un salon, etc.

        // Exemple : donner un rôle (remplacez par votre ID de rôle)
        const rewardRoleId = 'ID_DU_ROLE_RECOMPENSE'; // à adapter

        const member =
          interaction.member ??
          (await interaction.guild.members.fetch(userId).catch(() => null));

        if (member && rewardRoleId !== 'ID_DU_ROLE_RECOMPENSE') {
          await member.roles.add(rewardRoleId).catch(() => null);
        }

        return interaction.editReply(
          "✅ Vote validé sur DiscordTop ! Vos récompenses ont été appliquées sur le serveur."
        );
      }

      if (status === 404) {
        return interaction.editReply(
          "Aucun vote récent n'a été trouvé pour votre compte. Assurez-vous d'avoir voté sur la bonne page et réessayez dans quelques instants."
        );
      }

      if (status === 429) {
        const retryAfter = body?.retry_after ?? 60;
        return interaction.editReply(
          `🚫 Vous effectuez trop de vérifications de vote. Merci de patienter **${retryAfter} secondes** avant de réessayer.`
        );
      }

      if (status === 401 || status === 403) {
        return interaction.editReply(
          "⚠️ La configuration de l'API DiscordTop semble incorrecte (clé invalide ou serveur non autorisé). Contactez un administrateur."
        );
      }

      // Autres erreurs (500, 400, etc.)
      console.error('Erreur API DTOP:', status, body);
      return interaction.editReply(
        "❌ Une erreur est survenue lors de la vérification du vote. Merci de réessayer plus tard."
      );
    } catch (err) {
      console.error('Erreur lors de l’appel à DTOP :', err);
      return interaction.editReply(
        "❌ Impossible de contacter l’API DiscordTop pour le moment."
      );
    }
  }
});

client.login(process.env.DISCORD_TOKEN);
```

***

### 5. Où brancher votre propre logique de récompenses ?

Dans l’exemple ci-dessus, le bloc important est ici :

```js
if (status === 200) {
  const hasVoted = body?.has_voted ?? true;

  if (!hasVoted) {
    return interaction.editReply(
      "Il semble que vous n'ayez pas encore voté pour ce serveur. Essayez de voter puis réessayez dans quelques secondes."
    );
  }

  // 👉 VOTRE LOGIQUE DE RÉCOMPENSE
  // Exemple : donner un rôle, XP, etc.
}
```

C’est à cet endroit précis que vous pouvez :

* incrémenter un champ XP dans votre base,
* ajouter un rôle avec `member.roles.add(...)`,
* logger l’événement dans un salon staff,
* comptabiliser les votes journaliers, etc.

***

### 6. Résumé du flux côté bot

1. L’utilisateur tape `/vote`
2. Le bot répond avec :
   * un bouton **lien** → page de vote DiscordTop,
   * un bouton **“Vérifier mon vote”**
3. L’utilisateur clique “Vérifier mon vote”
4. Le bot appelle :

```http
GET https://api.discordtop.net/v7/vote-check?api_token=VOTRE_CLE_API&discord_id=USER_ID
```

5. Selon la réponse :
   * ✅ 200 → vote valide → vos récompenses sont appliquées
   * ❌ 404 → pas de vote récent
   * 🚫 429 → trop de requêtes, respectez `retry_after`
   * 🔐 401/403 → problème de configuration API
   * 💥 500 → erreur côté DTOP (à réessayer plus tard)


# DiscordPY

Intégrer la vérification des votes dans un bot Discord (Python)

Dans cet exemple, nous créons une commande `/vote` qui :

1. Envoie un message avec :
   * un bouton **« Voter sur DiscordTop »** → ouvre la page de vote,
   * un bouton **« Vérifier mon vote »** → appelle l’API DiscordTop.
2. Si le vote est validé par l’API, **vous appliquez vos propres récompenses** :
   * donner un rôle,
   * ajouter de l’XP,
   * débloquer un salon, etc.

***

#### 1. Pré-requis

* Python **3.10+**
* Un bot Discord fonctionnel (token)
* Un **token API DiscordTop** (`api_token`) associé à votre serveur

Packages à installer :

```bash
pip install -U discord.py aiohttp python-dotenv
```

***

#### 2. Configuration du projet

Créez un fichier `.env` à la racine de votre projet :

```env
DISCORD_TOKEN=VOTRE_TOKEN_BOT
DISCORD_GUILD_ID=ID_DU_SERVEUR_DE_TEST
DTOP_API_TOKEN=VOTRE_CLE_API_DISCORDTOP
DTOP_GUILD_ID=ID_DU_SERVEUR_SUR_DTOP
```

> `DTOP_GUILD_ID` correspond à l’ID du serveur tel qu’il apparaît sur DiscordTop (en général, c’est le même que l’ID Discord).

***

#### 3. Bot Python complet : commande `/vote` + boutons + appel API DTOP

Créez un fichier `bot.py` avec le contenu suivant :

```python
import os
import asyncio
from dotenv import load_dotenv

import aiohttp
import discord
from discord import app_commands
from discord.ext import commands

load_dotenv()

DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
DISCORD_GUILD_ID = int(os.getenv("DISCORD_GUILD_ID"))
DTOP_API_TOKEN = os.getenv("DTOP_API_TOKEN")
DTOP_GUILD_ID = os.getenv("DTOP_GUILD_ID")  # ID du serveur sur DiscordTop

# Exemple : rôle récompense (à remplacer par un ID réel si vous utilisez cette partie)
REWARD_ROLE_ID = 0  # ex: 123456789012345678

intents = discord.Intents.default()
intents.members = True  # nécessaire si vous attribuez des rôles

bot = commands.Bot(command_prefix="!", intents=intents)


def get_discordtop_vote_url() -> str:
    """Construit l'URL de vote DiscordTop pour votre serveur."""
    return f"https://discordtop.net/guild/{DTOP_GUILD_ID}/vote"


async def check_vote_on_discordtop(user_id: int):
    """
    Appelle l’API DiscordTop pour vérifier si l’utilisateur a voté.

    GET https://api.discordtop.net/v7/check-vote?discord_id=...

    Retourne (status, body_json | None).
    """
    url = "https://api.discordtop.net/v7/vote-check"
    params = {
        "discord_id": str(user_id),
        # "locale": "fr",  # décommentez pour forcer la langue si besoin
    }

    async with aiohttp.ClientSession() as session:
        async with session.get(
            url,
            params=params,
            headers={"Authorization": f"Bearer {DTOP_API_TOKEN}","Accept-Language": "fr-FR"},
        ) as resp:
            status = resp.status
            try:
                data = await resp.json()
            except Exception:
                data = None
            return status, data


class VoteView(discord.ui.View):
    """Vue avec les boutons de vote."""

    def __init__(self):
        super().__init__(timeout=60 * 5)  # 5 minutes
        # Bouton lien vers la page de vote DTOP
        self.add_item(
            discord.ui.Button(
                label="Voter sur DiscordTop",
                style=discord.ButtonStyle.link,
                url=get_discordtop_vote_url(),
            )
        )

    @discord.ui.button(
        label="Vérifier mon vote",
        style=discord.ButtonStyle.primary,
        custom_id="dtop-check-vote",
    )
    async def check_vote_button(
        self,
        interaction: discord.Interaction,
        button: discord.ui.Button,
    ):
        """Callback du bouton 'Vérifier mon vote'."""
        await interaction.response.defer(ephemeral=True)

        user_id = interaction.user.id

        try:
            status, body = await check_vote_on_discordtop(user_id)
        except Exception as exc:
            print("Erreur lors de l'appel à l'API DiscordTop :", exc)
            return await interaction.edit_original_response(
                content="❌ Impossible de contacter l’API DiscordTop pour le moment."
            )

        # Gestion des principaux statuts HTTP
        if status == 200:
        
            if not isinstance(body, dict):
                return await interaction.edit_original_response(
                    content="❌ Réponse inattendue de l’API DiscordTop."
                )
            # Selon la structure de la réponse, vous pouvez vérifier un champ comme `has_voted`

            has_voted = bool(body.get("has_voted", False))
            
            if not has_voted:
                return await interaction.edit_original_response(
                    content=(
                        "❌ Vous n'avez pas encore voté sur DiscordTop !\n"
                        "Cliquez sur « Voter sur DiscordTop », votez, puis réessayez."
                    )
                )
                
            # 👉 C'est ici que vous appliquez VOS récompenses :
            # - ajout de rôle
            # - ajout d'XP
            # - accès à un salon, etc.
            
            if REWARD_ROLE_ID:
                try:
                    member = interaction.user
                    if not isinstance(member, discord.Member):
                        member = await interaction.guild.fetch_member(user_id)

                    role = interaction.guild.get_role(REWARD_ROLE_ID)
                    if role:
                        await member.add_roles(
                            role, reason="Récompense de vote DiscordTop"
                        )
                except Exception as exc:
                    print("Erreur lors de l'attribution du rôle :", exc)

            return await interaction.edit_original_response(
                content="✅ Vote validé sur DiscordTop ! Vos récompenses ont été appliquées."
            )

        if status == 404:
            return await interaction.edit_original_response(
                content=(
                    "Aucun vote récent n'a été trouvé pour votre compte.\n"
                    "Assurez-vous d'avoir voté sur la bonne page et réessayez dans quelques instants."
                )
            )

        if status == 429:
            retry_after = 60
            if isinstance(body, dict):
                retry_after = int(body.get("retry_after", retry_after))

            return await interaction.edit_original_response(
                content=(
                    f"🚫 Vous effectuez trop de vérifications de vote.\n"
                    f"Merci de patienter **{retry_after} secondes** avant de réessayer."
                )
            )

        if status in (401, 403):
            return await interaction.edit_original_response(
                content=(
                    "⚠️ La configuration de l'API DiscordTop semble incorrecte "
                    "(clé invalide ou serveur non autorisé). Contactez un administrateur."
                )
            )

        # Autres erreurs (400, 500, etc.)
        print("Erreur API DiscordTop :", status, body)
        return await interaction.edit_original_response(
            content=(
                "❌ Une erreur est survenue lors de la vérification du vote.\n"
                "Merci de réessayer plus tard."
            )
        )


@bot.event
async def on_ready():
    print(f"✅ Connecté en tant que {bot.user} (ID: {bot.user.id})")

    # Synchronisation des commandes slash sur une guilde précise (plus rapide pour les tests)
    guild = discord.Object(id=DISCORD_GUILD_ID)
    bot.tree.copy_global_to(guild=guild)
    await bot.tree.sync(guild=guild)
    print(f"🧵 Commandes synchronisées sur la guilde {DISCORD_GUILD_ID}")


@bot.tree.command(
    name="vote",
    description="Lien de vote DiscordTop + vérification du vote.",
)
async def vote_command(interaction: discord.Interaction):
    """Commande /vote : envoie les boutons."""
    view = VoteView()
    await interaction.response.send_message(
        (
            "Merci de soutenir le serveur en votant sur DiscordTop !\n"
            "Cliquez sur **Voter sur DiscordTop**, puis utilisez **Vérifier mon vote** "
            "pour recevoir vos récompenses."
        ),
        view=view,
        ephemeral=True,
    )


def main():
    bot.run(DISCORD_TOKEN)


if __name__ == "__main__":
    asyncio.run(main())
```

***

#### 4. Où appliquer vos propres récompenses ?

Le bloc à modifier est ici :

```python
if status == 200:
    # 👉 C'est ici que vous appliquez VOS récompenses :
    # - ajout de rôle
    # - ajout d'XP
    # - accès à un salon, etc.
```

À cet endroit, vous pouvez :

* incrémenter un champ XP dans votre base de données,
* donner un rôle temporaire ou permanent,
* ouvrir l’accès à un salon réservé aux voteurs,
* logger l’événement dans un salon staff, etc.

***

#### 5. Résumé du flux côté bot

1. L’utilisateur exécute la commande `/vote`.
2. Le bot envoie un message avec :
   * un bouton **lien** → page de vote DiscordTop,
   * un bouton **« Vérifier mon vote »**.
3. L’utilisateur clique sur **« Vérifier mon vote »**.
4. Le bot appelle :

```http
GET https://api.discordtop.net/v7/check-vote?discord_id=USER_ID
```

5. Selon la réponse :
   * ✅ 200 → vote valide → vos récompenses sont appliquées
   * ❌ 404 → pas de vote récent
   * 🚫 429 → trop de requêtes, respectez `retry_after`
   * 🔐 401/403 → problème de configuration API
   * 💥 500 → erreur côté DTOP (à réessayer plus tard)


# AzuriomCMS - Plugin Vote

Intégrer DiscordTop dans Azuriom (plugin Vote)

Cette page explique comment connecter un site Azuriom au système de vote DiscordTop afin de :

* vérifier automatiquement les votes,
* attribuer des récompenses in-game (argent, items, permissions…),
* utiliser `external_id` pour relier un joueur Minecraft au vote DiscordTop.

Cette intégration utilise uniquement l’interface native d’Azuriom → **aucun code supplémentaire n’est requis**.

***

## Ajouter DiscordTop dans Azuriom

Dans votre panel Azuriom :

```
Plugins → Vote → Sites
```

Cliquez sur **“Ajouter un site”**, puis remplissez les champs comme suit :

***

### 🔧 Paramètres recommandés

#### **Nom :**

```
DISCORDTOP
```

#### **URL du site :**

*(Remplacez `GUILD_ID` par l’ID de votre serveur DiscordTop)*

```
https://discordtop.net/guild/GUILD_ID/vote?external_id={player}
```

💡 **Important** :\
`{player}` sera automatiquement remplacé par le pseudonyme du joueur Minecraft.\
Ce champ permettra à DiscordTop de relier le vote au joueur → et à Azuriom d’attendre la validation API côté DTOP.

***

## Activer la vérification des votes

Activez l’option :

```
✔ Activer la vérification des votes
```

Puis entrez votre clé API DiscordTop :

```
Clé d’API : VOTRE_CLE_API_DISCORDTOP
```

Elle est disponible dans votre dashboard DiscordTop (section Développeur → API) - [Trouver ma clé API](/api-reference/concepts-cles/authentification).

***

## Configuration du délai entre les votes

Azuriom gère automatiquement l’intervalle minimal :

```
Délai fixe entre les votes : 60 minutes
```

Vous pouvez modifier la valeur au besoin, mais il est fortement recommandé de laisser à 60 minutes.

{% hint style="warning" %}
Attention, le délai minimal entre chaque vote est de 60 minutes côté DiscordTop !
{% endhint %}

***

## Activer le site

Terminez en activant la case :

```
✔ Activer le site
```

Puis sauvegardez.

***

## Attribution des récompenses

Une fois DiscordTop ajouté dans Azuriom, configurez les récompenses :

```
Plugins → Vote → Récompenses
```

Vous pouvez attribuer :

* 💰 de la monnaie (`money`),
* 🎁 des items,
* 🔧 des commandes personnalisées,
* 🎖 des permissions,
* :tada: n’importe quel script via les commandes console.

Exemple :

```
give {player} diamond 3
eco give {player} 250
lp user {player} permission set votant true
```

> 🔍 **Remarque** :\
> Azuriom exécutera les récompenses uniquement si DiscordTop renvoie **has\_voted = true** via l’API.\
> La sécurité est garantie côté DTOP → impossible de tricher en spoofant un vote.

***

## Comment DiscordTop valide le vote ?

Azuriom appelle :

```
GET https://api.discordtop.net/v7/check-vote?external_id={player}
```

DiscordTop répondra :

#### 👉 Cas 1 : Le joueur a voté

```json
{
  "status": 200,
  "has_voted": true
}
```

→ Azuriom applique la récompense (en fonction de leurs configurations).

***

#### 👉 Cas 2 : Le joueur n’a pas voté

```json
{
  "status": 404,
  "has_voted": false
}
```

→ On informe que c'est KO.

***

#### 👉 Cas 3 : Trop de demandes

```json
{
  "status": 429,
  "error": "Rate limit exceeded.",
  "retry_after": 60
}
```

→ Azuriom réessayera automatiquement plus tard.

***

## Configuration finale — Résultat attendu

Une fois terminé, votre page Azuriom ressemble à ceci :

🔹 Nom : **DISCORDTOP**\
🔹 URL : `https://discordtop.net/guild/{YOUR_DISCORD_GUILD_ID}/vote?external_id={player}`\
🔹 Vérification des votes : **activée**\
🔹 Clé d’API : **VOTRE\_CLE**\
🔹 Délai entre votes : **60 minutes**\
🔹 Site activé : **oui**

Et côté joueurs :\
Ils votent → DiscordTop vérifie → Azuriom applique les récompenses → tout est automatique 🎉

***

## FAQ

#### **➡ Le joueur doit-il être connecté à Discord ?**

Non. Le lien `external_id={player}` permet d’associer le vote via son pseudo.

#### **➡ Puis-je personnaliser les récompenses ?**

Oui, complètement. Azuriom exécute vos commandes comme sur un serveur classique.

#### **➡ Faut-il installer un plugin Minecraft spécial ?**

Non. Azuriom s’occupe de tout.

#### **➡ Faut-il installer un plugin Azuriom spécial ?**

Non. le seul plugin obligatoire est le [Plugin Vote](https://github.com/Azuriom/Plugin-Vote).


# Discord Bot


# Site web


# Introduction

Bienvenue dans la référence officielle de l’API DiscordTop.

Cette page présente les **fondations techniques** de l’API DiscordTop :\
comment y accéder, dans quel format elle répond, et quelles conventions elle utilise.

Pour des informations plus détaillées (authentification, erreurs, versioning…), consultez les pages dédiées dans la section API Reference.

## Accès à l’API

L’API DiscordTop est accessible via l’URL suivante :

```bash
https://api.discordtop.net/v{VERSION}/
```

Exemple :

```bash
https://api.discordtop.net/v7/vote-check
```

{% hint style="warning" %}
Les requêtes non chiffrées (HTTP) ne sont pas autorisées.
{% endhint %}

La version est un élément **obligatoire** de toutes les routes (`/v7/`).\
Chaque version représente un contrat stable.

Pour plus d’informations :\
➡️ *Voir :* [*Versioning*](/api-reference/concepts-cles/versioning)

## Format des réponses JSON

L’API DiscordTop renvoie **uniquement** des réponses au format **JSON UTF-8**.

Exemple générique :

```json
{
  "ok": true,
  "data": { ... }
}
```

En cas d’erreur :

```json
{
  "ok": false,
  "error": "ERROR_CODE",
  "message": "Description de l'erreur."
}
```

La liste complète des codes d’erreur est disponible ici :\
➡️ *Voir :*[ *Erreurs & Codes de réponse*](/api-reference/concepts-cles/erreurs-and-codes-de-reponse)

## Convention HTTP

L’API suit des conventions simples :

<table data-header-hidden><thead><tr><th width="252">Élément</th><th>Descriptions</th></tr></thead><tbody><tr><td>Méthodes</td><td>Les endpoints utilisent principalement <code>GET</code></td></tr><tr><td>Corps de requête</td><td>Non utilisé sur les endpoints publics actuels</td></tr><tr><td>Paramètres</td><td>Transmis via query string (<code>?key=value</code>)</td></tr><tr><td>En-têtes</td><td>Standard HTTP (User-Agent recommandé)</td></tr><tr><td>Codes HTTP</td><td>Utilisés de manière cohérente (<code>200</code>, <code>400</code>, <code>401</code>, <code>404</code>, <code>429</code>, etc.)</td></tr></tbody></table>

Structure d’un appel typique :

```bash
GET https://api.discordtop.net/v7/vote-check?param=value
```

## Règles de base de l’API

Avant d’exploiter les endpoints, voici les principes fondamentaux à connaître :

#### &#x20;1. Toutes les réponses sont `application/json`

Aucun autre format n’est jamais renvoyé.

#### 2. Toutes les routes commencent par `/v{VERSION}/`

Aucune route sans version n’est supportée.

#### 3. La structure des champs est stable

Les champs non documentés ne doivent pas être utilisés.

#### 4. L’API est strictement case-sensitive

Les noms de paramètres et valeurs doivent respecter la casse.

#### 5. L’API ne renvoie jamais d’HTML

Même en cas d’erreur → toujours JSON.

#### 6. Le fuseau horaire est toujours en ISO 8601

Format : `YYYY-MM-DDTHH:mm:ss.sss+TZ`


# Versioning

L’API DiscordTop est versionnée directement dans l’URL.

Chaque version représente un **contrat stable** : une fois publiée, elle ne change pas de manière incompatible.

Toutes les routes suivent le format :

```bash
https://api.discordtop.net/v{VERSION}/endpoint
```

Exemple :&#x20;

```bash
/v7/vote-check
```

## Objectifs du versioning

Le versioning garantit que :

* vos intégrations **ne cassent jamais** après une mise à jour,
* les évolutions majeures sont publiées dans une **nouvelle version**,
* chaque version possède un **comportement stable et prévisible**.

Une version existante n’introduit **jamais** de breaking changes.

## Règles de versionnage

#### - Une version = un contrat stable

Les endpoints d’une version ne changeront pas dans leur logique, leurs paramètres ou leur format.

#### - Les nouvelles fonctionnalités = même version

Ajouter un champ non requis, un nouvel endpoint ou un comportement optionnel **ne crée pas une nouvelle version**.

#### - Breaking changes = nouvelle version

Toute modification pouvant casser une intégration existante entraîne automatiquement :

```bash
/v8/
```

ou toute version supérieure.

#### - Les anciennes versions restent disponibles

Tant qu’une version n’est pas déclarée "deprecated", elle reste 100% fonctionnelle.

## Tableau des versions

<table><thead><tr><th width="174">Version</th><th width="261">Statut</th><th>Description</th></tr></thead><tbody><tr><td><strong>v7</strong></td><td>🟢 Active (version actuelle)</td><td>Première version publique stable de l’API. Inclut l’endpoint <code>/check-vote</code>.</td></tr><tr><td><strong>v6</strong></td><td>🔴 Deprecated / Ancienne génération</td><td>Ancien système interne non documenté, plus destiné à être utilisé.</td></tr><tr><td><strong>v5 et antérieures</strong></td><td>🔴 Indisponibles</td><td>API obsolètes, retirées ou internes.</td></tr></tbody></table>

## Cycle de version

Lorsqu’une nouvelle version est prévue :

1. Une **période de migration** est annoncée (documentation dédiée).
2. La version précédente devient **“deprecated”** mais reste accessible.
3. Une date de retrait est communiquée (optionnel).
4. Plus tard : la version ancienne peut être supprimée.

## Exemple concret

Vous appelez l’API via :

```bash
/v7/vote-check
```

Si une future version introduit des changements incompatibles, vous pourrez choisir explicitement :

```bash
/v8/vote-check
```

Votre intégration restera stable **tant que vous n’aurez pas choisi vous-même** de migrer.

## Compatibilité ascendante

Les éléments suivants **ne créent pas de nouvelle version** :

* ajout d’un nouveau champ optionnel dans la réponse
* ajout d’un nouvel endpoint
* amélioration de la documentation
* correctifs internes sans impact sur le contrat API
* optimisation de performance
* format de date identique (`ISO 8601`)

Ceux-ci sont considérés comme **non-ruptures**.


# Authentification

L’API DiscordTop nécessite une authentification pour toutes les requêtes.

Chaque serveur Discord possède sa propre **clé API**, utilisée pour identifier et sécuriser les appels effectués vers l’API.

Ce mécanisme garantit que seules les intégrations autorisées peuvent interagir avec les données liées à votre serveur.

***

## API Token

L’accès à l’API se fait via un **API Token unique**, généré dans votre tableau de bord DiscordTop, dans la section de votre serveur :

```bash
Développeur & API → Clé API du serveur
```

Chaque serveur dispose **d’une seule clé active**, que vous pouvez régénérer à tout moment.

***

## Génération du token

Pour récupérer votre clé API, vous devez vous rendre sur votre tableau de bord puis :

1. Selectionnez le serveur concerné
2. Allez dans **Développeur & API**
3. Cliquez sur **Générer ma clé API**
4. Copiez-la et voilà.

<div><figure><img src="https://docs.discordtop.net/~gitbook/image?url=https%3A%2F%2F4036411232-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FZ1O8haSztScRfu0fy1yo%252Fuploads%252FUl5pZo9oSipMeZtQvASe%252FSans%2520titre-1.png%3Falt%3Dmedia%26token%3De4601f48-f304-431e-a5ea-0d23db95fc46&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=df3bcf1e&#x26;sv=2" alt=""><figcaption></figcaption></figure> <figure><img src="https://docs.discordtop.net/~gitbook/image?url=https%3A%2F%2F4036411232-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FZ1O8haSztScRfu0fy1yo%252Fuploads%252FuvzIpASK34fDw2bh8x78%252FSans%2520titre-1.png%3Falt%3Dmedia%26token%3D02fd1c36-464f-4d6a-8fff-e9c324768cc5&#x26;width=768&#x26;dpr=4&#x26;quality=100&#x26;sign=721aefab&#x26;sv=2" alt=""><figcaption></figcaption></figure></div>

***

## Transmission du token

L’API DiscordTop utilise un **système d’authentification par token**.\
Pour des raisons de sécurité et de compatibilité avec les standards modernes, **le token doit désormais être envoyé dans les headers** sous la forme suivante :

```
Authorization: Bearer VOTRE_API_TOKEN
```

***

### Méthode recommandée

#### **Header HTTP standard**

```http
Authorization: Bearer VOTRE_API_TOKEN
```

#### Exemple :

```http
GET https://api.discordtop.net/v7/vote-check?discord_id=123456789
Authorization: Bearer dtop_xxxxxxxxxxxxx
Accept-Language: fr
```

***

## Méthodes alternatives (compatibilité)

Les formats ci-dessous restent supportés temporairement, mais **sont dépréciés** et ne doivent plus être utilisés pour de nouvelles intégrations :

#### 1) Header alternatif

```
x-api-token: VOTRE_API_TOKEN
```

#### 2) Query string (déprécié)

```
?api_token=VOTRE_API_TOKEN
```

***

## Exemple complet d’appel (format recommandé)

```http
GET https://api.discordtop.net/v7/vote-check?discord_id=123456789
Authorization: Bearer dtop_abcdef123456
Accept-Language: fr
```

Réponse :

```json
{
  "ok": true,
  "has_voted": true,
  "cooldown_remaining_seconds": 0
}
```

***

## ⚠️ **Sécurité**

#### Ne partagez jamais votre clé API

Ne l’exposez pas dans :

* le front-end de votre site
* des dépôts publics (GitHub, GitLab…)
* des screenshots ou vidéos publiques

{% hint style="danger" %}
Toutes les personnes ayant la permission `ADMINISTRATOR` sur votre serveur peut y avoir accès, veillez à bien vérifier les autorisations de vos équipes !
{% endhint %}

#### Stockez-la dans des variables d’environnement

Exemples :

```bash
DISCORDTOP_API_TOKEN="dtop_xxxxxxxx"
```

#### Régénérez la clé en cas de doute

Depuis le tableau de bord, vous pouvez :

* régénérer la clé
* invalider immédiatement toute ancienne clé
* entraîner automatiquement un rafraîchissement côté API

{% hint style="info" %}
Attention, pensez à changer votre variable d'environnement dès la génération d'une nouvelle clé. Auquel cas, les requêtes échoueront directement (dès la nouvelle génération).
{% endhint %}

#### Une clé = un serveur

La clé ne donne accès **qu’aux données du serveur Discord associé**.

## Erreurs liées à l’authentification

En cas de problème d’authentification, l’API renvoie :

### `INVALID_API_TOKEN`

```json
{
  "ok": false,
  "error": "INVALID_API_TOKEN",
  "message": "The provided API token is invalid."
}
```

```json
{
  "ok": false,
  "error": "INVALID_API_TOKEN",
  "message": "The provided API token is invalid."
}
```

Causes possibles :

* Token incorrect
* Token expiré ou régénéré
* Token manquant
* Token associé à un autre serveur Discord

### `API_NOT_SET`

```json
{
  "ok": false,
  "error": "API_NOT_SET",
  "message": "The API token is not set correctly.",
}
```

Cause : aucune clé valide n'a pu permettre l'identification de votre serveur Discord

La liste complète des erreurs se trouve ici :\
➡️ [*Erreurs & Codes de réponse*](/api-reference/concepts-cles/erreurs-and-codes-de-reponse)


# Erreurs & codes de réponse

L’API DiscordTop renvoie toujours des réponses JSON, même en cas d’erreur.

Cette page répertorie l’ensemble des erreurs **globales** (communes à tous les endpoints), ainsi que celles spécifiques à la validation des paramètres.

Toutes les erreurs suivent la structure suivante :

```json
{
  "ok": false,
  "error": "ERROR_CODE",
  "message": "Description de l'erreur."
}
```

***

## 📌 **Codes d’erreur globaux**

### &#x20;`INVALID_API_TOKEN`

Le token fourni ne correspond à aucun serveur Discord existant ou a été régénéré.

```json
{
  "ok": false,
  "error": "INVALID_API_TOKEN",
  "message": "The provided API token is invalid !"
}
```

**Causes possibles :**

* Token incorrect
* Token expiré (rotation)
* Token remplacé dans le tableau de bord
* Token copié partiellement
* Mauvaise guilde associée
* La guilde associé possède actuellement une sanction

***

### `API_NOT_SET`

Le token fourni n'est pas correctement configuré ou un placeholder non configuré.

```json
{
  "ok": false,
  "error": "API_NOT_SET",
  "message": "The API token is not set correctly !"
}
```

**Concerne souvent :**

* Une mauvaise génération de votre Token
* Un CMS mal configuré
* Une variable d’environnement manquante
* Une intégration qui n’a pas renseigné la clé API

***

### `PREMIUM_REQUIRED`

Le serveur lié au token API ne possède pas un abonnement Premium actif.

**Rappel :**\
Vous devez fournir **exactement un** identifiant pour vérifier un vote.

```json
{
  "ok": false,
  "error": "PREMIUM_REQUIRED",
  "message": "This endpoint is reserved for Premium servers !"
}
```

{% hint style="info" %}
Vous faites un appel sur une route nécessitant l'abonnement Premium, attention, un délai d'activation peut-être nécessaire (1 heure max). En cas de non renouvellement, l'autorisation d'accès s'arrête également.
{% endhint %}

***

## **Erreurs liées aux identifiants utilisateur**

Ces erreurs se produisent lorsque les paramètres `discord_id` et `external_id` sont mal utilisés.

***

### &#x20;`MISSING_IDENTIFIER`

Aucun identifiant n’a été fourni :\
ni `discord_id`, ni `external_id`.

```json
{
  "ok": false,
  "error": "MISSING_IDENTIFIER",
  "message": "Provide either discord_id or external_id !"
}
```

***

### &#x20;`MULTIPLE_IDENTIFIERS`

Les deux identifiants ont été fournis en même temps.

```json
{
  "ok": false,
  "error": "MULTIPLE_IDENTIFIERS",
  "message": "You must provide only one identifier at a time !"
}
```

**Rappel :**\
Un seul identifiant autorisé par requête :

* Soit `discord_id`
* Soit `external_id`
* Mais pas les deux

***

## **Erreurs de validation des paramètres**

Ces erreurs sont générées automatiquement par notre API lorsque les paramètres ne respectent pas le schéma attendu.

#### Exemple : ID trop court, trop long, format invalide…

Format général :

```json
{
  "errors": [
    {
      "rule": "minLength",
      "field": "api_token",
      "message": "minLength validation failed"
    }
  ]
}
```

**Important :**\
Ces erreurs proviennent du validator et peuvent varier en fonction de votre implémentation future.\
Elles sont distinctes des erreurs API “contractuelles”.

## Erreurs liées aux votes

### `INVALID_DATE`

Une date fournie est invalide ou n’est pas au format ISO.

```json
{
  "ok": false,
  "error": "INVALID_DATE",
  "message": "Les dates doivent être au format ISO."
}
```

Exemple valide :

```bash
2026-06-01T00:00:00.000Z
```

***

### `INVALID_DATE_RANGE`

La date `from` doit être antérieure à la date `to`.

```json
{
  "ok": false,
  "error": "INVALID_DATE_RANGE",
  "message": "La date from doit être antérieure à la date to."
}
```

***

### `DATE_RANGE_TOO_LARGE`

La période demandée dépasse la limite autorisée.

```json
{
  "ok": false,
  "error": "DATE_RANGE_TOO_LARGE",
  "message": "La période demandée ne peut pas dépasser 31 jours."
}
```

Les routes de récupération des votes acceptent une période maximale de **31 jours**.

***

### `INVALID_CURSOR`

Le curseur de pagination fourni est invalide.

```json
{
  "ok": false,
  "error": "INVALID_CURSOR",
  "message": "Le curseur fourni est invalide."
}
```

Les curseurs doivent être utilisés tels quels depuis la réponse précédente :

```json
{
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0Ijoi..."
  }
}
```

***

### `INVALID_USER_ID`

L’identifiant Discord fourni est invalide.

```json
{
  "ok": false,
  "error": "INVALID_USER_ID",
  "message": "L'identifiant utilisateur Discord est invalide."
}
```

Cette erreur peut être retournée par :

```bash
GET /guild/votes/users/{user_id}
```

***

## **Erreur 404 — Jamais renvoyée en tant que JSON d’erreur**

À noter :\
Dans le cas d’un `api_token` invalide, l'API renvoie un **401 Unauthorized**, pas un 404.

C’est voulu pour éviter la confusion entre :

* une ressource inexistante (404)
* une authentification invalide (401)

***

## Récapitulatif des codes d’erreur

| Code                   | Signification                                                     |
| ---------------------- | ----------------------------------------------------------------- |
| `INVALID_API_TOKEN`    | La clé API ne correspond à nos critères de sécurité.              |
| `API_NOT_SET`          | Le token fourni est un placeholder non configuré.                 |
| `MISSING_IDENTIFIER`   | Aucun identifiant (`discord_id` ou `external_id`) fourni.         |
| `MULTIPLE_IDENTIFIERS` | Les deux identifiants ont été reçus.                              |
| `PREMIUM_REQUIRED`     | Cette route est réservée aux serveurs ayant l'abonnement Premium. |
| *(Validation errors)*  | Format ou longueur invalide, gérés par nos validateurs.           |

## Bonnes pratiques

* Stockez le dernier `next_cursor` pour récupérer la suite des votes.
* Ne modifiez jamais manuellement un curseur.
* Utilisez des dates ISO en UTC.
* Limitez vos périodes de synchronisation à 31 jours maximum.
* Gérez toujours les erreurs `401`, `403` et `429`.
* Ne considérez pas un `403 PREMIUM_REQUIRED` comme une erreur temporaire : le serveur doit être Premium pour accéder à la route.


# Rate limit

Pour garantir la stabilité et les performances de l’API DiscordTop, chaque intégration est soumise à des limites de requêtes (Rate Limits).

Lorsque ces limites sont atteintes, l’API renvoie une erreur dédiée.

Les rate limits permettent d’éviter :

* le spam involontaire,
* les boucles infinies dans les bots,
* les attaques ou abus de l’API,
* et d’assurer une expérience fiable pour tous les serveurs.

***

## Comment fonctionne le Rate Limit ?

L’API utilise un système de limitation basé sur :

* **le token API** (`api_token` du serveur Discord),
* et, en absence de token, **l’adresse IP** de l’appelant.

Chaque token dispose d’un quota indépendant.

Par défaut :

```
30 requêtes / minute / api_token
```

Lorsque la limite est atteinte, l’API renvoie automatiquement une erreur `RATE_LIMITED`.

***

## Erreur `RATE_LIMITED`

En cas de dépassement, l’API renvoie une réponse JSON standardisée :

```json
{
  "ok": false,
  "error": "RATE_LIMITED",
  "message": "Rate limit exceeded for this API token."
}
```

Code HTTP renvoyé :

```
429 Too Many Requests
```

#### Champs renvoyés :

<table><thead><tr><th width="213">Champ</th><th>Description</th></tr></thead><tbody><tr><td><code>status</code></td><td>Code HTTP 429</td></tr><tr><td><code>error</code></td><td>Message clair, adapté à la locale (<code>fr</code>, <code>en</code>, etc.)</td></tr><tr><td><code>retry_after</code></td><td>Temps recommandé (en secondes) avant de réessayer</td></tr></tbody></table>

***

## Reset de la limite

Le compteur se réinitialise automatiquement au bout d’une minute.

Pendant ce temps, il est recommandé de :

* **mettre en cache les résultats**,
* **réduire la fréquence des appels**,
* **éviter les appels simultanés**,
* **centraliser vos appels API** dans un seul module.

***

## Locale et messages d’erreur

L’API DiscordTop adapte automatiquement les messages selon la langue de l’utilisateur :

L’ordre de priorité est :

1. `locale=xx` (query ou body)
2. `Accept-Language`
3. `x-locale`
4. Fallback → `en`

Exemples :

<table><thead><tr><th width="190">Locale</th><th>Message</th></tr></thead><tbody><tr><td><code>fr</code></td><td><code>"Trop de requêtes. Veuillez réessayer plus tard."</code></td></tr><tr><td><code>en</code></td><td><code>"Rate limit exceeded. Please try again later."</code></td></tr></tbody></table>

***

## Bonnes pratiques

Voici quelques recommandations pour éviter d’être limité :

#### - Cachez la réponse si possible

Exemples :

* mémoriser la réponse pendant 2–5 secondes dans votre bot
* éviter de multiplier les appels identiques

#### - Ne faites pas d’appel via le front-end

Les navigateurs peuvent spam / reload plus facilement → utilisez uniquement votre backend.

#### - Centralisez vos appels

Évitez d’appeler l’API dans plusieurs endroits simultanément (ex. inside command + middleware + cron).

#### - Vérifiez toujours que l’utilisateur **peut** déclencher une action avant d’appeler l’API

(ex : s’il spam un bouton Discord)

#### - En cas d'échec à cause d’un 429, appliquez un *retry after* progressif

1ère tentative : attendre 1 seconde\
2ème tentative : attendre 3 secondes\
3ème tentative : annuler

{% hint style="info" %}
*Note : des en-têtes de suivi du taux de requêtes pourront être ajoutés dans une prochaine mise à jour de l’API.*
{% endhint %}

***

## Exemple de gestion simple

```ts
const res = await fetch(url);

if (res.status === 429) {
  const body = await res.json();
  console.log(body.error, "Réessayez dans", body.retry_after, "s");
  return;
}

const data = await res.json();
```

## Notes

* Le système de rate limit peut évoluer (Redis distribué, quotas Premium, etc.).
* Des headers d’observation (`X-RateLimit-Remaining`, etc.) pourront être ajoutés dans une prochaine version.


# Localisation / langues

La localisation est optionnelle, et n’affecte pas les champs techniques ou contractuels.

L’API DiscordTop permet d’adapter certains champs textuels en fonction de la langue souhaitée.\
Cela permet aux intégrations (bots, sites, CMS, jeux…) d’afficher des messages ou métadonnées dans la langue préférée de leurs utilisateurs.

***

## Objectif

L’option `locale` sert à :

* personnaliser les champs **lisibles par l’utilisateur**,
* tout en gardant les champs techniques **strictement identiques** (dates, booléens, identifiants…).

Elle n’affecte pas :

* les noms d’attributs JSON,
* les codes d’erreur,
* les boolean/number/date,
* les valeurs contractuelles de l’API.

***

## Utilisation du paramètre `locale`

La langue peut être spécifiée via la query string :

```bash
?locale=fr
?locale=en
```

Exemple complet :

```bash
GET https://api.discordtop.net/v7/vote-check?api_token=xxx&discord_id=123456&locale=fr
```

***

## Langues supportées

| Code | Langue               |
| ---- | -------------------- |
| `en` | Anglais (par défaut) |
| `fr` | Français             |

Si aucune langue n’est fournie, l’API renvoie les messages en **anglais**.

***

## Champs concernés

L’option `locale` n’affecte que :

* les champs `message` dans les erreurs
* les champs textuels destinés à l’utilisateur final (lorsqu'il y a usage)

Exemple :\
Erreur `INVALID_API_TOKEN` en français :

```json
{
  "ok": false,
  "error": "INVALID_API_TOKEN",
  "message": "Le token API fourni est invalide."
}
```

En anglais (par défaut) :

```json
{
  "ok": false,
  "error": "INVALID_API_TOKEN",
  "message": "The provided API token is invalid."
}
```

Les autres champs, eux, ne changent jamais :

* `ok`
* `error`
* `guild_id`
* `has_voted`
* `last_vote_at`
* `next_vote_at`
* `cooldown_remaining_seconds`\
  → identiques dans toutes les langues.

***

## Comportement en cas de langue inconnue

Si vous fournissez une locale non reconnue :

```
?locale=jp
?locale=de
```

L’API retournera automatiquement la langue **par défaut** :

```
en
```

Exemple :

```json
{
  "ok": false,
  "error": "INVALID_API_TOKEN",
  "message": "The provided API token is invalid."
}
```

Aucune erreur spécifique n’est renvoyée.

***

## Exemple complet

#### Requête :

```
GET https://api.discordtop.net/v7/vote-check?discord_id=123&locale=fr
```

#### Réponse :

```json
{
  "ok": true,
  "has_voted": true,
  "last_vote_at": "2025-11-27T17:57:33.017+01:00",
  "next_vote_at": "2025-11-27T18:57:33.017+01:00",
  "cooldown_remaining_seconds": 3579.103
}
```

*(Aucun champ technique n’est localisé.)*

***

## Notes importantes

* Les valeurs textuelles resteront **minimales** pour éviter les ambiguïtés.
* Les identifiants d’erreurs resteront **toujours en anglais** (`INVALID_API_TOKEN`).
* La localisation évoluera dans les futures versions (ex : endpoints “human-friendly”).
* L’API n’analyse pas `Accept-Language` pour éviter les comportements imprévisibles.


# Votes

Vérification et consultation des votes utilisateurs.

## Vérifier si un utilisateur est en cooldown de vote

> Vérifie si un utilisateur, via \`discord\_id\` ou \`external\_id\`, a voté pour la guilde associée\
> au token développeur dans la dernière heure.\
> \
> \*\*Important :\*\*\
> \- Un seul identifiant doit être fourni.\
> \- Le cooldown est fixé à \*\*1h\*\*.\
> \- Le token doit être passé en Bearer ou via \`X-Api-Token\`.\
> \- Le paramètre \`api\_token\` reste supporté pour compatibilité, mais son usage est déconseillé.<br>

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"tags":[{"name":"Votes","description":"Vérification et consultation des votes utilisateurs."}],"servers":[{"url":"https://api.discordtop.net/v7","description":"API Developer - Production (v7)"}],"security":[{"ApiTokenAuth":[]}],"components":{"securitySchemes":{"ApiTokenAuth":{"type":"http","scheme":"bearer","bearerFormat":"DTOP-Dev-Token"}},"parameters":{"DiscordIdQuery":{"name":"discord_id","in":"query","required":false,"description":"ID Discord de l'utilisateur à vérifier.","schema":{"type":"string","maxLength":20,"pattern":"^[0-9]{15,20}$"}},"ExternalIdQuery":{"name":"external_id","in":"query","required":false,"description":"Identifiant externe de l'utilisateur.","schema":{"type":"string","maxLength":191}},"ApiTokenQuery":{"name":"api_token","in":"query","required":false,"description":"Ancienne méthode pour passer le token dans l'URL.\nIl est recommandé d'utiliser `Authorization: Bearer <token>`.\n","schema":{"type":"string"},"deprecated":true},"LocaleQuery":{"name":"locale","in":"query","required":false,"description":"Force la langue de la réponse (`fr`, `en`, etc.).\nSi absent : utilise `X-Locale`, puis `Accept-Language`, sinon fallback `en`.\n","schema":{"type":"string"}},"XLocaleHeader":{"name":"X-Locale","in":"header","required":false,"description":"Locale forcée, surpasse `Accept-Language`.","schema":{"type":"string"}},"AcceptLanguageHeader":{"name":"Accept-Language","in":"header","required":false,"description":"Détection automatique de la langue.","schema":{"type":"string"}},"XApiTokenHeader":{"name":"X-Api-Token","in":"header","required":false,"description":"Token développeur alternatif.","schema":{"type":"string"}}},"schemas":{"VoteCheckSuccess":{"type":"object","required":["ok","guild_id","has_voted","is_doubled","last_vote_at","next_vote_at","cooldown_remaining_seconds"],"properties":{"ok":{"type":"boolean"},"guild_id":{"type":"string"},"has_voted":{"type":"boolean","description":"`true` : l'utilisateur est encore dans le cooldown.\n`false` : aucun vote récent ou cooldown terminé.\n"},"is_doubled":{"type":"boolean","description":"Indique si le vote d'un utilisateur est considéré comme doublé."},"last_vote_at":{"type":"string","format":"date-time","nullable":true},"next_vote_at":{"type":"string","format":"date-time","nullable":true},"cooldown_remaining_seconds":{"type":"integer"}}},"ErrorResponse":{"type":"object","required":["ok","error","message"],"properties":{"ok":{"type":"boolean"},"error":{"type":"string"},"message":{"type":"string"}}},"ValidationError":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"rule":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}}}}}},"RateLimitError":{"type":"object","properties":{"message":{"type":"string"}}},"ServerError":{"type":"object","properties":{"message":{"type":"string"}}}},"responses":{"UnauthorizedApiToken":{"description":"Erreurs liées au token développeur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ValidationError":{"description":"Erreurs de validation Adonis Validator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"RateLimitExceeded":{"description":"Rate limit atteint.","headers":{"Retry-After":{"description":"Nombre de secondes à attendre avant de refaire une requête.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}},"InternalServerError":{"description":"Erreur interne de l'API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerError"}}}}}},"paths":{"/vote-check":{"get":{"tags":["Votes"],"operationId":"voteCheck","summary":"Vérifier si un utilisateur est en cooldown de vote","description":"Vérifie si un utilisateur, via `discord_id` ou `external_id`, a voté pour la guilde associée\nau token développeur dans la dernière heure.\n\n**Important :**\n- Un seul identifiant doit être fourni.\n- Le cooldown est fixé à **1h**.\n- Le token doit être passé en Bearer ou via `X-Api-Token`.\n- Le paramètre `api_token` reste supporté pour compatibilité, mais son usage est déconseillé.\n","parameters":[{"$ref":"#/components/parameters/DiscordIdQuery"},{"$ref":"#/components/parameters/ExternalIdQuery"},{"$ref":"#/components/parameters/ApiTokenQuery"},{"$ref":"#/components/parameters/LocaleQuery"},{"$ref":"#/components/parameters/XLocaleHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"},{"$ref":"#/components/parameters/XApiTokenHeader"}],"responses":{"200":{"description":"Résultat de vérification du vote.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoteCheckSuccess"}}}},"400":{"description":"Erreurs liées aux identifiants fournis.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/UnauthorizedApiToken"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimitExceeded"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Récupérer les votes récents de sa guilde

> Retourne la liste des votes de la guilde associée au token développeur sur une période donnée.\
> \
> \*\*Important :\*\*\
> \- Cette route est réservée aux serveurs Premium.\
> \- La période demandée ne peut pas dépasser \*\*31 jours\*\*.\
> \- Les votes sont triés par \`timestamp\` croissant.\
> \- La pagination utilise un curseur opaque (\`next\_cursor\`).\
> \- Le champ \`is\_doubled\` indique si le vote est considéré comme doublé.\
> \- Aucune donnée personnelle autre que l'ID Discord de l'utilisateur n'est retournée.<br>

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"tags":[{"name":"Votes","description":"Vérification et consultation des votes utilisateurs."}],"servers":[{"url":"https://api.discordtop.net/v7","description":"API Developer - Production (v7)"}],"security":[{"ApiTokenAuth":[]}],"components":{"securitySchemes":{"ApiTokenAuth":{"type":"http","scheme":"bearer","bearerFormat":"DTOP-Dev-Token"}},"parameters":{"FromQuery":{"name":"from","in":"query","required":false,"description":"Date de début de la période à récupérer.\n\nFormat recommandé : ISO 8601.\nSi absent, l'API peut utiliser le début du mois courant.\n","schema":{"type":"string","format":"date-time"}},"ToQuery":{"name":"to","in":"query","required":false,"description":"Date de fin de la période à récupérer.\n\nFormat recommandé : ISO 8601.\nSi absent, l'API peut utiliser la date courante.\n","schema":{"type":"string","format":"date-time"}},"CursorQuery":{"name":"cursor","in":"query","required":false,"description":"Curseur opaque retourné par `pagination.next_cursor`.\nÀ réutiliser tel quel pour récupérer la page suivante.\n","schema":{"type":"string"}},"LimitQuery":{"name":"limit","in":"query","required":false,"description":"Nombre maximum d'éléments retournés.\nValeur par défaut : 100.\nValeur maximum : 1000.\n","schema":{"type":"integer","minimum":1,"maximum":1000,"default":100}},"ApiTokenQuery":{"name":"api_token","in":"query","required":false,"description":"Ancienne méthode pour passer le token dans l'URL.\nIl est recommandé d'utiliser `Authorization: Bearer <token>`.\n","schema":{"type":"string"},"deprecated":true},"LocaleQuery":{"name":"locale","in":"query","required":false,"description":"Force la langue de la réponse (`fr`, `en`, etc.).\nSi absent : utilise `X-Locale`, puis `Accept-Language`, sinon fallback `en`.\n","schema":{"type":"string"}},"XLocaleHeader":{"name":"X-Locale","in":"header","required":false,"description":"Locale forcée, surpasse `Accept-Language`.","schema":{"type":"string"}},"AcceptLanguageHeader":{"name":"Accept-Language","in":"header","required":false,"description":"Détection automatique de la langue.","schema":{"type":"string"}},"XApiTokenHeader":{"name":"X-Api-Token","in":"header","required":false,"description":"Token développeur alternatif.","schema":{"type":"string"}}},"schemas":{"GuildVotesSuccess":{"allOf":[{"$ref":"#/components/schemas/VotesListBaseSuccess"},{"type":"object","required":["guild_id"],"properties":{"guild_id":{"type":"string"}}}]},"VotesListBaseSuccess":{"type":"object","required":["ok","votes","total","pagination","period"],"properties":{"ok":{"type":"boolean"},"votes":{"type":"array","description":"Liste des votes logiques retournés pour la page courante.","items":{"$ref":"#/components/schemas/VoteEvent"}},"total":{"type":"integer","description":"Nombre total de votes logiques correspondant aux filtres demandés, sur la période.\nCe total n'est pas limité par `pagination.limit`.\n"},"pagination":{"$ref":"#/components/schemas/CursorPagination"},"period":{"$ref":"#/components/schemas/Period"}}},"VoteEvent":{"type":"object","required":["user_id","timestamp","is_doubled"],"properties":{"user_id":{"type":"string","description":"ID Discord de l'utilisateur ayant voté."},"timestamp":{"type":"string","format":"date-time","description":"Date du vote."},"is_doubled":{"type":"boolean","description":"Indique si ce vote est considéré comme doublé.\n\nTant qu'aucune colonne dédiée n'existe en base de données, cette valeur peut être déduite\npar l'API à partir de deux votes similaires dans un lapse de temps très court.\n"}}},"CursorPagination":{"type":"object","required":["limit","has_more","next_cursor"],"properties":{"limit":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true,"description":"Curseur à utiliser pour récupérer la page suivante. Null s'il n'y a plus de page."}}},"Period":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}},"ErrorResponse":{"type":"object","required":["ok","error","message"],"properties":{"ok":{"type":"boolean"},"error":{"type":"string"},"message":{"type":"string"}}},"ValidationError":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"rule":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}}}}}},"RateLimitError":{"type":"object","properties":{"message":{"type":"string"}}},"ServerError":{"type":"object","properties":{"message":{"type":"string"}}}},"responses":{"UnauthorizedApiToken":{"description":"Erreurs liées au token développeur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"PremiumRequired":{"description":"La guilde associée au token n'a pas accès à cette route.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ValidationError":{"description":"Erreurs de validation Adonis Validator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"RateLimitExceeded":{"description":"Rate limit atteint.","headers":{"Retry-After":{"description":"Nombre de secondes à attendre avant de refaire une requête.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}},"InternalServerError":{"description":"Erreur interne de l'API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerError"}}}}}},"paths":{"/guild/votes":{"get":{"tags":["Votes"],"operationId":"listGuildVotes","summary":"Récupérer les votes récents de sa guilde","description":"Retourne la liste des votes de la guilde associée au token développeur sur une période donnée.\n\n**Important :**\n- Cette route est réservée aux serveurs Premium.\n- La période demandée ne peut pas dépasser **31 jours**.\n- Les votes sont triés par `timestamp` croissant.\n- La pagination utilise un curseur opaque (`next_cursor`).\n- Le champ `is_doubled` indique si le vote est considéré comme doublé.\n- Aucune donnée personnelle autre que l'ID Discord de l'utilisateur n'est retournée.\n","parameters":[{"$ref":"#/components/parameters/FromQuery"},{"$ref":"#/components/parameters/ToQuery"},{"$ref":"#/components/parameters/CursorQuery"},{"$ref":"#/components/parameters/LimitQuery"},{"$ref":"#/components/parameters/ApiTokenQuery"},{"$ref":"#/components/parameters/LocaleQuery"},{"$ref":"#/components/parameters/XLocaleHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"},{"$ref":"#/components/parameters/XApiTokenHeader"}],"responses":{"200":{"description":"Liste paginée des votes de la guilde.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuildVotesSuccess"}}}},"400":{"description":"Erreurs liées aux dates ou au curseur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/UnauthorizedApiToken"},"403":{"$ref":"#/components/responses/PremiumRequired"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimitExceeded"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```

## Récupérer les votes récents d'un utilisateur sur sa guilde

> Retourne la liste des votes d'un utilisateur précis sur la guilde associée au token développeur.\
> \
> \*\*Important :\*\*\
> \- Cette route est réservée aux serveurs Premium.\
> \- La période demandée ne peut pas dépasser \*\*31 jours\*\*.\
> \- Le \`user\_id\` est l'ID Discord de l'utilisateur.\
> \- Les votes sont triés par \`timestamp\` croissant.\
> \- La pagination utilise un curseur opaque (\`next\_cursor\`).\
> \- Aucune donnée personnelle autre que l'ID Discord de l'utilisateur n'est retournée.<br>

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"tags":[{"name":"Votes","description":"Vérification et consultation des votes utilisateurs."}],"servers":[{"url":"https://api.discordtop.net/v7","description":"API Developer - Production (v7)"}],"security":[{"ApiTokenAuth":[]}],"components":{"securitySchemes":{"ApiTokenAuth":{"type":"http","scheme":"bearer","bearerFormat":"DTOP-Dev-Token"}},"parameters":{"FromQuery":{"name":"from","in":"query","required":false,"description":"Date de début de la période à récupérer.\n\nFormat recommandé : ISO 8601.\nSi absent, l'API peut utiliser le début du mois courant.\n","schema":{"type":"string","format":"date-time"}},"ToQuery":{"name":"to","in":"query","required":false,"description":"Date de fin de la période à récupérer.\n\nFormat recommandé : ISO 8601.\nSi absent, l'API peut utiliser la date courante.\n","schema":{"type":"string","format":"date-time"}},"CursorQuery":{"name":"cursor","in":"query","required":false,"description":"Curseur opaque retourné par `pagination.next_cursor`.\nÀ réutiliser tel quel pour récupérer la page suivante.\n","schema":{"type":"string"}},"LimitQuery":{"name":"limit","in":"query","required":false,"description":"Nombre maximum d'éléments retournés.\nValeur par défaut : 100.\nValeur maximum : 1000.\n","schema":{"type":"integer","minimum":1,"maximum":1000,"default":100}},"ApiTokenQuery":{"name":"api_token","in":"query","required":false,"description":"Ancienne méthode pour passer le token dans l'URL.\nIl est recommandé d'utiliser `Authorization: Bearer <token>`.\n","schema":{"type":"string"},"deprecated":true},"LocaleQuery":{"name":"locale","in":"query","required":false,"description":"Force la langue de la réponse (`fr`, `en`, etc.).\nSi absent : utilise `X-Locale`, puis `Accept-Language`, sinon fallback `en`.\n","schema":{"type":"string"}},"XLocaleHeader":{"name":"X-Locale","in":"header","required":false,"description":"Locale forcée, surpasse `Accept-Language`.","schema":{"type":"string"}},"AcceptLanguageHeader":{"name":"Accept-Language","in":"header","required":false,"description":"Détection automatique de la langue.","schema":{"type":"string"}},"XApiTokenHeader":{"name":"X-Api-Token","in":"header","required":false,"description":"Token développeur alternatif.","schema":{"type":"string"}}},"schemas":{"GuildUserVotesSuccess":{"allOf":[{"$ref":"#/components/schemas/VotesListBaseSuccess"},{"type":"object","required":["guild_id","user_id"],"properties":{"guild_id":{"type":"string"},"user_id":{"type":"string","description":"ID Discord de l'utilisateur demandé."}}}]},"VotesListBaseSuccess":{"type":"object","required":["ok","votes","total","pagination","period"],"properties":{"ok":{"type":"boolean"},"votes":{"type":"array","description":"Liste des votes logiques retournés pour la page courante.","items":{"$ref":"#/components/schemas/VoteEvent"}},"total":{"type":"integer","description":"Nombre total de votes logiques correspondant aux filtres demandés, sur la période.\nCe total n'est pas limité par `pagination.limit`.\n"},"pagination":{"$ref":"#/components/schemas/CursorPagination"},"period":{"$ref":"#/components/schemas/Period"}}},"VoteEvent":{"type":"object","required":["user_id","timestamp","is_doubled"],"properties":{"user_id":{"type":"string","description":"ID Discord de l'utilisateur ayant voté."},"timestamp":{"type":"string","format":"date-time","description":"Date du vote."},"is_doubled":{"type":"boolean","description":"Indique si ce vote est considéré comme doublé.\n\nTant qu'aucune colonne dédiée n'existe en base de données, cette valeur peut être déduite\npar l'API à partir de deux votes similaires dans un lapse de temps très court.\n"}}},"CursorPagination":{"type":"object","required":["limit","has_more","next_cursor"],"properties":{"limit":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true,"description":"Curseur à utiliser pour récupérer la page suivante. Null s'il n'y a plus de page."}}},"Period":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}},"ErrorResponse":{"type":"object","required":["ok","error","message"],"properties":{"ok":{"type":"boolean"},"error":{"type":"string"},"message":{"type":"string"}}},"ValidationError":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"rule":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}}}}}},"RateLimitError":{"type":"object","properties":{"message":{"type":"string"}}},"ServerError":{"type":"object","properties":{"message":{"type":"string"}}}},"responses":{"UnauthorizedApiToken":{"description":"Erreurs liées au token développeur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"PremiumRequired":{"description":"La guilde associée au token n'a pas accès à cette route.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ValidationError":{"description":"Erreurs de validation Adonis Validator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"RateLimitExceeded":{"description":"Rate limit atteint.","headers":{"Retry-After":{"description":"Nombre de secondes à attendre avant de refaire une requête.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}},"InternalServerError":{"description":"Erreur interne de l'API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerError"}}}}}},"paths":{"/guild/votes/users/{user_id}":{"get":{"tags":["Votes"],"operationId":"listGuildUserVotes","summary":"Récupérer les votes récents d'un utilisateur sur sa guilde","description":"Retourne la liste des votes d'un utilisateur précis sur la guilde associée au token développeur.\n\n**Important :**\n- Cette route est réservée aux serveurs Premium.\n- La période demandée ne peut pas dépasser **31 jours**.\n- Le `user_id` est l'ID Discord de l'utilisateur.\n- Les votes sont triés par `timestamp` croissant.\n- La pagination utilise un curseur opaque (`next_cursor`).\n- Aucune donnée personnelle autre que l'ID Discord de l'utilisateur n'est retournée.\n","parameters":[{"name":"user_id","in":"path","required":true,"description":"ID Discord de l'utilisateur dont les votes doivent être récupérés.","schema":{"type":"string","maxLength":20,"pattern":"^[0-9]{15,20}$"}},{"$ref":"#/components/parameters/FromQuery"},{"$ref":"#/components/parameters/ToQuery"},{"$ref":"#/components/parameters/CursorQuery"},{"$ref":"#/components/parameters/LimitQuery"},{"$ref":"#/components/parameters/ApiTokenQuery"},{"$ref":"#/components/parameters/LocaleQuery"},{"$ref":"#/components/parameters/XLocaleHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"},{"$ref":"#/components/parameters/XApiTokenHeader"}],"responses":{"200":{"description":"Liste paginée des votes de l'utilisateur sur la guilde.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuildUserVotesSuccess"}}}},"400":{"description":"Erreurs liées à l'utilisateur, aux dates ou au curseur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/UnauthorizedApiToken"},"403":{"$ref":"#/components/responses/PremiumRequired"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimitExceeded"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```


# Guild

Consultation des statistiques DiscordTop de la guilde associée au token développeur.

## Récupérer les statistiques DiscordTop de sa guilde

> Retourne les statistiques DiscordTop de la guilde associée au token développeur.\
> \
> \*\*Important :\*\*\
> \- Cette route est réservée aux serveurs Premium.\
> \- Le token doit être passé en Bearer ou via \`X-Api-Token\`.\
> \- La guilde doit être active sur DiscordTop.\
> \- Les informations retournées sont uniquement des métriques DiscordTop.<br>

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"tags":[{"name":"Guild","description":"Consultation des statistiques DiscordTop de la guilde associée au token développeur."}],"servers":[{"url":"https://api.discordtop.net/v7","description":"API Developer - Production (v7)"}],"security":[{"ApiTokenAuth":[]}],"components":{"securitySchemes":{"ApiTokenAuth":{"type":"http","scheme":"bearer","bearerFormat":"DTOP-Dev-Token"}},"parameters":{"ApiTokenQuery":{"name":"api_token","in":"query","required":false,"description":"Ancienne méthode pour passer le token dans l'URL.\nIl est recommandé d'utiliser `Authorization: Bearer <token>`.\n","schema":{"type":"string"},"deprecated":true},"LocaleQuery":{"name":"locale","in":"query","required":false,"description":"Force la langue de la réponse (`fr`, `en`, etc.).\nSi absent : utilise `X-Locale`, puis `Accept-Language`, sinon fallback `en`.\n","schema":{"type":"string"}},"XLocaleHeader":{"name":"X-Locale","in":"header","required":false,"description":"Locale forcée, surpasse `Accept-Language`.","schema":{"type":"string"}},"AcceptLanguageHeader":{"name":"Accept-Language","in":"header","required":false,"description":"Détection automatique de la langue.","schema":{"type":"string"}},"XApiTokenHeader":{"name":"X-Api-Token","in":"header","required":false,"description":"Token développeur alternatif.","schema":{"type":"string"}}},"schemas":{"GuildMeSuccess":{"type":"object","required":["ok","guild_id","votes","lifetime_votes","votes_last_7_days","rank","boosts","reviews","rating"],"properties":{"ok":{"type":"boolean"},"guild_id":{"type":"string"},"votes":{"type":"integer","description":"Nombre de votes de la guilde sur le classement mensuel en cours."},"lifetime_votes":{"type":"integer","description":"Nombre total de votes reçus par la guilde depuis son ajout sur DiscordTop."},"votes_last_7_days":{"type":"integer","description":"Nombre de votes reçus sur les 7 derniers jours."},"rank":{"type":"integer","nullable":true,"description":"Position actuelle de la guilde dans le classement mensuel."},"boosts":{"type":"integer","description":"Nombre de boosts actifs sur la guilde."},"reviews":{"type":"integer","description":"Nombre total d'avis publiés pour la guilde."},"rating":{"type":"number","format":"float","nullable":true,"description":"Note moyenne des avis publiés, arrondie à une décimale. Null si aucun avis n'est disponible."}}},"ErrorResponse":{"type":"object","required":["ok","error","message"],"properties":{"ok":{"type":"boolean"},"error":{"type":"string"},"message":{"type":"string"}}},"RateLimitError":{"type":"object","properties":{"message":{"type":"string"}}},"ServerError":{"type":"object","properties":{"message":{"type":"string"}}}},"responses":{"UnauthorizedApiToken":{"description":"Erreurs liées au token développeur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"PremiumRequired":{"description":"La guilde associée au token n'a pas accès à cette route.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"RateLimitExceeded":{"description":"Rate limit atteint.","headers":{"Retry-After":{"description":"Nombre de secondes à attendre avant de refaire une requête.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}},"InternalServerError":{"description":"Erreur interne de l'API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerError"}}}}}},"paths":{"/guild/me":{"get":{"tags":["Guild"],"operationId":"getMyGuildStats","summary":"Récupérer les statistiques DiscordTop de sa guilde","description":"Retourne les statistiques DiscordTop de la guilde associée au token développeur.\n\n**Important :**\n- Cette route est réservée aux serveurs Premium.\n- Le token doit être passé en Bearer ou via `X-Api-Token`.\n- La guilde doit être active sur DiscordTop.\n- Les informations retournées sont uniquement des métriques DiscordTop.\n","parameters":[{"$ref":"#/components/parameters/ApiTokenQuery"},{"$ref":"#/components/parameters/LocaleQuery"},{"$ref":"#/components/parameters/XLocaleHeader"},{"$ref":"#/components/parameters/AcceptLanguageHeader"},{"$ref":"#/components/parameters/XApiTokenHeader"}],"responses":{"200":{"description":"Statistiques DiscordTop de la guilde associée au token développeur.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuildMeSuccess"}}}},"401":{"$ref":"#/components/responses/UnauthorizedApiToken"},"403":{"$ref":"#/components/responses/PremiumRequired"},"429":{"$ref":"#/components/responses/RateLimitExceeded"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}
```


# Models

## The VoteCheckSuccess object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"VoteCheckSuccess":{"type":"object","required":["ok","guild_id","has_voted","is_doubled","last_vote_at","next_vote_at","cooldown_remaining_seconds"],"properties":{"ok":{"type":"boolean"},"guild_id":{"type":"string"},"has_voted":{"type":"boolean","description":"`true` : l'utilisateur est encore dans le cooldown.\n`false` : aucun vote récent ou cooldown terminé.\n"},"is_doubled":{"type":"boolean","description":"Indique si le vote d'un utilisateur est considéré comme doublé."},"last_vote_at":{"type":"string","format":"date-time","nullable":true},"next_vote_at":{"type":"string","format":"date-time","nullable":true},"cooldown_remaining_seconds":{"type":"integer"}}}}}}
```

## The GuildMeSuccess object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"GuildMeSuccess":{"type":"object","required":["ok","guild_id","votes","lifetime_votes","votes_last_7_days","rank","boosts","reviews","rating"],"properties":{"ok":{"type":"boolean"},"guild_id":{"type":"string"},"votes":{"type":"integer","description":"Nombre de votes de la guilde sur le classement mensuel en cours."},"lifetime_votes":{"type":"integer","description":"Nombre total de votes reçus par la guilde depuis son ajout sur DiscordTop."},"votes_last_7_days":{"type":"integer","description":"Nombre de votes reçus sur les 7 derniers jours."},"rank":{"type":"integer","nullable":true,"description":"Position actuelle de la guilde dans le classement mensuel."},"boosts":{"type":"integer","description":"Nombre de boosts actifs sur la guilde."},"reviews":{"type":"integer","description":"Nombre total d'avis publiés pour la guilde."},"rating":{"type":"number","format":"float","nullable":true,"description":"Note moyenne des avis publiés, arrondie à une décimale. Null si aucun avis n'est disponible."}}}}}}
```

## The GuildVotesSuccess object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"GuildVotesSuccess":{"allOf":[{"$ref":"#/components/schemas/VotesListBaseSuccess"},{"type":"object","required":["guild_id"],"properties":{"guild_id":{"type":"string"}}}]},"VotesListBaseSuccess":{"type":"object","required":["ok","votes","total","pagination","period"],"properties":{"ok":{"type":"boolean"},"votes":{"type":"array","description":"Liste des votes logiques retournés pour la page courante.","items":{"$ref":"#/components/schemas/VoteEvent"}},"total":{"type":"integer","description":"Nombre total de votes logiques correspondant aux filtres demandés, sur la période.\nCe total n'est pas limité par `pagination.limit`.\n"},"pagination":{"$ref":"#/components/schemas/CursorPagination"},"period":{"$ref":"#/components/schemas/Period"}}},"VoteEvent":{"type":"object","required":["user_id","timestamp","is_doubled"],"properties":{"user_id":{"type":"string","description":"ID Discord de l'utilisateur ayant voté."},"timestamp":{"type":"string","format":"date-time","description":"Date du vote."},"is_doubled":{"type":"boolean","description":"Indique si ce vote est considéré comme doublé.\n\nTant qu'aucune colonne dédiée n'existe en base de données, cette valeur peut être déduite\npar l'API à partir de deux votes similaires dans un lapse de temps très court.\n"}}},"CursorPagination":{"type":"object","required":["limit","has_more","next_cursor"],"properties":{"limit":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true,"description":"Curseur à utiliser pour récupérer la page suivante. Null s'il n'y a plus de page."}}},"Period":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}}}}}
```

## The GuildUserVotesSuccess object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"GuildUserVotesSuccess":{"allOf":[{"$ref":"#/components/schemas/VotesListBaseSuccess"},{"type":"object","required":["guild_id","user_id"],"properties":{"guild_id":{"type":"string"},"user_id":{"type":"string","description":"ID Discord de l'utilisateur demandé."}}}]},"VotesListBaseSuccess":{"type":"object","required":["ok","votes","total","pagination","period"],"properties":{"ok":{"type":"boolean"},"votes":{"type":"array","description":"Liste des votes logiques retournés pour la page courante.","items":{"$ref":"#/components/schemas/VoteEvent"}},"total":{"type":"integer","description":"Nombre total de votes logiques correspondant aux filtres demandés, sur la période.\nCe total n'est pas limité par `pagination.limit`.\n"},"pagination":{"$ref":"#/components/schemas/CursorPagination"},"period":{"$ref":"#/components/schemas/Period"}}},"VoteEvent":{"type":"object","required":["user_id","timestamp","is_doubled"],"properties":{"user_id":{"type":"string","description":"ID Discord de l'utilisateur ayant voté."},"timestamp":{"type":"string","format":"date-time","description":"Date du vote."},"is_doubled":{"type":"boolean","description":"Indique si ce vote est considéré comme doublé.\n\nTant qu'aucune colonne dédiée n'existe en base de données, cette valeur peut être déduite\npar l'API à partir de deux votes similaires dans un lapse de temps très court.\n"}}},"CursorPagination":{"type":"object","required":["limit","has_more","next_cursor"],"properties":{"limit":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true,"description":"Curseur à utiliser pour récupérer la page suivante. Null s'il n'y a plus de page."}}},"Period":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}}}}}
```

## The VotesListBaseSuccess object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"VotesListBaseSuccess":{"type":"object","required":["ok","votes","total","pagination","period"],"properties":{"ok":{"type":"boolean"},"votes":{"type":"array","description":"Liste des votes logiques retournés pour la page courante.","items":{"$ref":"#/components/schemas/VoteEvent"}},"total":{"type":"integer","description":"Nombre total de votes logiques correspondant aux filtres demandés, sur la période.\nCe total n'est pas limité par `pagination.limit`.\n"},"pagination":{"$ref":"#/components/schemas/CursorPagination"},"period":{"$ref":"#/components/schemas/Period"}}},"VoteEvent":{"type":"object","required":["user_id","timestamp","is_doubled"],"properties":{"user_id":{"type":"string","description":"ID Discord de l'utilisateur ayant voté."},"timestamp":{"type":"string","format":"date-time","description":"Date du vote."},"is_doubled":{"type":"boolean","description":"Indique si ce vote est considéré comme doublé.\n\nTant qu'aucune colonne dédiée n'existe en base de données, cette valeur peut être déduite\npar l'API à partir de deux votes similaires dans un lapse de temps très court.\n"}}},"CursorPagination":{"type":"object","required":["limit","has_more","next_cursor"],"properties":{"limit":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true,"description":"Curseur à utiliser pour récupérer la page suivante. Null s'il n'y a plus de page."}}},"Period":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}}}}}
```

## The VoteEvent object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"VoteEvent":{"type":"object","required":["user_id","timestamp","is_doubled"],"properties":{"user_id":{"type":"string","description":"ID Discord de l'utilisateur ayant voté."},"timestamp":{"type":"string","format":"date-time","description":"Date du vote."},"is_doubled":{"type":"boolean","description":"Indique si ce vote est considéré comme doublé.\n\nTant qu'aucune colonne dédiée n'existe en base de données, cette valeur peut être déduite\npar l'API à partir de deux votes similaires dans un lapse de temps très court.\n"}}}}}}
```

## The CursorPagination object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"CursorPagination":{"type":"object","required":["limit","has_more","next_cursor"],"properties":{"limit":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true,"description":"Curseur à utiliser pour récupérer la page suivante. Null s'il n'y a plus de page."}}}}}}
```

## The Period object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"Period":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}}}}}
```

## The ErrorResponse object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"ErrorResponse":{"type":"object","required":["ok","error","message"],"properties":{"ok":{"type":"boolean"},"error":{"type":"string"},"message":{"type":"string"}}}}}}
```

## The ValidationError object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"ValidationError":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"rule":{"type":"string"},"field":{"type":"string"},"message":{"type":"string"}}}}}}}}}
```

## The RateLimitError object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"RateLimitError":{"type":"object","properties":{"message":{"type":"string"}}}}}}
```

## The ServerError object

```json
{"openapi":"3.0.3","info":{"title":"DiscordTop Developer API - Version 7","version":"7.0"},"components":{"schemas":{"ServerError":{"type":"object","properties":{"message":{"type":"string"}}}}}}
```


# Changelog

Un petit point sur les grosses mises à jour qu'à reçu DTOP.

## Juin 2026

Qu'est-ce qu'il y a de nouveau ce mois-ci ?

### Developer Update

* 🚀 Ouverture de l'API publique DiscordTop
* 📊 Accès aux statistiques de votes pour les serveurs Premium
* 👤 Consultation de l'historique de vote d'un utilisateur
* 📚 Mise en ligne de la documentation développeur
* 📄 Publication du schéma OpenAPI officiel

### API

* Ajout de l'endpoint `GET /guild/votes`
* Ajout de l'endpoint `GET /guild/votes/users/{user_id}`
* Ajout de la pagination par curseur
* Ajout des filtres temporels `from` et `to`

### Fix

* Amélioration des contrôles d'accès aux routes développeurs
* Uniformisation des réponses d'erreur de l'API

## Novembre 2025

Qu'est-ce qu'il y a de nouveau en novembre..

#### Developer Update

* Création d'une API publique

#### Publicity Update

* Ajout de nouveau emplacement publicitaire
* Ajout du système de crédit
* Ajout des 5 packs d'achat d'impression

<details>

<summary>Fix</summary>

* Modification des systèmes de sécurité
* Suppression des bots dans le comptage des impressions
* Activation du suivi des clics sur les campagnes (BETA)

</details>

***

## Août 2025

Qu'est-ce qu'il y a de nouveau en août..

#### Système de récompenses

* Création de nouveau type de récompense
* Nouveau design lié aux récompenses de serveurs
* Ajout d'une section Récompense sur les fiches de serveur

#### Expérimentation

Les abonnés Premium auront leur votes triplés sur le site et 45% de chance de doubler leurs votes depuis le bot.\
Pour les non abonnés, les votes restent doublés sur le site et vous avez 10% de chance de doubler vos votes depuis le bot.

<details>

<summary>Fix</summary>

* Modification globale du design des serveurs
* Introduction d'emplacement publicitaire gérer par un tier
* Correction du bug lorsque le bot ne peux pas envoyer de message privée

</details>


# Help Center

<h2 align="center">Vous avez besoin d'aide ?</h2>

<p align="center">Il n'est pas forcément évidant d'utiliser nos outils, bien qu'ils soient conçues pour être le plus simple possible !</p>

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><h4><i class="fa-leaf">:leaf:</i></h4></td><td><strong>Basiques</strong></td><td>Comprendre DiscordTop</td><td><a href="https://www.gitbook.com/">https://www.gitbook.com/</a></td></tr><tr><td><h4><i class="fa-plug">:plug:</i></h4></td><td><strong>Integrations</strong></td><td>Allons plus loins ...</td><td><a href="https://www.gitbook.com/">https://www.gitbook.com/</a></td></tr><tr><td><h4><i class="fa-heart">:heart:</i></h4></td><td><strong>Community</strong></td><td>Rejoins notre communauté</td><td><a href="https://www.gitbook.com/">https://www.gitbook.com/</a></td></tr><tr><td><h4><i class="fa-computer-mouse">:computer-mouse:</i></h4></td><td><strong>Administration</strong></td><td>Comprendre votre tableau de bord</td><td><a href="https://www.gitbook.com/">https://www.gitbook.com/</a></td></tr></tbody></table>


