S Sloteflix API v1
/

Documentação de Integração Sloteflix

A Sloteflix é um agregador de jogos. Você integra uma vez e recebe acesso ao catálogo completo de provedores, com liquidação financeira em tempo real via Seamless Wallet — o saldo do jogador nunca sai da sua plataforma.

Visão geral

A integração tem exatamente duas metades. Entender essa divisão é a única coisa que realmente importa antes de escrever código:

MetadeDireçãoQuem implementaO que faz
API Sloteflix Você → Sloteflix Nós (já pronto) Listar catálogo, listar provedores, abrir jogo, free spins.
Seamless Wallet Sloteflix → Você Você Debitar aposta, creditar prêmio, estornar, consultar saldo.
ℹ
A carteira é sua. A Sloteflix não guarda saldo de jogador. Toda aposta e todo prêmio são uma chamada HTTP para a sua API, que é a fonte da verdade. Se a sua API responder erro, a rodada não acontece.

Como funciona

Fluxo completo de uma rodada, do clique do jogador até o crédito do prêmio:

 ┌──────────┐        ┌────────────────┐        ┌─────────────┐        ┌──────────┐
 │  Jogador │        │  SUA PLATAFORMA│        │  SLOTEFLIX  │        │ PROVEDOR │
 └────┬─────┘        └───────┬────────┘        └──────┬──────┘        └────┬─────┘
      │  clica no jogo       │                        │                    │
      │─────────────────────▶│                        │                    │
      │                      │  GET games/game_launch │                    │
      │                      │───────────────────────▶│                    │
      │                      │                        │  launch (interno)  │
      │                      │                        │───────────────────▶│
      │                      │   { "game_url": ... }  │                    │
      │                      │◀───────────────────────│                    │
      │  redirect / iframe   │                        │                    │
      │◀─────────────────────│                        │                    │
      │                      │                        │                    │
      │  ── o jogo abre e o jogador gira o rolo ──    │                    │
      │                      │                        │                    │
      │                      │                        │◀── bet ────────────│
      │  POST /api/sloteflix_api  { method:"transaction", type:"bet" }     │
      │                      │◀───────────────────────│                    │
      │                      │  { status:1, balance } │                    │
      │                      │───────────────────────▶│                    │
      │                      │                        │─── ok ────────────▶│
      │                      │                        │                    │
      │                      │                        │◀── win ────────────│
      │  POST /api/sloteflix_api  { method:"transaction", type:"win" }     │
      │                      │◀───────────────────────│                    │
      │                      │  { status:1, balance } │                    │
      │                      │───────────────────────▶│                    │
      │  saldo atualizado    │                        │                    │
      │◀─────────────────────│                        │                    │

Credenciais

Ao ser cadastrado como agente você recebe quatro valores. Todos são obrigatórios em algum ponto da integração:

CredencialTipoOnde é usada
agent_code string Identifica sua operação. Vai em quase toda requisição e em todo callback seamless.
agent_token string(32) Autentica chamadas de jogo (launch, free spins). Também chega nos callbacks para você validar a origem.
agent_secret_key string(32) Junto com o token, gera o Bearer da autenticação JWT. Nunca exponha no front-end.
endpoint url A base URL da sua plataforma. É onde a Sloteflix vai bater o seamless. Configurável no painel.
⚠
Nunca coloque agent_token ou agent_secret_key em código de navegador. A chamada de launch tem que sair do seu backend. Se o token vazar, qualquer pessoa abre jogos em nome dos seus jogadores.

Ambientes & URLs

Base URL da API Sloteflix

Todos os endpoints que você chama ficam sob o prefixo /api/v1:

Base URL
https://{SLOTEFLIX_HOST}/api/v1

O host exato é entregue pelo time de integração no momento do cadastro. Não faça hardcode — deixe em variável de ambiente.

URL do seu endpoint seamless

A Sloteflix monta o destino do callback a partir do campo endpoint do seu agente, aplicando exatamente estas três transformações:

  1. substitui o domínio lumosgaming.com por connectionslm.com (roteamento interno de rede);
  2. remove a barra final, se houver;
  3. concatena o caminho fixo /api/sloteflix_api.
Resolução do endpoint
endpoint configurado : https://minhaoperacao.com
callback resolvido   : https://minhaoperacao.com/api/sloteflix_api
⚠
O caminho /api/sloteflix_api é fixo e não configurável. Você define só a base (esquema + host + porta opcional). Salve o endpoint sem barra no final e sem path.

Orçamento de latência — leia isto

O cliente HTTP que dispara o seamless é agressivo de propósito: o jogador está esperando o rolo girar. Estes números são do código de produção, não uma recomendação:

ParâmetroValorConsequência
Timeout de conexão100 msTLS handshake lento já queima a tentativa.
Timeout de resposta600 msSeu handler tem que responder em menos de 600 ms.
Tentativas3Sem espera entre elas. Header X-Attempt traz o número.
Após 3 falhasstatus: 15Tratado como Timeout (OP_47) — a rodada é rejeitada.
⏱

600 ms é o orçamento total, incluindo rede, TLS, framework e banco. Na prática mire em < 150 ms no p99.

O que quebra integrações, na ordem: consulta de saldo sem índice, cold start de serverless, middleware de sessão/CSRF no caminho, e log síncrono. Tire tudo isso da rota do seamless.

Todas as tentativas chegam com estes headers:

Headers do callback
Content-Type: application/json
Accept: application/json
Connection: keep-alive
X-Attempt: 1        # 1, 2 ou 3

