← LOTO Developers/Jogo do Bicho

Jogo do Bicho pela API

Guia de integração

Guia de integração

Apostar no Jogo do Bicho pela API

Guia para quem vai integrar uma aplicação própria à plataforma, sem usar o aplicativo.

A integração aposta como um apostador comum: cria um usuário, coloca saldo, e envia bilhetes. Não há papel especial nem rota paralela — o que o aplicativo faz, você faz pelos mesmos endereços.


Antes de começar: três coisas que confundem

1. O endereço identifica a loja. Toda requisição vai para o domínio da loja, e é o cabeçalho Host que diz ao sistema de qual loja se trata. Autenticar num domínio e apostar em outro não funciona, e a mensagem de erro não vai deixar isso óbvio.

https://sualoja.com.br/api/v1/...

2. Colocar saldo não é imediato. A rota de crédito devolve um QR Code Pix. O saldo só entra quando o banco confirma o pagamento, o que pode levar segundos. Consulte o saldo antes de apostar — não assuma que creditou.

3. A descarga é automática. Quando uma aposta ultrapassa o limite de exposição da loja, o sistema a repassa ao parceiro sozinho. Não existe parâmetro para pedir isso. Você reconhece pelo estado do bilhete depois: forwarded.


1. Autenticar

Todas as rotas, exceto o cadastro, exigem um token.

requisição
curl -X POST https://sualoja.com.br/auth/login \
  -H 'Content-Type: application/json' \
  -d '{
    "username": "11144477735",
    "password": "suasenha"
  }'

O campo username aceita CPF, e-mail ou nome de usuário — o sistema reconhece pelo formato: com @ trata como e-mail, com 11 dígitos como CPF, o resto como nome de usuário.

Resposta:

resposta
{
  "access_token": "eyJhbGciOiJSUzI1NiIs…",
  "expires_in": 300,
  "refresh_token": "eyJhbGciOiJIUzI1NiIs…",
  "refresh_expires_in": 1800,
  "token_type": "Bearer"
}

O access_token vale 300 segundos; o refresh_token, 1800.

Use o access_token em todas as chamadas seguintes:

Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Dois tokens, dois papéis

A integração usa dois tokens diferentes, e trocá-los é o erro mais comum na primeira tentativa — a resposta é um 401 seco, sem explicar que o papel está errado.

TokenDe quemUsado em
**$TOKEN_ADMIN**conta admin, manager ou employee da lojasó a seção 2 (criar apostador)
**$TOKEN_PLAYER**do próprio apostadorseções 3, 4 e 5 (crédito, aposta, consulta)

Nos exemplos deste documento, cada curl mostra qual dos dois ele espera, escrito como $TOKEN_ADMIN ou $TOKEN_PLAYER. **Onde você vir isso, coloque o access_token da conta correspondente**, obtido na chamada de login acima.

Cada token sai de um login diferente:

requisição
# token de administrador — só para criar apostadores (seção 2)
curl -X POST https://sualoja.com.br/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username": "usuario.admin", "password": "senha"}'

# token do apostador — para crédito, aposta e consulta (seções 3, 4 e 5)
curl -X POST https://sualoja.com.br/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username": "11144477735", "password": "senha"}'

Cada resposta traz o seu access_token. Guarde os dois: eles valem por poucos minutos e são usados em chamadas diferentes.

Renovar o token

O token expira. Quando isso acontecer, as chamadas passam a responder 401 — renove em vez de pedir a senha de novo:

requisição
curl -X POST https://sualoja.com.br/auth/refresh_token \
  -H 'Content-Type: application/json' \
  -d '{"refresh_token": "eyJhbGciOiJIUzI1NiIs..."}'
resposta
{
  "access_token": "eyJhbGciOiJSUzI1NiIs…",
  "expires_in": 300,
  "refresh_token": "eyJhbGciOiJIUzI1NiIs…",
  "token_type": "Bearer"
}

