Green Studios

Green Studios · Integration

Integration API v1.2

The two directions

IntegrationDirectionWhat it carries
Catalogue & launch operator → provider which games exist, opening a game for a player, and free bets
Wallet provider → operator balance, debit, credit, rollback

Authentication: one scheme, both directions

X-Signature: hex(HMAC-SHA256(request_body, secret))

Sign the body exactly as sent.

timestamp goes inside the signed body, in milliseconds. There is no separate header. Requests outside a 5 minute window are rejected on both sides.

Every request carries operator and timestamp. Requests we send you also carry request_id and currency.

Authentication failures

Unknown operator, disabled operator, missing secret, wrong signature: all return the same 401 INVALID_SIGNATURE.

What the provider exposes

operator → provider

Base URL: https://api-games.greenstudios.com.br/v1

Version 1.2 (20 October 2026) adds branding; 1.1 added free bets. Both only add routes and fields, so the routes stay under /v1.

POST /v1/games every game enabled for the operator
{"operator":"test-operator", "timestamp":1755680000000,
 "currency":"BRL", "limit":100, "offset":0}
{
  "status": "ok",
  "operator": "test-operator",
  "count": 1,
  "total": 60,
  "offset": 0,
  "currencies": ["BRL", "USD"],
  "games": [
    {
      "game_id": "ronda-online",
      "name": "Ronda Online",
      "studio": "greenstudios",
      "category": "LIVE",
      "demo": true, "mobile": true, "desktop": true, "freebet": true,
      "image_url": "https://greenstudios.com.br/thumbnails/300x300/ronda-online-thumb.webp",
      "currency": "BRL",
      "min_bet": …, "max_bet": …, "max_win": …,
      "freebet_limits": {…}
    }
  ]
}

count is the size of this page, total is the whole catalogue. A total larger than count means there is more: ask for the next page with offset.

freebet says whether the game accepts free bets (Free bets).

The three limits

They appear only when you send currency, hold for that currency, and are in integer cents.

FieldMeaning
min_betsmallest accepted stake
max_betlargest accepted stake
max_winpayout ceiling; a bigger prize is paid at the ceiling
Missing limits

A missing limit field means the limit is unknown, not that there is none.

Free bet limits

Also only with currency, on games with freebet: true: the range /v1/freebets/grant accepts in that currency.

"freebet_limits": {"min_amount":100, "max_amount":10000, "min_rounds":1,
                   "max_rounds":1000, "max_total":100000, "max_expiry_days":30}

amount is per round and max_total caps rounds × amount. A missing max_* field has no ceiling; a missing block means the range is unknown: do not grant.

image_url is absent when the game has no artwork. currencies is the set of currencies enabled for you.

POST /v1/launch opens a game for one player

Returns the URL to load in an iframe or redirect.

{
  "operator": "test-operator", "timestamp": 1755680000000,
  "game_id": "ronda-online",
  "player_id": "618004",
  "token": "your-session-token",
  "currency": "BRL",
  "language": "pt",
  "demo": false, "mobile": true,
  "lobby_url": "https://your-site/lobby",
  "deposit_url": "https://your-site/deposit"
}
FieldRequiredNotes
game_idyesmust be in your catalogue
tokenunless demosent on every wallet call
player_idunless demoyour player identifier
currencyrecommendedthe session currency
languagenoISO 639-1. Default: en
demo, mobilenodemo needs neither token nor player
freebetsnofree bets granted at session start (below)
brandnoone of your brands: picks the look (Branding)
{"status":"ok", "url":"https://…/?gameId=…&casinoId=…&session=…&lang=…"}

The URL carries a session id. Your token and player_id are not in it: we take them server to server and exchange them for that id. Open the URL within 15 minutes.

Errors: MISSING_GAME_ID, MISSING_TOKEN, MISSING_PLAYER_ID, INVALID_TOKEN (400), GAME_NOT_FOUND (404).

Branding

Green Studios registers your look: logo, card logo, colours and a loading splash. Without brand, every game opens with your operator's look.

Running several sites? Send brand in /v1/launch and each site gets its own look. A brand we have not registered opens with your operator's look. It works on real and demo launches.

WhereWhat changes
provider gameslogo, card logo, colours, balance and rules display
every gamethe loading splash, when enabled for the brand

brand: 1 to 100 characters among lowercase letters, digits, . _ -. Tell us your brands during integration.

Free bets

You grant free rounds to a player. The player plays them in the game, and your wallet sees them as in Free rounds in the wallet. Requires a freebet_mode at provisioning (capabilities).

POST /v1/freebets/grant grants free rounds to one player
{
  "operator": "test-operator", "timestamp": 1755680000000,
  "freebet_id": "promo-2026-001",
  "player_id": "618004",
  "game_id": "ronda-online",
  "currency": "BRL",
  "rounds": 10, "amount": 100,
  "expires_at": "2026-11-30T23:59:59Z"
}
FieldRequiredNotes
freebet_idyesyour id and the idempotency key. 1 to 128 characters: letters, digits, . _ : -
player_idyesthe player does not need to have played before
game_idyesa game with freebet: true
currencyyesthe currency of the player's account
roundsyesnumber of free rounds
amountyesstake per round, in cents, within freebet_limits
expires_atnoRFC 3339. Default: 72 hours
{
  "status": "ok",
  "freebet_id": "promo-2026-001", "grant_id": "8c1f4c2e-…",
  "state": "granted",
  "player_id": "618004", "game_id": "ronda-online", "currency": "BRL",
  "amount": 100, "rounds_total": 10, "rounds_remaining": 10,
  "expires_at": "2026-11-30T23:59:59Z", "created_at": "2026-10-19T18:00:00Z"
}