Quickstart

  1. Receba suas credenciais Peça ao time de integração o agent_code, agent_token, agent_secret_key e o host da API. Guarde em variáveis de ambiente.
  2. Configure seu endpoint Informe a base URL da sua plataforma (ex.: https://minhaoperacao.com). É para lá que vão os callbacks. Veja Painel de configuração.
  3. Implemente POST /api/sloteflix_api Um único endpoint, roteado pelo campo method. Comece por account_details, user_balance e transaction — sem esses três nenhum jogo abre.
  4. Sincronize o catálogo Chame GET games/all e persista os jogos. Rode de novo periodicamente — o catálogo muda.
  5. Abra o primeiro jogo Chame GET games/game_launch do seu backend e redirecione o jogador para a game_url retornada.
Primeira chamada
# 1) confirme que o catálogo responde
curl -s "https://{SLOTEFLIX_HOST}/api/v1/games/games-status" | head -c 300

# 2) abra um jogo (troque game_id por um game_code do catálogo)
curl -s -G "https://{SLOTEFLIX_HOST}/api/v1/games/game_launch" \
  --data-urlencode "agent_code=meu_agente" \
  --data-urlencode "agent_token=$SLOTEFLIX_AGENT_TOKEN" \
  --data-urlencode "game_id=vs20olympgate" \
  --data-urlencode "type=CHARGED" \
  --data-urlencode "currency=BRL" \
  --data-urlencode "lang=BR" \
  --data-urlencode "demo_agent=0" \
  --data-urlencode "user_id=1042"
use Illuminate\Support\Facades\Http;

$response = Http::withQueryParameters([
    'agent_code'  => config('sloteflix.agent_code'),
    'agent_token' => config('sloteflix.agent_token'),
    'game_id'     => 'vs20olympgate',
    'type'        => 'CHARGED',
    'currency'    => 'BRL',
    'lang'        => 'BR',
    'demo_agent'  => 0,
    'user_id'     => (string) $player->id,
])->get(config('sloteflix.base_url') . '/games/game_launch');

if ($response->successful() && $response->json('game_url')) {
    return redirect()->away($response->json('game_url'));
}

report(new RuntimeException('Sloteflix launch falhou: ' . $response->body()));
return back()->withErrors('Jogo indisponível no momento.');
const params = new URLSearchParams({
  agent_code:  process.env.SLOTEFLIX_AGENT_CODE,
  agent_token: process.env.SLOTEFLIX_AGENT_TOKEN,
  game_id:     'vs20olympgate',
  type:        'CHARGED',
  currency:    'BRL',
  lang:        'BR',
  demo_agent:  '0',
  user_id:     String(player.id),
});

const res  = await fetch(`${process.env.SLOTEFLIX_BASE_URL}/games/game_launch?${params}`);
const json = await res.json();

if (!res.ok || !json.game_url) throw new Error(`launch falhou: ${JSON.stringify(json)}`);
return json.game_url;
import os, requests

params = {
    "agent_code":  os.environ["SLOTEFLIX_AGENT_CODE"],
    "agent_token": os.environ["SLOTEFLIX_AGENT_TOKEN"],
    "game_id":     "vs20olympgate",
    "type":        "CHARGED",
    "currency":    "BRL",
    "lang":        "BR",
    "demo_agent":  0,
    "user_id":     str(player.id),
}

r = requests.get(
    f"{os.environ['SLOTEFLIX_BASE_URL']}/games/game_launch",
    params=params, timeout=10,
)
r.raise_for_status()
game_url = r.json()["game_url"]

Convenções

Valores monetários

Apostas e prêmios trafegam como números decimais na moeda do jogador — não em centavos. A persistência interna é DECIMAL(20,2), ou seja duas casas decimais.

Correto vs. errado
{ "bet": 1.50, "win": 0 }      // ✅ R$ 1,50
{ "bet": 150,  "win": 0 }      // ❌ isso é R$ 150,00, não R$ 1,50
⚠
Se a sua carteira guarda centavos como inteiro, converta na borda do handler seamless — nunca no meio da regra de negócio. Erro de escala aqui é o bug mais caro que existe nesse tipo de integração. Arredonde sempre para 2 casas com half-up.

Moeda

A moeda é fixada por agente (currency, padrão BRL) e informada no launch. Os callbacks seamless não repetem a moeda em todos os métodos — trate a moeda do agente como a moeda de todas as transações daquele agente.

Identificadores

CampoFormatoObservação
user_idstring|intÉ o seu ID de jogador. Você envia no launch e recebe de volta em todo callback. Não pode ser 0 nem vazio.
game_idstringNo launch é o game_code do catálogo, não o id numérico.
gamestringNos callbacks, o código do jogo daquela rodada.
transaction_idstringPrefixado pelo distribuidor: timeless-123, betby-456. Globalmente único — use como chave de idempotência.
round_idstringAgrupa aposta e prêmio da mesma rodada. Também prefixado.
session_idstringToken da sessão de jogo, gerado pela Sloteflix.
distributorstringOrigem da transação: timeless, betby, clone, playgaming, sourcepark…

Códigos HTTP

Nas rotas da API Sloteflix: 200 em sucesso, 400 em erro de validação/negócio, 403 em token administrativo inválido, 404 quando o recurso não existe.

ℹ
No seamless é diferente: responda sempre HTTP 200 e sinalize o resultado pelo campo status do corpo JSON. Veja Códigos de status.

Autenticação

Existem três esquemas em uso. Qual usar depende do endpoint:

EsquemaComo enviarEndpoints
Par de agente agent_code + agent_token como parâmetros launch, free spins, get-agent-data
Bearer JWT Authorization: Bearer <access_token> games/provider
Bearer básico Authorization: Bearer base64("token:secret") authentication, clone status
ℹ
Os endpoints de catálogo (games/all, games-status, to-add, list-clone) hoje não exigem autenticação. Ainda assim, chame-os do backend: o contrato pode endurecer sem aviso e você não quer descobrir isso em produção.

Obter o token JWT

POST /api/v1/auth/authentication Você → Sloteflix

Concatene agent_token e agent_secret_key com dois-pontos, codifique em Base64 e envie como Bearer. A resposta é um JWT para as rotas protegidas.

Headers

HeaderValor
AuthorizationOBRIG.Bearer base64(agent_token + ":" + agent_secret_key)
Requisição
TOKEN=$(printf '%s:%s' "$SLOTEFLIX_AGENT_TOKEN" "$SLOTEFLIX_AGENT_SECRET" | base64)

curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/auth/authentication" \
  -H "Authorization: Bearer $TOKEN"
use Illuminate\Support\Facades\Http;

$basic = base64_encode(config('sloteflix.agent_token') . ':' . config('sloteflix.agent_secret_key'));

$response = Http::withHeaders(['Authorization' => "Bearer {$basic}"])
    ->post(config('sloteflix.base_url') . '/auth/authentication');

abort_unless($response->successful(), 502, 'Sloteflix auth falhou');

$jwt = $response->json('access_token');
const basic = Buffer
  .from(`${process.env.SLOTEFLIX_AGENT_TOKEN}:${process.env.SLOTEFLIX_AGENT_SECRET}`)
  .toString('base64');

const res = await fetch(`${process.env.SLOTEFLIX_BASE_URL}/auth/authentication`, {
  method:  'POST',
  headers: { Authorization: `Bearer ${basic}` },
});

const { access_token } = await res.json();
200 · Sucesso
{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "token_type": "bearer"
}
400 · Erros
// Bearer ausente, não-Base64, ou sem os dois-pontos
{ "status": false, "error": "INVALID_TOKEN" }

// par token:secret não corresponde a nenhum agente
{ "status": false, "error": "INVALID_AGENT" }
⚠
Não gere um JWT por requisição. Faça cache respeitando o TTL do token e renove só quando expirar ou quando uma rota protegida responder 401.

Launch Game

GET /api/v1/games/game_launch Você → Sloteflix

O endpoint central da integração. Devolve uma URL pronta para você abrir em iframe ou redirecionar o jogador. A Sloteflix resolve o distribuidor, cria a sessão e assina a URL.

Parâmetros (query string)

ParâmetroTipoDescrição
agent_codeOBRIG.stringSeu código de agente.
agent_tokenOBRIG.stringSeu token. Validado contra o agent_code.
game_idOBRIG.stringO game_code do catálogo. Não o id numérico.
typeOBRIG.stringModo da sessão. Use CHARGED para dinheiro real.
currencyOBRIG.stringISO 4217 — ex.: BRL. Precisa bater com a moeda do agente.
langOBRIG.stringIdioma da interface do jogo — ex.: BR, EN.
demo_agentOBRIG.0 | 10 = dinheiro real, 1 = demo. Precisa estar presente mesmo valendo 0.
user_idOBRIG.string|intSeu ID de jogador. Rejeitado se vazio ou 0.
force_demoopcionalboolForça o caminho demo, mesmo com demo_agent=0.
rtpopcionalintRTP alvo, quando o distribuidor suporta. Ignorado caso não.
⚠
demo_agent é obrigatório na validação. Enviar demo_agent=0 é correto e significa dinheiro real; omitir o parâmetro devolve erro de validação.

Resposta

200 · Sucesso
{
  "game_url": "https://provider.example/launch?token=eyJhbGciOi...&lang=BR"
}
400 · Erros
// falta um parâmetro obrigatório (formato padrão do validador Laravel)
{ "game_id": ["The game id field is required."] }

// user_id vazio ou igual a 0
{ "error": "Invalid user ID" }

// agent_code não existe
{ "error": "Agent not found" }

// agent_token não corresponde ao agent_code
{ "error": "Invalid agent token" }

// jogo inexistente, inativo, ou nenhum distribuidor conseguiu abrir
{ "status": false, "error": "INVALID_GAME" }
⚠

Formatos de erro não são uniformes neste endpoint. Você pode receber {"error": "..."}, {"status": false, "error": "CODE"}, ou um mapa de erros de validação — todos com HTTP 400.

Trate defensivamente: qualquer resposta sem game_url é falha. Não faça parsing por formato específico.

Resolução de distribuidor

Internamente o launch percorre esta lógica. Você não controla nada disso, mas conhecer explica comportamentos:

  1. Busca o jogo por game_code com status = 1 (ativo).
  2. Se demo_agent ou force_demo: exige que o jogo tenha origem própria (is_source) e abre pelo distribuidor de clone.
  3. Caso contrário, lista os distribuidores ativos do jogo ordenados por priority decrescente.
  4. Se o agente tem is_cloned = false, ou se o provedor é Pragmatic Play, o distribuidor clone é removido da lista.
  5. Tenta cada distribuidor em ordem — clone, timeless, playgaming, sourcepark — e retorna a primeira URL obtida.
  6. Se nenhum responder: INVALID_GAME.
ℹ
INVALID_GAME significa "não consegui abrir agora", não necessariamente "esse jogo não existe". Pode ser indisponibilidade momentânea de um distribuidor. Não remova o jogo do seu catálogo por causa de um único erro — use games-status como fonte de verdade.

Listar jogos

GET /api/v1/games/all Você → Sloteflix

Catálogo completo de jogos ativos, com todos os metadados. É o endpoint de sincronização.

⚠
Retorna o catálogo inteiro, sem paginação — a resposta pode passar de vários MB. Chame em job de background, nunca no caminho de uma requisição de usuário. Uma sincronização diária (ou a cada poucas horas) é suficiente.
200 · Sucesso
{
  "status": true,
  "games": [
    {
      "id": 1841,
      "provider_id": 12,
      "game_server_url": null,
      "game_id": "1301",
      "game_name": "Gates of Olympus",
      "game_code": "vs20olympgate",
      "game_type": "slot",
      "description": null,
      "cover": "https://cdn.example/games/vs20olympgate.png",
      "status": "1",
      "technology": "html5",
      "has_lobby": 0,
      "is_mobile": 1,
      "has_freespins": 1,
      "has_tables": 0,
      "only_demo": 0,
      "distribution": "slots",
      "created_at": "2024-03-02T18:11:04.000000Z",
      "updated_at": "2025-06-18T09:42:57.000000Z"
    }
  ]
}

Esquema do jogo

CampoTipoDescrição
idintID interno da Sloteflix. Não use no launch.
provider_idintReferencia provedores.
game_idstringID do jogo no provedor original.
game_namestringNome de exibição.
game_codestringÚnico. É o que você passa como game_id no launch.
game_typestring|nullEx.: slot, live, crash.
coverstringURL da arte. Espelhe no seu CDN se for servir em escala.
statusstring"1" = ativo. Este endpoint já filtra por ativos.
technologystring|nullEx.: html5.
has_lobby0 | 1O jogo tem lobby próprio.
is_mobile0 | 1Compatível com mobile.
has_freespins0 | 1Aceita a API de free spins.
has_tables0 | 1Jogo de mesa.
only_demo0 | 1Só permite modo demo.
distributionstringCategoria de distribuição — útil para agrupar na sua vitrine.
ℹ
Faça upsert pela chave game_code, nunca por id. E não apague jogos que saíram da lista: marque como inativos, para não perder histórico de transações.

Status dos jogos

GET /api/v1/games/games-status Você → Sloteflix

Versão leve: só os game_code ativos. Perfeita para reconciliação frequente — compare com o que está ativo na sua base e ajuste a vitrine.

200 · Sucesso
{
  "status": true,
  "games": ["vs20olympgate", "vs20fruitsw", "spribe_aviator", "pgs_fortunetiger"]
}

Filtrar catálogo

POST /api/v1/games/to-add Você → Sloteflix

Recebe uma lista de game_code e devolve os registros completos apenas dos que estão ativos. Use para hidratar códigos novos sem baixar o catálogo inteiro.

Corpo

CampoTipoDescrição
gamesOBRIG.string[]Lista de game_code.
Requisição
curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/games/to-add" \
  -H "Content-Type: application/json" \
  -d '{"games":["vs20olympgate","vs20fruitsw"]}'
{
  "status": true,
  "games": [
    { "id": 1841, "game_code": "vs20olympgate", "game_name": "Gates of Olympus", "...": "..." }
  ]
}
⚠
O campo games é lido diretamente do corpo, sem validação. Enviar um corpo sem esse campo, ou com um valor que não seja array, gera erro 500. Sempre envie um array — mesmo vazio.

Listar provedores

GET /api/v1/games/provider Você → Sloteflix

Todos os provedores ativos. Requer JWT — pegue em authentication.

Headers

HeaderValor
AuthorizationOBRIG.Bearer <access_token>
Requisição
curl -s "https://{SLOTEFLIX_HOST}/api/v1/games/provider" \
  -H "Authorization: Bearer $JWT"
$response = Http::withToken($jwt)
    ->get(config('sloteflix.base_url') . '/games/provider');

$providers = $response->json('providers');
200 · Sucesso
{
  "status": true,
  "providers": [
    { "id": 12, "name": "Pragmatic Play", "code": "pragmatic-play", "status": 1 }
  ]
}

Jogos clone

"Clone" é uma trilha de distribuição alternativa para parte do catálogo. Quando o agente tem is_cloned = true, o launch prioriza essa trilha (exceto para Pragmatic Play, sempre excluída). Estes endpoints deixam você inspecionar e alternar o comportamento.

Listar jogos clone

GET /api/v1/games/list-clone Você → Sloteflix

Retorna os jogos disponíveis pela trilha de clone. O formato acompanha a resposta do distribuidor de clone.

Consultar flag de clone

POST /api/v1/games/get-clone-status Você → Sloteflix

Headers

HeaderValor
AuthorizationOBRIG.Bearer base64(agent_token + ":" + agent_secret_key)
Requisição & resposta
TOKEN=$(printf '%s:%s' "$SLOTEFLIX_AGENT_TOKEN" "$SLOTEFLIX_AGENT_SECRET" | base64)

curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/games/get-clone-status" \
  -H "Authorization: Bearer $TOKEN"
// 200
{ "status": true, "is_cloned": 1 }

// 400 — Bearer ausente
{ "status": false, "error": "INVALID_TOKEN" }

// 400 — Base64 decodificado não tem o formato "token:secret"
{ "status": false, "error": "INVALID_TOKEN2" }

// 400 — par não corresponde a nenhum agente
{ "status": false, "error": "INVALID_AGENT" }

Alternar flag de clone

POST /api/v1/games/change-clone-status Você → Sloteflix

Mesma autenticação do anterior. Inverte o valor atual e retorna o novo — não aceita um valor alvo.

200 · Sucesso
{ "status": true, "is_cloned": false }
⚠
Endpoint de toggle, não de set. Um retry cego reverte a mudança. Sempre confira o valor com get-clone-status antes e depois. Para definir um valor explícito, use agents/toggle-status, que aceita o campo status.

Free spins

Concede e cancela rodadas grátis. Disponível para jogos com has_freespins = 1, servidos pelo distribuidor que suporta o recurso.

Criar free spins

POST /api/v1/games/freespin Você → Sloteflix
CampoTipoDescrição
agent_codeOBRIG.stringSeu código de agente.
agent_tokenOBRIG.stringSeu token.
user_idOBRIG.string|intSeu ID de jogador.
game_idOBRIG.stringO game_code do catálogo.
betlevelOBRIG.intNível de aposta de cada rodada grátis.
freespincountOBRIG.intQuantidade de rodadas.
expiretimeOBRIG.stringMomento de expiração da concessão.
freespinIDOBRIG.stringSeu identificador. Guarde — é o que permite cancelar depois.
rtpopcionalintRTP alvo.
Requisição & resposta
curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/games/freespin" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_code":    "meu_agente",
    "agent_token":   "'"$SLOTEFLIX_AGENT_TOKEN"'",
    "user_id":       "1042",
    "game_id":       "vs20olympgate",
    "betlevel":      1,
    "freespincount": 20,
    "expiretime":    "2026-08-30 23:59:59",
    "freespinID":    "promo-2026-08-1042"
  }'