Devolve um par novo. O refresh_token antigo deixa de valer — guarde o novo.


2. Criar um apostador (opcional)

Só se a sua aplicação for cadastrar pessoas. Se o apostador já existe, pule para a seção 3.

Esta rota não é a mesma que o site da loja usa. O cadastro público (POST /users/player) exige reCAPTCHA, que pressupõe um navegador e um usuário humano — uma integração servidor a servidor não consegue atendê-lo. O caminho para integração é a rota autenticada abaixo, a mesma que o promotor usa para cadastrar clientes.

requisição
curl -X POST https://sualoja.com.br/api/v1/users \
  -H "Authorization: Bearer $TOKEN_ADMIN" \
  -H 'Content-Type: application/json' \
  -d '{
    "cpf": "11144477735",
    "password": "umaSenhaForte",
    "username": "NomeDoUsuario",
    "password_confirmation": "umaSenhaForte",
    "role": "player",   
    "phone": "(11)99999-0000",
    "profile": {
      "birthdate": "01/01/1990"
    }
  }'

Resposta:

resposta
{
  "id": "6ac8f2b1b0b17b30838ffc45",
  "uuid": "a7d4e912-3f6b-4c85-b218-9e0a5d7f4c61",
  "cpf": "11144477735",
  "email": null,
  "phone": "(85)99999-0000",
  "roles": [
    "player"
  ],
  "status": "actived",
  "profile": {
    "full_name": "FULANO DE TAL",
    "birthdate": "15/03/1988"
  },
  "created_at": "2026-10-09T10:35:00.000-03:00"
}

O uuid é o identificador do apostador para login; o id é o do registro na loja.

O token precisa ser de um usuário com papel **admin, manager ou employee** — não serve o token do apostador. Autentique essa conta pela seção 1 e use o access_token dela.

Os campos

CampoObrigatórioFormato
cpfsim11 dígitos, só números
password + password_confirmationsimmínimo 6 caracteres
rolesimplayer
phonesim(XX)XXXXX-XXXX — com parênteses e hífen
profile.birthdatequase sempredd/mm/aaaa — ver abaixo
emaildepende da loja—
profile.zipcode (endereço)depende da loja—
employee_id (código promocional)depende da loja—

O phone é exigido sempre, em qualquer loja, e o formato é validado: 85999990000 é recusado, (85)99999-0000 passa.

O username não se aplica a apostador. Se você enviar, o sistema o descarta e usa o CPF como identificador de login — é por isso que o apostador autentica pelo CPF, nunca por um nome escolhido.

Os quatro campos que variam por loja

Cada loja marca estes campos como escondido, visível ou obrigatório. Só o último valor torna o campo exigido no cadastro:

CampoPadrão do sistemaNa prática
profile.birthdateobrigatórioenvie sempre — a maioria das lojas exige
emailescondidoenvie se a loja pedir
profile.zipcodeescondidoenvie se a loja pedir
employee_idescondidoenvie se a loja pedir

A data de nascimento é o caso que pega. É o único dos quatro que nasce obrigatório, então uma loja que nunca mexeu nessa configuração vai recusar o cadastro sem ela:

resposta
{"validation_error": "{:birthdate=>\"Data de nascimento é Obrigatório\"}"}

Curiosamente, o valor que você envia não é o que fica salvo: a consulta de CPF devolve a data de nascimento oficial e ela substitui a sua. A exigência é de presença, não de exatidão — mas a validação de maioridade usa a data da consulta, então não adianta inventar uma para contornar.

Antes de fixar o conjunto de campos no seu código, confirme as quatro configurações com a operação da loja.

Dois comportamentos importantes

O CPF é consultado numa base externa. CPF inexistente ou em situação irregular faz o cadastro falhar. CPFs de teste inventados não passam.

O nome e a data de nascimento vêm de lá, não do que você enviar. Se você mandar full_name: "Fulano" e a consulta devolver outro nome, vale o da consulta. Não é erro, é o comportamento esperado.

