Développeurs

API Membres

Lis les rôles, boosts, tags, niveaux et invitations des membres de ton serveur depuis ton plugin ou ton site, et sois prévenu en direct par webhook.

01.Démarrer

Crée une clé dans le dashboard : ton serveur → Paramètres → API développeurs → Nouvelle clé (réservé aux gestionnaires du serveur). L'adresse de l'API est :

https://hurix.xyz/api/v1/guild

L'API est en lecture seule : seules les requêtes GET existent (toute autre méthode répond 405). Elle ne peut rien modifier, ni sur Discord ni dans Hurix. La clé désigne le serveur : aucune adresse ne contient d'identifiant de serveur, et une clé ne lit jamais les membres d'un autre serveur.

shell
curl https://hurix.xyz/api/v1/guild/members/123456789012345678 \
  -H "Authorization: Bearer hxm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
json
{
  "id": "123456789012345678",
  "username": "lea",
  "globalName": "Léa",
  "nickname": "Léa | Admin",
  "avatarUrl": "https://cdn.discordapp.com/avatars/…",
  "bot": false,
  "joinedAt": "2025-03-14T18:22:00.000Z",
  "accountCreatedAt": "2019-07-02T10:00:00.000Z",
  "roles": ["111111111111111111", "222222222222222222"],
  "boosting": true,
  "boostingSince": "2026-08-01T12:00:00.000Z",
  "tag": { "tag": "HRX", "guildId": "987654321098765432", "isThisGuild": true },
  "status": "online",
  "customStatus": "discord.gg/hurix"
}

02.Clés et permissions

Envoie la clé dans l'en-tête Authorization, à chaque appel :

Authorization: Bearer hxm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

À la création, tu choisis ce que la clé peut lire. Les permissions marquées « sensible » sont décochées par défaut.

  • members.read — fiche : pseudo, avatar, date d'arrivée, date de création du compte
  • roles.read — rôles du membre et du serveur
  • boosts.read — boost en cours et depuis quand
  • tag.read — tag de serveur affiché sur le profil
  • status.check — « le statut contient-il X ? » (oui/non, sans le texte)
  • status.read — état et texte du statut personnalisé (sensible)
  • levels.read — XP, niveau, rang, temps en vocal
  • invites.read — invitations et qui l'a invité
  • economy.read — solde, séries, inventaire
  • activity.read — messages et minutes de vocal sur 7 / 30 jours
  • verification.read — vérification (captcha) réussie
  • moderation.read — nombre de sanctions, sanction en cours (sans raisons ni staff) (sensible)
  • birthday.read — jour et mois de l'anniversaire (sensible)

Une clé est affichée une seule fois et seule son empreinte est conservée. Révoque-la à tout moment depuis le dashboard. Elle est révoquée automatiquement si Hurix quitte le serveur. Utilise l'API depuis un serveur (plugin, back-end, script), jamais depuis le code d'une page web : la clé y serait visible de tous. GET /me renvoie la clé utilisée et ses permissions.

03.Erreurs et limites

En cas d'erreur, la réponse contient un code stable et un message lisible :

http
HTTP/1.1 403 Forbidden
{ "error": "missing_scope", "message": "Cette clé n'a pas la permission « boosts.read »." }
  • 400 invalid · 401 unauthorized · 403 missing_scope · 404 not_member, module_disabled, not_found · 405 method_not_allowed · 429 rate_limited · 503 bot_unavailable · 504 bot_timeout

Limite : un nombre de requêtes par minute et par serveur, toutes clés confondues, selon l'offre du serveur (affiché dans le dashboard). Chaque réponse porte X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset. Au-delà, la réponse est 429 avec Retry-After (en secondes). Les fiches sont gardées 30 secondes : inutile d'appeler plus souvent pour un même membre. Préfère les webhooks à des appels en boucle.

04.Membres (Discord)

GET/toute clé

Le serveur : nom, icône, nombre de membres, nombre de boosts, niveau de boost.

