Quick Start

Three steps to a live order: sign up, mint a launch token, and embed the game_url the launch call returns.

Correction

There is no domain step any more — the platform returns a ready game_url you embed.

Earlier versions of this chapter had you register a host and point one CNAME before the first launch. That step is gone. The launch call now returns a fully-formed, platform-provided game_url you embed as returned, so there is no CNAME to point and no per-merchant domain to review. What used to be four steps is three.

A launch can still be refused, but the meaning changed. merchant_domain_not_ready (409) no longer means “no enabled CNAME”; it now means the merchant is not go-live ready — its setup is not complete (for example no site_key). Merchant-owned custom domains (CNAME) still work for anyone who enabled one before this changed, but they are deprecated and no longer accepted for new registration. See Custom domain (CNAME).

Three steps take you from a signed contract to a player placing a real order in the sandbox. They are the same three in either wallet model — hosted or self-hosted only changes who holds the money and who implements the hooks, not how a player reaches the game. See Wallet modes for that choice, which you make in step 1.

1

Get signed up, pick a wallet model, collect your keys

Nothing here is self-service: the operator provisions a sandbox merchant for you. One item below is a decision rather than a deliverable — your wallet model — and it is not a flag you can flip later, so make it deliberately up front rather than discovering it once you are already integrating.

  • • Your wallet model. Hosted means player balances sit on the operator's ledger — money commands settle inside the platform and you write no callback hooks. Self-hosted means funds stay in your ledger and the operator calls your wallet hooks for every debit and credit. This is a property of your merchant record, not a flag you flip in a request.
  • • A completed merchant profile. A launch needs your merchant to be go-live ready — a settlement currency and a site_key, both set on your merchant record by the operator. You point no CNAME and register no player domain of your own: the game is served from a platform-provided host, and the launch call returns the URL for you.
  • • One API key/secret pair per environment. Sandbox keys answer 401 against production and the reverse, with no hint that the key simply belongs elsewhere.
From your onboardingenv
API_BASE_URL      = https://api.sandbox.predmask.com   # the host you sign requests against
API_KEY           = ak_9dc9a497...                    # B2B access key
API_SECRET        = <32+ bytes>                       # signing secret — server-side only, never in a browser

# That is the whole list. Two things you might expect here are deliberately absent:
#
#   merchant id — never sent. Your merchant is derived from API_KEY on every request,
#                 so there is nothing to configure and nothing to get wrong. You will
#                 still see the id in your own data; it is not a credential.
#
#   game client URL — not fixed, and not yours to build. Step 2 returns a game_url on a
#                 platform-provided host, so there is nothing to configure and nothing
#                 to redeploy. There is no default to fall back to: a merchant that is
#                 not launch-ready gets a refusal, not a URL.
#
# Two runtime strings deserve the same care as the secret, though they are short-lived:
# launch_token and game_url. The URL embeds the token, so logging the URL logs a live
# session credential. We never write either to our own logs; do the same on your side.
2

Mint a launch token from your backend

When a player opens the game, your server mints a one-time launch token with your B2B key. The player is named in the body as player_id and created on first use, so there is no separate provisioning call, and the merchant comes from the key — no console credentials are involved anywhere in this flow. Sign the exact body bytes you send. The token is single-use and short-lived — expires_in tells you the window, 60 seconds in production — so mint one per launch rather than caching it.

These three steps describe the embedded model, where the player's browser holds the session. If instead your own backend will call the player endpoints — quoting, ordering and closing on the player's behalf — you never mint a launch token at all: take a session at POST /b2b/v1/auth/session and send it as Authorization: Bearer. See Authentication.

Server-side — your backend → operatorbash
BODY='{"player_id":"PLAYER-123"}'      # sign these exact bytes, then send them

curl -X POST "$API_BASE_URL/b2b/v1/auth/launch-token" \
  -H "X-Api-Key: $API_KEY" \
  -H "X-Timestamp: $TS" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIG" \
  -H "Content-Type: application/json" \
  --data-raw "$BODY"

