Portail développeurs
Expérimental La plateforme de bots et d'apps est en développement actif. La prise en charge des serveurs auto-hébergés est arrivée le 13/08/2026, avec moins de fonctionnalités que le cloud. Voir ce qui est pris en charge.
← Docs

Se connecter avec GameVox

Permettez aux gens de se connecter à votre site web ou app avec leur compte GameVox. C’est le flux OAuth2 standard par code d’autorisation, calqué sur celui de Discord, et il ne nécessite aucun bot : votre app demande l’accès à certaines parties du compte, la personne accepte, et votre serveur obtient un jeton.

À quoi ça sert

  • Connexion et liaison de comptes. Identifiez la personne (un ID utilisateur GameVox stable, son nom d’utilisateur, son avatar) et, avec son accord, son adresse e-mail vérifiée.
  • Sélecteurs de serveur et tableaux de bord. Avec guilds, voyez sur quels serveurs se trouve la personne et lesquels elle peut administrer.
  • Pas pour agir à la place de la personne. Un jeton utilisateur ne peut pas envoyer de messages, rejoindre un salon vocal ni rien modifier sur GameVox. Tout ce qui agit dans GameVox nécessite un bot.

Avant de commencer

  1. Créez une application sur developers.gamevox.com. L’ID client de son onglet OAuth2 est votre client_id.
  2. Dans l’onglet OAuth2, ajoutez chaque URI de redirection que votre app utilisera (10 au maximum). Elles doivent être en https://, ou en http://localhost / http://127.0.0.1 pour le développement, sans #fragment. La redirect_uri que vous envoyez doit correspondre exactement à l’une d’elles, query string comprise.
  3. Copiez le secret client. Il n’est affiché qu’une fois, à la création de l’app ou à la réinitialisation du secret. Gardez-le sur votre serveur : chaque requête de jeton en a besoin.

Endpoints

GET  https://gamevox.com/oauth2/authorize/
POST https://app-api.gamevox.com/api/oauth2/token
POST https://app-api.gamevox.com/api/oauth2/token/revoke
GET  https://app-api.gamevox.com/api/users/@me
GET  https://app-api.gamevox.com/api/users/@me/guilds
GET  https://app-api.gamevox.com/api/oauth2/@me
PUT  https://api.gamevox.com/api/users/@me/applications/{application.id}/role-connection

Conservez la barre oblique finale de /oauth2/authorize/. Les endpoints de jeton et d’utilisateur répondent aussi sous /api/v10/, donc les bibliothèques qui mettent une version dans le chemin fonctionnent sans modification. Les connexions de rôle se trouvent sur api.gamevox.com, pas sur app-api.

1. Envoyer la personne vers GameVox

Redirigez le navigateur (une navigation normale, pas une popup ni une iframe) vers l’URL d’autorisation :

https://gamevox.com/oauth2/authorize/
  ?response_type=code
  &client_id=123456789012345678
  &scope=identify%20email
  &redirect_uri=https%3A%2F%2Fyourapp.example.com%2Fauth%2Fgamevox
  &state=Zx81kq0vT2
ParamètreObligatoireDescription
response_type Oui Toujours code.
client_id Oui L’ID client de l’onglet OAuth2 de votre app.
scope Oui Séparés par des espaces (encodés en URL sous la forme %20 ou +). Voir Scopes. N’incluez pas bot : avec bot, cette URL devient une installation serveur.
redirect_uri Oui L’une de vos URI de redirection enregistrées, à l’identique.
state Non Une valeur impossible à deviner, liée à la session de la personne (1024 caractères au maximum). Elle revient inchangée ; vérifiez-la pour bloquer les attaques par falsification de requête intersite (CSRF).
code_challenge Non Challenge PKCE facultatif, de 43 à 128 caractères.
code_challenge_method Non S256 quand vous envoyez un challenge. plain est refusé.
prompt Non none saute l’écran de consentement quand la personne a déjà autorisé votre app pour tous ces scopes. Tout nouveau scope déclenche quand même la demande.

Ce que voit la personne

  • Un écran de consentement sur gamevox.com avec le nom, l’icône et la description de votre app, le compte avec lequel elle est connectée, la liste en langage clair de ce que vous pourrez voir, et le site vers lequel elle sera renvoyée.
  • Si elle n’est pas connectée, le bouton « Log in » la fait passer par l’app web GameVox puis la ramène directement sur ce même écran.
  • Les liens vers vos conditions d’utilisation et votre politique de confidentialité s’affichent sous les boutons si vous les avez renseignés dans l’onglet Informations générales.