// 200 — o conteúdo de "status" vem do distribuidor
{ "status": true }

// 400
{ "error": "Agent not found" }
{ "error": "Invalid agent token" }
{ "error": "Game not found or inactive" }
{ "error": "No active distributor found for this game" }

Cancelar free spins

POST /api/v1/games/cancelfreespin Você → Sloteflix

Cancela uma concessão. Exige agent_code, agent_token, user_id, game_id e o mesmo freespinID usado na criação. rtp é opcional.

ℹ
Os prêmios de rodadas grátis chegam como transaction com type: "win", exatamente como um prêmio normal. Não existe callback específico de free spin — se você quiser separar bônus de dinheiro real na contabilidade, correlacione pelo round_id das rodadas concedidas.

Seamless Wallet — visão geral

Esta é a parte que você implementa. Um único endpoint recebe todos os eventos financeiros, roteados pelo campo method do corpo JSON.

POST {seu_endpoint}/api/sloteflix_api Sloteflix → Você
methodQuando disparaVocê precisaObrigatório
account_details Abertura da sessão de jogo Devolver dados + saldo do jogador sim
user_balance Jogo consulta saldo Devolver o saldo atual sim
transaction · type:"bet" Jogador aposta Debitar e devolver o novo saldo sim
transaction · type:"win" Rodada premiada Creditar e devolver o novo saldo sim
refund Rodada cancelada / descartada Estornar a aposta sim
rollback Reversão de crédito já pago Retirar o valor creditado recomendado