The same freebet_id with the same fields returns the original grant. With different fields: 409 FREEBET_DUPLICATE.

Each free round is a round at exactly amount, in that game and currency.

POST /v1/freebets/cancel cancels a grant
{"operator":"test-operator", "timestamp":1755680000000, "freebet_id":"promo-2026-001"}
{"status":"ok", "freebet_id":"promo-2026-001", "grant_id":"8c1f4c2e-…",
 "state":"cancelled", "rounds_total":10, "rounds_remaining":6,
 "cancelled_at":"2026-10-20T09:00:00Z"}

Rounds already played stay played. A repeated cancel returns the same 200.

POST /v1/freebets/status one grant, or the grants of a player
{"operator":"test-operator", "timestamp":1755680000000, "freebet_id":"promo-2026-001"}

Returns the grant, in the shape of the grant response. Send player_id instead of freebet_id for the player's grants, most recent first, up to 100:

{"status":"ok", "player_id":"618004", "count":1, "freebets":[{…}]}

States

stateMeaning
grantedno round played yet
in_progresssome rounds played
completedclosed: all rounds played, or closed with the payout in aggregate mode
expiredexpired before all rounds were played
cancelledcancelled

Granting at launch

Send freebets in /v1/launch to grant at the start of the session. Each item is a grant without player_id; currency and game_id default to the launch values.

"freebets": [{"freebet_id":"promo-2026-001", "rounds":10, "amount":100}]

The launch response adds freebets: one result per item, in order. A success is the grant; a refusal is {"freebet_id":…, "status":"error", "error_code":…, "message":…}. A refused free bet never blocks the launch. Up to 10 per launch, none on demo.

Error codes

CodeHTTPMeaningRetry?
INVALID_REQUEST400missing or invalid field, or outside freebet_limitsno
GAME_NOT_SUPPORTED400game not in your catalogue, or without free betsno
CURRENCY_NOT_SUPPORTED400currency not enabled for this gameno
FREEBETS_NOT_ENABLED403free bets not enabled for your operatorno
FREEBET_NOT_FOUND404unknown freebet_idno
FREEBET_DUPLICATE409freebet_id already used with other fieldsno
FREEBET_ALREADY_USED409all rounds playedno
FREEBET_EXPIRED409grant expiredno
FREEBET_ALREADY_CANCELLED409grant cancelledno
INTERNAL_ERROR502our side failedyes, same request

Error body: {"status":"error", "error_code":…, "message":…}.

Currencies and languages

All money is integer cents. 1500 means 15.00 in the session currency.

A monetary field in currency units, or in floating point, is invalid.

Currencies

29 currencies are supported. The set enabled for one operator comes back in currencies from POST /v1/games.

ARS BDT BOB BRL CAD CDF CLP COP CRC EUR GYD INR KES KRW LSL MXN NGN PEN PHP PKR PYG TOP TZS UAH USD UYU VES XAF ZAR

Languages

pt, en and es. Any other value, and an absent language, falls back to en.

What the operator exposes

provider → operator

Four routes.

POST {wallet_url}/balance     token → player, balance, currency
POST {wallet_url}/bet         debit
POST {wallet_url}/win         credit
POST {wallet_url}/rollback    refund

Response format

{"status":"ok", "balance":98500, "currency":"BRL",
 "player_id":"618004", "transaction_id":"…"}
{"status":"error", "error_code":"INSUFFICIENT_FUNDS", "error_message":"…"}

balance is integer cents, after the operation, and is required on success.

currency is the currency of the account. Send it on every response.

The requests we send

RouteFields
/balanceplayer_id, token
/betplayer_id, token, amount, transaction_id, round_id, freebet_id
/winplayer_id, token, amount, transaction_id, bet_id, round_id, type, freebet_id
/rollbackplayer_id, token, amount, transaction_id, bet_id, round_id, external_bet_id

Plus timestamp, request_id and currency on every one, and game_id when the game is known.

type on /winMeaning
winprize of a paid round
freebetprize of a free round
freebet_payouttotal of a grant, aggregate mode
zero_winzero-amount credit that closes a round

Error codes

CodeMeaningWe retry?
INSUFFICIENT_FUNDSnot enough balanceno
TOKEN_EXPIREDsession expiredno
TOKEN_INVALIDsession never existedno
PLAYER_NOT_FOUNDunknown playerno
BET_NOT_FOUNDthe referenced bet does not existno
CURRENCY_MISMATCHwrong currency for this accountno
INTERNAL_ERRORyour side failedyes

Any code we do not recognise is treated as retryable.

Free rounds in the wallet

freebet_id is on every /bet and /win: null on paid rounds, and on free rounds your freebet_id (our grant_id when the grant came from our back office).

Mode per_round

RouteOn a free round
/betamount: 0, freebet_id, and freebet_amount: the nominal stake, for your accounting only
/wintype: "freebet", freebet_id, amount = the full payout. A lost free round ends with type: "zero_win" if you declared zero_win
/rollbacknever sent: the free round debited nothing

Mode aggregate

No traffic per round. When the grant closes (all rounds played, expired or cancelled), one pair:

POST /bet   amount 0, freebet_id, transaction_id "fb-<grant_id>"
POST /win   type "freebet_payout", amount = total won, bet_id = that transaction_id

A total of zero is sent too: it is what closes the bonus on your side.

Identity and session

1

The token is the session identity.

2

The token is random.

It must not equal the player_id, be derived from it, or be your own site session id. We refuse a launch whose token equals the player_id or is shorter than 16 characters, with INVALID_TOKEN.

