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/guildL'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.
curl https://hurix.xyz/api/v1/guild/members/123456789012345678 \
-H "Authorization: Bearer hxm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{
"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/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)
/toute cléLe serveur : nom, icône, nombre de membres, nombre de boosts, niveau de boost.
/metoute cléLa clé utilisée : nom, serveur, permissions.
/rolesroles.readRôles du serveur (id, nom, couleur, position), du plus haut au plus bas.
/members/{userId}members.readFiche 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.
/members?ids={id1},{id2},…members.readPlusieurs 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.
/members/{userId}/rolesroles.readIdentifiants des rôles du membre.
/members/{userId}/has-role/{roleId}roles.read{ hasRole: true | false } : le plus simple pour une whitelist.
/members/{userId}/boostboosts.read{ boosting, boostingSince }.
/members/{userId}/tagtag.readTag de serveur affiché sur le profil Discord : { tag, guildId, isThisGuild }, ou null. isThisGuild indique si c'est le tag de ton serveur.
/members/{userId}/statusstatus.readstatus : 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.
/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.
/members/{userId}/levelslevels.readxp, level, xpIntoLevel, xpForNextLevel, progress, rank, rankedMembers, voiceSeconds.
/members/{userId}/invitesinvites.readinvites { total, real, fake, left, score } et invitedBy { inviterId, code, source, joinedAt }.
/members/{userId}/economyeconomy.readcurrency, balance, totalEarned, totalSpent, dailyStreak, weeklyStreak, inventory[].
/members/{userId}/activityactivity.readlast7Days et last30Days : { messages, voiceMinutes }.
/members/{userId}/verificationverification.readstatus : verified, pending (captcha en cours) ou unknown (aucune trace, par exemple arrivé avant l'activation du captcha), et verifiedAt.
/members/{userId}/moderationmoderation.readcounts { warn, mute, kick, ban } et active { mute, tempBan } avec leur fin. Jamais les raisons ni le membre du staff.
/members/{userId}/birthdaybirthday.read{ day, month } ou null. L'année n'est jamais enregistrée.
06.Classements
/leaderboards/{levels|invites|economy}?limit=10levels.read · invites.read · economy.readLes 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.
{
"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.
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
});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
// 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));
});-- 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