Entrar com o GameVox
Deixe as pessoas entrarem no seu site ou app com a conta do GameVox. É o fluxo OAuth2 padrão de código de autorização, no mesmo formato do Discord, e não precisa de bot: seu app pede acesso a partes da conta, a pessoa aprova e o seu servidor recebe um token.
Para que serve
- Login e vinculação de contas. Descubra quem é a pessoa (um ID de usuário estável do GameVox, nome de usuário, avatar) e, com a permissão dela, o e-mail verificado.
- Seletores de servidor e painéis. Com
guilds, veja em quais servidores a pessoa está e em quais ela pode gerenciar as configurações. - Não serve para agir em nome da pessoa. Um token de usuário não pode enviar mensagens, entrar em canais de voz nem alterar nada no GameVox. Qualquer coisa que aja dentro do GameVox precisa de um bot.
Antes de começar
- Crie uma aplicação em developers.gamevox.com. O ID do cliente na aba OAuth2 dela é o seu
client_id. - Na aba OAuth2, adicione cada URI de redirecionamento que o seu app vai usar (até 10). Elas precisam ser
https://, ouhttp://localhost/http://127.0.0.1para desenvolvimento, sem#fragment. Aredirect_urique você enviar precisa corresponder exatamente a uma delas, incluindo a query string. - Copie o segredo do cliente. Ele aparece uma única vez, quando o app é criado ou o segredo é redefinido. Mantenha-o no seu servidor: toda requisição de token precisa dele.
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 Mantenha a barra final em /oauth2/authorize/. Os endpoints de token e de usuário também respondem em /api/v10/, então bibliotecas que colocam uma versão no caminho funcionam sem mudanças. As conexões de cargo ficam em api.gamevox.com, não em app-api.
1. Envie a pessoa para o GameVox
Redirecione o navegador (uma navegação normal, não um popup nem um iframe) para a URL de autorização:
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 | Parâmetro | Obrigatório | Descrição |
|---|---|---|
response_type | Sim | Sempre code. |
client_id | Sim | O ID do cliente da aba OAuth2 do seu app. |
scope | Sim | Separados por espaço (codificados em URL como %20 ou +). Veja Scopes. Não inclua bot: com bot, esta URL vira uma instalação em servidor. |
redirect_uri | Sim | Uma das suas URIs de redirecionamento registradas, exatamente igual. |
state | Não | Um valor impossível de adivinhar, ligado à sessão da pessoa (até 1024 caracteres). Ele volta sem alterações; confira-o para impedir falsificação de requisição entre sites (CSRF). |
code_challenge | Não | Challenge PKCE opcional, de 43 a 128 caracteres. |
code_challenge_method | Não | S256 quando você envia um challenge. plain é recusado. |
prompt | Não | none pula a tela de consentimento quando a pessoa já aprovou o seu app para todos esses scopes. Qualquer scope novo ainda gera a pergunta. |
O que a pessoa vê
- Uma tela de consentimento em gamevox.com com o nome, o ícone e a descrição do seu app, a conta com que a pessoa está conectada, uma lista em linguagem simples do que você poderá ver e o site para onde ela será enviada.
- Se ela não estiver conectada, o botão “Log in” a leva pelo app web do GameVox e a traz direto de volta para a mesma tela.
- Os links para os seus Termos de Serviço e Política de Privacidade aparecem abaixo dos botões quando você os define na aba Informações gerais.
2. Trate o retorno
Depois que a pessoa clica em “Authorize”, o GameVox a envia para a sua URI de redirecionamento com um código:
https://yourapp.example.com/auth/gamevox?code=3vR0b...&state=Zx81kq0vT2 Se ela clicar em “Cancel”:
https://yourapp.example.com/auth/gamevox?error=access_denied
&error_description=The+user+denied+the+request&state=Zx81kq0vT2 - Rejeite o retorno se o
statenão for o que você emitiu para este navegador. - Um código expira em 10 minutos e funciona uma única vez. Troque-o imediatamente, a partir do seu servidor.
3. Troque o código por tokens
A partir do seu servidor, faça um POST com corpo codificado como formulário (application/x-www-form-urlencoded, não JSON) para o endpoint de token:
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"
} - Autentique com HTTP Basic (
client_id:client_secret) como acima, ou envieclient_ideclient_secretcomo campos do formulário. redirect_uriprecisa ser o mesmo valor enviado no passo 1. Adicionecode_verifierse você usou PKCE.- As respostas trazem
Cache-Control: no-store. Nunca coloque o segredo do cliente nem os tokens em código do navegador ou em URLs.
4. Chame a API
Envie o token de acesso como Authorization: Bearer. GET /users/@me precisa do 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é o ID estável da pessoa no GameVox. Use-o como chave dos seus registros de conta; nomes de usuário podem mudar.gamevox_idé o mesmo usuário no formato UUID nativo do GameVox.emaileverifiedsó aparecem com o scopeemail, e só se a pessoa tiver um e-mail cadastrado. O GameVox só armazena endereços verificados, entãoverifiedé sempretruequandoemailestá presente.avataré semprenull(as bibliotecas do Discord montariam com ele um link para a CDN do Discord). Usegamevox_avatar_url, que é omitido quando a pessoa não tem foto.
Os servidores da pessoa
GET /users/@me/guilds precisa do 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é um resumo para escolher servidores, não um cálculo completo: todos os bits (incluindoADMINISTRATOR) para o dono e os administradores,MANAGE_GUILD(32) maisVIEW_CHANNEL(1024) onde a pessoa pode editar as configurações do servidor, eVIEW_CHANNELpara todos os demais.- Até
limitservidores por resposta (de 1 a 200, padrão 200), dos mais antigos para os mais novos. Pagine comafteroubeforedefinido como oidde um servidor da página anterior, como no Discord.
A autorização atual
GET /oauth2/@me funciona com qualquer scope e informa a qual app o token pertence, o que foi concedido e quando ele expira. user vem incluído com identify.
{
"application": { "id": "123456789012345678", "name": "RaidPlanner", ... },
"scopes": ["identify", "email"],
"expires": "2026-10-15T15:03:20Z",
"user": { "id": "1557709980780474368", "username": "alice", ... }
} Cargos vinculados
Com role_connections.write você pode registrar qual conta a pessoa tem do seu lado. Repare no host: 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": {}}' A pessoa vê isso em Settings > Authorized Apps como “Linked as ShadowMage on RaidPlanner”. As chaves de metadata precisam ser as que você definiu nos metadados de conexão de cargo do seu app. Um GET na mesma URL lê os dados de volta.
Renovação
Tokens de acesso duram 7 dias (expires_in é em segundos). Obtenha um novo par com o refresh token:
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... - Refresh tokens são rotacionados. A resposta traz um novo refresh token, e o refresh token antigo e o token de acesso dele param de funcionar imediatamente, então salve o novo par antes de usá-lo.
- Você pode enviar
scopepara restringir o novo token a menos scopes. Pedir um scope que a pessoa nunca concedeu falha cominvalid_scope. - Um refresh token sem uso expira depois de 90 dias. Cada pessoa mantém no máximo 25 pares de tokens ativos por app; criar o 26º descarta o mais antigo.
Revogação (logout)
Revogue quando a pessoa sair do seu app. Qualquer um dos tokens de um par revoga o par inteiro. O endpoint sempre responde 200 para um cliente válido, quer o token exista ou não.
curl -X POST https://app-api.gamevox.com/api/oauth2/token/revoke \
-u "123456789012345678:CS.your-client-secret" \
-d token=GVO.mJ4c... PKCE
Opcional, e recomendado se a URL de autorização puder ser montada em um lugar onde outra pessoa possa vê-la. O GameVox só aceita S256, e uma requisição de token continua exigindo o seu segredo do cliente: não existem clientes públicos sem segredo.
// 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
| Scope | Permite ao seu app |
|---|---|
identify | Ler o ID, o nome de usuário e o avatar da pessoa: GET /users/@me, e user em GET /oauth2/@me. |
email | Adicionar o e-mail verificado da pessoa a /users/@me. |
guilds | Ler a lista de servidores da pessoa: GET /users/@me/guilds. |
role_connections.write | Ler e gravar os dados de cargo vinculado da pessoa para o seu app. |
Qualquer outro scope, incluindo connections, guilds.join e applications.commands sem bot, é recusado na tela de consentimento com uma mensagem explicando o motivo.
Erros
Os endpoints de token e de revogação respondem erros no formato OAuth2 padrão, {"error": "...", "error_description": "..."}, então bibliotecas OAuth os leem sem adaptação:
| error | HTTP | Significado |
|---|---|---|
invalid_client | 401 | ID do cliente ou segredo incorreto, app excluído ou conta do dono do app suspensa. |
invalid_grant | 400 | O código ou o refresh token é desconhecido, expirou, já foi usado, foi emitido para outro app, ou o redirect_uri ou o code_verifier não corresponde. Também é retornado depois que a pessoa desautoriza o seu app. |
invalid_request | 400 | Falta um campo obrigatório, ou o corpo não está codificado como formulário. |
invalid_scope | 400 | Uma renovação pediu um scope que a concessão original não inclui. |
unsupported_grant_type | 400 | Só authorization_code e refresh_token são suportados. |
slow_down | 429 | Falhas demais de autenticação de cliente a partir do seu IP (30 por minuto). Aguarde um minuto. |
Chamadas à API com um token inválido, expirado ou revogado recebem 401; um token sem o scope necessário recebe 403 (50001 Missing Access). As respostas trazem os cabeçalhos X-RateLimit-* de sempre, contados por pessoa e por app.
Quando o acesso termina
Um token pode parar de funcionar antes de expirar. Isso acontece quando:
- a pessoa clica em Deauthorize em Settings > Authorized Apps (todos os tokens que você tem dela deixam de valer na hora, e a sua entrada de cargo vinculado para ela é excluída)
- a conta da pessoa é desativada
- a sua aplicação é excluída
- a sua conta de desenvolvedor é suspensa
Trate um 401 ou invalid_grant como “desconectado” e mande a pessoa pelo passo 1 de novo.
Exemplo completo (Node.js + Express)
Login com verificação de state, a troca do código e a leitura do usuário. Node 18+ para o fetch nativo.
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); Diferenças em relação ao Discord
- Só a concessão por código de autorização. Sem concessão implícita (
response_type=token) e semclient_credentials. - Sem clientes públicos: toda requisição de token precisa do segredo do cliente, com ou sem PKCE.
- Instalações em conta (
applications.commandssembot,integration_type=1) ainda não estão disponíveis. - Sem os scopes
connections,guilds.join,guilds.members.readourpc. - Em
/users/@me/guilds,permissionsé um resumo para escolher servidores, não um cálculo completo (veja acima).