Skip to content

Developer

API documentation

Everything you need to integrate: authentication, scopes, the four endpoints, errors, rate limits and signed webhooks.

Overview

The PointsStation API lets you load points, redeem points into prepaid game pockets, reset game-account passwords, read balances, create payment links/gateways (and check their payments), and request payouts — programmatically. All endpoints live under one base URL and return a consistent JSON envelope.

text
Base URL   https://pointsstation.com/api/v1
Auth       Authorization: Bearer psk_live_…   (or  x-api-key: psk_live_…)
Format     JSON in, JSON out — every response is { "ok": true, "data": … } or { "ok": false, "error": … }

Create keys and webhook endpoints in the dashboard under API & Webhooks. Prepaid pockets are managed under Game Points. A machine-readable openapi.json is linked above (import it into Postman/Insomnia or generate a client).

Authentication

Every request authenticates with an API key. A key acts as its owning account — loads debit that account's wallet or pockets and are attributed to it. Keys look like psk_live_… and are shown in full only once at creation (we store only a hash). Send the key either way:

bash
# Preferred — bearer token
curl https://pointsstation.com/api/v1/balance -H "Authorization: Bearer psk_live_xxxxxxxx"

# Or the x-api-key header
curl https://pointsstation.com/api/v1/balance -H "x-api-key: psk_live_xxxxxxxx"

A missing/invalid/revoked key → 401 UNAUTHORIZED. If the owning account isn't active → 403 FORBIDDEN.

Scopes

Each key carries a set of scopes; a call to an endpoint whose scope the key lacks returns 403 SCOPE_REQUIRED. Agent keys may hold load, reset, payments and payouts; merchant and owner keys may hold all of them.

ScopeGrantsAgent keys
loadCreate loads (push points to a player)
allowed
redeemRedeem points from a player account into a pocket
merchant/owner only
resetReset a game-account password
allowed
balanceRead game-account / pocket balances
merchant/owner only
verifyVerify a player exists
merchant/owner only
paymentsCreate payment links/gateways & check payment status
allowed
payoutsRequest payouts (cash-outs) & check their status
allowed
accountsCreate game-panel accounts (single or bulk)
allowed

Errors

Failures return a non-2xx status and { "ok": false, "error": { "code", "message", "details?" } }. Branch on the stablecode, not the message.

A panel rejection is not an HTTP error. On POST /load a declined load returns 200 withdata.status = "FAILED" (auto-refunded). Wallet loads may also return "PENDING_REVIEW" when the provider status is unconfirmed (pocket loads treat that as FAILED and refund). Always inspect data.status.

HTTPCodeMeaning
400VALIDATION_ERRORBody failed schema validation (details = field errors).
400BAD_REQUESTMalformed JSON or a non-positive amount.
401UNAUTHORIZEDMissing, invalid or revoked API key.
403FORBIDDENOwning account is not active.
403SCOPE_REQUIREDThe key lacks the scope this endpoint needs.
403GAME_NOT_ALLOWEDAgent key restricted to an allow-list that excludes this game.
404PLAYER_NOT_FOUNDThe player doesn't exist at the provider.
404ACCOUNT_NOT_FOUNDNo game account with that username under your account (reset).
404GAME_NOT_FOUNDUnknown providerKey.
422INSUFFICIENT_FUNDSWallet balance below the charge (wallet loads).
422INSUFFICIENT_POINTSNot enough prepaid pocket points (pocket loads).
422OVER_SINGLE_LIMIT / OVER_DAILY_LIMITLoad exceeds your cap (wallet loads).
422WALLET_FROZEN / NO_WALLET / PLAYER_BLOCKEDAccount or player state blocks the load.
422PANEL_NOT_LIVE / UNSUPPORTED / PANEL_REJECTEDThe game panel can't or won't process it.
429RATE_LIMITEDToo many requests — message includes seconds to wait.
503MAINTENANCEPlatform maintenance (wallet loads only).
500INTERNALUnexpected server error.

Rate limits

Limits are per owning account, fixed-window. Exceeding one returns 429 RATE_LIMITED with a message telling you when to retry (there are no X-RateLimit-* headers).

text
POST /load           120 requests / minute
POST /redeem         120 requests / minute
GET  /balance        120 requests / minute
POST /reset           60 requests / minute
POST /payment-link    60 requests / minute
POST /payout          20 requests / minute