3

A new token for every /v1/launch.

4

A token belongs to one player.

If a second player arrives with a token already in use, we refuse the session and link nothing.

5

Your wallet attests the token owner.

Every /balance success response carries player_id: the player that token belongs to. When it differs from the player of the session, we refuse and the game does not open.

6

An expired token refuses every call.

/balance, /bet, /win and /rollback all return TOKEN_EXPIRED.

The rules that actually break integrations

1

Idempotency

The same transaction_id must never debit twice. We retry when we do not get a response.

Store the response, not only the id: the second call returns the same balance as the first.

Business refusals are not memorised.

2

A zero-amount credit is valid and must be accepted

It is the terminal event of a losing round.

3

Business errors are HTTP 200, not 5xx

Insufficient funds is a 200 with an error_code. 5xx is treated as a transport failure and retried.

4

Currency belongs to the session, not to the operator

It is fixed at launch and is sent on every wallet call.

5

Exactly one terminal event per round

Every round ends exactly once, with a win or with a zero-amount credit.

6

freebet_id is always present

null on a paid round. Branch on its value, never on whether the key exists.

7

A free round's /bet has amount: 0

Never debit freebet_amount: it is the nominal stake, for reporting.

8

A free round is never refunded

No /rollback is sent for it, because it debited nothing.

9

The prize of a free round has type: "freebet"

amount is the full payout of the round. The type is what tells it apart from a paid prize.

10

The aggregate payout comes even when it is zero

Close the bonus on freebet_payout, whatever the amount.

Optional capabilities

Declared at provisioning.

CapabilityWhat it means
freebet_mode: per_roundfree rounds one by one: a /bet of 0 and its /win per round
freebet_mode: aggregatefree rounds produce one /bet of 0 and one credit at the end
zero_winyou require the zero-amount credit to close a lost free round

The two freebet modes are mutually exclusive. Without a freebet_mode, free bets are off and /v1/freebets/grant returns 403 FREEBETS_NOT_ENABLED.

Green Studios · Integração

Integration API v1.2

As duas direções

IntegraçãoDireçãoO que carrega
Catálogo e launch operador → provedor quais jogos existem, abrir um jogo para um jogador, e free bets
Carteira provedor → operador saldo, débito, crédito, estorno

Autenticação: um esquema só, nas duas direções

X-Signature: hex(HMAC-SHA256(corpo_da_requisicao, segredo))

Assinem o corpo exatamente como enviado.

O timestamp vai dentro do corpo assinado, em milissegundos. Não há header separado. Requisições fora de uma janela de 5 minutos são recusadas dos dois lados.

Toda requisição carrega operator e timestamp. As que enviamos a vocês carregam também request_id e currency.

Falhas de autenticação

Operador desconhecido, operador desativado, segredo ausente, assinatura errada: todos devolvem o mesmo 401 INVALID_SIGNATURE.

O que o provedor expõe

operador → provedor

URL base: https://api-games.greenstudios.com.br/v1

A versão 1.2 (20 de outubro de 2026) traz a marca; a 1.1 trouxe as free bets. As duas só acrescentam rotas e campos, por isso as rotas continuam em /v1.

POST /v1/games todos os jogos liberados para o operador
{"operator":"test-operator", "timestamp":1755680000000,
 "currency":"BRL", "limit":100, "offset":0}
{
  "status": "ok",
  "operator": "test-operator",
  "count": 1,
  "total": 60,
  "offset": 0,
  "currencies": ["BRL", "USD"],
  "games": [
    {
      "game_id": "ronda-online",
      "name": "Ronda Online",
      "studio": "greenstudios",
      "category": "LIVE",
      "demo": true, "mobile": true, "desktop": true, "freebet": true,
      "image_url": "https://greenstudios.com.br/thumbnails/300x300/ronda-online-thumb.webp",
      "currency": "BRL",
      "min_bet": …, "max_bet": …, "max_win": …,
      "freebet_limits": {…}
    }
  ]
}

count é o tamanho desta página, total é o catálogo inteiro. total maior que count significa que há mais: peçam a página seguinte com offset.

freebet diz se o jogo aceita free bets (Free bets).

Os três limites

Eles aparecem somente quando vocês enviam currency, valem para aquela moeda, e estão em centavos inteiros.

CampoSignificado
min_betmenor aposta aceita
max_betmaior aposta aceita
max_winteto de pagamento; prêmio acima é pago no teto
Limite ausente

Um campo de limite ausente significa que o limite é desconhecido, não que não há limite.

Limites de free bet

Também só com currency, nos jogos com freebet: true: a faixa que a /v1/freebets/grant aceita naquela moeda.

"freebet_limits": {"min_amount":100, "max_amount":10000, "min_rounds":1,
                   "max_rounds":1000, "max_total":100000, "max_expiry_days":30}

amount é por rodada e max_total limita rounds × amount. Campo max_* ausente não tem teto; bloco ausente quer dizer faixa desconhecida: não concedam.

image_url não aparece quando o jogo não tem arte. currencies é o conjunto de moedas habilitadas para vocês.

POST /v1/launch abre um jogo para um jogador

Devolve a URL para carregar num iframe ou redirecionar.