Erros que você vai encontrar

RespostaCausa
422 com phoneformato errado — use (XX)XXXXX-XXXX
422 com birthdatea loja exige data de nascimento e ela não foi enviada — é o padrão do sistema
422 com cpfCPF duplicado nesta loja, ou irregular na base externa
401o token não é de admin, manager ou employee
500indisponibilidade da consulta de CPF — tente de novo, não é erro do seu payload

3. Colocar saldo (opcional)

Gera um QR Code Pix para o apostador pagar.

requisição
curl -X POST https://sualoja.com.br/api/v1/invoices/credit \
  -H "Authorization: Bearer $TOKEN_PLAYER" \
  -H 'Content-Type: application/json' \
  -d '{"amount": 50.00}'

Resposta:

resposta
{
  "id": "6ac8f1a4b0b17b30838ffc12",
  "kind": "Credit",
  "type": "PIX",
  "description": "Depósito via Pix",
  "customer_cpf": "11144477735",
  "customer_name": "Fulano de Tal",
  "transaction_id": "e3f21a90-7c4b-4d18-9a52-0b7e6d1f8c33",
  "status": "pending",
  "pay_day": null,
  "due_date": "2026-10-10T10:30:00.000-03:00",
  "amount_paid": "0.00",
  "amount": "50.00",
  "qrcode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA…",
  "pix_copy_paste": "00020126580014BR.GOV.BCB.PIX0136e3f21a90-7c4b-4d18-9a52-0b7e6d1f8c335204000053039865802BR…",
  "manually": false,
  "created_at": "2026-10-09T10:30:00.000-03:00"
}
CampoO que fazer com ele
qrcodeimagem em base64 — exiba para o apostador escanear
pix_copy_pasteo código Pix em texto — ofereça o botão de copiar
transaction_ididentificador do pagamento no gateway
statuscomeça em pending
due_datedepois disso o QR Code expira

A resposta traz o código copia-e-cola e a imagem do QR Code.

O saldo não entra na hora. O pagamento é confirmado pelo banco por fora, depois. Para saber que caiu, consulte o saldo (seção 5) até ele mudar — ou aguarde alguns segundos antes de apostar.


4. Apostar

É o coração da integração. Um bilhete tem duas partes: em quais sorteios ele vale, e quais são os palpites.

requisição
curl -X POST https://sualoja.com.br/api/v1/tickets \
  -H "Authorization: Bearer $TOKEN_PLAYER" \
  -H 'Content-Type: application/json' \
  -d '{
    "draws": [
      { "draw_name": "pt_rio", "date": "06/10/2026", "time": "14:20" }
    ],
    "bets": [
      {
        "game": "milhar",
        "number": "1234",
        "prize": "1",
        "amount": 2.00,
        "for_all": true
      }
    ]
  }'

draws — onde a aposta vale

Cada item é um sorteio. Mesmo bilhete em três sorteios: três itens.

CampoFormatoExemplo
draw_namenome técnico da extração"pt_rio"
datedd/mm/aaaa"06/10/2026"
timehora da extração"14:20"

Descobrir as extrações

Primeiro os estados, que é rota pública:

requisição
curl https://sualoja.com.br/states

Resposta:

resposta
["Bahia", "Ceará", "Brasilia (DF)", "Goiás", "Minas Gerais", "Paraíba",
 "Pernambuco", "Paraná", "Rio de Janeiro", ...]

Depois as extrações do estado — o nome vai exatamente como veio acima, codificado na URL:

requisição
curl 'https://sualoja.com.br/api/v1/draws/states/Rio%20de%20Janeiro' \
  -H "Authorization: Bearer $TOKEN_PLAYER"

Resposta:

resposta
[
  {
    "name": "pt_rio",
    "canonical_name": "PT-Rio",
    "state": "Rio de Janeiro",
    "Segunda": ["09:20", "11:20", "14:20", "16:20", "18:20", "21:20"],
    "Terça":   ["09:20", "11:20", "14:20", "16:20", "18:20", "21:20"]
  }
]

