The two directions
| Integration | Direction | What 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.
Unknown operator, disabled operator, missing secret, wrong signature: all return
the same 401 INVALID_SIGNATURE.
What the provider exposes
operator → providerBase 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.
{"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.
| Field | Meaning |
|---|---|
min_bet | smallest accepted stake |
max_bet | largest accepted stake |
max_win | payout ceiling; a bigger prize is paid at the ceiling |
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.
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"
}
| Field | Required | Notes |
|---|---|---|
game_id | yes | must be in your catalogue |
token | unless demo | sent on every wallet call |
player_id | unless demo | your player identifier |
currency | recommended | the session currency |
language | no | ISO 639-1. Default: en |
demo, mobile | no | demo needs neither token nor player |
freebets | no | free bets granted at session start (below) |
brand | no | one 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.
| Where | What changes |
|---|---|
| provider games | logo, card logo, colours, balance and rules display |
| every game | the 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).
{
"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"
}
| Field | Required | Notes |
|---|---|---|
freebet_id | yes | your id and the idempotency key. 1 to 128 characters: letters, digits, . _ : - |
player_id | yes | the player does not need to have played before |
game_id | yes | a game with freebet: true |
currency | yes | the currency of the player's account |
rounds | yes | number of free rounds |
amount | yes | stake per round, in cents, within freebet_limits |
expires_at | no | RFC 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.
{"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.
{"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
state | Meaning |
|---|---|
granted | no round played yet |
in_progress | some rounds played |
completed | closed: all rounds played, or closed with the payout in aggregate mode |
expired | expired before all rounds were played |
cancelled | cancelled |
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
| Code | HTTP | Meaning | Retry? |
|---|---|---|---|
INVALID_REQUEST | 400 | missing or invalid field, or outside freebet_limits | no |
GAME_NOT_SUPPORTED | 400 | game not in your catalogue, or without free bets | no |
CURRENCY_NOT_SUPPORTED | 400 | currency not enabled for this game | no |
FREEBETS_NOT_ENABLED | 403 | free bets not enabled for your operator | no |
FREEBET_NOT_FOUND | 404 | unknown freebet_id | no |
FREEBET_DUPLICATE | 409 | freebet_id already used with other fields | no |
FREEBET_ALREADY_USED | 409 | all rounds played | no |
FREEBET_EXPIRED | 409 | grant expired | no |
FREEBET_ALREADY_CANCELLED | 409 | grant cancelled | no |
INTERNAL_ERROR | 502 | our side failed | yes, 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.
Languages
pt, en and es. Any other value, and an absent
language, falls back to en.
What the operator exposes
provider → operatorFour 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
| Route | Fields |
|---|---|
/balance | player_id, token |
/bet | player_id, token, amount, transaction_id, round_id, freebet_id |
/win | player_id, token, amount, transaction_id, bet_id, round_id, type, freebet_id |
/rollback | player_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 /win | Meaning |
|---|---|
win | prize of a paid round |
freebet | prize of a free round |
freebet_payout | total of a grant, aggregate mode |
zero_win | zero-amount credit that closes a round |
Error codes
| Code | Meaning | We retry? |
|---|---|---|
INSUFFICIENT_FUNDS | not enough balance | no |
TOKEN_EXPIRED | session expired | no |
TOKEN_INVALID | session never existed | no |
PLAYER_NOT_FOUND | unknown player | no |
BET_NOT_FOUND | the referenced bet does not exist | no |
CURRENCY_MISMATCH | wrong currency for this account | no |
INTERNAL_ERROR | your side failed | yes |
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
| Route | On a free round |
|---|---|
/bet | amount: 0, freebet_id, and freebet_amount: the nominal stake, for your accounting only |
/win | type: "freebet", freebet_id, amount = the full payout. A lost free round ends with type: "zero_win" if you declared zero_win |
/rollback | never 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
The token is the session identity.
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.
A new token for every /v1/launch.
A token belongs to one player.
If a second player arrives with a token already in use, we refuse the session and link nothing.
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.
An expired token refuses every call.
/balance, /bet, /win and
/rollback all return
TOKEN_EXPIRED.
The rules that actually break integrations
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.
A zero-amount credit is valid and must be accepted
It is the terminal event of a losing round.
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.
Currency belongs to the session, not to the operator
It is fixed at launch and is sent on every wallet call.
Exactly one terminal event per round
Every round ends exactly once, with a win or with a zero-amount credit.
freebet_id is always present
null on a paid round. Branch on its value, never on whether the key
exists.
A free round's /bet has amount: 0
Never debit freebet_amount: it is the nominal stake, for reporting.
A free round is never refunded
No /rollback is sent for it, because it debited nothing.
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.
The aggregate payout comes even when it is zero
Close the bonus on freebet_payout, whatever the amount.
Optional capabilities
Declared at provisioning.
| Capability | What it means |
|---|---|
freebet_mode: per_round | free rounds one by one: a /bet of 0 and its /win per round |
freebet_mode: aggregate | free rounds produce one /bet of 0 and one credit at the end |
zero_win | you 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.