2. Traiter le retour

Une fois que la personne a cliqué sur « Authorize », GameVox la renvoie vers votre URI de redirection avec un code :

https://yourapp.example.com/auth/gamevox?code=3vR0b...&state=Zx81kq0vT2

Si elle clique plutôt sur « Cancel » :

https://yourapp.example.com/auth/gamevox?error=access_denied
  &error_description=The+user+denied+the+request&state=Zx81kq0vT2
  • Rejetez le retour si state n’est pas celui que vous avez émis pour ce navigateur.
  • Un code expire au bout de 10 minutes et ne sert qu’une seule fois. Échangez-le immédiatement, depuis votre serveur.

3. Échanger le code contre des jetons

Depuis votre serveur, envoyez en POST un corps encodé en formulaire (application/x-www-form-urlencoded, pas du JSON) à l’endpoint de jeton :

curl -X POST https://app-api.gamevox.com/api/oauth2/token \
  -u "123456789012345678:CS.your-client-secret" \
  -d grant_type=authorization_code \
  -d code=3vR0b... \
  -d redirect_uri=https://yourapp.example.com/auth/gamevox
{
  "access_token": "GVO.mJ4c...",
  "token_type": "Bearer",
  "expires_in": 604800,
  "refresh_token": "GVR.9tQe...",
  "scope": "identify email"
}
  • Authentifiez-vous en HTTP Basic (client_id:client_secret) comme ci-dessus, ou envoyez client_id et client_secret comme champs de formulaire.
  • redirect_uri doit être exactement la valeur envoyée à l’étape 1. Ajoutez code_verifier si vous avez utilisé PKCE.
  • Les réponses portent Cache-Control: no-store. Ne mettez jamais le secret client ni les jetons dans du code côté navigateur ou dans des URL.

4. Appeler l’API

Envoyez le jeton d’accès en Authorization: Bearer. GET /users/@me nécessite le scope identify :

curl https://app-api.gamevox.com/api/users/@me \
  -H "Authorization: Bearer GVO.mJ4c..."
{
  "id": "1557709980780474368",
  "gamevox_id": "edf412bc-a7b8-4f2a-a810-8b9eb8c02462",
  "username": "alice",
  "discriminator": "0",
  "global_name": "alice",
  "avatar": null,
  "gamevox_avatar_url": "https://...",
  "email": "alice@example.com",
  "verified": true
}
  • id est l’ID GameVox stable de la personne. Indexez vos comptes dessus ; les noms d’utilisateur peuvent changer. gamevox_id désigne la même personne sous la forme UUID native de GameVox.
  • email et verified ne sont présents qu’avec le scope email, et seulement si la personne a une adresse e-mail enregistrée. GameVox ne conserve que des adresses vérifiées, donc verified vaut toujours true quand email est renseigné.
  • avatar vaut toujours null (les bibliothèques Discord construiraient à partir de lui un lien vers le CDN de Discord). Utilisez gamevox_avatar_url, qui est omis quand la personne n’a pas de photo.

Les serveurs de la personne

GET /users/@me/guilds nécessite le scope guilds :

[
  { "id": "1557445672742031360", "name": "Raid Night", "icon": null,
    "owner": true,  "permissions": "2251799813685247", "features": [] },
  { "id": "1557445672742031361", "name": "Guild Hall", "icon": null,
    "owner": false, "permissions": "1056", "features": [] }
]
  • permissions est un résumé destiné à choisir un serveur, pas un calcul complet : tous les bits (y compris ADMINISTRATOR) pour le propriétaire et les administrateurs, MANAGE_GUILD (32) plus VIEW_CHANNEL (1024) là où la personne peut modifier les réglages du serveur, et VIEW_CHANNEL pour tous les autres.
  • Jusqu’à limit serveurs par réponse (de 1 à 200, 200 par défaut), du plus ancien au plus récent. Paginez avec after ou before défini sur l’id d’un serveur de la page précédente, comme sur Discord.

L’autorisation en cours

GET /oauth2/@me fonctionne avec n’importe quel scope et indique à quelle app appartient le jeton, ce qui lui a été accordé et quand il expire. user est inclus avec identify.

