Reference
Operations Every operation travels in the same envelope. A platform declares the ops it answers in its capabilities; the hub routes only to platforms that declared them. This page is generated from the schemas in spec/schemas/ops.
Joining
# hub.hello@1
Platform โ Hub Required
Register or update a platform. The hub checks https://<platform_id>/.well-known/sgip.json lists the same key.
Payload Field Type Notes namerequired string max 60 characters iconstring max 8 characters inboxrequired string visitstring public_keyrequired string capabilitiesrequired op []
Result Field Type Notes registeredrequired object platform_idrequired platformId hub_public_keyrequired string
Example payload {
"name" : "Berlin Days" ,
"icon" : "๐ป" ,
"inbox" : "https://berlin.example/sgip/inbox" ,
"visit" : "https://berlin.example/sgip/visit" ,
"public_key" : "A6EHv/POEL4dcN0Y50vAmWfk1jCbpQ1fHdyGZBJVMbg=" ,
"capabilities" : [
"hub.ping@1",
"player.lookup@1",
"message.send@1",
"visit.end@1"
]
}result {
"registered" : true ,
"platform_id" : "berlin.example" ,
"hub_public_key" : "1A4Pun+lbVo1w72cjWIq2WRNyVw+1DT4PehBGHINEts="
}
JSON Schema
# hub.ping@1
Both Required
Health check, in either direction.
Payload An empty object.
Result Field Type Notes platformrequired string timerequired timestamp
Example payload {}result {
"platform" : "berlin.example" ,
"time" : "2026-10-09T12:00 :00 Z"
}
JSON Schema
# hub.check@1
Platform โ Hub
Platform โ Hub. Ask the hub to test this platform against the contract (section 9.1). Signed with the platform's own key; only the platform can ask for its own check.
Payload An empty object.
Result Field Type Notes conformsrequired boolean checksrequired object[] checks[].namerequired string checks[].passedrequired boolean checks[].requiredrequired boolean checks[].detailrequired string
Example payload {}result {
"conforms" : true ,
"checks" : [
{
"name" : "Descriptor is served" ,
"passed" : true ,
"required" : true ,
"detail" : "https://berlin.example/.well-known/sgip.json"
},
{
"name" : "Answers a hub-signed hub.ping@1" ,
"passed" : true ,
"required" : true ,
"detail" : ""
},
{
"name" : "Rejects a replayed nonce" ,
"passed" : true ,
"required" : true ,
"detail" : ""
},
{
"name" : "Visit page loads" ,
"passed" : true ,
"required" : false ,
"detail" : "https://berlin.example/sgip/visit answered 200."
}
]
}
JSON Schema
Players
# player.lookup@1
Platform โ Hub โ Platform Required
Public profile of a discoverable player. not_found for players who have not opted in.
Payload Field Type Notes playerrequired playerAddress
Result Field Type Notes playerrequired playerAddress subrequired string display_namerequired string avatarobject badgesstring[]
Example payload {
"player" : "lena@berlin.example"
}result {
"player" : "lena@berlin.example" ,
"sub" : "01JA00000000000000000000SB" ,
"display_name" : "Lena from Kreuzberg" ,
"avatar" : {
"color" : "#e76f51"
},
"badges" : [
"newcomer"
]
}
JSON Schema
# player.profile@1
Platform โ Hub โ Platform
A player's public card: what other games may show about them. Platforms MUST answer not_found for players who are not discoverable, exactly as player.lookup@1 does.
Payload Field Type Notes playerrequired playerAddress
Result Field Type Notes playerrequired playerAddress displayrequired display biostring max 280 characters sincetimestamp careerobject career.fieldcareerField career.titlerequired string max 60 characters career.levelinteger career.top_levelinteger skillsobject[] skills[].skillrequired skill skills[].levelrequired integer 0โ10 homestring The district they live in, never an exact home address.. max 60 characters presencepresence
Example payload {
"player" : "lena@berlin.example"
}result {
"player" : "lena@berlin.example" ,
"display" : {
"name" : "Lena from Kreuzberg" ,
"color" : "#e76f51" ,
"badges" : [
"Newcomer"
]
},
"since" : "2026-10-08T22:09 :50 Z" ,
"career" : {
"field" : "technology" ,
"title" : "Developer" ,
"level" : 1 ,
"top_level" : 4
},
"skills" : [
{
"skill" : "coding" ,
"level" : 4
},
{
"skill" : "music" ,
"level" : 2
}
],
"home" : "Kreuzberg" ,
"presence" : "online"
}
JSON Schema
# presence.update@1
Platform โ Hub โ subscribers Queued if offline
A player's presence changed. Only sent for discoverable players; the hub fans it out to subscribers.
Payload Field Type Notes playerrequired playerAddress statusrequired presence activityobject What they are doing, if they share it. activity.verbrequired verb activity.labelstring max 60 characters activity.placeplaceAddress activity.posepose
Result An empty object.
Example payload {
"player" : "lena@berlin.example" ,
"status" : "online" ,
"activity" : {
"verb" : "eat" ,
"label" : "Currywurst at the Imbiss" ,
"pose" : "stand"
}
}result {}
JSON Schema
# presence.subscribe@1
Platform โ Hub
Receive presence.update@1 for these players.
Payload Field Type Notes playersrequired playerAddress []
Result Field Type Notes subscribedrequired integer
Example payload {
"players" : [
"tunde@lagos.example"
]
}result {
"subscribed" : 1
}
JSON Schema
Messages and invites
# message.send@1
Platform โ Hub โ Platform Required Queued if offline
A direct message between players. Envelope from.player and to.player are required.
Payload Field Type Notes textrequired plainText from_namestring max 60 characters langstring max 12 characters
Result An empty object.
Example payload {
"text" : "How far? Greetings from Lagos" ,
"from_name" : "Tunde" ,
"lang" : "en"
}result {
"delivered" : true
}
JSON Schema
# message.receipt@1
Platform โ Hub โ Platform Queued if offline
Delivered or read receipts for messages the recipient got.
Payload Field Type Notes message_idsrequired ulid []statusrequired "delivered" | "read"
Result An empty object.
Example payload {
"message_ids" : [
"01JA0000000000000000000011"
],
"status" : "read"
}result {
"updated" : 1
}
JSON Schema
# invite.send@1
Platform โ Hub โ Platform Queued if offline
Invite a player to visit the sender's game.
Payload Field Type Notes invite_idrequired ulid from_namestring max 60 characters hostrequired platformId placeplaceAddress notestring max 200 characters expires_attimestamp
Result An empty object.
Example payload {
"invite_id" : "01JA00000000000000000000N1" ,
"from_name" : "Lena" ,
"host" : "berlin.example" ,
"place" : "place://berlin.example/mitte/hauptbahnhof" ,
"note" : "Currywurst is on me" ,
"expires_at" : "2026-10-10T12:00 :00 Z"
}result {
"received" : true
}
JSON Schema
# invite.respond@1
Platform โ Hub โ Platform Queued if offline
Accept or decline an invite.
Payload Field Type Notes invite_idrequired ulid acceptedrequired boolean
Result An empty object.
Example payload {
"invite_id" : "01JA00000000000000000000N1" ,
"accepted" : true
}result {}
JSON Schema
Visits
# visit.request@1
Home โ Hub
Get (or refresh, with the same visit_id) a passport and launch URL for a host platform.
Payload Field Type Notes playerrequired playerAddress subrequired string max 64 characters hostrequired platformId visit_idulid arrivalplaceAddress scopesscope []displaydisplay budgetmoney Sent with the pay scope: the most the host may charge on this visit, in a home currency
Result Field Type Notes visit_idrequired ulid passportrequired string launch_urlrequired string host_originstring host_namestring expires_atrequired integer When the passport expires, in Unix seconds: its `exp` claim embedobject embed.allowedboolean
Example payload {
"player" : "tunde@lagos.example" ,
"sub" : "01JA00000000000000000000SB" ,
"host" : "berlin.example" ,
"scopes" : [
"visit",
"chat",
"act"
],
"display" : {
"name" : "Tunde" ,
"color" : "#2e4a6b"
}
}result {
"visit_id" : "01JA00000000000000000000V1" ,
"passport" : "eyJhbGciOiJFZERTQSJ9.e30.c2ln" ,
"launch_url" : "https://berlin.example/sgip/visit" ,
"host_origin" : "https://berlin.example" ,
"host_name" : "Berlin Days" ,
"expires_at" : 1791480900 ,
"embed" : {
"allowed" : true
}
}
JSON Schema
# visit.end@1
Host โ Hub โ Home Queued if offline
The visit is over; the home game takes the player back and caps each souvenir at ยฑ20.
Payload Field Type Notes visit_idrequired ulid playerrequired playerAddress reasonrequired "left" | "idle" | "removed" | "expired"duration_sinteger minutesinteger Game minutes that passed for the guest souvenirsstatChanges last_placeplaceAddress summarystring max 200 characters activitiesobject[] What the guest did, by verb, so the home game can tell the story. activities[].verbrequired verb activities[].countrequired integer
Result An empty object.
Example payload {
"visit_id" : "01JA00000000000000000000V1" ,
"player" : "tunde@lagos.example" ,
"reason" : "left" ,
"duration_s" : 640 ,
"minutes" : 120 ,
"souvenirs" : {
"mood" : 12 ,
"hunger" : -8
},
"last_place" : "place://berlin.example/kreuzberg/kneipe-am-kanal" ,
"activities" : [
{
"verb" : "drink" ,
"count" : 1
},
{
"verb" : "explore" ,
"count" : 2
}
]
}result {
"returned" : true
}
JSON Schema
# travel.quote@1
Platform โ Hub โ Platform
Where a traveller would arrive in the host game, and how long it takes. The fare is charged by the game the player leaves.
Payload
Result Field Type Notes moderequired transportMode arrivalrequired placeAddress arrival_namestring duration_minutesinteger gatewaysobject[]
Example payload {
"mode" : "flight" ,
"from" : "place://lagos.example/ikeja/airport"
}result {
"mode" : "flight" ,
"arrival" : "place://berlin.example/mitte/hauptbahnhof" ,
"arrival_name" : "Berlin Hauptbahnhof" ,
"duration_minutes" : 420
}
JSON Schema
The world
# world.places@1
Platform โ Hub โ Platform
A platform's public places.
Payload An empty object.
Result Field Type Notes placesrequired object[] places[].idrequired placeAddress places[].namerequired string places[].districtstring places[].kindrequired placeKind places[].latnumber places[].lngnumber places[].gatewayboolean places[].guests_allowedboolean places[].capacityinteger places[].hoursarray | null places[].osmstring The OpenStreetMap element it came from, like node/1504407936.
Example payload {}result {
"places" : [
{
"id" : "place://berlin.example/kreuzberg/kneipe-am-kanal" ,
"name" : "Kneipe am Kanal" ,
"district" : "kreuzberg" ,
"kind" : "pub" ,
"lat" : 52.4966 ,
"lng" : 13.4244 ,
"gateway" : false ,
"guests_allowed" : true ,
"capacity" : 60 ,
"hours" : [
16 ,
26
]
}
]
}
JSON Schema
# place.status@1
Platform โ Hub โ Platform
How a place is right now: open or shut, how busy, how many players are there. Cheap to answer; meant for "is it worth going?" before a visit.
Payload Field Type Notes placerequired placeAddress
Result Field Type Notes placerequired placeAddress kindplaceKind openrequired boolean busynessnumber 0 empty, 1 packed.. 0โ1 playersinteger Players there now, counting visitors. A count only, never who. opens_attimestamp closes_attimestamp eventsstring[] Ids of world.events@1 events on now.
Example payload {
"place" : "place://berlin.example/neukoelln/stadtbad"
}result {
"place" : "place://berlin.example/neukoelln/stadtbad" ,
"kind" : "swimming_pool" ,
"open" : true ,
"busyness" : 0.6 ,
"players" : 3 ,
"closes_at" : "2026-10-09T20:00 :00 Z"
}
JSON Schema
# world.events@1
Platform โ Hub โ Platform
What is on at a platform: concerts, matches, market days, weddings. Lets other games show "what's on in Berlin tonight" and invite players to it.
Payload Field Type Notes fromtimestamp totimestamp kindeventKind limitinteger 1โ100
Result Field Type Notes eventsrequired object[] events[].idrequired string max 64 characters events[].kindrequired eventKind events[].titlerequired string max 80 characters events[].descriptionstring max 500 characters events[].placerequired placeAddress events[].starts_atrequired timestamp events[].ends_attimestamp events[].pricemoney events[].verbverb events[].gamegameKind events[].guests_allowedboolean Whether visitors from other games may attend.
Example payload {
"kind" : "match"
}result {
"events" : [
{
"id" : "berlin-derby" ,
"kind" : "match" ,
"title" : "The Berlin derby" ,
"place" : "place://berlin.example/koepenick/stadium" ,
"starts_at" : "2026-10-10T15:30 :00 Z" ,
"ends_at" : "2026-10-10T17:30 :00 Z" ,
"price" : {
"amount" : 2500 ,
"currency" : "EUR"
},
"verb" : "watch" ,
"guests_allowed" : true
}
]
}
JSON Schema
# action.list@1
Platform โ Hub โ Platform
What a guest can do at a place, in shared verbs and stats.
Payload Field Type Notes placerequired placeAddress
Result Field Type Notes placerequired placeAddress actionsrequired object[] actions[].idrequired string actions[].verbrequired verb actions[].labelrequired string actions[].duration_minutesrequired integer actions[].pricerequired money actions[].paysmoney | nullactions[].effectsrequired statChanges actions[].tagsverb []More verbs that also describe it (a pub quiz: play, talk). actions[].posepose actions[].skillsobject Skill points it trains. actions[].interactioninteraction actions[].gamegameKind actions[].guests_allowedboolean Hosts SHOULD list only what guests may do; this flags the rest when they are shown.
Example payload {
"place" : "place://berlin.example/kreuzberg/kneipe-am-kanal"
}result {
"place" : "place://berlin.example/kreuzberg/kneipe-am-kanal" ,
"actions" : [
{
"id" : "beer" ,
"verb" : "drink" ,
"label" : "Glass of Berliner Pils" ,
"duration_minutes" : 40 ,
"price" : {
"amount" : 450 ,
"currency" : "EUR"
},
"pays" : null ,
"effects" : {
"mood" : 10 ,
"social" : 15 ,
"energy" : -5
},
"pose" : "sit" ,
"tags" : [
"talk"
]
}
]
}
JSON Schema
Money
# wallet.balance@1
Host โ Hub
Host โ Hub. What a visitor can spend, in each of the host's currencies that has a rate for the budget's currency (section 7.4).
Payload Field Type Notes playerrequired playerAddress visit_idrequired ulid
Result Field Type Notes spendablerequired money []
Example payload {
"player" : "tunde@lagos.example" ,
"visit_id" : "01JA00000000000000000000V1"
}result {
"spendable" : [
{
"amount" : 10000 ,
"currency" : "EUR"
}
]
}
JSON Schema
# wallet.charge@1
Host โ Hub
Host โ Hub. Charge a visitor's home money for something they chose. The envelope id is the charge id (section 7.4).
Payload Field Type Notes playerrequired playerAddress visit_idrequired ulid amountrequired money In one of the host's currencies descriptionrequired string max 120 characters verbverb
Result Field Type Notes charge_idrequired ulid chargedrequired money debitedrequired money What the home game took, in its currency spendablerequired money []
Example payload {
"player" : "tunde@lagos.example" ,
"visit_id" : "01JA00000000000000000000V1" ,
"amount" : {
"amount" : 450 ,
"currency" : "EUR"
},
"description" : "Glass of Berliner Pils" ,
"verb" : "drink"
}result {
"charge_id" : "01JA2B3C4D5E6F7G8H9J0K1M2N" ,
"charged" : {
"amount" : 450 ,
"currency" : "EUR"
},
"debited" : {
"amount" : 225000 ,
"currency" : "NGN"
},
"spendable" : [
{
"amount" : 9550 ,
"currency" : "EUR"
}
]
}
JSON Schema
# wallet.refund@1
Host โ Hub
Host โ Hub. Return all or part of a charge within 24 hours. Without an amount, returns whatever remains (section 7.4).
Payload Field Type Notes charge_idrequired ulid amountmoney In the charge's currency; at most what remains unrefunded reasonstring max 120 characters
Result Field Type Notes refund_idrequired ulid refundedrequired money creditedrequired money What the home game returned, in its currency
Example payload {
"charge_id" : "01JA2B3C4D5E6F7G8H9J0K1M2N" ,
"amount" : {
"amount" : 450 ,
"currency" : "EUR"
},
"reason" : "Closed before it was served"
}result {
"refund_id" : "01JA2B3C4D5E6F7G8H9J0K1M3P" ,
"refunded" : {
"amount" : 450 ,
"currency" : "EUR"
},
"credited" : {
"amount" : 225000 ,
"currency" : "NGN"
}
}
JSON Schema
# wallet.read@1
Hub โ Home
Hub โ Home. A player's balance in one currency (section 7.4).
Payload Field Type Notes playerrequired playerAddress currencyrequired currency
Result Field Type Notes balancerequired money
Example payload {
"player" : "tunde@lagos.example" ,
"currency" : "NGN"
}result {
"balance" : {
"amount" : 50000000 ,
"currency" : "NGN"
}
}
JSON Schema
# wallet.debit@1
Hub โ Home
Hub โ Home. Take an amount if the player has it, in one step, or answer insufficient_funds. A repeated charge_id is the same debit; a charge_id already voided by wallet.credit@1 is refused (section 7.4).
Payload Field Type Notes charge_idrequired ulid playerrequired playerAddress amountrequired money hostrequired platformId host_amountrequired money descriptionrequired string max 120 characters
Result Field Type Notes balancerequired money
Example payload {
"charge_id" : "01JA2B3C4D5E6F7G8H9J0K1M2N" ,
"player" : "tunde@lagos.example" ,
"amount" : {
"amount" : 225000 ,
"currency" : "NGN"
},
"host" : "berlin.example" ,
"host_amount" : {
"amount" : 450 ,
"currency" : "EUR"
},
"description" : "Glass of Berliner Pils"
}result {
"balance" : {
"amount" : 49775000 ,
"currency" : "NGN"
}
}
JSON Schema
# wallet.credit@1
Hub โ Home
Hub โ Home. Return a refunded or voided amount. A repeated refund_id is the same credit. For a charge_id never debited, record it and refuse any later debit with that id (section 7.4).
Payload Field Type Notes charge_idrequired ulid refund_idrequired ulid playerrequired playerAddress amountrequired money
Result Field Type Notes balancerequired money
Example payload {
"charge_id" : "01JA2B3C4D5E6F7G8H9J0K1M2N" ,
"refund_id" : "01JA2B3C4D5E6F7G8H9J0K1M3P" ,
"player" : "tunde@lagos.example" ,
"amount" : {
"amount" : 225000 ,
"currency" : "NGN"
}
}result {
"balance" : {
"amount" : 50000000 ,
"currency" : "NGN"
}
}
JSON Schema