Messages and manifest
The exact shapes OGS and a game exchange. The tables on this page are generated from the zod schemas in packages/ogs-protocol when the site is built, so they match the code. When anything here and the code disagree, the code wins.
With profile-kit you do not post or parse these by hand: see profile-kit reference. This page is for games that do, for tests that fake the launcher, and for agents that want the precise contract.
Transport
- The launcher and the TV page talk with
window.postMessage. Each message is a plain object with atypefield. - The launcher posts to your iframe's
contentWindow. Your page posts towindow.parent(profile-kit uses target origin"*"; the launcher checks the origin instead). - The launcher accepts game messages only from the origin of the current game's TV URL, and drops
anything that fails
GameToLauncherSchema. Anogs:instancewithout atitlelabels nothing. - Validate what you receive: ignore anything whose
event.sourceis notwindow.parent, and parse with the schema (LauncherToGameSchema.safeParse(event.data)). Unknown messages are ignored, never errors. - Every message is optional for the game. A game that answers nothing still runs.
Launcher → game
LauncherToGameSchema in frame.ts.
ogs:start
The sitting starts or continues. Sent on the frame's load, and again after each ogs:ready.
| Field | Type | Required | Description |
|---|---|---|---|
instanceId |
string | yes | The OGS sitting this frame is for. |
mode |
"continue" | "new" | yes | continue an earlier sitting, or start a new one. |
roster |
RosterEntry[] | yes | Who sits where: profile, role and device, when the couch picked roles. |
token |
string | yes | A game token for this game and session (aud = appId, players + sid); "" when there is none. |
players |
GamePlayer[] | no | Who's on the couch: profile id, @id, name, avatar. |
room |
RoomId | no | Join this room (another couch made it) instead of making one (multiCouch games). |
ogs:suspend
The launcher parked the frame (Home, or another game). It stays loaded: go silent.
No payload: { type: "ogs:suspend" }.
ogs:resume
Continue: the same parked frame is shown again, no reload. Resume sound.
No payload: { type: "ogs:resume" }.
Game → launcher
GameToLauncherSchema in frame.ts.
ogs:ready
The page is listening; the launcher re-sends ogs:start for the current sitting.
No payload: { type: "ogs:ready" }.
ogs:resume-point
The sitting's label (older form of ogs:instance).
| Field | Type | Required | Description |
|---|---|---|---|
label |
string | yes | For example "Mission 6". |
ogs:room
The room the TV page shows (multiCouch games): the couch session keeps it on the sitting.
| Field | Type | Required | Description |
|---|---|---|---|
room |
RoomId | yes | The game's own room code. |
ogs:instance
The sitting's report; the launcher uses report.title as its label.
| Field | Type | Required | Description |
|---|---|---|---|
report |
InstanceReport | yes | What the game says about this sitting. |
Phone page → app
In the OGS app's WebView a game page talks to the app over the app bridge, not postMessage to a
parent. profile-kit uses two bridge stores:
| Store | Direction | Content |
|---|---|---|
profile |
app → page | { status: "asking" }, { status: "ready", profile: OgsProfile } or { status: "none" } |
ogs |
page → app | event { type: "INSTANCE_REPORT", report: InstanceReport } (OgsBridgeEventSchema) |
Room games declare their TV page with cast-kit's useCastViewUrl(url); the app forwards it to the
launcher. See the contract, §3.
A typical sequence
launcher your TV page (iframe)
| load iframe (allow="autoplay; fullscreen") |
|--- ogs:start {instanceId, mode, roster, token, players} --->|
|<-------------------------- ogs:ready ---------------------- | (profile-kit, on start-up)
|--- ogs:start (again, for the current sitting) ------------> |
|<---------- ogs:instance {report: {title: "Mission 1"}} ---- |
| ... the couch plays ... |
|--- ogs:suspend -------------------------------------------> | Home: go silent, stay loaded
|--- ogs:resume --------------------------------------------> | Continue: sound back, no reload
Manifest
A game is config, not code: one Manifest (ManifestSchema in
manifest.ts). Fields with a default may be left out.
| Field | Type | Required | Description |
|---|---|---|---|
appId |
string (^[a-z0-9-]+$) |
yes | The game's id; also the aud of every game token for this game. |
name |
string (non-empty) | yes | Shown in the Library and on the TV. |
tagline |
string | no (default "") |
One line under the name in the Library. |
shape |
"couch" | "live" | "async" | yes | couch: played together in one room; live: real-time online; async: turns over days. |
tv |
"none" | "optional" | "required" | yes | Whether the game needs a TV. |
startUrl |
string (URL) | yes | Played on a phone or tablet (also the controller URL when the TV shows tvUrl). |
tvUrl |
string (URL) | no | A static TV page. Room-based games omit it: their phone page sends the room's TV URL at runtime (cast-kit useCastViewUrl). |
roles |
Role[] | no (default []) |
Who plays: each role's audience lets OGS suggest who sits where. |
art |
object | yes | The art kit (see Art kit and catalogue). |
art.tile |
string (non-empty) | yes | Older captured TV screenshot, 16:9 (required). |
art.hero |
string | no | Older captured hero image, 16:9. |
art.icon |
string (non-empty) | no | 1:1 icon. |
art.cover |
string (non-empty) | no | 2:3 cover with the title. |
art.logo |
string (non-empty) | no | Transparent logo. |
art.heroClean |
string (non-empty) | no | 16:9 hero with no text and no HUD. |
art.theme |
string (non-empty) | no | The game's music theme: a 20–40 s seamless audio loop (music only) the launcher's Home plays while the game is focused. |
art.safe |
object | no | Crops a HUD out of tile/hero when there is no clean art: zoom about a point. |
art.safe.scale |
number (> 0) | yes | Zoom factor. |
art.safe.ox |
number | yes | Zoom origin x, percent of the width. |
art.safe.oy |
number | yes | Zoom origin y, percent of the height. |
shop |
object | no (default {}) |
Shown on the game's page in the app. |
shop.ages |
string | no | For example "4+". |
shop.minutes |
[number, number] | no | Typical sitting length, [min, max] minutes. |
shop.players |
string | no | For example "2-4". |
multiCouch |
boolean | no | Several couches may join one room of this game (spec §7). Absent = single couch. |
instanceTtlMs |
number (> 0) | no (default 604800000) |
How long an instance may stay silent before it expires (ms). |
An example:
{
"appId": "space-bakery",
"name": "Space Bakery",
"tagline": "Bake for aliens, together.",
"shape": "couch",
"tv": "required",
"startUrl": "https://space-bakery.example.com/",
"roles": [
{ "id": "chef", "label": "Chef", "audience": "grownup" },
{ "id": "helper", "label": "Helper", "audience": "kid" }
],
"art": {
"icon": "/art/space-bakery/icon.png",
"cover": "/art/space-bakery/cover.jpg",
"logo": "/art/space-bakery/logo.png",
"heroClean": "/art/space-bakery/hero-clean.jpg",
"tile": "/art/space-bakery/tv.jpg"
},
"shop": { "ages": "4+", "minutes": [10, 20], "players": "2-4" }
}
No tvUrl: this is a room game whose phone page declares the room's TV page at runtime. A game with
one TV page for everyone adds "tvUrl": "https://space-bakery.example.com/tv".
Shared types
GamePlayer
Someone on the couch (ogs:start.players, GameToken.players).
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (non-empty) | yes | OGS profile id. |
handle |
string (non-empty) | yes | The profile's @handle. |
name |
string (non-empty) | yes | Display name. |
avatar |
string (URL) | yes | Avatar image URL. |
RosterEntry
Who sits where (ogs:start.roster).
| Field | Type | Required | Description |
|---|---|---|---|
profileId |
string | yes | OGS profile id. |
roleId |
string | yes | One of the manifest's roles[].id. |
deviceId |
string | no | The device this player plays on. |
InstanceReport
What a game reports about a sitting (ogs:instance, reportOgsSitting). Only instanceId, appId and status are needed.
| Field | Type | Required | Description |
|---|---|---|---|
instanceId |
string (non-empty) | yes | The sitting's id. |
appId |
string (non-empty) | yes | The game's appId. |
status |
InstanceStatus | yes | Where the sitting is. |
title |
string | no (default "") |
The sitting's label, for example "Mission 6" or "Room KQTP". |
detail |
string | no (default "") |
A second line under the label. |
yourTurn |
boolean | no | For async games: true when it's this profile's move. |
startsAt |
number | no | For scheduled sittings (a game night): when it starts, ms since epoch. |
resumeUrl |
string (URL) | no | Where to send people back in. |
InstanceStatus
One of lobby, active, suspended, waiting, completed, expired.
GameToken
The claims of a game token, as verifyOgsToken returns them.
| Field | Type | Required | Description |
|---|---|---|---|
iss |
string (non-empty) | yes | The OGS API that signed it. |
aud |
string (non-empty) | yes | The game (appId) this token is for. |
sub |
string (non-empty) | yes | OGS profile id (a session token: the host's). |
handle |
string (non-empty) | yes | The profile's @handle. |
name |
string (non-empty) | yes | Display name. |
avatar |
string (URL) | yes | Avatar image URL. |
sid |
string (non-empty) | no | The couch session (TV tokens only). |
players |
GamePlayer[] | no | Who's on the couch (TV tokens only). |
couch |
CouchClaim | no | The couch this token was issued for (TV tokens, and phones that asked with their session). |
iat |
number (integer, ≥ 0) | yes | Issued at, seconds since epoch. |
exp |
number (integer, > 0) | yes | Expiry, seconds since epoch (1 hour after iat). |
CouchClaim
The couch a token was issued for.
| Field | Type | Required | Description |
|---|---|---|---|
sid |
string (non-empty) | yes | The couch session id. Players with the same sid sit on the same couch. |
label |
string (non-empty) | yes | The couch's label (the host's name). |
Role
One of the manifest's roles.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string (non-empty) | yes | The role's id (RosterEntry.roleId). |
label |
string (non-empty) | yes | Shown to people, for example "Captain". |
audience |
"grownup" | "kid" | "little" | yes | Who the role suits: a grown-up, a kid, or a little one. |
RoomId
A string matching ^[A-Za-z0-9_-]{1,64}$: the game's own room code.