{
  "operator": "test-operator", "timestamp": 1755680000000,
  "game_id": "ronda-online",
  "player_id": "618004",
  "token": "seu-token-de-sessao",
  "currency": "BRL",
  "language": "pt",
  "demo": false, "mobile": true,
  "lobby_url": "https://seu-site/lobby",
  "deposit_url": "https://seu-site/deposito"
}
CampoObrigatórioObservações
game_idsimtem que estar no seu catálogo
tokenexceto em demovai em toda chamada de carteira
player_idexceto em demoo seu identificador de jogador
currencyrecomendadoa moeda da sessão
languagenãoISO 639-1. Padrão: en
demo, mobilenãodemo dispensa token e jogador
freebetsnãofree bets concedidas no início da sessão (abaixo)
brandnãouma das marcas de vocês: escolhe a identidade (Marca)
{"status":"ok", "url":"https://…/?gameId=…&casinoId=…&session=…&lang=…"}

A URL leva um identificador session. O token e o player_id de vocês não vão nela: nós os recebemos de servidor para servidor e trocamos pelo identificador. Abram a URL dentro de 15 minutos.

Erros: MISSING_GAME_ID, MISSING_TOKEN, MISSING_PLAYER_ID, INVALID_TOKEN (400), GAME_NOT_FOUND (404).

Marca

A Green Studios cadastra a identidade de vocês: logo, logo do verso da carta, cores e uma splash de carregamento. Sem brand, todo jogo abre com a identidade do operador de vocês.

Têm vários sites? Enviem brand no /v1/launch e cada site ganha a sua identidade. Uma marca que não cadastramos abre com a identidade do operador. Vale no launch real e no demo.

OndeO que muda
jogos de provedorlogo, logo da carta, cores, exibição de saldo e regras
todos os jogosa splash de carregamento, quando ligada para a marca

brand: 1 a 100 caracteres entre letras minúsculas, dígitos e . _ -. Informem as marcas de vocês durante a integração.

Free bets

Vocês concedem rodadas grátis a um jogador. Ele as joga no jogo, e a carteira de vocês as vê como em Rodadas grátis na carteira. Exige um freebet_mode no provisionamento (capacidades).

POST /v1/freebets/grant concede rodadas grátis a um jogador
{
  "operator": "test-operator", "timestamp": 1755680000000,
  "freebet_id": "promo-2026-001",
  "player_id": "618004",
  "game_id": "ronda-online",
  "currency": "BRL",
  "rounds": 10, "amount": 100,
  "expires_at": "2026-11-30T23:59:59Z"
}
CampoObrigatórioObservações
freebet_idsimo id de vocês e a chave de idempotência. 1 a 128 caracteres: letras, dígitos, . _ : -
player_idsimo jogador não precisa ter jogado antes
game_idsimum jogo com freebet: true
currencysima moeda da conta do jogador
roundssimquantidade de rodadas grátis
amountsimaposta por rodada, em centavos, dentro de freebet_limits
expires_atnãoRFC 3339. Padrão: 72 horas
{
  "status": "ok",
  "freebet_id": "promo-2026-001", "grant_id": "8c1f4c2e-…",
  "state": "granted",
  "player_id": "618004", "game_id": "ronda-online", "currency": "BRL",
  "amount": 100, "rounds_total": 10, "rounds_remaining": 10,
  "expires_at": "2026-11-30T23:59:59Z", "created_at": "2026-10-19T18:00:00Z"
}

O mesmo freebet_id com os mesmos campos devolve a concessão original. Com campos diferentes: 409 FREEBET_DUPLICATE.

Cada rodada grátis é uma rodada de exatamente amount, naquele jogo e naquela moeda.

POST /v1/freebets/cancel cancela uma concessão
{"operator":"test-operator", "timestamp":1755680000000, "freebet_id":"promo-2026-001"}
{"status":"ok", "freebet_id":"promo-2026-001", "grant_id":"8c1f4c2e-…",
 "state":"cancelled", "rounds_total":10, "rounds_remaining":6,
 "cancelled_at":"2026-10-20T09:00:00Z"}

Rodadas já jogadas continuam jogadas. Repetir o cancelamento devolve o mesmo 200.

POST /v1/freebets/status uma concessão, ou as de um jogador
{"operator":"test-operator", "timestamp":1755680000000, "freebet_id":"promo-2026-001"}

Devolve a concessão, no formato da resposta da concessão. Enviem player_id no lugar de freebet_id para as concessões do jogador, da mais recente para a mais antiga, até 100:

{"status":"ok", "player_id":"618004", "count":1, "freebets":[{…}]}

Estados

stateSignificado
grantednenhuma rodada jogada ainda
in_progressparte das rodadas jogada
completedencerrada: todas as rodadas jogadas, ou fechada com o pagamento no modo aggregate
expiredvenceu antes de todas as rodadas serem jogadas
cancelledcancelada

Concessão no launch

Enviem freebets no /v1/launch para conceder no início da sessão. Cada item é uma concessão sem player_id; currency e game_id assumem os valores do launch.

"freebets": [{"freebet_id":"promo-2026-001", "rounds":10, "amount":100}]

A resposta do launch ganha freebets: um resultado por item, na mesma ordem. O sucesso é a concessão; a recusa é {"freebet_id":…, "status":"error", "error_code":…, "message":…}. Uma free bet recusada nunca impede o launch. Até 10 por launch, nenhuma em demo.

Códigos de erro

CódigoHTTPSignificadoRetentar?
INVALID_REQUEST400campo ausente ou inválido, ou fora de freebet_limitsnão
GAME_NOT_SUPPORTED400jogo fora do catálogo de vocês, ou sem free betsnão
CURRENCY_NOT_SUPPORTED400moeda não habilitada para este jogonão
FREEBETS_NOT_ENABLED403free bets não habilitadas para o operador de vocêsnão
FREEBET_NOT_FOUND404freebet_id desconhecidonão
FREEBET_DUPLICATE409freebet_id já usado com outros camposnão
FREEBET_ALREADY_USED409todas as rodadas jogadasnão
FREEBET_EXPIRED409concessão vencidanão
FREEBET_ALREADY_CANCELLED409concessão canceladanão
INTERNAL_ERROR502o nosso lado falhousim, o mesmo pedido