{
  "application": { "id": "123456789012345678", "name": "RaidPlanner", ... },
  "scopes": ["identify", "email"],
  "expires": "2026-10-15T15:03:20Z",
  "user": { "id": "1557709980780474368", "username": "alice", ... }
}

Rôles liés

Avec role_connections.write, vous pouvez enregistrer le compte que la personne possède de votre côté. Attention à l’hôte : api.gamevox.com.

curl -X PUT \
  https://api.gamevox.com/api/users/@me/applications/123456789012345678/role-connection \
  -H "Authorization: Bearer GVO.mJ4c..." \
  -H "Content-Type: application/json" \
  -d '{"platform_name": "RaidPlanner", "platform_username": "ShadowMage", "metadata": {}}'

La personne le voit dans Settings > Authorized Apps sous la forme « Linked as ShadowMage on RaidPlanner ». Les clés de metadata doivent être celles que vous avez définies dans les métadonnées de connexion de rôle de votre app. Un GET sur la même URL les relit.

Rafraîchissement

Les jetons d’accès durent 7 jours (expires_in est en secondes). Obtenez une nouvelle paire avec le jeton de rafraîchissement :

curl -X POST https://app-api.gamevox.com/api/oauth2/token \
  -u "123456789012345678:CS.your-client-secret" \
  -d grant_type=refresh_token \
  -d refresh_token=GVR.9tQe...
  • Les jetons de rafraîchissement tournent. La réponse contient un nouveau jeton de rafraîchissement, et l’ancien jeton de rafraîchissement ainsi que son jeton d’accès cessent immédiatement de fonctionner : enregistrez la nouvelle paire avant de l’utiliser.
  • Vous pouvez envoyer scope pour limiter le nouveau jeton à moins de scopes. Demander un scope que la personne n’a jamais accordé échoue avec invalid_scope.
  • Un jeton de rafraîchissement inutilisé expire au bout de 90 jours. Chaque personne conserve au plus 25 paires de jetons actives par app ; en créer une 26e supprime la plus ancienne.

Révocation (déconnexion)

Révoquez quand la personne se déconnecte de votre app. N’importe quel jeton d’une paire révoque toute la paire. L’endpoint répond toujours 200 pour un client valide, que le jeton ait existé ou non.

curl -X POST https://app-api.gamevox.com/api/oauth2/token/revoke \
  -u "123456789012345678:CS.your-client-secret" \
  -d token=GVO.mJ4c...

PKCE

Facultatif, et recommandé si l’URL d’autorisation peut être construite à un endroit où quelqu’un d’autre pourrait la voir. GameVox ne prend en charge que S256, et une requête de jeton exige quand même votre secret client : il n’existe pas de clients publics sans secret.

// Before redirecting: keep the verifier with the user's pending login.
const verifier = base64url(crypto.randomBytes(32));
const challenge = base64url(crypto.createHash('sha256').update(verifier).digest());
// authorize URL: ...&code_challenge=CHALLENGE&code_challenge_method=S256
// token request: ...&code_verifier=VERIFIER (client secret still required)

Scopes

ScopePermet à votre app de
identify Lire l’ID, le nom d’utilisateur et l’avatar de la personne : GET /users/@me, et user dans GET /oauth2/@me.
email Ajouter l’adresse e-mail vérifiée de la personne à /users/@me.
guilds Lire la liste des serveurs de la personne : GET /users/@me/guilds.
role_connections.write Lire et écrire les informations de rôle lié de la personne pour votre app.

Tout autre scope, y compris connections, guilds.join et applications.commands sans bot, est refusé sur l’écran de consentement avec un message qui l’indique.

Erreurs

Les endpoints de jeton et de révocation renvoient les erreurs au format OAuth2 standard, {"error": "...", "error_description": "..."}, que les bibliothèques OAuth lisent telles quelles :

errorHTTPSignification
invalid_client 401 ID client ou secret incorrect, app supprimée, ou compte du propriétaire de l’app suspendu.
invalid_grant 400 Le code ou le jeton de rafraîchissement est inconnu, expiré, déjà utilisé, émis pour une autre app, ou son redirect_uri ou son code_verifier ne correspond pas. Également renvoyé après que la personne a retiré l’autorisation de votre app.
invalid_request 400 Un champ obligatoire manque, ou le corps n’est pas encodé en formulaire.
invalid_scope 400 Un rafraîchissement a demandé un scope que l’octroi initial n’inclut pas.
unsupported_grant_type 400 Seuls authorization_code et refresh_token sont pris en charge.
slow_down 429 Trop d’échecs d’authentification client depuis votre IP (30 par minute). Patientez une minute.