GET/metoute clé

La clé utilisée : nom, serveur, permissions.

GET/rolesroles.read

Rôles du serveur (id, nom, couleur, position), du plus haut au plus bas.

GET/members/{userId}members.read

Fiche du membre. Les champs rôles, boost, tag et statut n'apparaissent que si la clé a la permission correspondante. 404 not_member si le compte n'est pas sur le serveur.

GET/members?ids={id1},{id2},…members.read

Plusieurs fiches d'un coup (100 au plus), par exemple les joueurs connectés. Renvoie items et missing (comptes absents du serveur). Il n'existe pas de liste complète des membres.

GET/members/{userId}/rolesroles.read

Identifiants des rôles du membre.

GET/members/{userId}/has-role/{roleId}roles.read

{ hasRole: true | false } : le plus simple pour une whitelist.

GET/members/{userId}/boostboosts.read

{ boosting, boostingSince }.

GET/members/{userId}/tagtag.read

Tag de serveur affiché sur le profil Discord : { tag, guildId, isThisGuild }, ou null. isThisGuild indique si c'est le tag de ton serveur.

GET/members/{userId}/statusstatus.read

status : online, idle, dnd ou offline ; customStatus : texte du statut personnalisé ("" s'il n'y en a pas). Discord ne donne pas le statut d'un membre hors ligne ou invisible : customStatus vaut alors null.

GET/members/{userId}/status/contains?text=discord.gg/hurixstatus.check

{ contains: true | false | null } sans renvoyer le texte. null : statut inconnu (hors ligne ou invisible), pas « non ». Majuscules ignorées.

05.Données Hurix

Lues seulement si le module correspondant est activé sur le serveur (sinon 404 module_disabled). Elles restent lisibles pour un compte qui a quitté le serveur.

GET/members/{userId}/levelslevels.read

xp, level, xpIntoLevel, xpForNextLevel, progress, rank, rankedMembers, voiceSeconds.

GET/members/{userId}/invitesinvites.read

invites { total, real, fake, left, score } et invitedBy { inviterId, code, source, joinedAt }.

GET/members/{userId}/economyeconomy.read

currency, balance, totalEarned, totalSpent, dailyStreak, weeklyStreak, inventory[].

GET/members/{userId}/activityactivity.read

last7Days et last30Days : { messages, voiceMinutes }.

GET/members/{userId}/verificationverification.read