Corpo de erro: {"status":"error", "error_code":…, "message":…}.

Moedas e idiomas

Todo valor monetário é em centavos inteiros. 1500 significa 15,00 na moeda da sessão.

Campo monetário em unidades da moeda, ou em ponto flutuante, é inválido.

Moedas

São 29 moedas suportadas. O conjunto habilitado para um operador volta em currencies no POST /v1/games.

ARS BDT BOB BRL CAD CDF CLP COP CRC EUR GYD INR KES KRW LSL MXN NGN PEN PHP PKR PYG TOP TZS UAH USD UYU VES XAF ZAR

Idiomas

pt, en e es. Qualquer outro valor, e a ausência de language, cai em en.

O que o operador expõe

provedor → operador

Quatro rotas.

POST {wallet_url}/balance     token → jogador, saldo, moeda
POST {wallet_url}/bet         débito
POST {wallet_url}/win         crédito
POST {wallet_url}/rollback    estorno

Formato da resposta

{"status":"ok", "balance":98500, "currency":"BRL",
 "player_id":"618004", "transaction_id":"…"}
{"status":"error", "error_code":"INSUFFICIENT_FUNDS", "error_message":"…"}

balance é centavos inteiros, depois da operação, e é obrigatório no sucesso.

currency é a moeda da conta. Enviem em toda resposta.

As requisições que enviamos

RotaCampos
/balanceplayer_id, token
/betplayer_id, token, amount, transaction_id, round_id, freebet_id
/winplayer_id, token, amount, transaction_id, bet_id, round_id, type, freebet_id
/rollbackplayer_id, token, amount, transaction_id, bet_id, round_id, external_bet_id

Mais timestamp, request_id e currency em todas, e game_id quando o jogo é conhecido.

type no /winSignificado
winprêmio de rodada paga
freebetprêmio de rodada grátis
freebet_payouttotal de uma concessão, modo aggregate
zero_wincrédito de valor zero que fecha uma rodada

Códigos de erro

CódigoSignificadoRetentamos?
INSUFFICIENT_FUNDSsaldo insuficientenão
TOKEN_EXPIREDsessão expiradanão
TOKEN_INVALIDsessão nunca existiunão
PLAYER_NOT_FOUNDjogador desconhecidonão
BET_NOT_FOUNDa aposta referenciada não existenão
CURRENCY_MISMATCHmoeda errada para esta contanão
INTERNAL_ERRORo seu lado falhousim

Qualquer código que não reconhecemos é tratado como retentável.

Rodadas grátis na carteira

freebet_id vai em todo /bet e /win: null nas rodadas pagas e, nas grátis, o freebet_id de vocês (o nosso grant_id quando a concessão veio do nosso backoffice).

Modo per_round

RotaNa rodada grátis
/betamount: 0, freebet_id e freebet_amount: a aposta nominal, só para a contabilidade de vocês
/wintype: "freebet", freebet_id, amount = o pagamento completo. Rodada grátis perdida termina com type: "zero_win" se vocês declararam zero_win
/rollbacknunca é enviado: a rodada grátis não debitou nada

Modo aggregate

Nenhum tráfego por rodada. Quando a concessão fecha (rodadas jogadas, vencida ou cancelada), um par só:

POST /bet   amount 0, freebet_id, transaction_id "fb-<grant_id>"
POST /win   type "freebet_payout", amount = total ganho, bet_id = esse transaction_id

Total zero também é enviado: é ele que fecha o bônus do lado de vocês.

Identidade e sessão

1

O token é a identidade da sessão.

2

O token é aleatório.

Ele não pode ser igual ao player_id, derivado dele, nem ser o identificador de sessão do site de vocês. Recusamos com INVALID_TOKEN o launch cujo token seja igual ao player_id ou tenha menos de 16 caracteres.

3

Um token novo a cada /v1/launch.

4

Um token pertence a um jogador só.

Se um segundo jogador chegar com um token já em uso, recusamos a sessão e nada é vinculado.

5

A carteira de vocês atesta o dono do token.

Toda resposta de sucesso do /balance traz player_id: o jogador a quem aquele token pertence. Quando ele diverge do jogador da sessão, recusamos e o jogo não abre.

6

Token vencido recusa toda chamada.

/balance, /bet, /win e /rollback devolvem TOKEN_EXPIRED.

As regras que realmente quebram integrações

1

Idempotência

O mesmo transaction_id nunca pode debitar duas vezes. Retentamos quando não recebemos resposta.

Guardem a resposta, não só o id: a segunda chamada devolve o mesmo saldo da primeira.

Recusas de negócio não são memorizadas.

2

Um crédito de valor zero é válido e tem que ser aceito

Ele é o evento terminal de uma rodada perdida.

3

Erro de negócio é HTTP 200, não 5xx

Saldo insuficiente é um 200 com error_code. Tratamos 5xx como falha de transporte e retentamos.

4

A moeda pertence à sessão, não ao operador

Ela é fixada no launch e é enviada em toda chamada de carteira.

5

Exatamente um evento terminal por rodada

Toda rodada termina exatamente uma vez: com um prêmio, ou com um crédito de valor zero.

6

freebet_id está sempre presente

null na rodada paga. Decidam pelo valor, nunca pela presença da chave.

7

O /bet da rodada grátis tem amount: 0