Les appels d’API avec un jeton invalide, expiré ou révoqué reçoivent 401 ; un jeton sans le scope nécessaire reçoit 403 (50001 Missing Access). Les réponses portent les en-têtes X-RateLimit-* habituels, comptés par personne et par app.

Quand l’accès prend fin

Un jeton peut cesser de fonctionner avant son expiration. Cela arrive quand :

  • la personne clique sur Deauthorize dans Settings > Authorized Apps (tous les jetons que vous détenez pour elle sont invalidés d’un coup, et votre entrée de rôle lié la concernant est supprimée)
  • le compte de la personne est désactivé
  • votre application est supprimée
  • votre compte développeur est suspendu

Traitez un 401 ou un invalid_grant comme « déconnecté » et refaites passer la personne par l’étape 1.

Exemple complet (Node.js + Express)

Connexion avec vérification du state, échange du code et lecture des informations de la personne. Node 18+ pour le fetch intégré.

import express from 'express';
import crypto from 'node:crypto';

const CLIENT_ID = process.env.GAMEVOX_CLIENT_ID;
const CLIENT_SECRET = process.env.GAMEVOX_CLIENT_SECRET;
const REDIRECT_URI = 'https://yourapp.example.com/auth/gamevox';

// state must be tied to THIS browser, or someone can hand a victim a callback
// URL carrying the attacker's own code and sign them in as the attacker.
// A short-lived HttpOnly cookie does that without a session store.
const STATE_COOKIE = 'gv_oauth_state';
const readCookie = (req, name) =>
  (req.headers.cookie || '').split(';').map((c) => c.trim().split('='))
    .find(([k]) => k === name)?.[1] || '';

const app = express();

app.get('/login', (req, res) => {
  const state = crypto.randomBytes(16).toString('hex');
  res.cookie(STATE_COOKIE, state, {
    httpOnly: true, secure: true, sameSite: 'lax', maxAge: 10 * 60 * 1000, path: '/auth/gamevox',
  });
  const url = new URL('https://gamevox.com/oauth2/authorize/');
  url.search = new URLSearchParams({
    response_type: 'code',
    client_id: CLIENT_ID,
    scope: 'identify email',
    redirect_uri: REDIRECT_URI,
    state,
  }).toString();
  res.redirect(url.toString());
});

app.get('/auth/gamevox', async (req, res) => {
  const { code, state, error } = req.query;
  const expected = readCookie(req, STATE_COOKIE);
  res.clearCookie(STATE_COOKIE, { path: '/auth/gamevox' });
  if (!expected || typeof state !== 'string' || state !== expected) {
    return res.status(400).send('Login expired or started in another browser');
  }
  if (error) return res.redirect('/?login=cancelled');
  if (typeof code !== 'string') return res.status(400).send('Missing code');

  const tokenRes = await fetch('https://app-api.gamevox.com/api/oauth2/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Authorization: 'Basic ' + Buffer.from(CLIENT_ID + ':' + CLIENT_SECRET).toString('base64'),
    },
    body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: REDIRECT_URI }),
  });
  if (!tokenRes.ok) return res.status(502).send('GameVox login failed');
  const tokens = await tokenRes.json();

  const me = await fetch('https://app-api.gamevox.com/api/users/@me', {
    headers: { Authorization: 'Bearer ' + tokens.access_token },
  }).then((r) => r.json());

  // me.id is the stable GameVox user id: key your account record on it.
  // Store tokens.refresh_token server-side if you need access later.
  res.send('Signed in as ' + me.username);
});

app.listen(3000);

Différences avec Discord

  • Uniquement l’octroi par code d’autorisation. Pas d’octroi implicite (response_type=token) ni de client_credentials.
  • Pas de clients publics : chaque requête de jeton exige le secret client, avec ou sans PKCE.
  • Les installations utilisateur (applications.commands sans bot, integration_type=1) ne sont pas encore disponibles.
  • Pas de scopes connections, guilds.join, guilds.members.read ni rpc.
  • Dans /users/@me/guilds, permissions est un résumé destiné à choisir un serveur, pas un calcul complet (voir plus haut).

← Retour à la documentation