Os horários vêm por dia da semana. Para apostar em 06/10/2026, que é uma segunda, use um dos horários de Segunda.

A resposta

resposta
{
  "success": [
    {
      "id": "6ac8e712b0b17b30838ffb90",
      "published_at": "2026-10-09T10:07:30.745-03:00",
      "draw_name": "pt_rio",
      "draw_time": "2026-10-09T11:20:00.000-03:00",
      "amount_bet": "2.00",
      "amount_potential_cash_return": "12000.00",
      "paid_with_cash": "2.00",
      "paid_with_reward": "0.00",
      "bets": [
        {
          "id": "6ac8e712b0b17b30838ffb92",
          "game": "milhar",
          "prize": "1",
          "prizes": [
            1
          ],
          "number": "1234",
          "numbers": [
            "1234"
          ],
          "quote": {
            "milhar": 6000.0
          },
          "has_milhar_bonus": false,
          "potential_cash_return": 12000.0,
          "amount": "2.00"
        }
      ],
      "state": "published",
      "comission_at": null,
      "comission_amount": 0.0,
      "comission_player_amount": 0.0
    }
  ],
  "errors": [],
  "user": {
    "username": "playerbicho",
    "cpf": "111.444.777-35",
    "email": "playerbicho@bicho.local",
    "roles": [
      "player"
    ],
    "status": "actived",
    "phone": "(85)99999-0000",
    "id": "6a8708adaf9f93dd050159d7",
    "uuid": "fc3af4a6-63b9-482f-a866-405b50bdb898",
    "profile": {
      "full_name": "Player Local",
      "birthdate": "01/01/1990",
      "amount_credit": 0.0,
      "amount_withdraw": 0.0,
      "amount_awarded": 0.0,
      "quote_id": "6a87068735340824d42428bf"
    },
    "balance": {
      "real_balance": "946.0",
      "blocked_balance": "0.0",
      "manual_blocked_balance": "0.0",
      "bonus_balance": "0.0",
      "updated_at": "2026-10-09T10:07:30.751-03:00"
    }
  }
}

A resposta tem três partes, sempre as três, mesmo quando uma vem vazia:

ParteO que traz
successum objeto por sorteio criado — três sorteios no pedido, três bilhetes aqui
errorsum objeto por sorteio recusado: a message com o motivo, mais os campos do sorteio que falhou (draw_name, date, time), para você saber qual deles foi
usero cadastro do apostador, com o saldo já atualizado

**Dentro de cada bilhete em success:**

CampoO que é
ido identificador do bilhete — guarde, é com ele que você consulta na seção 5
statesituação do bilhete. Começa em published
draw_name / draw_timeo sorteio em que este bilhete vale
amount_betquanto foi debitado
amount_potential_cash_returnquanto o apostador recebe se ganhar
bets[].quotea cotação da loja aplicada — aqui, milhar paga 6000×
bets[].numbers / prizesos palpites e os prêmios, já convertidos em lista
paid_with_cash / paid_with_rewardquanto saiu do saldo real e quanto do saldo de bônus

O status HTTP não indica sucesso

Esta é a parte mais importante da integração. Um bilhete recusado também responde **200 OK** — o que muda é o conteúdo:

resposta
{
  "success": [],
  "errors": [
    {
      "message": "Extração indisponível no momento.",
      "draw_name": "pt_rio",
      "date": "09/10/2026",
      "time": "03:00"
    }
  ],
  "user": {
    "username": "fulano",
    "balance": "944.00"
  }
}

Repare: success veio vazio e o motivo está em errors. O bilhete não foi criado e nada foi debitado.

Sempre verifique o conteúdo, nunca o código HTTP:

O terceiro caso acontece quando você manda vários sorteios num bilhete só e um deles está indisponível. Trate-o: ignorá-lo faz você cobrar do cliente uma aposta que não existe.