Contrato base

Campos presentes em todo callback

CampoTipoDescrição
methodstringRoteia a operação. Sempre presente.
agent_codestringSeu código de agente.
agent_tokenstringSeu token — valide.
agent_secret_keystringSua secret — valide.
🔒

A autenticação do seamless é o trio agent_code + agent_token + agent_secret_key no corpo. Não há assinatura HMAC nem header de autenticação nesses callbacks.

Portanto, na sua ponta: (1) compare os três valores em tempo constante contra o que você tem guardado, (2) rejeite se qualquer um divergir, (3) exija HTTPS, e (4) restrinja por IP de origem junto ao time de integração. Sem essa validação, qualquer um que descubra a URL debita da sua carteira.

Formato da resposta

Responda sempre HTTP 200 com Content-Type: application/json:

Contrato de resposta
// Sucesso
{
  "status": 1,
  "balance": 1234.56
}

// Falha — use o código apropriado da tabela de status
{
  "status": 0,
  "error": "Insufficient funds"
}
CampoTipoDescrição
statusOBRIG.int1 = sucesso. Qualquer outro valor é erro — veja a tabela.
balanceOBRIG.numberSaldo do jogador depois da operação, 2 casas decimais.
erroropcionalstringMensagem legível. Ajuda no diagnóstico, não altera comportamento.
⚠

Sempre inclua balance, mesmo em erro. Vários caminhos do agregador testam a presença desse campo antes de olhar o status. Uma resposta sem balance pode ser tratada como falha de infraestrutura em vez do erro específico que você quis sinalizar.

balance deve ser numérico — 1234.56, não "1234.56".

⚠
Retornar HTML (página de erro do framework, redirect de login, tela de manutenção) faz o parser falhar. Após 3 respostas não-JSON, a Sloteflix assume status: 15 (Timeout) e rejeita a rodada. Garanta que /api/sloteflix_api esteja fora de qualquer middleware de autenticação de sessão, CSRF ou maintenance mode.

method: account_details

Primeiro callback do ciclo de vida, disparado quando o jogo autentica a sessão. Se falhar, o jogo não abre.

Sloteflix → Você
{
  "method": "account_details",
  "game": "vs20olympgate",
  "agent_code": "meu_agente",
  "agent_token": "3f8a…",
  "agent_secret_key": "9b1c…",
  "user_id": "1042"
}
Você → Sloteflix
{
  "status": 1,
  "message": "SUCCESS",
  "balance": 1500.00,
  "user_id": "1042",
  "email": "jogador@example.com",
  "date": "2024-11-02 14:31:07"
}
Campo da respostaDescrição
statusOBRIG.1 em sucesso.
balanceOBRIG.Saldo atual. Sem ele o launch é abortado.
user_idrecomendadoEcoe o valor recebido. Alguns distribuidores exigem para amarrar a sessão.
email, date, messageopcionalMetadados de conveniência.
⚠
Este é o ponto certo para aplicar regras de bloqueio: jogador autoexcluído, limite de jogo responsável atingido, conta suspensa. Devolva status: 11 (Player is blocked) ou status: 10 (Limits reached) — a sessão não é criada e o jogador nunca chega a apostar.

method: user_balance

O callback mais frequente da integração — o jogo consulta saldo a cada atualização de tela. Otimize este caminho acima de todos os outros.

Sloteflix → Você
{
  "method": "user_balance",
  "game": "vs20olympgate",
  "agent_code": "meu_agente",
  "agent_token": "3f8a…",
  "agent_secret_key": "9b1c…",
  "user_id": "1042"
}
Você → Sloteflix
{ "status": 1, "balance": 1498.50 }
ℹ
Uma resposta sem balance aqui é convertida em OP_31 · Insufficient funds para o provedor — o jogador vê "saldo insuficiente" mesmo tendo dinheiro. Se a leitura falhar de verdade, prefira devolver status: 5 (Internal error) com um balance.
⚠

Existe uma variante method: "balance" no código do agregador com payload praticamente idêntico (sem o campo game, com distributor). Ela está dormente — nenhum caminho de produção a dispara hoje.

Ainda assim, trate "balance" como sinônimo de "user_balance" no seu roteador. Custa uma linha e evita um incidente se a variante for ativada.

method: transaction — aposta

Debita a aposta. É a operação mais crítica: se você responder erro, a rodada é rejeitada e o jogador recebe uma mensagem do provedor.

Sloteflix → Você
{
  "method": "transaction",
  "type": "bet",
  "session_id": "eyJpdiI6IkQr…",
  "game": "vs20olympgate",
  "agent_code": "meu_agente",
  "agent_token": "3f8a…",
  "agent_secret_key": "9b1c…",
  "round_id": "timeless-88213004",
  "transaction_id": "timeless-99551277",
  "bet": 1.50,
  "win": 0,
  "user_id": "1042",
  "distributor": "timeless"
}
CampoTipoDescrição
type"bet"Discrimina a operação dentro de transaction.
transaction_idstringChave de idempotência. Único globalmente.
round_idstringAgrupa aposta e prêmio da mesma rodada.
betnumberValor a debitar, positivo, 2 casas.
winnumberSempre 0 em aposta.
session_idstringSessão de jogo. Útil para auditoria.
distributorstringOrigem da transação.
Você → Sloteflix
// Debitado com sucesso
{ "status": 1, "balance": 1497.00 }

// Saldo insuficiente — a rodada é rejeitada
{ "status": 0, "balance": 0.80, "error": "Insufficient funds" }

// Já processada — devolva sucesso com o saldo atual (idempotência)
{ "status": 1, "balance": 1497.00 }
⚠

Debite atomicamente. A verificação de saldo e a escrita precisam estar na mesma transação de banco, com SELECT … FOR UPDATE na carteira (ou UPDATE … WHERE balance >= :bet checando as linhas afetadas). Um jogador com duas abas abertas gera apostas concorrentes.

Grave o transaction_id com índice único na mesma transação. Isso resolve idempotência e corrida de uma vez.

method: transaction — prêmio

Credita o prêmio. Aparece também no estorno de rodada, quando o distribuidor devolve valor como crédito.

Sloteflix → Você
{
  "method": "transaction",
  "type": "win",
  "session_id": "eyJpdiI6IkQr…",
  "game": "vs20olympgate",
  "agent_code": "meu_agente",
  "agent_token": "3f8a…",
  "agent_secret_key": "9b1c…",
  "transaction_id": "timeless-99551301",
  "transaction_id_old": "timeless-88213004",
  "round_id": "timeless-88213004",
  "bet": 0,
  "win": 45.00,
  "user_id": "1042",
  "distributor": "timeless"
}
CampoTipoDescrição
type"win"Operação de crédito.
transaction_idstringChave de idempotência do crédito. Diferente do transaction_id da aposta.
transaction_id_oldstringAponta para a rodada de origem. Presente em créditos, ausente em apostas.
winnumberValor a creditar, 2 casas.
betnumberSempre 0 em prêmio.
Você → Sloteflix
{ "status": 1, "balance": 1542.00 }
⚠