Nunca debitem freebet_amount: é a aposta nominal, para relatório.

8

Rodada grátis nunca é estornada

Nenhum /rollback é enviado para ela, porque ela não debitou nada.

9

O prêmio da rodada grátis tem type: "freebet"

amount é o pagamento completo da rodada. É o type que o distingue de um prêmio pago.

10

O pagamento agregado chega mesmo quando é zero

Fechem o bônus no freebet_payout, seja qual for o valor.

Capacidades opcionais

Declaradas no provisionamento.

CapacidadeO que significa
freebet_mode: per_roundrodadas grátis uma a uma: um /bet de 0 e o seu /win por rodada
freebet_mode: aggregaterodadas grátis produzem um /bet de 0 e um crédito no fim
zero_winvocês exigem o crédito de valor zero para fechar uma rodada grátis perdida

Os dois modos de freebet são mutuamente exclusivos. Sem freebet_mode, as free bets ficam desligadas e a /v1/freebets/grant devolve 403 FREEBETS_NOT_ENABLED.

Green Studios · Integración

Integration API v1.2

Las dos direcciones

IntegraciónDirecciónQué transporta
Catálogo y launch operador → proveedor qué juegos existen, abrir un juego para un jugador, y free bets
Billetera proveedor → operador saldo, débito, crédito, reembolso

Autenticación: un solo esquema, en ambas direcciones

X-Signature: hex(HMAC-SHA256(cuerpo_de_la_peticion, secreto))

Firmen el cuerpo exactamente como se envía.

El timestamp va dentro del cuerpo firmado, en milisegundos. No hay cabecera aparte. Las peticiones fuera de una ventana de 5 minutos se rechazan en ambos lados.

Cada petición lleva operator y timestamp. Las que les enviamos llevan además request_id y currency.

Fallos de autenticación

Operador desconocido, operador desactivado, secreto ausente, firma incorrecta: todos devuelven el mismo 401 INVALID_SIGNATURE.

Lo que expone el proveedor

operador → proveedor

URL base: https://api-games.greenstudios.com.br/v1

La versión 1.2 (20 de octubre de 2026) trae la marca; la 1.1 trajo las free bets. Las dos solo agregan rutas y campos, por eso las rutas siguen en /v1.

POST /v1/games todos los juegos habilitados para el operador
{"operator":"test-operator", "timestamp":1755680000000,
 "currency":"BRL", "limit":100, "offset":0}
{
  "status": "ok",
  "operator": "test-operator",
  "count": 1,
  "total": 60,
  "offset": 0,
  "currencies": ["BRL", "USD"],
  "games": [
    {
      "game_id": "ronda-online",
      "name": "Ronda Online",
      "studio": "greenstudios",
      "category": "LIVE",
      "demo": true, "mobile": true, "desktop": true, "freebet": true,
      "image_url": "https://greenstudios.com.br/thumbnails/300x300/ronda-online-thumb.webp",
      "currency": "BRL",
      "min_bet": …, "max_bet": …, "max_win": …,
      "freebet_limits": {…}
    }
  ]
}

count es el tamaño de esta página, total es el catálogo completo. Un total mayor que count significa que hay más: pidan la página siguiente con offset.

freebet dice si el juego acepta free bets (Free bets).

Los tres límites

Aparecen solo cuando envían currency, valen para esa moneda, y van en céntimos enteros.

CampoSignificado
min_betapuesta mínima aceptada
max_betapuesta máxima aceptada
max_wintecho de pago; un premio mayor se paga en el techo
Límite ausente

Un campo de límite ausente significa que el límite es desconocido, no que no haya límite.

Límites de free bet

También solo con currency, en los juegos con freebet: true: el rango que /v1/freebets/grant acepta en esa moneda.

"freebet_limits": {"min_amount":100, "max_amount":10000, "min_rounds":1,
                   "max_rounds":1000, "max_total":100000, "max_expiry_days":30}

amount es por ronda y max_total limita rounds × amount. Un campo max_* ausente no tiene techo; un bloque ausente significa rango desconocido: no concedan.

image_url no aparece cuando el juego no tiene arte. currencies es el conjunto de monedas habilitadas para ustedes.

POST /v1/launch abre un juego para un jugador

Devuelve la URL para cargar en un iframe o redirigir.

{
  "operator": "test-operator", "timestamp": 1755680000000,
  "game_id": "ronda-online",
  "player_id": "618004",
  "token": "su-token-de-sesion",
  "currency": "BRL",
  "language": "es",
  "demo": false, "mobile": true,
  "lobby_url": "https://su-sitio/lobby",
  "deposit_url": "https://su-sitio/deposito"
}
CampoObligatorioNotas
game_idsídebe estar en su catálogo
tokensalvo en demova en cada llamada de billetera
player_idsalvo en demosu identificador de jugador
currencyrecomendadola moneda de la sesión
languagenoISO 639-1. Por defecto: en
demo, mobilenodemo no necesita token ni jugador
freebetsnofree bets concedidas al inicio de la sesión (abajo)
brandnouna de sus marcas: elige la identidad (Marca)
{"status":"ok", "url":"https://…/?gameId=…&casinoId=…&session=…&lang=…"}

La URL lleva un identificador session. Su token y su player_id no van en ella: los recibimos de servidor a servidor y los cambiamos por ese identificador. Abran la URL dentro de 15 minutos.

Errores: MISSING_GAME_ID, MISSING_TOKEN, MISSING_PLAYER_ID, INVALID_TOKEN (400), GAME_NOT_FOUND (404).

Marca