bets — os palpites

CampoO que é
gamea modalidade (tabela abaixo)
numbero palpite. Vários números vão separados por espaço
prizeem quais prêmios vale. Vários vão separados por vírgula: "1,2,3,4,5"
amountvalor da aposta, em reais
for_alltrue = o valor vale para o conjunto · false = multiplica pelo número de palpites

for_all muda quanto você paga

É o detalhe que mais gera dúvida.

number: "1234 5678"   amount: 2.00   for_all: true    →  R$ 2,00 no total
number: "1234 5678"   amount: 2.00   for_all: false   →  R$ 4,00 (2 palpites × R$ 2,00)

As modalidades

**number sempre usa espaço entre os palpites. prize sempre usa vírgula** entre os prêmios.

gameNomePalpitesPrêmios aceitosCotação
grupoGrupo15, 10grupo
grupo_meioGrupo Meio15, 10grupo
grupo_esquerdaGrupo Esquerda15, 10grupo
grupo_combinadoGrupo Combinado2grupo_combinado
milharMilhar15, 10milhar
milhar_invertidaMilhar Invertida75, 10milhar
centenaCentena15, 10centena
centena_esquerdaCentena Esquerda15, 10centena
centena_invertidaCentena Invertida75, 10centena
centena_invertida_esquerdaCentena Invertida Esquerda15, 10centena
milhar_centenaMilhar com Centena15, 10milhar + centena
milhar_centena_invertidaMilhar com Centena Invertida75, 10milhar + centena
dezenaDezena15, 10dezena
dezena_esquerdaDezena Esquerda15, 10dezena
dezena_meioDezena Meio15, 10dezena
unidadeUnidade15, 10unidade
grupo_duplaDupla de Grupo25grupo_dupla_5
grupo_dupla_esquerdaDupla de Grupo Esquerda25grupo_dupla_5
grupo_dupla_meioDupla de Grupo Meio25grupo_dupla_5
grupo_ternoTerno de Grupo3
grupo_terno_esquerdaTerno de Grupo Esquerda3
grupo_terno_meioTerno de Grupo Meio3
grupo_quadraQuadra de Grupo45grupo_quadra_5
grupo_quadra_meioQuadra de Grupo Meio45grupo_quadra_5
grupo_quadra_esquerdaQuadra de Grupo Esquerda45grupo_quadra_5
grupo_quinaQuina de Grupo55grupo_quina_5
grupo_quina_esquerdaQuina de Grupo Esquerda55grupo_quina_5
grupo_quina_meioQuina de Grupo Meio55grupo_quina_5
dezena_duqueDuque de Dezena15dezena_duque_5
dezena_duque_esquerdaDuque de Dezena Esquerda15dezena_duque_5
dezena_duque_meioDuque de Dezena Meio15dezena_duque_5
dezena_ternoTerno de Dezena15dezena_terno_5
dezena_terno_esquerdaTerno de Dezena Esquerda15dezena_terno_5
dezena_terno_meioTerno de Dezena Meio15dezena_terno_5
passe_vaiPasse Vai2
passe_vai_vemPasse Vai e Vem2
palpitaoPalpitão205palpitao
grupo_senaSena de Grupo106grupo_sena
grupo_sena_esquerdaSena de Grupo Esquerda106grupo_sena
grupo_sena_meioSena de Grupo Meio106grupo_sena

Exemplos por tipo

Número direto — milhar, centena, dezena, unidade

resposta
{ "game": "milhar",  "number": "1234", "prize": "1",         "amount": 2.00, "for_all": true }
{ "game": "centena", "number": "234",  "prize": "1,2,3,4,5", "amount": 1.00, "for_all": true }
{ "game": "dezena",  "number": "34 56 78", "prize": "1,2,3,4,5", "amount": 1.00, "for_all": false }

Grupo — o palpite é o número do grupo, de 1 a 25