Nunca rejeite um prêmio por falta de saldo. Crédito só aumenta saldo. Se você responder erro aqui, o jogador ganhou e não recebeu — e a reconciliação vira trabalho manual.

Numa falha real de infraestrutura, responda status: 5 e alerte on-call. As 3 tentativas do agregador dão margem para uma indisponibilidade curta.

ℹ
Rodadas sem prêmio não geram callback de win. Não espere um crédito de valor zero para fechar a rodada — use round_id para correlacionar.

method: refund

Estorna uma aposta: rodada cancelada pelo provedor, aposta descartada, ou falha depois do débito. Devolva o valor da aposta ao jogador.

Sloteflix → Você
{
  "method": "refund",
  "agent_code": "meu_agente",
  "agent_token": "3f8a…",
  "agent_secret_key": "9b1c…",
  "transaction_id": "timeless-99551277",
  "user_id": "1042",
  "distributor": "timeless",
  "balance": true
}
CampoTipoDescrição
transaction_idstringAponta para a transação original que você deve estornar. Não é um ID novo.
user_idstring|intJogador. Ausente em alguns distribuidores — resolva pelo transaction_id quando faltar.
balanceboolQuando true, o agregador vai ler balance da sua resposta. Ausente em alguns distribuidores.
distributorstringOrigem.
⚠
O payload de refund varia por distribuidor. Alguns enviam apenas agent_code, agent_token, agent_secret_key, transaction_id e distributor. Sua implementação precisa achar o jogador a partir do transaction_id e não depender de user_id.
Você → Sloteflix
// Estornado
{ "status": 1, "balance": 1498.50 }

// Já estornado — devolva sucesso, não erro (idempotência)
{ "status": 1, "balance": 1498.50 }

// transaction_id desconhecido
{ "status": 14, "balance": 1498.50, "error": "Transaction not found" }
ℹ
Alguns caminhos aceitam status: 1 ou status: 0 como "estorno reconhecido", mas outros exigem estritamente status: 1. Sempre devolva 1 quando o estorno estiver efetivado — inclusive quando já estava efetivado antes.

method: rollback

O inverso do refund: retira do jogador um valor que já havia sido creditado. Usado quando o provedor reverte um prêmio pago.

Sloteflix → Você
{
  "method": "rollback",
  "agent_code": "meu_agente",
  "agent_token": "3f8a…",
  "agent_secret_key": "9b1c…",
  "user_id": "1042",
  "transaction_id": "betby-77120043",
  "amount": 45.00,
  "distributor": "betby"
}
CampoTipoDescrição
amountnumberValor a retirar, positivo. Note: amount, não bet/win.
transaction_idstringIdentifica a reversão. Use como chave de idempotência.
user_idstring|intJogador de quem retirar.
Você → Sloteflix
{ "status": 1, "balance": 1497.00 }
⚠

O agregador exige estritamente status: 1 aqui — qualquer outro valor é tratado como erro de rollback.

Decida a política de saldo negativo antes do go-live. Se o jogador já sacou o prêmio revertido, retirar leva a saldo negativo. As opções são permitir negativo, registrar como débito pendente, ou zerar e abrir um caso — mas responda status: 1 em todas elas, senão o agregador reprocessa.

Códigos de status do seamless

O status que você devolve é traduzido para o código de erro que o provedor mostra ao jogador. Escolher o código certo é o que faz a mensagem na tela ter sentido.

Você enviaViraSignificadoQuando usar
1 sucesso—Operação concluídaSempre que der certo, inclusive em repetição idempotente.
0OP_31Insufficient fundsSaldo menor que a aposta. Nunca em prêmio.
2OP_20Invalid hashAssinatura ou formato inválido.
4OP_34Player not founduser_id não existe na sua base.
5OP_50Internal errorFalha do seu lado. Prefira este a devolver JSON inválido.
6OP_50Unknown errorErro não classificado.
7OP_21Invalid tokenTrio de credenciais não confere.
8OP_22Authorization failedLaunch não pode ser autorizado.
9OP_30Invalid currencyMoeda da transação ≠ moeda do jogador.
10OP_32Limits reachedLimite de jogo responsável atingido.
11OP_33Player is blockedConta suspensa ou autoexcluída.
12OP_35Game is disabledJogo desabilitado na sua operação.
13OP_40Duplicated transaction IDUse com cuidado — veja o aviso abaixo.
14OP_41Transaction not foundRefund/rollback de transaction_id desconhecido.
15OP_47TimeoutReservado — é o que a Sloteflix assume quando você não responde.
16OP_49Operation failedOperação recusada por regra de negócio.
17OP_50Internal errorErro interno não coberto acima.
⚠

Não use status: 13 para transação repetida. É contraintuitivo, mas é o comportamento correto: quando o mesmo transaction_id chega de novo, a operação já está efetivada — devolva status: 1 com o saldo atual.

Responder 13 faz o provedor tratar como falha e a rodada pode ser cancelada mesmo já tendo sido debitada. Reserve 13 para colisão real de ID entre transações semanticamente diferentes.

ℹ
Qualquer status fora desta tabela cai no padrão OP_50 · Internal error. Nunca invente códigos.

Idempotência & reconciliação

Redes falham, timeouts acontecem, e a Sloteflix tenta 3 vezes com o mesmo transaction_id. Idempotência não é otimização aqui — é requisito.

A chave é o transaction_id

Ele já vem prefixado pelo distribuidor (timeless-, betby-, …), então é único no escopo global. Guarde exatamente como recebeu — não remova o prefixo.

Esquema mínimo
CREATE TABLE sloteflix_transactions (
  id              BIGSERIAL PRIMARY KEY,
  transaction_id  VARCHAR(191) NOT NULL,
  round_id        VARCHAR(191),
  player_id       BIGINT       NOT NULL,
  method          VARCHAR(32)  NOT NULL,   -- transaction | refund | rollback
  type            VARCHAR(16),             -- bet | win
  amount          DECIMAL(20,2) NOT NULL,
  balance_after   DECIMAL(20,2) NOT NULL,
  distributor     VARCHAR(32),
  game            VARCHAR(191),
  session_id      VARCHAR(191),
  refunded_at     TIMESTAMP NULL,
  created_at      TIMESTAMP NOT NULL DEFAULT now(),

  CONSTRAINT uq_sloteflix_tx UNIQUE (transaction_id)
);

CREATE INDEX idx_sloteflix_player_round ON sloteflix_transactions (player_id, round_id);
public function bet(array $payload): array
{
    return DB::transaction(function () use ($payload) {
        // 1) idempotência: já vi esse transaction_id?
        $existing = SloteflixTransaction::where('transaction_id', $payload['transaction_id'])->first();
        if ($existing) {
            return ['status' => 1, 'balance' => (float) $existing->balance_after];
        }

        // 2) trava a carteira — jogador com duas abas gera apostas concorrentes
        $wallet = Wallet::where('user_id', $payload['user_id'])->lockForUpdate()->first();
        if (! $wallet) {
            return ['status' => 4, 'balance' => 0, 'error' => 'Player not found'];
        }

        $bet = round((float) $payload['bet'], 2);
        if (bccomp((string) $wallet->balance, (string) $bet, 2) < 0) {
            return ['status' => 0, 'balance' => (float) $wallet->balance, 'error' => 'Insufficient funds'];
        }

        // 3) debita e registra na MESMA transação
        $wallet->decrement('balance', $bet);
        $wallet->refresh();

        SloteflixTransaction::create([
            'transaction_id' => $payload['transaction_id'],
            'round_id'       => $payload['round_id']    ?? null,
            'player_id'      => $payload['user_id'],
            'method'         => 'transaction',
            'type'           => 'bet',
            'amount'         => $bet,
            'balance_after'  => $wallet->balance,
            'distributor'    => $payload['distributor'] ?? null,
            'game'           => $payload['game']        ?? null,
            'session_id'     => $payload['session_id']  ?? null,
        ]);

        return ['status' => 1, 'balance' => (float) $wallet->balance];
    }, attempts: 3);
}
ℹ
Se duas tentativas chegarem em paralelo, a checagem inicial não basta: as duas passam pelo first() antes de qualquer INSERT. É o índice único que garante a integridade — capture a violação de unicidade, releia a transação existente e devolva status: 1 com o saldo dela.

