HAOLABS //GAMES
// API v1 reference

Plug into the platform.

Everything a game needs: player sign-in, records, leaderboards, and chip currency — one REST API. Register your app at /developers and start calling.

Basics

Base URL: https://games.haolabs.co/api/v1. Every response is JSON in one envelope:

{ "success": true,  "data": { … } }
{ "success": false, "error": "human-readable reason" }

POST bodies may be application/json or form-encoded — both are parsed. CORS is fully open (Access-Control-Allow-Origin: *), so browser games can call the API directly; tokens ride the Authorization header, never cookies.

Authentication

Standard OAuth 2.0 authorization-code flow. You need an app registered at /developers — that gives you a client_id, a client_secret (shown once), and your game_slug.

Send the player to the consent screen.
https://games.haolabs.co/oauth/authorize?client_id=…&redirect_uri=…&scope=identity%20records&state=…
Players who aren't signed in log in first, then approve. redirect_uri must exactly match the one you registered.
Catch the code. On approval the player lands on <redirect_uri>?code=…&state=…. The code is single-use and expires in 5 minutes. On deny you get ?error=access_denied.
Exchange it server-side (keep your secret off the client):
# POST /api/v1/oauth/token
curl -X POST https://games.haolabs.co/api/v1/oauth/token \
  -d "grant_type=authorization_code" \
  -d "client_id=hlgapp_…" -d "client_secret=hlgsec_…" -d "code=…"

# → 200
{ "success": true, "data": {
    "access_token": "hlg_9f2c…",
    "token_type": "Bearer",
    "expires_in": 2592000,
    "scope": "identity records" } }

Tokens live 30 days; there is no refresh token yet — send the player back through the (instant, already-consented) authorize flow to get a new one. Then call everything with:

Authorization: Bearer hlg_9f2c…

Scopes

ScopeGrants
identityUsername, 18+ flag, team, member-since. Always included.
recordsRead the player's game records; post scores under your game_slug.
walletRead the player's chip balance.
wallet:spendDebit and credit chips inside your game (capped — see Wallet).

Ask only for what you need — the consent screen shows the player every scope you request, and you can only request scopes your app registered.

Identity

GET/api/v1/meidentity

Who the token belongs to.

{ "success": true, "data": {
    "id": 42,
    "username": "crazy_max",
    "adult": true,
    "team": { "name": "Night Shift", "tag": "NSFT", "color": "#ff4a2d" },
    "member_since": "2026-07-09 16:12:02" } }

Records & scores

GET/api/v1/me/recordsrecords
ParamDescription
gameOptional filter. Defaults to your app's game_slug; pass ?game= (empty) for all games.
{ "success": true, "data": {
    "games":  [ { "game": "neon-runner", "played": 31, "wins": 18,
                  "best": 4200, "last_played": "2026-07-10 12:13:04" } ],
    "recent": [ { "game": "neon-runner", "mode": "arcade", "score": 4200,
                  "won": true, "at": "2026-07-10 12:13:04" } ] } }
POST/api/v1/scoresrecords

Record a result for the player, under your game_slug. It feeds their public profile, your leaderboard, and their team's standings.

FieldDescription
scoreInteger, ±100,000,000. Required.
modeYour own label, a-z0-9_-, 2-16 chars. Default match.
wonOptional 1/0 — drives win rates and team points.
curl -X POST https://games.haolabs.co/api/v1/scores \
  -H "Authorization: Bearer hlg_…" -H "Content-Type: application/json" \
  -d '{"score": 4200, "mode": "arcade", "won": 1}'

# → { "success": true, "data": { "recorded": 4200, "game": "neon-runner" } }

Limit: 100 submissions per player per day per game.

Wallet

Chips are the platform's virtual currency. Wallet routes work only when the platform's betting flag is on and the player is a verified 18+ account — check betting before showing chip features.

GET/api/v1/me/walletwallet
{ "success": true, "data": { "chips": 1250, "betting": true } }
POST/api/v1/wallet/debitwallet:spend
FieldDescription
amount1 – 2,000 chips per transaction.
reasonOptional label (≤24 chars) shown in the player's ledger.
# entry fee, buy-in, item…
{ "success": true, "data": { "chips": 700, "debited": 300 } }
# 402 { "success": false, "error": "Not enough chips" }

Cap: 5,000 chips net per player per day per app.

POST/api/v1/wallet/creditwallet:spend

Payouts and refunds. Same fields as debit. Hard invariant: across all time, your app can never credit a player more than it has debited from them — apps cannot mint chips. A credit that would cross that line returns 403 "Credit exceeds what this app has debited".

Leaderboard

GET/api/v1/leaderboardpublic

Your game's top 10 — no token needed, just your client_id. Perfect for a title-screen board.

ParamDescription
client_idRequired.
periodweekly (default) or alltime.
{ "success": true, "data": { "game": "neon-runner",
    "leaderboard": [ { "rank": 1, "name": "crazy_max", "score": 4200 }, … ] } }

Errors & limits

StatusMeaning
400Bad input — the error string says what.
401Missing/expired token, bad client credentials, or bad code.
402Not enough chips for a debit.
403Token lacks the scope, chip play unavailable (flag/under-18), or the no-minting rule.
404Unknown route.
429Rate limit (120 req/min per token) or a daily cap.

Handle 429 by backing off for the rest of the minute. All limits are per player per app, so one heavy player never throttles your whole game.

Quickstart — browser game in 20 lines

// 1. kick off sign-in (your server keeps the secret + handles /callback)
location.href = 'https://games.haolabs.co/oauth/authorize?' + new URLSearchParams({
  client_id: 'hlgapp_…', redirect_uri: 'https://yourgame.com/callback',
  scope: 'identity records', state: crypto.randomUUID() });

// 2. your server traded ?code for a token — now, from the game:
const api = (route, body) =>
  fetch('https://games.haolabs.co/api/v1/' + route, {
    method: body ? 'POST' : 'GET',
    headers: { Authorization: 'Bearer ' + token,
               ...(body && { 'Content-Type': 'application/json' }) },
    body: body && JSON.stringify(body) }).then(r => r.json());

const player = await api('me');                       // hello, {player.data.username}
await api('scores', { score: 4200, won: 1 });        // onto the boards
const top = await api('leaderboard?client_id=hlgapp_…'); // show it off

Questions or a stuck integration? Register your app at /developers — the panel shows live player counts per app so you can see connections land.