You don't set up a server. You don't write sockets. You don't type a project id. Spark hands you a live multiplayer room — you paste a game into it, and it works.
spark-game.html).file://) — no server, no setup, no terminal.https:// room and puts you in it. It gives you a share link — send it to friends.Everything below reads Spark.project.id from the URL for you, so there is nothing to invent — no project id, no socket code, no protocol. Start with the-one-pattern (the tiny canonical example) and grows into Blob Arena (a full game).
Open it, View Source → copy the whole file, then replace only the drawing/game logic. Download it and double-click — it works from file:// too.
<script src="https://spark.boqsc.eu/spark-client.js"></script>
// That's it. Now on your hosted page you have:
// Spark.project.id → your public sp_... (auto-read from the URL, never type it)
// Spark.connect() → opens the realtime socket (explains any error in plain English)
const ws = await Spark.connect(Spark.project.id, 'arena'); // Spark.project.id is read from the URL automatically. You never invent a room // or a project id. If it's wrong, Spark.connect tells you what's wrong.
file://When you double-click a downloaded .html file, your browser opens it as
file:///C:/Users/.../game.html. A file:// page cannot talk to a server, so
Spark can't connect and it fails silently. You'll see something like this in the console:
WebSocket connection to 'wss://spark.boqsc.eu/api/socket?...' failed: Unsafe attempt to load URL file:///C:/Users/.../game.html ... 'file:' URLs are treated as unique security origins.
The fix: never serve the game from file://. Spark gives it a real https:// address
in one line — add this to your game and it handles both double-clicks and joins automatically:
<script src="https://spark.boqsc.eu/spark-client.js"></script>
<script>
Spark.deploy({ index: document.documentElement.outerHTML })
.then(app => { location.href = app.scaffold.site_url; });
</script>
Or just click the button above and copy the page it opens — that page is already on https://.
sp_… value is given to you by Spark — it's in the URL of your live page and it's read for you by Spark.project.id. Don't write demo_arena or anything made-up; use the real one.
<script src="https://spark.boqsc.eu/spark-client.js"></script> <script> // This makes ANY single HTML file a Spark multiplayer game. const app = await Spark.deploy({ index: document.documentElement.outerHTML }); location.href = app.scaffold.site_url; // go to the live https:// version </script>
curl -s -X POST https://spark.boqsc.eu/api/db/query \ -H "Authorization: Bearer $KEY" \ -d '{"sql":"CREATE TABLE scores(level INT, score INT)"}' curl -s -X POST https://spark.boqsc.eu/api/db/query \ -H "Authorization: Bearer $KEY" \ -d '{"sql":"INSERT INTO scores VALUES(1, 9000)"}' curl -s -X POST https://spark.boqsc.eu/api/db/query \ -H "Authorization: Bearer $KEY" \ -d '{"sql":"SELECT * FROM scores"}' # → {"ok":true,"columns":["level","score"],"rows":[[1,9000]],"usage":{...}}
curl -s -X POST "https://spark.boqsc.eu/api/files?path=index.html" \ -H "Authorization: Bearer $KEY" \ --data-binary '<h1>hi</h1>' # your site, live (no server restart needed): curl -s https://spark.boqsc.eu/~/sp_.../index.html # download API: /api/download?path=... (add &inline=1 to preview) # list: /api/files delete: /api/files?path=... (DELETE)
<script src="https://spark.boqsc.eu/spark-client.js"></script>
// provides SparkBinary (compact binary codec) and SparkInterp (smooth
// snapshot interpolation using the server ts/seq stamps).
const ws = new WebSocket(`wss://spark.boqsc.eu/api/socket?project=PROJECT_ID&room=alpha`); ws.binaryType = 'arraybuffer'; // compact binary by default ws.onmessage = e => { const m = e.data.byteLength !== undefined ? SparkBinary.decode(e.data) : JSON.parse(e.data); if (m.type === 'event') console.log(m.nickname, m.event, m.data); }; ws.send(SparkBinary.encode({type:'event', event:'move', data:{x:0.5, y:0.5}})); // ~60% smaller frames than JSON, per-room ts/seq for interpolation, and // server-authoritative push: POST /api/broadcast {room, event, data}. // Legacy JSON clients can opt in with ?fmt=j.
const interp = new SparkInterp({ delayMs: 30, historyMs: 400, extrapolateMs: 500 }); // delayMs: render slightly behind the newest sample (smoothing) // extrapolateMs: dead reckoning — glide through delivery gaps so remote // players never freeze-then-teleport; the render clock is derived from the // server ts, so observer-side movement can't shift it. // Full playbook: https://guide.boqsc.eu/multiplayer-performance.html
?fmt=j.?snap=<hz> only for very large rooms where bandwidth matters more.SparkInterp({ extrapolateMs }) for remote players.curl -s -X POST https://spark.boqsc.eu/api/run \ -H "Authorization: Bearer $KEY" \ -d '{"code":"print(6*7)"}' # → {"ok":true,"exit_code":0,"stdout":"42\n","stderr":""}
curl -s -X POST https://spark.boqsc.eu/api/register \ -H "Authorization: Bearer $KEY" \ -d '{"username":"sam","password":"hunter2"}' # forgot the key? login, then rotate for a fresh one: # POST /api/login {username,password} → info | POST /api/rotate {username,password} → new key
One click scaffolds a live multiplayer page and mints a key. No signup, no setup.
Per-key SQLite. Run raw SQL via /api/db/query. Enforced quota, one statement per call.
Upload, download, list and delete files with the key. Every file has a public URL.
Drop in index.html and your page is live at spark.boqsc.eu/~/<id>/.
WebSocket rooms on a compact binary protocol by default (with ts/seq stamps and a client helper). Presence, events, direct messages, server push via /api/broadcast.
Sandboxed Python with a timeout. No OS, network or file access.
Issued instantly, no account needed. Register a username + password later to manage and rotate it.
id (like sp_...) is public — use it to
address your site and realtime rooms. The key (like spk_...) is secret — send it as
Authorization: Bearer <key> on every API call. Keys are stored hashed server-side; the raw key
is shown once. Quotas are enforced per project, and all API responses are CORS-open for any origin.