Reconciliação

Use POST agents/consult para comparar seus totais com os da Sloteflix por provedor e período. Rode diariamente.

⚠
Logue todo callback recebido — payload cru, resposta enviada, latência e o header X-Attempt — com retenção de pelo menos 90 dias. Sem esse log, disputa financeira com provedor não se resolve.

Servidor de exemplo

Esqueleto completo do roteador seamless. Adapte a persistência à sua carteira.

Roteador seamless
// routes/api.php
// IMPORTANTE: fora dos middlewares de sessão/CSRF, e fora do maintenance mode.
Route::post('/sloteflix_api', [SloteflixWalletController::class, 'handle'])
    ->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);


class SloteflixWalletController extends Controller
{
    public function handle(Request $request)
    {
        $payload = $request->all();

        // 1) autentica o trio de credenciais em tempo constante
        if (! $this->validCredentials($payload)) {
            return response()->json(['status' => 7, 'balance' => 0, 'error' => 'Invalid token']);
        }

        // 2) roteia por method (+ type quando for transaction)
        $key = $payload['method'] ?? '';
        if ($key === 'transaction') {
            $key .= ':' . ($payload['type'] ?? '');
        }

        try {
            return response()->json(match ($key) {
                'account_details'     => $this->wallet->accountDetails($payload),
                'user_balance',
                'balance'             => $this->wallet->balance($payload),
                'transaction:bet'     => $this->wallet->bet($payload),
                'transaction:win'     => $this->wallet->win($payload),
                'refund'              => $this->wallet->refund($payload),
                'rollback'            => $this->wallet->rollback($payload),
                default               => ['status' => 6, 'balance' => 0, 'error' => "Unknown method: {$key}"],
            });
        } catch (\Throwable $e) {
            // NUNCA deixe uma exceção virar HTML — sempre JSON com status numérico
            report($e);
            return response()->json(['status' => 5, 'balance' => 0, 'error' => 'Internal error']);
        }
    }

    private function validCredentials(array $p): bool
    {
        $agent = config('sloteflix.agents.' . ($p['agent_code'] ?? ''));
        if (! $agent) {
            return false;
        }

        return hash_equals($agent['token'],  (string) ($p['agent_token']      ?? ''))
            && hash_equals($agent['secret'], (string) ($p['agent_secret_key'] ?? ''));
    }
}
import express from 'express';
import crypto from 'node:crypto';

const app = express();
app.use(express.json({ limit: '256kb' }));

const AGENTS = {
  [process.env.SLOTEFLIX_AGENT_CODE]: {
    token:  process.env.SLOTEFLIX_AGENT_TOKEN,
    secret: process.env.SLOTEFLIX_AGENT_SECRET,
  },
};

const safeEqual = (a = '', b = '') => {
  const ba = Buffer.from(String(a));
  const bb = Buffer.from(String(b));
  return ba.length === bb.length && crypto.timingSafeEqual(ba, bb);
};

app.post('/api/sloteflix_api', async (req, res) => {
  const p = req.body ?? {};
  const agent = AGENTS[p.agent_code];

  if (!agent || !safeEqual(agent.token, p.agent_token) || !safeEqual(agent.secret, p.agent_secret_key)) {
    return res.json({ status: 7, balance: 0, error: 'Invalid token' });
  }

  const key = p.method === 'transaction' ? `transaction:${p.type}` : p.method;

  try {
    switch (key) {
      case 'account_details':  return res.json(await wallet.accountDetails(p));
      case 'user_balance':
      case 'balance':          return res.json(await wallet.balance(p));
      case 'transaction:bet':  return res.json(await wallet.bet(p));
      case 'transaction:win':  return res.json(await wallet.win(p));
      case 'refund':           return res.json(await wallet.refund(p));
      case 'rollback':         return res.json(await wallet.rollback(p));
      default:
        return res.json({ status: 6, balance: 0, error: `Unknown method: ${key}` });
    }
  } catch (err) {
    req.log?.error({ err, payload: p }, 'seamless handler failed');
    return res.json({ status: 5, balance: 0, error: 'Internal error' });
  }
});

// Handler de erro final: garante JSON mesmo em body malformado
app.use((err, _req, res, _next) => res.json({ status: 5, balance: 0, error: 'Internal error' }));
import hmac, os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

AGENTS = {
    os.environ["SLOTEFLIX_AGENT_CODE"]: {
        "token":  os.environ["SLOTEFLIX_AGENT_TOKEN"],
        "secret": os.environ["SLOTEFLIX_AGENT_SECRET"],
    }
}

def valid(p: dict) -> bool:
    agent = AGENTS.get(p.get("agent_code", ""))
    if not agent:
        return False
    return (hmac.compare_digest(agent["token"],  str(p.get("agent_token", "")))
            and hmac.compare_digest(agent["secret"], str(p.get("agent_secret_key", ""))))

@app.post("/api/sloteflix_api")
async def sloteflix_api(request: Request):
    p = await request.json()

    if not valid(p):
        return JSONResponse({"status": 7, "balance": 0, "error": "Invalid token"})

    key = f"transaction:{p.get('type')}" if p.get("method") == "transaction" else p.get("method")

    handlers = {
        "account_details":  wallet.account_details,
        "user_balance":     wallet.balance,
        "balance":          wallet.balance,
        "transaction:bet":  wallet.bet,
        "transaction:win":  wallet.win,
        "refund":           wallet.refund,
        "rollback":         wallet.rollback,
    }

    handler = handlers.get(key)
    if handler is None:
        return JSONResponse({"status": 6, "balance": 0, "error": f"Unknown method: {key}"})

    try:
        return JSONResponse(await handler(p))
    except Exception:
        logger.exception("seamless handler failed", extra={"payload": p})
        return JSONResponse({"status": 5, "balance": 0, "error": "Internal error"})
✓

Note o padrão: nas três linguagens, toda saída é JSON com status numérico e balance. Nenhum caminho — nem o de exceção — produz HTML ou status HTTP diferente de 200. É isso que mantém a integração previsível.

Testando localmente

Reproduza um callback contra o seu handler e cheque a latência:

Simular callback
curl -s -o /dev/null -w 'HTTP %{http_code} · %{time_total}s\n' \
  -X POST "https://minhaoperacao.com/api/sloteflix_api" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "X-Attempt: 1" \
  -d '{
    "method":           "transaction",
    "type":             "bet",
    "session_id":       "test-session",
    "game":             "vs20olympgate",
    "agent_code":       "meu_agente",
    "agent_token":      "'"$SLOTEFLIX_AGENT_TOKEN"'",
    "agent_secret_key": "'"$SLOTEFLIX_AGENT_SECRET"'",
    "round_id":         "test-round-1",
    "transaction_id":   "test-tx-1",
    "bet":              1.50,
    "win":              0,
    "user_id":          "1042",
    "distributor":      "timeless"
  }'

# time_total tem que ficar bem abaixo de 0.600s.
# Rode de novo com o MESMO transaction_id: o saldo não pode mudar (idempotência).

Agents API

Endpoints administrativos para provisionar e gerenciar agentes. Úteis para quem revende a integração ou opera múltiplas marcas.