resposta
{ "game": "grupo",        "number": "7",     "prize": "1,2,3,4,5", "amount": 5.00, "for_all": true }
{ "game": "grupo_dupla",  "number": "7 12",  "prize": "1,2,3,4,5", "amount": 5.00, "for_all": true }
{ "game": "grupo_terno",  "number": "7 12 3","prize": "1,2,3,4,5", "amount": 5.00, "for_all": true }
{ "game": "grupo_quina",  "number": "1 7 12 19 23", "prize": "1,2,3,4,5", "amount": 5.00, "for_all": true }

Grupo 7 é o Carneiro, 12 o Elefante. A lista dos 25 vem de:

requisição
curl https://sualoja.com.br/api/v1/games -H "Authorization: Bearer $TOKEN_PLAYER"

Resposta:

resposta
[
  {
    "name": "grupo",
    "canonical_name": "GRUPO",
    "initials": "G",
    "options": {
      "maxSelection": 10,
      "Avestruz": "01 02 03 04",
      "Águia": "05 06 07 08",
      "Burro": "09 10 11 12",
      "Borboleta": "13 14 15 16",
      "Cachorro": "17 18 19 20",
      "Cabra": "21 22 23 24",
      "Carneiro": "25 26 27 28",
      "Camelo": "29 30 31 32",
      "Cobra": "33 34 35 36",
      "Coelho": "37 38 39 40",
      "Cavalo": "41 42 43 44",
      "Elefante": "45 46 47 48",
      "Galo": "49 50 51 52",
      "Gato": "53 54 55 56",
      "Jacaré": "57 58 59 60",
      "Leão": "61 62 63 64",
      "Macaco": "65 66 67 68",
      "Porco": "69 70 71 72",
      "Pavão": "73 74 75 76",
      "Peru": "77 78 79 80",
      "Touro": "81 82 83 84",
      "Tigre": "85 86 87 88",
      "Urso": "89 90 91 92",
      "Veado": "93 94 95 96",
      "Vaca": "97 98 99 00"
    },
    "status": "actived"
  },
  {
    "name": "milhar",
    "canonical_name": "MILHAR SECA",
    "initials": "MS",
    "options": {
      "mask": "9999"
    },
    "status": "actived"
  },
  {
    "name": "milhar_invertida",
    "canonical_name": "MILHAR INVERTIDA",
    "initials": "MI",
    "options": {
      "mask": "9999"
    },
    "status": "actived"
  }
]

A lista completa traz as 17 modalidades que a loja tem ativas.

A mesma resposta traz, para cada modalidade, quantos palpites ela aceita e quais prêmios são válidos — é a versão viva da tabela acima.

Duque e terno de dezena — duas ou três dezenas no mesmo palpite

resposta
{ "game": "dezena_duque", "number": "34 56",    "prize": "1,2,3,4,5", "amount": 3.00, "for_all": true }
{ "game": "dezena_terno", "number": "34 56 78", "prize": "1,2,3,4,5", "amount": 3.00, "for_all": true }

Passe — dois grupos, um saindo no primeiro prêmio e outro em prêmio seguinte

resposta
{ "game": "passe_vai",     "number": "7 12", "prize": "1,2", "amount": 4.00, "for_all": true }
{ "game": "passe_vai_vem", "number": "7 12", "prize": "1,2", "amount": 4.00, "for_all": true }

Invertidas — um palpite, todas as combinações dos dígitos

resposta
{ "game": "milhar_invertida",  "number": "1234", "prize": "1", "amount": 2.00, "for_all": true }
{ "game": "centena_invertida", "number": "234",  "prize": "1", "amount": 2.00, "for_all": true }

1234 invertida cobre 1243, 1324, 1342 e assim por diante. O custo acompanha: é como apostar em cada combinação. Com for_all: true o valor informado é o total.

Milhar com centena — uma aposta que vale nas duas modalidades