The read-only checks (GET /payment-link/{code}, GET /payout/{code}) have no per-route limit — poll them at a sensible interval.

Idempotency

POST /load requires an idempotencyKey (8–80 chars). Replaying the same key returns the original transaction with no second debit — safe to retry on a network error. Use a fresh key per intended load (e.g. your order id).

POST /payment-link accepts an optional idempotencyKey (8–200 chars): a retried create with the same key returns the original link instead of minting a duplicate.

POST /redeem requires an idempotencyKey (8–80 chars; body field or Idempotency-Key header) and is idempotent — replaying the same key returns the original result, never a second redeem. POST /reset is not idempotent — each call makes a new password; retry it only after confirming the first didn't succeed.

Prepaid game pockets

A pocket is a prepaid points reservoir for one game. Buy points up front (in the dashboard), then load them onto any username of that same game with POST /load + source: "pocket" — no wallet charge. Points youredeem from a player land back in that game's pocket. Pocket points are game-locked and never convert to wallet cash. Check balances with GET /balance.

GET /api/v1/games

GET/api/v1/games
scope: load

Discover the providerKey values /load accepts, each with a display name, whether the panel is live (connected), and this account's list price in ¢/point (so you can convert dollars → points). Only load games where live is true.

Example request

bash
curl https://pointsstation.com/api/v1/games -H "Authorization: Bearer psk_live_xxxxxxxx"

Example response

json
{
  "ok": true,
  "data": {
    "games": [
      {
        "key": "game-vault",
        "name": "Game Vault",
        "category": "Fish",
        "live": true,
        "ratePerPointCents": 1,
        "pointsPerDollar": 100
      },
      {
        "key": "juwa",
        "name": "Juwa",
        "category": "Slots",
        "live": true,
        "ratePerPointCents": 1,
        "pointsPerDollar": 100
      }
    ]
  }
}

Errors

401 UNAUTHORIZED403 SCOPE_REQUIRED429 RATE_LIMITED

POST /api/v1/account

POST/api/v1/account
scope: accounts
30 / min

Create one account on a game's panel. You pick the username; we generate the password and return it once. /load later requires the username to exist — create it here first.

Request body

FieldTypeReqNotes
providerKeystringGame key from GET /games (e.g. game-vault).
usernamestring (4–20)Letters/numbers only — the player's game username.
notestringOptional internal note.

Example request

bash
curl -X POST https://pointsstation.com/api/v1/account \
  -H "Authorization: Bearer psk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "providerKey": "game-vault", "username": "luckydragon88" }'

Example response

json
{
  "ok": true,
  "data": {
    "username": "luckydragon88",
    "password": "Kp7mQ9xR2tvW",
    "providerKey": "game-vault",
    "status": "ACTIVE"
  }
}

Errors

400 VALIDATION_ERROR401 UNAUTHORIZED403 SCOPE_REQUIRED404 GAME_NOT_FOUND409 ACCOUNT_EXISTS422 PANEL_REJECTED429 RATE_LIMITED

POST /api/v1/account/bulk

POST/api/v1/account/bulk
scope: accounts
5 / min

Create up to 50 accounts on ONE game in a single call. Sequential; one bad username never fails the batch — each result has its generated password (once) or an error.

Request body

FieldTypeReqNotes
providerKeystringGame key from GET /games.
usernamesstring[] (1–50)Letters/numbers only, 4–20 chars each.

Example request

bash
curl -X POST https://pointsstation.com/api/v1/account/bulk \
  -H "Authorization: Bearer psk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "providerKey": "game-vault", "usernames": ["luckydragon88", "storealpha2"] }'

Example response

json
{
  "ok": true,
  "data": {
    "created": 2,
    "failed": 0,
    "results": [
      { "username": "luckydragon88", "ok": true, "password": "Kp7mQ9xR2tvW" },
      { "username": "storealpha2", "ok": true, "password": "Bn4kT8wL5qzX" }
    ]
  }
}

Errors

400 VALIDATION_ERROR401 UNAUTHORIZED403 SCOPE_REQUIRED429 RATE_LIMITED

POST /api/v1/load

POST/api/v1/load
scope: load
120 / min

Push game points to a player. Funds from wallet cash (default) or a prepaid pocket. Returns the transaction — inspect data.status.

Request body

