Any avogames account can publish. Sign up — Google or email and password — then open the Developer Portal.
All developer docs, one page
Every article in full — the whole help centre in a single fetch. Point an AI assistant (or yourself) here to read it all at once.
Developer Support & FAQs
Accounts, the portal, rules, and how to reach us.
Developer Support & FAQs
Accounts, the portal, rules, and how to reach us.
It is at /developer, linked from the homepage and the footer. It lists your games; each has a dashboard with its status, versions, and the buttons to submit it or upload a new build.
Developer Support & FAQs
Accounts, the portal, rules, and how to reach us.
Email us from the contact page. Include the link to your game's dashboard and you save a round trip.
Write to us before you build if you need a published title renamed, or you are unsure whether your content is allowed.
Developer Support & FAQs
Accounts, the portal, rules, and how to reach us.
Browser games, broadly. The game page must describe the game you actually shipped — the icon comes from the real build.
- Mature themes are fine with an honest description; deceptive framing is not.
Developer Support & FAQs
Accounts, the portal, rules, and how to reach us.
- Anything illegal where we operate. Sexual content involving minors means instant, permanent removal and a report.
- Malware, or builds that change behaviour after review.
- No cryptomining.
- No reaching outside your own frame — do not read the parent page, its cookies or its storage.
- No injecting your own ads into the avogames shell.
- Keep the build online. Repeated downtime gets a game unpublished until it is fixed.
Game Submission
Getting a game from your machine onto the catalogue.
- 1. Press Upload New Game. A title is all you need to save a draft.
- 2. Fill in the details and drag your icon onto the Media drop zone — see image sizes.
- 3. From the dashboard, Upload New Version with your iframe URL. It lands in development, visible only to you.
- 4. Press Submit for review. We play it and reply by email. Approved games appear in the catalogue.
Nothing is visible to players until we publish it. Drafts and development builds are yours alone.
Game Submission
Getting a game from your machine onto the catalogue.
- The iframe URL is https and loads in a private window.
- Your server does not block framing — see My iframe will not connect.
- Description says what the player does in the first two lines.
- Instructions name the controls.
Game Submission
Getting a game from your machine onto the catalogue.
Icon — one image, 500 × 400 pixels or larger. PNG, JPG, GIF or WebP, up to 6 MB. We validate the file contents, not the extension.
Drag it onto the Media drop zone, or click to browse. It uploads immediately; the × on the thumbnail removes it.
Game Submission
Getting a game from your machine onto the catalogue.
Titles lock once a game is published, because links, bookmarks and the catalogue card all point at it. Before publication you can rename freely. Need one changed after that? Ask us.
Game Submission
Getting a game from your machine onto the catalogue.
On the dashboard, scroll to Delete this game, press Delete game…, and retype the exact title to confirm.
Deleting takes the whole record and removes the uploaded icon from disk. There is no undo. To pause a live game instead, ask us to unpublish it.
Game Development
Hosting, embedding and player sign-in.
On your server. avogames does not host builds — on Upload New Version you give us the iframe URL of the page we embed. You keep control of deploys.
- HTTPS. Browsers block mixed content, so an http:// game will not load.
- Framing allowed — see My iframe will not connect.
Game Development
Hosting, embedding and player sign-in.
Nearly always your server telling the browser not to allow framing: remove X-Frame-Options and allow avogames in your CSP.
Content-Security-Policy: frame-ancestors https://avogames.com
- The URL is http:// while avogames is https:// — the browser blocks mixed content.
- A redirect in the chain drops to http:// or lands somewhere that blocks framing.
- Cookies your game relies on are not
SameSite=None; Secure, so they are dropped inside a third-party frame. - The page requires a login your player does not have — embeds get no session from you.
Test with your own page embedded in a plain HTML file on another domain. If it fails there, it will fail here.
Game Development
Hosting, embedding and player sign-in.
Your build runs inside a frame on the /games/:slug page — the player never leaves avogames. For that to be worth anything, the frame has to arrive knowing who the player is; otherwise every visit is anonymous and your game has to show its own login screen. This page explains what avogames passes into the iframe, what each value is for, and the two places your game must use it: the backend, then the frontend.
What arrives in the iframe URL
When a signed-in player opens your game, the play page appends five parameters to the iframe URL you registered on Upload New Version:
https://<your-game>.example.com/play
?user_id=507f1f77bcf86cd799439011
&login_session=$2a$10$N9qo8uLOickgx2ZMRZoMye.qXjzLpQ9xDdLVsqRfX1YcD6R0dYzXy
&token=4f8b2c1a9d3e5f7a8b9c0d1e
&platform=avogames
&from_global=1
user_id— the player's avogames account id. You bind your own account to it (store it asglobal_id) so the same avogames account always lands on the same game account.login_session— a bcrypt hash ofuser_id + SESSION_KEY, minted at login. It proves the identity was issued by avogames, and your server can verify it offline — no network call — because you hold the sharedSESSION_KEYas yourbcrypt_hash_key.token— a random value stored on the account and replaced on every login. It proves this URL came from a live session rather than a replayed old link; as soon as the player signs in again, a leaked URL stops working.platform=avogames— which publisher issued the identity, so your server knows where the account actually lives. The same build can be reachable from several publishers.from_global=1— tells your server to enter its platform-login path when it sees these parameters.
A signed-out visitor gets the iframe URL without any of this, and your game shows whatever it shows anonymously. In practice avogames promotes first-time visitors to a guest account on Play, so the frame rarely loads with no identity at all.
Your backend: verify, then bind
The request that loads your page receives these parameters on the query string. A minimal implementation, in the order every game should follow:
// A minimal backend, in the order every game should follow.
// bcrypt_hash_key must be avogames' SESSION_KEY; secret_key must be SECRET_KEY.
// 1. Verify the identity offline - no network call.
const valid = await bcrypt.compare(user_id + bcrypt_hash_key, login_session);
if (!valid) return reject_login("not minted by avogames");
// 2. A returning player already has a local account bound by global_id.
let local = await User.findOne({ global_id: user_id });
if (!local) {
// 3. First visit: fetch the authoritative account from avogames.
const resp = await axios.post("https://avogames.com/get_user_data", {
api_key: secret_key,
user_id: user_id,
token: token, // proves this URL came from a live session
});
if (resp.data.error) return reject_login("no such account or stale token");
// 4. Create your account from resp.data.user (or link it onto an
// existing one by email / steam_id) and bind it:
local = await User.create(
Object.assign(pick(resp.data.user, ["username", "email"]), {
global_id: user_id,
})
);
}
// 5. Mint your own session and hand it to the frontend.
const session_id = await bcrypt.hash(server_session_secret + local.id, 10);
render_page({ user_id: local.id, session_id: session_id });
The account fetch in step 3 is the one server-to-server call your backend makes. Its contract:
POST /get_user_data (also aliased /validate_global_token)
in: { api_key, user_id, token }
out: { user: { id, username, email, ... } } success
{ error: 1 } wrong api_key or missing user_id
{ error: 2 } unknown user, or token is not current
Your frontend: carry the session
Render the server's session into the page (hidden inputs are the usual way) and have the boot code read them into the request params every request makes. Your backend re-validates them each time. That is the second half of the session: the backend established it on the request that loaded the page; the frontend carries it on every request after.
<!-- your play page renders the session the backend just minted -->
<input type="hidden" id="user_id" value="<%= req.session.user_id %>">
<input type="hidden" id="session_id" value="<%= req.session.session_id %>">
// boot code reads them once and sends them with every request
var socket_params = {
user_id: document.getElementById("user_id").value,
session_id: document.getElementById("session_id").value,
};
No cookies cross the domain boundary. The frame is a different origin, so the browser would drop avogames' cookies anyway (SameSite). The identity travels in the URL and is verified server-to-server — keep your own session cookie on your own domain with normal SameSite settings.
The two shared secrets
SESSION_KEY(avogames) = yourbcrypt_hash_key— verifieslogin_sessionwithout a network call.SECRET_KEY(avogames) = yourapi_key— authenticates your server-to-server/get_user_datacall.
Game Development
Hosting, embedding and player sign-in.
This is the exact flow that runs when a signed-in avogames player presses Play on matchess. It is the reference integration: matchess verifies the identity in both places — offline on its own server, then again against avogames — and binds the account by global_id so the player keeps their progress every time they come back.
1. The player signs in on avogames
- Login mints the session (
SessionController.create):authenticated = true, the account'suser_id, a freshlogin_session=bcrypt(user_id + SESSION_KEY), and a fresh randomtokenwritten onto the account. - Guests get the same shape (
GuestService), so even a visitor who never registers has a real account behind the game.
2. The play page hands the identity to the frame
Opening /games/matchess finds matchess' production build and appends the session to its iframe URL (GamesController.withPlayerIdentity). The frame loads something like:
https://<matchess-host>/play
?user_id=<avogames user_id>
&login_session=$2a$10$<hash of user_id + SESSION_KEY>
&token=<current token>
&platform=avogames
&from_global=1
3. matchess' backend verifies and binds
- The request passes matchess'
sessionAuthpolicy, which routes it throughSessionService.proceed_request. Seeingfrom_global, it runsglobal_login_flow. - First, offline:
bcrypt.compare(user_id + bcrypt_hash_key, login_session).bcrypt_hash_keyis avogames'SESSION_KEY, so this proves the identity is genuine with no network call. - Then it looks up a local account by
global_id= the avogamesuser_id. A returning player is found here and signed straight in. - A first-time player is not — so matchess calls back to
POST /validate_global_token(the same endpoint as/get_user_data) with{ api_key, user_id, token }. avogames returns the account only while the token is current. matchess links onto an existing local account by steam_id or email, or registers a fresh one carryingglobal_id. - matchess mints its own
session_id(bcrypt of its session secret + the local user id), setsuser_id+session_idon the session, and the page renders them into hidden inputs.
The code matchess actually runs — SessionService.global_login_flow, trimmed to the essentials (platform, locale and publisher are resolved earlier in the function):
// SessionService.global_login_flow - matchess, trimmed to the essentials
const valid = await bcryptjs.compare(
req.query.user_id + Config.params().bcrypt_hash_key,
req.query.login_session
);
if (!valid) return { error: "wrong_user_connection_1" };
let user = (await User.find({ global_id: req.query.user_id, server: 1 }))[0];
if (!user) {
// first visit: pull the authoritative account from avogames
const resp = await request_global_server(
{ api_key: Config.params().secret_key,
user_id: req.query.user_id,
token: req.query.token },
"validate_global_token",
platform === "avogames" ? "https://avogames.com/" : undefined
);
const g = resp && resp.data && resp.data.user;
if (g && g.email) user = (await User.find({ email: g.email.toLowerCase(), server: 1 }))[0];
if (!user) user = await global_local_registration_flow(req, locale, platform, publisher);
}
const session_id = await bcryptjs.hash(sails.config.session.secret + user.id, 10);
return {
session: {
authenticated: true, user_id: user.id, session_id: session_id,
publisher: "global", global_session_id: req.query.login_session,
}
};
4. matchess' frontend carries the session
- Boot code reads the hidden inputs into
socket_params = { user_id, session_id }. - Every request the game makes carries them, and
sessionAuthre-verifies thesession_ideach time. The player is fully signed in — inventory, progress, everything — on their avogames account.
5. Every other game, the same account
The same login_session + token are appended to every game's iframe URL. Each game runs the same two checks and stores the same global_id, so the account an avogames player is signed into anywhere on the platform is their one avogames account — created once, found on every return, no re-login and no duplicates. That is why the session, handled on both the game's backend and frontend, makes every subsequent game an instant sign-in.
Publisher routing: matchess asks avogames (PLATFORM_SERVER, default https://avogames.com) only when platform=avogames. Steam and the other publishers are still validated against matchess' own global server — the identity always goes back to whichever server actually issued it.
Publishing
Statuses, versions and updates.
- Ready to submit — a draft. Only you can see it.
- In review — with us. We play it and reply by email.
- Published — live in the catalogue. The title locks at this point.
Publishing
Statuses, versions and updates.
A game can have many versions; exactly one of them is live.
- Development — where every upload starts. Reachable by you for testing.
- Production — the build players get.
Pressing Publish to production promotes a version and demotes the previous one, so there is never more than one live build.
Publishing
Statuses, versions and updates.
Upload a new version, check it in development, promote it to production. You do not resubmit the game for review — review is about the game, not each release.
Because you host the build, you can also deploy behind the same URL and players get it immediately; registering a version gives you a labelled rollback point.