Green Studios registra su identidad: logo, logo del reverso de la carta, colores y una splash de carga. Sin brand, todo juego abre con la identidad de su operador.

¿Tienen varios sitios? Envíen brand en /v1/launch y cada sitio tiene su identidad. Una marca que no registramos abre con la identidad del operador. Vale en el launch real y en el demo.

DóndeQué cambia
juegos de proveedorlogo, logo de la carta, colores, visualización de saldo y reglas
todos los juegosla splash de carga, cuando está activada para la marca

brand: 1 a 100 caracteres entre letras minúsculas, dígitos y . _ -. Infórmennos sus marcas durante la integración.

Free bets

Ustedes conceden rondas gratis a un jugador. Él las juega en el juego, y su billetera las ve como en Rondas gratis en la billetera. Requiere un freebet_mode en el aprovisionamiento (capacidades).

POST /v1/freebets/grant concede rondas gratis a un jugador
{
  "operator": "test-operator", "timestamp": 1755680000000,
  "freebet_id": "promo-2026-001",
  "player_id": "618004",
  "game_id": "ronda-online",
  "currency": "BRL",
  "rounds": 10, "amount": 100,
  "expires_at": "2026-11-30T23:59:59Z"
}
CampoObligatorioNotas
freebet_idsísu id y la clave de idempotencia. 1 a 128 caracteres: letras, dígitos, . _ : -
player_idsíel jugador no necesita haber jugado antes
game_idsíun juego con freebet: true
currencysíla moneda de la cuenta del jugador
roundssícantidad de rondas gratis
amountsíapuesta por ronda, en céntimos, dentro de freebet_limits
expires_atnoRFC 3339. Por defecto: 72 horas
{
  "status": "ok",
  "freebet_id": "promo-2026-001", "grant_id": "8c1f4c2e-…",
  "state": "granted",
  "player_id": "618004", "game_id": "ronda-online", "currency": "BRL",
  "amount": 100, "rounds_total": 10, "rounds_remaining": 10,
  "expires_at": "2026-11-30T23:59:59Z", "created_at": "2026-10-19T18:00:00Z"
}

El mismo freebet_id con los mismos campos devuelve la concesión original. Con campos distintos: 409 FREEBET_DUPLICATE.

Cada ronda gratis es una ronda de exactamente amount, en ese juego y en esa moneda.

POST /v1/freebets/cancel cancela una concesión
{"operator":"test-operator", "timestamp":1755680000000, "freebet_id":"promo-2026-001"}
{"status":"ok", "freebet_id":"promo-2026-001", "grant_id":"8c1f4c2e-…",
 "state":"cancelled", "rounds_total":10, "rounds_remaining":6,
 "cancelled_at":"2026-10-20T09:00:00Z"}

Las rondas ya jugadas siguen jugadas. Repetir la cancelación devuelve el mismo 200.

POST /v1/freebets/status una concesión, o las de un jugador
{"operator":"test-operator", "timestamp":1755680000000, "freebet_id":"promo-2026-001"}

Devuelve la concesión, con el formato de la respuesta de concesión. Envíen player_id en lugar de freebet_id para las concesiones del jugador, de la más reciente a la más antigua, hasta 100:

{"status":"ok", "player_id":"618004", "count":1, "freebets":[{…}]}

Estados

stateSignificado
grantedninguna ronda jugada todavía
in_progressparte de las rondas jugada
completedcerrada: todas las rondas jugadas, o cerrada con el pago en el modo aggregate
expiredvenció antes de que se jugaran todas las rondas
cancelledcancelada

Concesión en el launch

Envíen freebets en /v1/launch para conceder al inicio de la sesión. Cada ítem es una concesión sin player_id; currency y game_id toman los valores del launch.

"freebets": [{"freebet_id":"promo-2026-001", "rounds":10, "amount":100}]

La respuesta del launch suma freebets: un resultado por ítem, en el mismo orden. El éxito es la concesión; el rechazo es {"freebet_id":…, "status":"error", "error_code":…, "message":…}. Una free bet rechazada nunca impide el launch. Hasta 10 por launch, ninguna en demo.

Códigos de error

CódigoHTTPSignificado¿Reintentar?
INVALID_REQUEST400campo ausente o inválido, o fuera de freebet_limitsno
GAME_NOT_SUPPORTED400juego fuera de su catálogo, o sin free betsno
CURRENCY_NOT_SUPPORTED400moneda no habilitada para este juegono
FREEBETS_NOT_ENABLED403free bets no habilitadas para su operadorno
FREEBET_NOT_FOUND404freebet_id desconocidono
FREEBET_DUPLICATE409freebet_id ya usado con otros camposno
FREEBET_ALREADY_USED409todas las rondas jugadasno
FREEBET_EXPIRED409concesión vencidano
FREEBET_ALREADY_CANCELLED409concesión canceladano
INTERNAL_ERROR502nuestro lado fallósí, la misma petición

Cuerpo de error: {"status":"error", "error_code":…, "message":…}.

Monedas e idiomas

Todo importe monetario va en céntimos enteros. 1500 significa 15,00 en la moneda de la sesión.

Un campo monetario en unidades de la moneda, o en coma flotante, es inválido.

Monedas

Son 29 monedas admitidas. El conjunto habilitado para un operador vuelve en currencies en POST /v1/games.

ARS BDT BOB BRL CAD CDF CLP COP CRC EUR GYD INR KES KRW LSL MXN NGN PEN PHP PKR PYG TOP TZS UAH USD UYU VES XAF ZAR

Idiomas

pt, en y es. Cualquier otro valor, y la ausencia de language, cae en en.

Lo que expone el operador