FieldTypeReqNotes
playerIdentifierstring (2–64)Player's game username.
pointsinteger (≥1)Points to load.
providerKeystringGame/provider key, e.g. game-vault.
idempotencyKeystring (8–80)Replay-safe key (e.g. your order id).
source"wallet" | "pocket"Default "wallet". "pocket" spends prepaid points.
gameNamestringOptional display name.

Example request

bash
curl -X POST https://pointsstation.com/api/v1/load \
  -H "Authorization: Bearer psk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "playerIdentifier": "luckydragon88",
    "points": 500,
    "providerKey": "game-vault",
    "idempotencyKey": "ord_2026_0001"
  }'

Example response

json
{
  "ok": true,
  "data": {
    "reference": "VPL-7K2M9QXR4T",
    "status": "COMPLETED",
    "amountCents": 2500,
    "points": 500,
    "fundingSource": "wallet",
    "playerUsername": "luckydragon88",
    "provider": "Game Vault",
    "providerTxnId": "PX-558210",
    "failureReason": null,
    "createdAt": "2026-06-19T14:03:21.114Z",
    "completedAt": "2026-06-19T14:03:22.880Z"
  }
}

Errors

400 VALIDATION_ERROR401 UNAUTHORIZED403 SCOPE_REQUIRED404 PLAYER_NOT_FOUND422 INSUFFICIENT_FUNDS422 INSUFFICIENT_POINTS422 OVER_DAILY_LIMIT429 RATE_LIMITED503 MAINTENANCE

POST /api/v1/redeem

POST/api/v1/redeem
scope: redeem
120 / min · merchant/owner keys only

Pull points out of a player's game account and credit them to your prepaid pocket for that game (never to wallet cash). Requires an idempotencyKey and is idempotent (same key → original result).

Request body

FieldTypeReqNotes
providerKeystringGame/provider key.
accountstring (2–64)Player username to redeem from.
pointsinteger (≥1)Points to redeem.
idempotencyKeystring (8–80)Required (body or Idempotency-Key header). Same key → original result, never a 2nd redeem.

Example request

bash
curl -X POST https://pointsstation.com/api/v1/redeem \
  -H "Authorization: Bearer psk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "providerKey": "game-vault", "account": "luckydragon88", "points": 250, "idempotencyKey": "rdm_2026_0001" }'

Example response

json
{
  "ok": true,
  "data": {
    "reference": "VPR-7KQ2M9X4PB",
    "providerKey": "game-vault",
    "providerName": "Game Vault",
    "pointsRedeemed": 250,
    "pocketBalance": 12500,
    "message": "Redeem successful"
  }
}

Errors

400 VALIDATION_ERROR401 UNAUTHORIZED403 FORBIDDEN403 SCOPE_REQUIRED422 PANEL_NOT_LIVE422 UNSUPPORTED422 PANEL_REJECTED429 RATE_LIMITED

POST /api/v1/reset

POST/api/v1/reset
scope: reset
60 / min

Regenerate the password for one of your own game accounts. Returns the new password exactly once.

Request body

FieldTypeReqNotes
providerKeystringGame/provider key.
accountstring (2–64)Your existing game-account username.

Example request

bash
curl -X POST https://pointsstation.com/api/v1/reset \
  -H "Authorization: Bearer psk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "providerKey": "game-vault", "account": "store_alpha" }'

Example response

json
{
  "ok": true,
  "data": {
    "username": "store_alpha",
    "password": "Kp7mQ9xR2tvW",
    "status": "ACTIVE"
  }
}

Errors

401 UNAUTHORIZED403 SCOPE_REQUIRED404 GAME_NOT_FOUND404 ACCOUNT_NOT_FOUND422 UNSUPPORTED422 PANEL_REJECTED429 RATE_LIMITED

GET /api/v1/balance

GET/api/v1/balance
scope: balance
120 / min

With ?account= & ?providerKey= → that game account's live panel balance. Otherwise → your prepaid pocket balances per game.

Example request

bash
# Pocket balances (all games)
curl https://pointsstation.com/api/v1/balance -H "Authorization: Bearer psk_live_xxxxxxxx"

# One account's live panel balance
curl "https://pointsstation.com/api/v1/balance?providerKey=game-vault&account=store_alpha" \
  -H "Authorization: Bearer psk_live_xxxxxxxx"

Example response

