Module npm : @flitt/api
Le client officiel de l'API publique Flitt pour TypeScript et JavaScript. Il appelle l'API à votre place, gère les erreurs, les relances et le temps réel, pour que votre code ne contienne que votre logique.
À quoi sert le module
@flitt/api enveloppe l'API publique : au lieu d'écrire vos requêtes HTTP, vous appelez des méthodes typées (flitt.servers.list(), flitt.votes.claim()…). Le module s'occupe de l'authentification, des délais, des relances, de la pagination et des erreurs.
- Aucune dépendance, ESM et CommonJS, types TypeScript complets.
- Fonctionne sur Node.js 20+, Deno, Bun et Cloudflare Workers (tout environnement avec
fetch). - Sûr par défaut : HTTPS uniquement, clé jamais affichée, redirections jamais suivies, refus de s'exécuter dans un navigateur.
- Résilient : délai par requête, relances avec backoff exponentiel,
Retry-Afterrespecté, réclamation de vote idempotente. - Temps réel : votes, joueurs, chat et statut poussés par WebSocket, avec reconnexion et reprise automatiques.
Installation
npm install @flitt/api
# pnpm add @flitt/api · yarn add @flitt/api · bun add @flitt/apiPremier appel, en ESM ou TypeScript :
import { Flitt } from "@flitt/api";
const flitt = new Flitt({ apiKey: process.env.FLITT_API_KEY });
const { data: servers } = await flitt.servers.list();
for (const server of servers) {
console.log(`${server.name}: ${server.playersOnline} players (${server.status})`);
}En CommonJS :
const { Flitt } = require("@flitt/api");Identifiants
Deux identifiants existent. Passez-en un ou les deux au constructeur, ou laissez le module les lire dans les variables d'environnement :
| Clé du compte | Jeton serveur | |
|---|---|---|
| Option | apiKey (env FLITT_API_KEY) | serverToken (env FLITT_SERVER_TOKEN) |
| Format | flt_… | flv_… |
| Où le créer | Dashboard → Profil → API | Dashboard → votre serveur → Votes → API |
| Donne accès à | Tous les endpoints, sur tous les serveurs dont vous êtes propriétaire ou membre (selon votre rôle) | Votes et avis d'un seul serveur |
| Quota | Mensuel, selon l'offre | 120 requêtes / minute |
| À utiliser | Votre backend, votre bot Discord, vos outils | Sur un serveur de jeu (Lua, Java, petit script) |
.env
FLITT_API_KEY=flt_...
FLITT_SERVER_TOKEN=flv_...Sur un serveur de jeu
const flitt = new Flitt({ serverToken: process.env.FLITT_SERVER_TOKEN });
const claim = await flitt.votes.claim("srv_123", { player: "Alex_RP" });Quotas et limites
| Offre | Clé du compte | Jeton serveur |
|---|---|---|
| Freemium | 1 000 requêtes / mois | 120 requêtes / minute |
| Premium | 20 000 requêtes / mois | 120 requêtes / minute |
| Entreprise | Illimité | 120 requêtes / minute |
Un compte propriétaire d'un serveur Premium serveur (ou d'un serveur partenaire) profite du quota Premium. Un flux d'événements temps réel compte pour une requête par connexion ; les événements reçus sont gratuits.
L'état du quota après chaque réponse est disponible dans flitt.rateLimit ({ limit, remaining, reset }, null = illimité) ou via le rappel onRateLimit :
const flitt = new Flitt({
apiKey: process.env.FLITT_API_KEY,
onRateLimit: ({ limit, remaining, reset }) => console.log(remaining, "/", limit, "until", reset),
});
await flitt.servers.list();
console.log(flitt.rateLimit);Méthodes disponibles
Toutes les méthodes renvoient une Promise et lèvent une erreur typée en cas d'échec. Chacune accepte un dernier argument d'options par requête.
Compte
flitt.usage.get()Quota du compte : offre, limite mensuelle, consommé, restant, date de remise à zéro.
Serveurs
flitt.servers.list({ page?, limit? })Vos serveurs (propriétaire ou membre), paginés.
flitt.servers.listAll()Itérateur sur toutes les pages.
flitt.servers.get(serverId)Détail d'un serveur, avec hold (maintenance, développement, fermeture) ; IP et port masqués pour le rôle VIEWER.
Joueurs
flitt.players.list(serverId, { status?, page?, limit? })Joueurs : online, offline ou all.
flitt.players.listAll(serverId, { status? })Itérateur sur tous les joueurs.
flitt.players.get(serverId, playerId)Un joueur, par id Flitt ou identifiant de jeu (SteamID, licence, UUID).
Pare-feu (anti-VPN)
flitt.firewall.get(serverId) / update(serverId, changes)Réglages : activé, blocage VPN, message de kick (OWNER ou ADMIN pour modifier).
flitt.firewall.rules.list / create / delete / deleteManyRègles ALLOW / DENY par identifiant, IP ou pays ; jusqu'à 100 règles par appel, succès partiel possible ; deleteMany refuse un filtre vide.
flitt.firewall.blocks(serverId, { limit? })Dernières connexions bloquées.
await flitt.firewall.update("srv_123", { enabled: true, blockVpn: true, kickMessage: "VPN not allowed" });
const result = await flitt.firewall.rules.create("srv_123", [
{ action: "DENY", kind: "IDENTIFIER", value: "76561198000000000", note: "Cheater" },
{ action: "DENY", kind: "COUNTRY", value: "XX" },
]);
for (const e of result.errors) console.warn(e.index, e.code, e.message);Commandes en jeu
flitt.commands.chat / notify(serverId, …)Message à tout le serveur ou à un joueur (rôle EDITOR).
flitt.commands.kick / ban / unban(serverId, externalId, …)Sanctions (rôle ADMIN). Jamais relancées automatiquement : un ban ne doit pas partir deux fois.
flitt.commands.setGroup / setName(serverId, externalId, …)Groupe (ULX, SAM, CAMI, ace, LuckPerms…) ou pseudo.
flitt.commands.groups(serverId)Groupes actuels des joueurs connectés.
Les commandes sont mises en file pour l'addon du serveur. Passez waitMs (15 000 au maximum) pour attendre sa réponse :
await flitt.commands.chat("srv_123", "Restart in 5 minutes!");
const res = await flitt.commands.ban("srv_123", "76561198000000000", { reason: "RDM", durationSeconds: 86_400 }, { waitMs: 5000 });
console.log(res.result?.ok, res.result?.output);Liaison Discord
flitt.discord.createLinkCode(discordId)Code à taper en jeu (!flitt CODE, ou /flitt link CODE sur Minecraft).
flitt.discord.findLinks / link / unlinkComptes Discord liés à un joueur, liaison et suppression manuelles.
flitt.discord.getModule / setModule(serverId, enabled)Module Discord de l'addon, pour une intégration sans le bot Flitt.
Votes et avis
flitt.votes.check(serverId, identity)CLAIMABLE, CLAIMED ou NOT_FOUND, avec nextVoteInMinutes.
flitt.votes.claim(serverId, identity, { idempotencyKey? })Réclame le plus ancien vote non réclamé (24 h), une seule fois, tous canaux confondus.
flitt.votes.list / listAll(serverId, { since?, limit? })Votes récents, pagination par curseur (30 jours).
flitt.votes.top(serverId, { period?, limit? })Meilleurs votants : current_month, last_month, all_time ou YYYY-MM.
flitt.votes.summary / stats(serverId)Compteurs, classement, note ; votes, vues et clics par jour, semaine, mois.
flitt.reviews.list / listAll(serverId, { sort?, limit? })Avis publiés.
Temps réel
flitt.events.connect(options?)Flux d'événements en temps réel (WebSocket).
Pagination et itérateurs
Les méthodes list() renvoient une page ({ data, pagination } ou un curseur pour les votes). Les méthodes listAll() enchaînent les pages pour vous, avec for await :
const page = await flitt.servers.list({ page: 1, limit: 25 });
console.log(page.data, page.pagination);
for await (const player of flitt.players.listAll("srv_123", { status: "all" })) {
console.log(player.name, player.totalPlaySeconds);
}Récompenser les votes
Pour récompenser un votant depuis votre propre code (par exemple sans l'addon Flitt) : vérifiez, réclamez le vote une seule fois, puis donnez la récompense. Fonctionne avec la clé du compte (rôle avec les permissions de votes) ou le jeton serveur.
const flitt = new Flitt({ serverToken: process.env.FLITT_SERVER_TOKEN });
const claim = await flitt.votes.claim("srv_123", { player: "Alex_RP" });
if (claim.status === "CLAIMED_NOW") {
giveReward("Alex_RP");
for (const bonus of claim.rewards ?? []) giveBonus("Alex_RP", bonus.id);
}Identifiez le joueur avec un seul champ parmi player, steamId, discordId, license, uuid, ip ou voteId. claim() envoie toujours un en-tête Idempotency-Key : une relance automatique après une erreur réseau ne réclame jamais deux votes. Passez votre propre clé (par exemple ${player}:${heure}) pour que vos propres relances le soient aussi.
Événements en temps réel
flitt.events.connect() ouvre un flux WebSocket qui reçoit les événements de vos serveurs au moment où ils arrivent : votes, arrivées et départs, chat, statut, avis, affluence. Sans requêtes en boucle et sans URL publique : idéal pour un bot Discord ou un programme qui tourne en continu.
const stream = flitt.events.connect({ events: ["vote_created", "player_joined"] });
stream.on("ready", ({ servers }) => console.log("following", servers));
stream.on("vote_created", (e) => console.log(`${e.data?.player} voted for ${e.server.name}`));
stream.on("player_joined", (e) => console.log("join", e.data));
stream.on("reconnecting", ({ delayMs }) => console.log("reconnecting in", delayMs, "ms"));
stream.on("error", (err) => console.error(err.message));Ou avec for await :
for await (const event of flitt.events.connect({ servers: ["srv_123"] })) {
if (event.event === "player_chat") relayToDiscord(event);
}| Option | Par défaut | Rôle |
|---|---|---|
servers | tous vos serveurs | Serveurs à suivre (200 au maximum) |
events | tous ceux autorisés par votre rôle | Types d'événements à recevoir |
resume | true | Après une reconnexion, renvoyer ce qui a été manqué (jusqu'à 1 heure) |
lastEventIds | aucun | Positions sauvegardées depuis stream.lastEventIds, pour reprendre après un redémarrage |
WebSocket | WebSocket global | Constructeur à utiliser (paquet ws sur Node.js 20) |
signal | aucun | AbortSignal qui ferme le flux |
maxReconnectDelayMs | 30000 | Attente maximale entre deux tentatives de reconnexion |
bufferSize | 1000 | Événements gardés pour une boucle for await lente (les plus anciens sont jetés) |
Écouteurs disponibles : ready, event et un par type (vote_created…), reconnecting, gap (événements trop anciens pour être rejoués), error et close. stream.close() l'arrête définitivement ; stream.connected et stream.lastEventIds donnent son état.
Ce que vous recevez. Avec la clé du compte : les serveurs dont vous êtes propriétaire ou membre, filtrés par votre rôle (statut, affluence, joueurs, chat, votes : voir le tableau des événements). Avec un jeton serveur : vote_created et review_created de son serveur uniquement.
Reprendre après un redémarrage de votre programme :
import { readFile, writeFile } from "node:fs/promises";
const saved = JSON.parse(await readFile("positions.json", "utf8").catch(() => "{}"));
const stream = flitt.events.connect({ lastEventIds: saved });
process.on("SIGTERM", async () => {
await writeFile("positions.json", JSON.stringify(stream.lastEventIds));
stream.close();
});Environnements. Le flux utilise le WebSocket natif (Node.js 22+, Deno, Bun). Sur Node.js 20, installez ws et passez-le :
import WebSocket from "ws";
const stream = flitt.events.connect({ WebSocket });Vérifier un webhook
Si vous préférez recevoir les événements sur votre propre URL, verifyWebhook() vérifie que la requête vient bien de Flitt : signature HMAC-SHA256, âge (5 minutes) et rejeu. Passez le corps brut (pas le JSON re-sérialisé) et le secret affiché à la création du webhook.
Express
import express from "express";
import { verifyWebhook, FlittWebhookError } from "@flitt/api";
const app = express();
app.post("/flitt-webhook", express.raw({ type: "application/json" }), async (req, res) => {
try {
const event = await verifyWebhook({
payload: req.body,
headers: req.headers,
secret: process.env.FLITT_WEBHOOK_SECRET!,
isReplay: (nonce) => seenBefore(nonce),
});
if (event.event === "server_down") console.log(`${event.server.name} is down`);
res.sendStatus(204);
} catch (err) {
res.sendStatus(err instanceof FlittWebhookError ? 400 : 500);
}
});Fetch API (Next.js, Workers, Deno, Bun)
export async function POST(request: Request) {
const event = await verifyWebhook({
payload: await request.text(),
headers: request.headers,
secret: process.env.FLITT_WEBHOOK_SECRET!,
});
return new Response(null, { status: 204 });
}Chaque événement a la même enveloppe, typée WebhookEvent : { event, timestamp, server: { id, name }, data }.
Erreurs
Toutes les erreurs héritent de FlittError. Les erreurs de l'API héritent de FlittAPIError et portent status, code, message, details, method, path, rateLimit et retryAfterMs. Elles ne contiennent jamais votre clé.
| Classe | Quand |
|---|---|
FlittConfigError | Option ou argument invalide, détecté avant toute requête |
FlittValidationError | 400 : paramètre invalide |
FlittAuthenticationError | 401 : clé ou jeton manquant, invalide ou révoqué |
FlittPermissionError | 403 : l'offre ou le rôle ne le permet pas |
FlittNotFoundError | 404 : introuvable, ou pas à vous |
FlittConflictError | 409 : conflit (règle en double, serveur inactif…) |
FlittRateLimitError | 429 : quota atteint, voir retryAfterMs et rateLimit.reset |
FlittServerError | 5xx : erreur temporaire côté Flitt |
FlittTimeoutError | Pas de réponse dans timeoutMs |
FlittConnectionError | Erreur réseau (DNS, TLS, coupure…) |
FlittAbortError | Annulé par votre AbortSignal |
import { FlittNotFoundError, FlittRateLimitError } from "@flitt/api";
try {
await flitt.servers.get("srv_123");
} catch (err) {
if (err instanceof FlittNotFoundError) console.log("Unknown server");
else if (err instanceof FlittRateLimitError) console.log(`Retry in ${err.retryAfterMs} ms`);
else throw err;
}Options
new Flitt({
apiKey: "flt_...",
serverToken: "flv_...",
baseUrl: "https://api.flitt.me",
timeoutMs: 15_000,
maxRetries: 2,
maxRetryDelayMs: 30_000,
fetch: customFetch,
userAgent: "my-bot/1.0",
onRateLimit: (rl) => metrics.gauge("flitt.remaining", rl.remaining ?? Infinity),
dangerouslyAllowBrowser: false,
});| Option | Par défaut | Rôle |
|---|---|---|
baseUrl | https://api.flitt.me | HTTPS obligatoire (HTTP accepté pour localhost) |
timeoutMs | 15000 | Délai par tentative |
maxRetries | 2 | Relances des requêtes idempotentes : erreurs réseau, 408, 425, 5xx, 429 courts |
maxRetryDelayMs | 30000 | Attente maximale entre deux relances ; un Retry-After plus long n'est pas attendu |
fetch | fetch global | Tests, proxy, instrumentation |
userAgent | flitt-api-js/<version> | Côté serveur uniquement |
onRateLimit | aucun | Appelé avec l'état du quota après chaque réponse |
dangerouslyAllowBrowser | false | Autorise l'exécution dans un navigateur (déconseillé) |
Chaque méthode accepte aussi un dernier argument { signal?, timeoutMs?, maxRetries? } :
const controller = new AbortController();
setTimeout(() => controller.abort(), 2000);
await flitt.servers.list({}, { signal: controller.signal, timeoutMs: 5000, maxRetries: 0 });Sécurité
- HTTPS uniquement (HTTP accepté pour
localhosten développement) ; identifiants, paramètres et fragments refusés dansbaseUrl. - Redirections jamais suivies : l'en-tête
Authorizationne peut pas fuir vers un autre domaine. - Segments de chemin encodés, segments
.et..refusés. - Identifiants gardés dans des champs privés :
console.log,util.inspect,JSON.stringifyet les erreurs ne les affichent jamais. - Format des identifiants vérifié localement : un jeton serveur passé comme clé de compte (ou l'inverse) échoue avant toute requête.
- Flux temps réel en
wss://uniquement ; l'identifiant voyage dans le premier message, jamais dans l'URL. - Réponses de plus de 32 Mo refusées.
Versions et mises à jour
Version actuelle : 1.3.0. Le module suit le versionnage sémantique : une version mineure (1.x) n'introduit jamais de changement cassant. Pour mettre à jour :
npm install @flitt/api@latest
npm ls @flitt/api| Version | Nouveautés |
|---|---|
| 1.3.0 | Événements en temps réel : flitt.events.connect(), reconnexion et reprise automatiques, erreurs typées. |
| 1.2.x | hold sur les serveurs (maintenance, développement, fermeture) et événements server_hold_started / server_hold_ended. |
| 1.1.0 | Votes et avis intégrés au client Flitt (flitt.votes, flitt.reviews), jeton serveur via serverToken. |
| 1.0.0 | Première version : serveurs, joueurs, pare-feu, commandes, Discord, webhooks. |
Depuis 1.2 : rien à changer, flitt.events est simplement nouveau. Depuis 1.0 : le client séparé FlittVotes fonctionne toujours mais est déprécié ; passez à flitt.votes :
const votes = new FlittVotes({ token: process.env.FLITT_VOTE_TOKEN });
await votes.claim({ player: "Alex_RP" });
const flitt = new Flitt({ serverToken: process.env.FLITT_VOTE_TOKEN });
await flitt.votes.claim("srv_123", { player: "Alex_RP" });La version installée est exportée par le module, et envoyée dans le User-Agent :
import { VERSION } from "@flitt/api";
console.log(VERSION);