🔒

Quatro destes cinco endpoints são protegidos por um token administrativo mestre, enviado no campo token do corpo. Ele é referenciado aqui como {SLOTEFLIX_MASTER_TOKEN} e vale para toda a plataforma — não é por agente.

Se você é um operador integrando a Sloteflix, você não recebe esse token. Use apenas get-agent-data, que autentica com as suas próprias credenciais.

Criar agente

POST /api/v1/agents/create Admin → Sloteflix
CampoTipoDescrição
tokenOBRIG.string{SLOTEFLIX_MASTER_TOKEN}.
emailOBRIG.emailE-mail de login do usuário do agente.
passwordOBRIG.stringMínimo 6 caracteres.
subdomainOBRIG.stringVira o agent_code, o nome do usuário e a base do endpoint.
200 · Criado
{
  "id": 87,
  "message": "Agente criado com sucesso",
  "data": {
    "agent_sponsor_id": 1,
    "agent_id": 412,
    "is_cloned": 1,
    "agent_code": "minhaoperacao",
    "agent_token": "Xk9pQ2mL7vRt4nZa8YbW3cJhF6sDgE1u",
    "agent_secret_key": "Tq5wH8jN2bV6xM1oPzL4rK7yA3cS9dFg",
    "agent_type": "default",
    "agent_status": 1,
    "api_type": "seamless",
    "ggr": 20,
    "ggr_cloned": 20,
    "balance": 200000000,
    "endpoint": "https://minhaoperacao.lumosgaming.com",
    "id": 87
  }
}

Valores atribuídos na criação

CampoValorSignificado
agent_tokenaleatório, 32 charsCredencial de jogo.
agent_secret_keyaleatório, 32 charsCredencial de autenticação JWT.
api_typeseamlessÚnico modo suportado.
agent_status1Ativo.
is_cloned1Trilha de clone habilitada por padrão.
ggr / ggr_cloned20Percentual de GGR do acordo comercial.
balance200000000Crédito interno do agente junto ao agregador — não saldo de jogador.
endpointhttps://{subdomain}.lumosgaming.comPlaceholder. Troque pela URL real antes do go-live.
⚠

O endpoint gerado na criação é um placeholder derivado do subdomain. Enquanto ele não apontar para a sua plataforma real, todos os callbacks seamless vão para o lugar errado e nenhuma aposta é liquidada.

Ajustar o endpoint é o primeiro passo depois da criação. Veja Painel de configuração.

Outras respostas
// 200 — e-mail já tem agente: devolve o existente, não cria outro
{ "message": "Agente já existente", "data": { "...": "..." } }

// 400 — e-mail existe como usuário, mas sem agente vinculado
{ "message": "Usuário existente, mas sem agente associado." }

// 400 — erro de validação
{ "email": ["The email field must be a valid email address."] }

// 403 — token mestre inválido
{ "message": "Acesso não autorizado. Token inválido." }

Relatório GGR

POST /api/v1/agents/consult Admin → Sloteflix

Totais de aposta e prêmio por provedor num intervalo de datas. É a base da reconciliação.

CampoTipoDescrição
tokenOBRIG.string{SLOTEFLIX_MASTER_TOKEN}.
agent_codeOBRIG.stringAgente a consultar.
start_dateOBRIG.Y-m-dInício, inclusivo.
end_dateOBRIG.Y-m-dFim, inclusivo.
Requisição & resposta
curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/agents/consult" \
  -H "Content-Type: application/json" \
  -d '{
    "token":      "'"$SLOTEFLIX_MASTER_TOKEN"'",
    "agent_code": "minhaoperacao",
    "start_date": "2026-07-01",
    "end_date":   "2026-07-31"
  }'
// 200
{
  "data": [
    { "provider": "pragmatic-play", "total_bet": "184320.50", "total_win": "171004.25" },
    { "provider": "pgsoft",         "total_bet": "92110.00",  "total_win": "88740.10"  }
  ]
}

// 404 — agente inexistente
{ "message": "Agente não encontrado." }

// 404 — nenhum dado no período (note: 404, não 200 com lista vazia)
{ "message": "Nenhum dado encontrado para o período especificado." }

// 400 — formato de data inválido
{ "start_date": ["The start date does not match the format Y-m-d."] }

// 403 — token mestre inválido
{ "message": "Acesso não autorizado. Token inválido." }
⚠
Período sem movimento devolve HTTP 404, não 200 com array vazio. Trate 404 como "zero transações", não como erro.
ℹ
A agregação exclui transações marcadas como afiliado (is_affiliate = 0) e agrupa por provedor. GGR por provedor = total_bet - total_win; a sua comissão é esse valor multiplicado pelo ggr do agente.

Dados do agente

POST /api/v1/agents/get-agent-data Você → Sloteflix

O único endpoint de agentes que você pode chamar com as suas próprias credenciais. Serve como health check: confirma que agent_code e agent_token estão válidos.

CampoTipoDescrição
agent_codeOBRIG.stringSeu código de agente.
agent_tokenOBRIG.stringSeu token.
Requisição & resposta
curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/agents/get-agent-data" \
  -H "Content-Type: application/json" \
  -d '{"agent_code":"minhaoperacao","agent_token":"'"$SLOTEFLIX_AGENT_TOKEN"'"}'
// 200
{ "id": 87, "agent_code": "minhaoperacao" }

// 400
{ "error": "Agent not found" }
{ "error": "Invalid agent token" }

Consultar status

POST /api/v1/agents/get-status Admin → Sloteflix

Retorna a flag de jogos clone do agente. Requer token mestre e agent_code.

200 · Sucesso
{
  "message": "Status consultado com sucesso",
  "data": { "id": 87, "agent_code": "minhaoperacao", "status": true }
}
⚠
Apesar do nome genérico, o campo status aqui reflete is_cloned — não o agent_status (ativo/inativo).

Alternar status

POST /api/v1/agents/toggle-status Admin → Sloteflix
CampoTipoDescrição
tokenOBRIG.string{SLOTEFLIX_MASTER_TOKEN}.
agent_codeOBRIG.stringAgente alvo.
statusopcionalboolDefine o valor. Se omitido, inverte o atual.
200 · Sucesso
{
  "message": "Status ativado com sucesso para o agente minhaoperacao",
  "data": { "id": 87, "agent_code": "minhaoperacao", "status": true }
}
✓
Sempre envie status explicitamente em automação. Omitir transforma a chamada num toggle, e um retry desfaz a mudança. Este é o endpoint preferível a change-clone-status, que só faz toggle.

Painel de configuração

As credenciais e o endpoint do seamless são gerenciados no painel administrativo da Sloteflix. Campos disponíveis no cadastro de agente:

CampoRótulo no painelNotas
agent_codeAgente CódigoIdentificador da operação.
agent_tokenAgente TokenSomente leitura — gerado automaticamente.
agent_secret_keyAgente SecretSomente leitura — gerado automaticamente.
endpointSite EndPointÉ aqui que você configura o destino do seamless. URL sem barra final.
api_typeTipo de APIseamless.
agent_typeTipo de Agentereseller permite criar subcontas.
ggrGGRPercentual do acordo comercial.
ggr_clonedGGR Jogos ClonesPercentual da trilha de clone.
balanceSaldoCrédito do agente junto ao agregador.
agent_statusStatusAtiva/desativa o agente.
is_clonedJogos ClonesHabilita a trilha de clone no launch.
currencyMoedaPadrão BRL.
⚠

Agentes são cacheados indefinidamente. O registro fica em cache sob as chaves agent_{id} e agent_{agent_code}_{agent_token}, gravadas com forever — sem TTL.

Consequência prática: mudar o endpoint direto no banco não tem efeito até o cache ser invalidado. Faça a alteração pelo painel ou por um fluxo que limpe as duas chaves. Depois de alterar, confirme com uma aposta de teste que o callback chegou na URL nova.

Códigos de erro