resposta
{ "game": "milhar_centena", "number": "1234", "prize": "1", "amount": 2.00, "for_all": true }

Paga como milhar se acertar os quatro dígitos, e como centena se acertar os três últimos. Por isso usa duas cotações.

O que a aposta devolve

resposta
{
  "success": [
    {
      "id": "6ac40709eddf17f28f62a036",
      "published_at": "2026-10-05T17:22:33.694-03:00",
      "draw_name": "pt_rio",
      "draw_time": "2026-10-06T14:20:00.000-03:00",
      "amount_bet": "2.00",
      "amount_potential_cash_return": "12000.00",
      "paid_with_cash": "2.00",
      "paid_with_reward": "0.00",
      "bets": [ ... ]
    }
  ]
}

Um bilhete por extração. Se você mandou três extrações em draws, success vem com três bilhetes, cada um com seu id. Guarde todos — é por eles que você consulta o resultado.

amount_potential_cash_return é quanto a aposta paga se acertar. paid_with_reward é a parte paga com saldo de bônus, quando houver.

Quando a aposta é recusada

A resposta traz o motivo em errors. Os mais comuns:

SituaçãoO que fazer
Saldo insuficienteconferir o saldo antes de enviar
Extração já encerradacada loja bloqueia alguns minutos antes do sorteio
Valor abaixo do mínimoo mínimo é configurado por loja
Prêmio acima do máximoa loja limita o prêmio por aposta e por extração
Formato de number inválidoconferir a tabela de modalidades

5. Consultar bilhete, saldo e extrato

O bilhete

requisição
curl https://sualoja.com.br/api/v1/tickets/{id} -H "Authorization: Bearer $TOKEN_PLAYER"

Resposta:

resposta
{
  "id": "6ac8ea92b0b17b30838ffb93",
  "published_at": "2026-10-09T10:22:26.740-03:00",
  "draw_name": "pt_rio",
  "draw_time": "2026-10-09T14:20:00.000-03:00",
  "amount_bet": "2.00",
  "amount_potential_cash_return": "12000.00",
  "paid_with_cash": "2.00",
  "paid_with_reward": "0.00",
  "bets": [
    {
      "id": "6ac8ea92b0b17b30838ffb95",
      "game": "milhar",
      "prize": "1",
      "prizes": [
        1
      ],
      "number": "1234",
      "numbers": [
        "1234"
      ],
      "quote": {
        "milhar": 6000.0
      },
      "has_milhar_bonus": false,
      "potential_cash_return": 12000.0,
      "amount": "2.00"
    }
  ],
  "user": {
    "username": "playerbicho",
    "link_code": "playerbicho",
    "link": "/register?invited_from=playerbicho"
  },
  "tag": null,
  "state": "published"
}

O campo state diz onde ele está:

StatusSignifica
draftcriado, ainda não confirmado
publishedvalendo — aposta registrada, aguardando o sorteio
archivedsorteio saiu e o bilhete foi conferido
refundedestornado, valor devolvido
forwardedrepassado ao parceiro — é a descarga
canceledcancelado

Listar os bilhetes

requisição
curl 'https://sualoja.com.br/api/v1/tickets?page=1' -H "Authorization: Bearer $TOKEN_PLAYER"

Resposta:

resposta
{
  "tickets": [
    {
      "id": "6ac8ea92b0b17b30838ffb93",
      "draw_name": "pt_rio",
      "draw_time": "2026-10-09T14:20:00.000-03:00",
      "amount_bet": "2.00",
      "state": "published"
    },
    "…"
  ],
  "count": 4
}

Pagina de 20 em 20. Use page=2, page=3 para avançar.

Saldo

requisição
curl https://sualoja.com.br/api/v1/users/{user_id}/balance -H "Authorization: Bearer $TOKEN_PLAYER"

Resposta:

resposta
{
  "user_id": "6a8708adaf9f93dd050159d7",
  "real_balance": "944.0",
  "blocked_balance": "0.0",
  "manual_blocked_balance": "0.0",
  "bonus_balance": "0.0"
}