json
{
  "ok": true,
  "data": {
    "pockets": [
      {
        "id": "ckpocket123",
        "providerKey": "game-vault",
        "providerName": "Game Vault",
        "logoUrl": "/logos/game-vault.png",
        "category": "Slots",
        "pointsBalance": 4200,
        "updatedAt": "2026-06-19T14:03:22.118Z"
      }
    ]
  }
}

Errors

401 UNAUTHORIZED403 SCOPE_REQUIRED422 UNSUPPORTED429 RATE_LIMITED

POST /api/v1/payout

POST/api/v1/payout
scope: payouts
20 / min

Request a cash-out from your wallet. Funds are reserved immediately and the request enters the owner's review queue as PENDING — it is NOT auto-paid. A flat payout fee may apply; the recipient receives amount − fee. payout.paid / payout.rejected fires when the owner acts.

Request body

FieldTypeReqNotes
amountnumber ($)Gross cash-out in dollars (must exceed the payout fee).
method"cashapp" | "chime" | "crypto" | "bank" | "other"Default "cashapp".
destinationstring (4–300)Where to send the funds (e.g. $cashtag, address).
notestringOptional note.

Example request

bash
curl -X POST https://pointsstation.com/api/v1/payout \
  -H "Authorization: Bearer psk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 100, "method": "cashapp", "destination": "$mystore", "note": "weekly payout" }'

Example response

json
{
  "ok": true,
  "data": {
    "reference": "VPW-7K2M9QXR4T",
    "amountCents": 10000,
    "feeCents": 1000,
    "netCents": 9000,
    "method": "cashapp",
    "destination": "$mystore",
    "status": "PENDING",
    "createdAt": "2026-06-23T18:04:11.425Z"
  }
}

Errors

400 VALIDATION_ERROR401 UNAUTHORIZED403 FORBIDDEN403 SCOPE_REQUIRED422 INSUFFICIENT_FUNDS422 BELOW_PAYOUT_FEE429 RATE_LIMITED

GET /api/v1/payout/{code}

GET/api/v1/payout/{code}
scope: payouts
no per-route limit

Look up one of your own payouts by its reference to see its current status (PENDING / APPROVED / PAID / REJECTED / CANCELLED), amount, fee and net.

Example request

bash
curl https://pointsstation.com/api/v1/payout/VPW-7K2M9QXR4T \
  -H "Authorization: Bearer psk_live_xxxxxxxx"

Example response

json
{
  "ok": true,
  "data": {
    "reference": "VPW-7K2M9QXR4T",
    "status": "PAID",
    "amountCents": 10000,
    "feeCents": 1000,
    "netCents": 9000,
    "method": "cashapp",
    "reviewedAt": "2026-06-23T19:10:02.000Z"
  }
}

Errors

401 UNAUTHORIZED403 SCOPE_REQUIRED404 NOT_FOUND

Webhooks

Register HTTPS endpoints under API & Webhooks to receive events. We POST { event, data, sentAt } with headersx-ps-event and x-ps-signature: sha256=…. Delivery is best-effort with a 10s timeout and no retries — acknowledge with any 2xx. Agent endpoints receive load, reset, payment and payout events; redeem.completed goes to merchant/owner only.

EventWhenAgent keys
load.completedA load completed successfully.
yes
load.failedA load failed (and was refunded).
yes
load.pending_reviewA load needs manual review.
yes
reset.completedA game-account password was reset.
yes
redeem.completedPoints were redeemed into a pocket.
no
payment.receivedA customer paid one of your links/gateways.
yes
payout.paidA payout (cash-out) was marked paid.
yes
payout.rejectedA payout was rejected (funds refunded).
yes
javascript
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const app = express();
const SECRET = process.env.PS_WEBHOOK_SECRET; // "whsec_…" shown once at creation

// Capture the RAW body so the signature matches byte-for-byte.
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }));

app.post("/webhooks/pointsstation", (req, res) => {
  const sig = req.header("x-ps-signature") || "";
  const expected = "sha256=" + createHmac("sha256", SECRET).update(req.rawBody).digest("hex");
  const a = Buffer.from(sig), b = Buffer.from(expected);
  if (a.length !== b.length || !timingSafeEqual(a, b)) return res.status(401).end();

  const { event, data, sentAt } = req.body; // verified
  // handle event…
  res.sendStatus(200); // any 2xx marks delivery successful
});