Developers / Docs / Builder
Pick where it runs and what your players get: several platforms, games, ways to play and shows at once. You get every step in order, with each file written for your picks and where to put it.
Your integration
4 steps. Every file below is written for these picks and updates as you change them.
One key for every game, mode and show. It lives on your server; pages and apps only ever see launch URLs.
# .env on your server only. Never in a page, an app or a repository.
PLAYMEZ_API_KEY=spk_…One file with every route your screens call. It holds the key, checks what is offered, and asks Playmez for launch URLs.
// server/playmez.mjs: every Playmez call your app needs. Node 18+ with Express.
// Mount it behind your own sign-in: app.use("/playmez", playmez)
import express from "express";
const API = "https://api.playmez.com";
const KEY = process.env.PLAYMEZ_API_KEY; // spk_… from your portal. Never send it to a page or an app.
/** The games and modes you offer. Anything else is refused before it reaches Playmez. */
const OFFER = {
ludo: ["computer"],
};
export const playmez = express.Router();
playmez.use(express.json());
/** Your signed-in user. Replace with your own session or auth middleware. */
function currentUser(req) {
if (!req.user) throw failure(401, "SIGN_IN", "Sign in to play");
return { id: String(req.user.id), name: String(req.user.name ?? "Player").slice(0, 32) };
}
function failure(status, code, message) {
return Object.assign(new Error(message ?? code), { status, code });
}
/** One call to the Playmez API with your key. Playmez errors come back as { status, code }. */
async function playmezApi(method, path, body) {
const res = await fetch(API + path, {
method,
headers: { "Content-Type": "application/json", "X-Api-Key": KEY },
body: body === undefined ? undefined : JSON.stringify(body),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw failure(res.status, data.code ?? "PLAYMEZ_ERROR", Array.isArray(data.message) ? data.message.join("; ") : data.message);
return data;
}
/** Wraps a handler: its value is the JSON answer, a thrown failure becomes { code, message } with its status. */
const route = (handler) => async (req, res) => {
try {
res.json(await handler(req));
} catch (err) {
res.status(err.status ?? 500).json({ code: err.code ?? "FAILED", message: err.message });
}
};
// Games. One route opens every game in every mode you offer; the page or app mounts the launchUrl it returns.
playmez.post("/launch", route(async (req) => {
const user = currentUser(req);
const { game, mode, tableCode } = req.body;
if (!OFFER[game]?.includes(mode)) throw failure(400, "NOT_OFFERED", "That game or mode is not offered");
const launch = { game, externalUserId: user.id, displayName: user.name };
const { launchUrl } = await playmezApi("POST", "/partner/launch", launch);
return { launchUrl }; // one use, valid for five minutes
}));One package per platform, the same version everywhere. It opens launch URLs and passes events back to your code.
# Nothing to install: the screen in the next step loads the SDK with one script tag.
# Prefer a bundler? npm install playmez-embedA ready screen with 1 game. It calls only your server, opens each launch, and comes back when the player leaves.
Your players' screen: games, join with a code and live shows, as you picked.
<!-- playmez.html: your games screen. It calls only your own server (/playmez/…), never Playmez with a key. -->
<script src="https://playmez.com/sdk/playmez.js"></script>
<section id="playmez-lobby">
<h2>Games</h2>
<ul id="playmez-games"></ul>
</section>
<p id="playmez-message" role="status"></p>
<div id="playmez-frame"></div>
<script>
const GAMES = [
{ id: "ludo", name: "Ludo", height: 720, modes: ["computer"] },
];
const MODE_LABEL = { computer: "vs computer" };
const $ = (id) => document.getElementById(id);
const say = (text) => ($("playmez-message").textContent = text);
let current = null;
/** Calls your server, signed in as your user; a failure throws its code (INVALID_CODE, ROOM_CLOSED, …). */
async function server(method, path, body) {
const res = await fetch("/playmez" + path, {
method,
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: body === undefined ? undefined : JSON.stringify(body),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.code || "FAILED");
return data;
}
function button(label, onClick) {
const b = document.createElement("button");
b.type = "button";
b.textContent = label;
b.addEventListener("click", onClick);
return b;
}
/** Back to the lobby: removes the frame and shows the list again. */
function back(text = "") {
current?.unmount();
current = null;
$("playmez-lobby").hidden = false;
say(text);
}
/** Hides the lobby and opens a launch URL; leaving the game brings the lobby back. */
function open(launchUrl, height = 720) {
$("playmez-lobby").hidden = true;
current = Playmez.mount($("playmez-frame"), {
launchUrl,
height,
onEvent: (e) => {
if (e.type === "close") back();
if (e.type === "error") back("That did not open (" + e.code + "). Try again.");
},
});
}
async function play(game, mode) {
say("Opening " + game.name + "…");
try {
const { launchUrl } = await server("POST", "/launch", { game: game.id, mode });
open(launchUrl, game.height);
say("");
} catch (err) {
say("Could not open " + game.name + " (" + err.message + ").");
}
}
for (const game of GAMES) {
const li = document.createElement("li");
li.append(game.name, " ", ...game.modes.map((mode) => button(MODE_LABEL[mode], () => play(game, mode))));
$("playmez-games").append(li);
}
</script>Ludoludo
Tick each one as you go. Nothing here is saved.
| Code | Meaning |
|---|---|
| INVALID_API_KEY | Unknown, rotated or switched-off key. Same on every /partner route. |
| LAUNCH_CAP | Unverified partner over its 1,000 live launches today (UTC). Sandbox launches are never capped. |
| LAUNCH_EXPIRED | The launch URL was older than five minutes. |
| LAUNCH_USED | The launch URL was opened once already. A reload of the same frame keeps its session. |
| PARTNER_DISABLED | Games are switched off for this partner. |
| NO_LAUNCH | The frame URL lost its launch token. Mount launchUrl as returned. |