API Sloteflix

Erros de código fixo usam o formato {"status": false, "error": "CODE"} com HTTP 400:

CódigoEndpointsSignificado & correção
INVALID_TOKENauthentication, clone statusHeader Authorization ausente ou vazio. Envie Bearer base64("token:secret").
INVALID_TOKEN2clone statusBase64 decodificou, mas não tem o formato token:secret. Verifique se há exatamente um dois-pontos.
INVALID_AGENTauthentication, clone statusO par token/secret não corresponde a nenhum agente. Confira as credenciais.
INVALID_GAMEgame_launchJogo inexistente, inativo, ou nenhum distribuidor conseguiu abrir. Cheque o game_code em games-status.

Erros de mensagem livre usam {"error": "mensagem"}, também com HTTP 400:

MensagemCausa
Agent not foundagent_code não existe.
Invalid agent tokenagent_token não corresponde ao agent_code.
Invalid user IDuser_id vazio ou 0.
Game not found or inactivegame_code inexistente ou com status ≠ 1.
No active distributor found for this gameNenhum distribuidor ativo serve esse jogo.

Erros de validação seguem o formato padrão do Laravel — mapa de campo para lista de mensagens, HTTP 400:

400 · Validação
{
  "game_id": ["The game id field is required."],
  "currency": ["The currency field is required."]
}
⚠
São três formatos de erro diferentes, todos com HTTP 400. Não escreva parser acoplado a um deles. A regra segura: 2xx + payload esperado presente = sucesso; qualquer outra coisa = falha, e logue o corpo cru.

Seamless

Do seu lado, use os códigos de status. Do lado da Sloteflix, status: 15 é sintetizado quando você não responde JSON válido em 3 tentativas:

Falha sintetizada
{
  "status": 15,
  "error": "Falha após 3 tentativas: resposta não é JSON válido"
}

Ver isso nos logs significa uma destas quatro coisas: seu handler passou de 600 ms, devolveu HTML, devolveu corpo vazio, ou a conexão não estabeleceu em 100 ms.

Troubleshooting

Sintomas reais de integração, e o que costuma estar por trás deles.

O jogo não abre

SintomaCausa provávelO que fazer
INVALID_GAME em todos os jogos Você está passando o id numérico em vez do game_code. Use o game_code de games-status.
INVALID_GAME em alguns jogos só Distribuidor indisponível, ou o jogo perdeu o distribuidor ativo. Confirme o código em games-status. Se estiver lá, é indisponibilidade momentânea — não remova o jogo do catálogo.
Erro de validação em demo_agent Você omitiu o parâmetro por ele valer 0. Envie demo_agent=0 explicitamente. É obrigatório.
Invalid user ID user_id vazio, null ou 0. Envie um ID de jogador real, como string.
Launch responde, mas o jogo mostra erro na tela O account_details falhou no seu endpoint. Cheque seus logs de /api/sloteflix_api. É onde o problema está, não no launch.
Jogo abre com saldo zerado account_details respondeu sem balance, ou com balance como string. Devolva balance numérico, sempre.

Apostas falhando

SintomaCausa provávelO que fazer
Nenhum callback chega no seu servidor O endpoint do agente está errado — provavelmente ainda no placeholder {subdomain}.lumosgaming.com. Corrija o endpoint e garanta a invalidação do cache — veja Painel.
Callbacks pararam depois de mudar o endpoint Cache do agente não foi invalidado — as chaves são gravadas com forever. Limpe agent_{id} e agent_{code}_{token}, e confirme com uma aposta de teste.
status: 15 nos logs do agregador Timeout, HTML na resposta, corpo vazio, ou conexão acima de 100 ms. Meça a latência do handler. Confirme que toda saída é JSON, inclusive no caminho de exceção.
Jogador vê "saldo insuficiente" tendo saldo Sua resposta de user_balance veio sem balance — vira OP_31. Inclua balance numérico em toda resposta, inclusive nas de erro.
Débito duplo na conta do jogador Sem idempotência: você processou a 2ª ou 3ª tentativa do mesmo transaction_id. Índice único em transaction_id; repetição devolve status: 1 com o saldo já gravado.
Rodada cancelada apesar do débito ter dado certo Você devolveu status: 13 numa tentativa repetida. Transação repetida = status: 1. Reserve 13 para colisão real de ID.
Saldo do jogador negativo do nada rollback de um prêmio que já havia sido sacado. Comportamento esperado do fluxo. Defina a política antes do go-live — mas continue respondendo status: 1.
Latência boa em teste, ruim em produção Cold start de serverless, ou consulta de saldo sem índice sob carga. Provisioned concurrency ou servidor sempre quente. Indexe a carteira por user_id.
Falhas intermitentes, sempre no X-Attempt: 1 Handshake TLS acima de 100 ms — o retry pega a conexão já quente. Habilite keep-alive e session resumption. Corte proxies extras do caminho.

Divergência de valores

SintomaCausa provávelO que fazer
Valores 100× maiores ou menores Você tratou os valores como centavos. São decimais na moeda do jogador. 1.50 é R$ 1,50 — veja Convenções.
Centavos somem na terceira casa Arredondamento inconsistente. Arredonde para 2 casas com half-up, e use aritmética decimal — não float.
Sua soma de apostas > a nossa Você debita antes de receber o callback. Só debite dentro do handler do seamless.
Sua soma de apostas < a nossa Callbacks perdidos. Busque status: 15 e respostas não-JSON nos logs.
consult devolve 404 Zero transações no período — não é erro. Trate 404 como resultado vazio.

Autenticação

SintomaCausa provávelO que fazer
INVALID_TOKEN na autenticação Header ausente, ou você mandou o par sem Base64. Bearer base64("token:secret") — Base64 simples, não Basic Auth.
INVALID_TOKEN2 O Base64 decodificou, mas não tem exatamente um dois-pontos. Verifique se não há espaço, quebra de linha ou dois-pontos extra na string.
INVALID_AGENT O par token/secret não corresponde a nenhum agente. Valide as credenciais com get-agent-data.
401 em games/provider JWT expirado ou ausente. Renove em authentication. Faça cache do JWT, mas respeite o TTL.
ℹ
Não achou o sintoma aqui? Abra um ticket com os cinco itens de Suporte. Sem o transaction_id e o payload cru, o diagnóstico vira adivinhação.

Checklist de go-live

Configuração

Seamless — corretude

Seamless — robustez

Catálogo

Operação

✓

Teste de aceitação mínimo antes de abrir para jogadores reais:

  1. Abrir um jogo e ver o saldo correto na tela.
  2. Apostar; conferir débito exato e balance retornado.
  3. Ganhar; conferir crédito exato.
  4. Reenviar o mesmo transaction_id de aposta; o saldo não pode mudar.
  5. Disparar um refund; conferir que a aposta voltou.
  6. Reenviar o mesmo refund; o saldo não pode mudar.
  7. Apostar com saldo insuficiente; conferir status: 0 e que nada foi debitado.

OpenAPI & Postman

Variáveis a definir depois de importar a coleção:

VariávelExemplo
base_urlhttps://{SLOTEFLIX_HOST}/api/v1
agent_codeminhaoperacao
agent_tokenseu token de 32 chars
agent_secret_keysua secret de 32 chars
game_codevs20olympgate
user_id1042

Suporte

Ao abrir um ticket, mande sempre estes cinco itens — sem eles o diagnóstico demora:

  1. Seu agent_code (nunca o token nem a secret).
  2. O transaction_id ou round_id afetado.
  3. Timestamp com fuso.
  4. Payload recebido e resposta enviada, crus.
  5. O que você esperava versus o que aconteceu.
ℹ
Rotação de credenciais: não há endpoint público para isso. Solicite ao time de integração — a troca invalida o cache do agente e derruba sessões de jogo abertas, então agende para janela de baixo movimento.
Sloteflix API · v1 · Documentação de integração Atualizado em 29 de julho de 2026