proveedor → operador

Cuatro rutas.

POST {wallet_url}/balance     token → jugador, saldo, moneda
POST {wallet_url}/bet         débito
POST {wallet_url}/win         crédito
POST {wallet_url}/rollback    reembolso

Formato de la respuesta

{"status":"ok", "balance":98500, "currency":"BRL",
 "player_id":"618004", "transaction_id":"…"}
{"status":"error", "error_code":"INSUFFICIENT_FUNDS", "error_message":"…"}

balance es céntimos enteros, después de la operación, y es obligatorio en el éxito.

currency es la moneda de la cuenta. Envíenla en cada respuesta.

Las peticiones que enviamos

RutaCampos
/balanceplayer_id, token
/betplayer_id, token, amount, transaction_id, round_id, freebet_id
/winplayer_id, token, amount, transaction_id, bet_id, round_id, type, freebet_id
/rollbackplayer_id, token, amount, transaction_id, bet_id, round_id, external_bet_id

Más timestamp, request_id y currency en todas, y game_id cuando el juego es conocido.

type en /winSignificado
winpremio de una ronda pagada
freebetpremio de una ronda gratis
freebet_payouttotal de una concesión, modo aggregate
zero_wincrédito de importe cero que cierra una ronda

Códigos de error

CódigoSignificado¿Reintentamos?
INSUFFICIENT_FUNDSsaldo insuficienteno
TOKEN_EXPIREDsesión expiradano
TOKEN_INVALIDla sesión nunca existióno
PLAYER_NOT_FOUNDjugador desconocidono
BET_NOT_FOUNDla apuesta referenciada no existeno
CURRENCY_MISMATCHmoneda equivocada para esta cuentano
INTERNAL_ERRORel lado del operador fallósí

Cualquier código que no reconozcamos se trata como reintentable.

Rondas gratis en la billetera

freebet_id va en todo /bet y /win: null en las rondas pagadas y, en las gratis, su freebet_id (nuestro grant_id cuando la concesión vino de nuestro backoffice).

Modo per_round

RutaEn la ronda gratis
/betamount: 0, freebet_id y freebet_amount: la apuesta nominal, solo para su contabilidad
/wintype: "freebet", freebet_id, amount = el pago completo. Una ronda gratis perdida termina con type: "zero_win" si declararon zero_win
/rollbacknunca se envía: la ronda gratis no debitó nada

Modo aggregate

Ningún tráfico por ronda. Cuando la concesión cierra (rondas jugadas, vencida o cancelada), un solo par:

POST /bet   amount 0, freebet_id, transaction_id "fb-<grant_id>"
POST /win   type "freebet_payout", amount = total ganado, bet_id = ese transaction_id

Un total de cero también se envía: es lo que cierra el bono de su lado.

Identidad y sesión

1

El token es la identidad de la sesión.

2

El token es aleatorio.

No puede ser igual al player_id, derivado de él, ni ser el identificador de sesión de su sitio. Rechazamos con INVALID_TOKEN el launch cuyo token sea igual al player_id o tenga menos de 16 caracteres.

3

Un token nuevo en cada /v1/launch.

4

Un token pertenece a un solo jugador.

Si un segundo jugador llega con un token ya en uso, rechazamos la sesión y no vinculamos nada.

5

Su billetera atesta al dueño del token.

Toda respuesta de éxito de /balance trae player_id: el jugador al que pertenece ese token. Cuando difiere del jugador de la sesión, rechazamos y el juego no abre.

6

Un token vencido rechaza toda llamada.

/balance, /bet, /win y /rollback devuelven TOKEN_EXPIRED.

Las reglas que de verdad rompen integraciones

1

Idempotencia

El mismo transaction_id nunca debe debitar dos veces. Reintentamos cuando no recibimos respuesta.

Guarden la respuesta, no solo el id: la segunda llamada devuelve el mismo saldo que la primera.

Los rechazos de negocio no se memorizan.

2

Un crédito de importe cero es válido y debe aceptarse

Es el evento terminal de una ronda perdida.

3

Los errores de negocio son HTTP 200, no 5xx

Saldo insuficiente es un 200 con error_code. Tratamos 5xx como fallo de transporte y reintentamos.

4

La moneda pertenece a la sesión, no al operador

Se fija en el launch y se envía en cada llamada de billetera.

5

Exactamente un evento terminal por ronda

Cada ronda termina exactamente una vez: con un premio, o con un crédito de importe cero.

6

freebet_id siempre está presente

null en la ronda pagada. Decidan por el valor, nunca por la presencia de la clave.

7

El /bet de la ronda gratis tiene amount: 0

Nunca debiten freebet_amount: es la apuesta nominal, para reportes.

8

Una ronda gratis nunca se reembolsa

No se envía /rollback para ella, porque no debitó nada.

9

El premio de la ronda gratis tiene type: "freebet"

amount es el pago completo de la ronda. Es el type lo que lo distingue de un premio pagado.

10

El pago agregado llega incluso cuando es cero

Cierren el bono en freebet_payout, sea cual sea el importe.

Capacidades opcionales

Se declaran en el aprovisionamiento.

CapacidadQué significa
freebet_mode: per_roundrondas gratis una a una: un /bet de 0 y su /win por ronda
freebet_mode: aggregatelas rondas gratis producen un /bet de 0 y un crédito al final
zero_winnecesitan el crédito de importe cero para cerrar una ronda gratis perdida

Los dos modos de freebet son mutuamente excluyentes. Sin freebet_mode, las free bets quedan apagadas y /v1/freebets/grant devuelve 403 FREEBETS_NOT_ENABLED.