OGS for game developers Markdown

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 a type field.
  • The launcher posts to your iframe's contentWindow. Your page posts to window.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. An ogs:instance without a title labels nothing.
  • Validate what you receive: ignore anything whose event.source is not window.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.