status : verified, pending (captcha en cours) ou unknown (aucune trace, par exemple arrivé avant l'activation du captcha), et verifiedAt.

GET/members/{userId}/moderationmoderation.read

counts { warn, mute, kick, ban } et active { mute, tempBan } avec leur fin. Jamais les raisons ni le membre du staff.

GET/members/{userId}/birthdaybirthday.read

{ day, month } ou null. L'année n'est jamais enregistrée.

06.Classements

GET/leaderboards/{levels|invites|economy}?limit=10levels.read · invites.read · economy.read

Les mêmes classements que sur Discord (100 premiers au plus) : rank, userId, username, globalName, avatarUrl, value (et level pour les niveaux).

07.Webhooks

Ajoute une adresse https dans Paramètres → API développeurs → Webhooks. À chaque événement choisi, Hurix envoie une requête POST en JSON. Les webhooks préviennent seulement : ils ne modifient rien.

json
{
  "id": "5b0f1c6e-8f0a-4c55-9a77-3f2b9f1d2c10",
  "event": "member.roles.update",
  "createdAt": 1762876800000,
  "guildId": "987654321098765432",
  "data": {
    "userId": "123456789012345678",
    "username": "lea",
    "globalName": "Léa",
    "nickname": null,
    "avatarUrl": "https://cdn.discordapp.com/avatars/…",
    "bot": false,
    "joinedAt": 1741976520000,
    "accountCreatedAt": 1562061600000,
    "added": ["222222222222222222"],
    "removed": [],
    "roles": ["111111111111111111", "222222222222222222"]
  }
}

Événements disponibles :

member.joinmember.leavemember.roles.updatemember.nickname.updatemember.boost.startmember.boost.stopmember.tag.updatemember.status.matchmember.status.unmatchmember.level.upmember.invite.joinmember.verifiedmember.sanctionping

  • member.roles.update : les changements d'un même membre en moins de 2 secondes sont regroupés (added, removed, roles).
  • member.status.match / unmatch : seulement pour les textes que tu surveilles (10 au plus), jamais à chaque passage en ligne ou absent. data.text est le texte surveillé, pas le statut entier.
  • member.sanction : type (warn, mute, kick, ban) et fin éventuelle, sans raison ni staff.
  • member.leave ne distingue pas un départ, une expulsion ou un bannissement.

En-têtes : X-Hurix-Event, X-Hurix-Delivery (identifiant unique, utile pour ignorer un doublon), X-Hurix-Timestamp (secondes) et X-Hurix-Signature = sha256=HMAC-SHA256(secret, « horodatage.corps »). Vérifie toujours la signature avec le corps brut, et refuse un horodatage de plus de 5 minutes.

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

// express.raw({ type: "application/json" }) : il faut le corps brut.
app.post("/hurix", express.raw({ type: "application/json" }), (req, res) => {
  const timestamp = req.get("X-Hurix-Timestamp");
  const signature = req.get("X-Hurix-Signature") ?? "";
  const expected = "sha256=" + createHmac("sha256", process.env.HURIX_WEBHOOK_SECRET)
    .update(`${timestamp}.${req.body}`)
    .digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
  if (!fresh || signature.length !== expected.length ||
      !timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(req.body);
  if (event.event === "member.boost.start") giveVip(event.data.userId);
  res.sendStatus(200); // réponds vite (moins de 8 s), traite ensuite
});
java
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] sig = mac.doFinal((timestamp + "." + rawBody).getBytes(StandardCharsets.UTF_8));
String expected = "sha256=" + HexFormat.of().formatHex(sig);
boolean ok = MessageDigest.isEqual(expected.getBytes(), signatureHeader.getBytes())
    && Math.abs(Instant.now().getEpochSecond() - Long.parseLong(timestamp)) < 300;

Réponds par un code 2xx en moins de 8 secondes. Sinon, Hurix réessaie après 1 min, 5 min, 30 min, 2 h, 6 h puis 24 h, et abandonne. Après 20 échecs d'affilée, le webhook est coupé ; réactive-le depuis le dashboard. Au plus 600 envois par minute et par serveur : au-delà, les envois attendent leur tour, rien n'est perdu. Les redirections ne sont pas suivies et seules les adresses publiques sont acceptées.

08.Exemples

java
// Spigot / Paper : grade VIP en jeu pour les boosters du Discord.
HttpClient http = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://hurix.xyz/api/v1/guild/members/" + discordId + "/boost"))
    .header("Authorization", "Bearer " + config.getString("hurix-key"))
    .GET().build();
http.sendAsync(req, HttpResponse.BodyHandlers.ofString()).thenAccept(res -> {
    if (res.statusCode() != 200) return; // 404 not_member : pas sur le Discord
    JsonObject json = JsonParser.parseString(res.body()).getAsJsonObject();
    boolean booster = json.get("boosting").getAsBoolean();
    Bukkit.getScheduler().runTask(plugin, () -> setVip(player, booster));
});
lua
-- FiveM (serveur) : le joueur a-t-il le rôle « Whitelist » sur le Discord ?
local KEY = GetConvar("hurix_key", "")
local ROLE = "222222222222222222"

local function hasRole(discordId, cb)
  PerformHttpRequest("https://hurix.xyz/api/v1/guild/members/" .. discordId .. "/has-role/" .. ROLE,
    function(status, body)
      if status ~= 200 then return cb(false) end
      cb(json.decode(body).hasRole == true)
    end, "GET", "", { ["Authorization"] = "Bearer " .. KEY })
end