# player_id is your own stable id for the player, not a UUID we issue.
# There is no X-Player-Id header any more: sending one is refused with
# 400 player_id_header_retired, before the signature is even checked.
Response · 200json
{
  "launch_token": "nTpntEN2ixa7Zodgmyc4iDk4y75dAE3PEDQ7Q7yx7PE",
  "expires_in": 60,
  "game_url": "https://<game-host>/?lt=nTpntEN2ixa7Zodgmyc4iDk4y75dAE3PEDQ7Q7yx7PE"
}

The host inside game_url is provided by the platform at mint time — a platform host, not one of yours. You do not build it and you do not configure it, and there is no CNAME behind it: the platform supplies the host and the launch token carries the tenant, so a launch works with nothing of yours in the address.

A missing setup is a refusal, not a fallback

If your merchant has no registered settlement currency, this call answers a stable merchant_currency_unset (409) and returns no game_url at all. It never substitutes some other host. Treat it as a configuration state to surface to your operations team, not a transient error to retry in a loop: retrying changes nothing until the currency is registered. Minting does not run the site_key / go-live gate — that check, and its merchant_domain_not_ready (409), lives on POST /b2b/v1/auth/session, not here.

A different refusal, launch_pool_empty (503), means your merchant was ready but the platform had no host available to serve the launch at that instant. That one is transient platform-side, so a short backoff and retry is reasonable.

  1. Your backendPOST /b2b/v1/auth/launch-token

    Signed with your B2B key, with {"player_id":"…"} as the body. Returns launch_token, expires_in (60 in production), and a game_url whose host is provided by the platform — or merchant_currency_unset (409) if no settlement currency is registered, or launch_pool_empty (503) if the platform has no host to serve the launch.

  2. The player's browserGET game_url ( …/?lt=<token> )

    This is the one leg where the token rides in a URL. Do not log it, do not cache it, do not put it anywhere a page refresh can replay — mint a fresh one per launch.

  3. game-clientexchanges lt for a player session

    Served from the platform-provided host, so the session, the player API and the realtime feed are all same-origin within the frame. The token burns on first use and expires after expires_in seconds either way: a game_url is good for exactly one load.

3

Load game_url in your page and place a real order

Render game_url as returned. The game-client redeems the launch token for a session — player identity, currency and a live price feed — and because it is served from the platform host, everything it calls afterwards is same-origin within the frame: you configure no path rules, proxy nothing and maintain no route table that can drift out of step with ours.

Client-side — your pagehtml
<iframe
  src="https://<game-host>/?lt=nTpntEN2ixa7Zodgmyc4iDk4y75dAE3PEDQ7Q7yx7PE"
  allow="clipboard-write"
  style="width:100%;height:100%;border:0"
  title="PredMask"
></iframe>

Two failures show up here for the first time. A blocked frame — the console says an ancestor violates frame-ancestors — means the page doing the framing is not on the allowlist the operator holds for your parent sites; raise the origins you embed from during onboarding. A blank frame usually means the launch token was already spent or has expired — a refresh, a cached URL, or a second load of the same one — since a game_url is good for exactly one load. Mint a fresh token rather than re-rendering the old one.

Redemption burns the token: it is consumed atomically on first use and expires after expires_in seconds either way. So a game_url is good for exactly one load. Do not cache it, do not put it in a link someone might revisit, and do not let a page refresh re-render the same one — mint a fresh token server-side each time the iframe is created, or the second load lands on a token that no longer exists.

That's the whole loop.

The player picks a market, stakes from the wallet you chose in step 1, and PredMask prices, holds the exposure and settles the outcome — crediting winnings back through that same wallet.

On timing, honestly: once you are signed up and your merchant is go-live ready, the code above is a short development cycle's work — one signed server-side call and one iframe, plus the wallet hooks if you self-host. There is no DNS, certificate or domain review to wait on any more: the platform is already live, so the elapsed time is the code and the onboarding, not a domain clock nobody controls.

Next: Launch & sessions for the token and session contract, including launching straight into a single market or event; Custom domain (CNAME) for how you embed the returned URL and the deprecated custom-domain path; and Wallet modes when you are ready to hold funds yourself.