O saldo vem separado: real, bônus e bloqueado. Para apostar vale o real disponível — o bloqueado está comprometido com apostas ainda em aberto.

Extrato

requisição
curl https://sualoja.com.br/api/v1/users/{user_id}/transactions -H "Authorization: Bearer $TOKEN_PLAYER"

Resposta:

resposta
{
  "transactions": [
    {
      "id": "6ac8ea923365fb352b15396c",
      "user_id": "6a8708adaf9f93dd050159d7",
      "user_balance_id": "6a8708adce1aa42f2b7bcc98",
      "source": "jb",
      "operation": "debit",
      "idempotency_key": "ticket::6a8708adaf9f93dd050159d7::6ac8ea92b0b17b30838ffb93::paid",
      "reference_id": "6ac8ea92b0b17b30838ffb93",
      "reference_type": "ticket",
      "description": "Bilhete 6AC8-FB93 pago!",
      "amount": "2.0",
      "real_balance": "2.0",
      "real_balance_before": "946.0",
      "real_balance_after": "944.0",
      "bonus_balance": "0.0",
      "bonus_balance_before": "0.0",
      "bonus_balance_after": "0.0",
      "created_at": "2026-10-09T13:22:26.755Z",
      "updated_at": "2026-10-09T13:22:26.755Z"
    },
    {
      "id": "6ac8e7123365fb352b15396b",
      "user_id": "6a8708adaf9f93dd050159d7",
      "user_balance_id": "6a8708adce1aa42f2b7bcc98",
      "source": "jb",
      "operation": "debit",
      "idempotency_key": "ticket::6a8708adaf9f93dd050159d7::6ac8e712b0b17b30838ffb90::paid",
      "reference_id": "6ac8e712b0b17b30838ffb90",
      "reference_type": "ticket",
      "description": "Bilhete 6AC8-FB90 pago!",
      "amount": "2.0",
      "real_balance": "2.0",
      "real_balance_before": "948.0",
      "real_balance_after": "946.0",
      "bonus_balance": "0.0",
      "bonus_balance_before": "0.0",
      "bonus_balance_after": "0.0",
      "created_at": "2026-10-09T13:07:30.785Z",
      "updated_at": "2026-10-09T13:07:30.785Z"
    }
  ],
  "count": 10,
  "balance": {
    "id": "6a8708adce1aa42f2b7bcc98",
    "user_id": "6a8708adaf9f93dd050159d7",
    "real_balance": "944.0",
    "blocked_balance": "0.0",
    "manual_blocked_balance": "0.0",
    "bonus_balance": "0.0",
    "created_at": "2026-08-20T11:01:17.951-03:00",
    "updated_at": "2026-10-09T10:22:26.746-03:00"
  }
}

Cada lançamento traz o saldo antes e depois, o que permite conferir sem recalcular.


O fluxo completo

1. POST /auth/login                      → token
2. POST /api/v1/users                    → cria o apostador        (token de admin)
3. POST /api/v1/invoices/credit          → QR Code Pix
   GET  /api/v1/users/{id}/balance       → confirma que o saldo entrou
4. POST /api/v1/tickets                  → aposta
5. GET  /api/v1/tickets/{id}             → acompanha o resultado

Dúvidas frequentes

Posso apostar em várias extrações de uma vez? Sim — um item por extração em draws. O valor de cada aposta é cobrado por extração.

Posso misturar modalidades no mesmo bilhete? Sim. bets aceita quantos itens quiser, com modalidades diferentes.

Como sei se ganhei? O bilhete passa a archived depois do sorteio, e o valor do prêmio aparece nele. O crédito entra no saldo automaticamente.

O que acontece se o sorteio for cancelado? O bilhete vai para refunded e o valor volta ao saldo.

Preciso tratar a descarga? Não. É automática, e o bilhete continua do apostador. O estado forwarded é informativo.