---
title: "Game SDK: vis, hvad spillerne spiller på mssgs"
description: "Udgiv en Spiller-status med en Deltag nu-knap fra dit spil, og tjek medlemskab af fællesskabet. mssgs Game SDK til native spil, browserspil og mobilspil."
canonical: https://docs.mss.gs/da/game-sdk
language: da
---

# Vis, hvad nogen spiller

Lad dit spil fortælle mssgs, hvad en spiller laver. Venner ser "Spiller" under navnet, åbner detaljerne og trykker på Deltag nu for at hoppe ind i det samme spil. Dit spil kan også tjekke, om en spiller er med i dit fællesskab.

## Det kan du

- **Udgiv en spillestatus** Spillet, hvad spilleren laver, spillerens rolle, og hvor fuld gruppen er.

- **Tilføj en Deltag nu-knap** Venner kommer ind i det samme spil, på den samme server eller i den samme lobby med ét tryk.

- **Tjek medlemskab** Spørg, om en spiller er med i dit fællesskab, og med hvilke roller.

- **Desktop, browser eller telefon** Native spil bruger den lokale bridge; browser- og mobilspil går gennem din backend.

I appen

Statussen under et navn og de detaljer, den åbner. Spillet udgav én JSON-blok; resten er appen.

- [Overblik](#overview)

- [Find klienten](#discover)

- [Bed om tilladelse](#authorize)

- [Spillestatus](#activity)

- [Deltag nu](#join)

- [Browser og telefon](#linked)

- [Reference](#reference)

## Overblik

mssgs' desktop-app kører en lille **lokal HTTP-bridge**, som et spil på den samme maskine taler med. Dit spil taler aldrig med vores servere, ser aldrig en kontoadgangskode eller et token og kan aldrig poste som spilleren. Det taler med den kopi af mssgs, som spilleren allerede er logget ind på, og den kopi bestemmer, hvad der svares.

Det kan du bruge den til:

- Registrere, at mssgs er installeret, og at nogen er logget ind.

- Læse, hvem spilleren er: user_guid, brugernavn, avatar.

- Spørge "er denne spiller med i fællesskab X?", og hvilken rolle spilleren har dér.

- Udgive en "Spiller …"-status med en **Deltag nu**-knap til andre.

- Modtage en deltagelsesoverdragelse, når nogen trykker på den knap.

### To veje ind

Et **native desktopspil** taler med den lokale bridge; det er det, de næste afsnit beskriver. Et spil i en **browser eller på en telefon** kan ikke nå den bridge. For dem udgiver din egen backend status for spillere, der har forbundet deres mssgs-konto via en QR-kode eller en kode på otte tegn: se [Browser- og mobilspil](#linked), med [CozyCity](https://cozycity.net) som det første eksempel. Selve spillestatussen er den samme blok i begge tilfælde.

### Minimal deling som standard

Scopes er bevidst ulige. Har du kun brug for at vide, "er denne person med i vores fællesskab", beder du om membership.query og angiver selv server_guid: du får ja/nej plus spillerens roller dér og lærer intet om resten af spillerens fællesskaber. Den fulde liste ligger bag et separat, højere scope, som spilleren skal godkende for sig.

## Find klienten

Bridgen lytter kun på 127.0.0.1 , på den første ledige port i et lille interval. Prøv dem i rækkefølge, indtil en svarer: **7440, 7441, 7442, 7443**. Udviklingsbuilds af mssgs lytter i stedet på **7540–7543**, så et testbuild aldrig svarer på et rigtigt spils kald.

Der kræves intet token, og svaret siger intet om spilleren, kun at mssgs er her, og om nogen er logget ind.

```json
{
  "product": "mssgs",
  "api": 1,
  "client": "desktop",
  "version": "14.2.20015",
  "platform": "darwin",
  "signed_in": true,
  "scopes": ["identity", "staff", "membership.query", "servers.list", "presence.write"]
}
```

Tjek product === "mssgs" og api , før du går videre. Svarer ingen af de fire porte, kører mssgs ikke. Tilbyd så bare din normale oplevelse i stedet for at lade spilleren vente.

## Bed om tilladelse

Alt undtagen /hello kræver et token, og et token findes først, når spilleren har godkendt dit spil i en dialog i appen. Bed kun om de scopes, du rent faktisk bruger: spilleren ser hvert enkelt for sig med en forklaring og kan fjerne fluebenet ved dem enkeltvis.

```bash
curl -X POST http://127.0.0.1:7440/mssgs/v1/authorize \
  -H "Content-Type: application/json" \
  -d '{
    "game_id": "com.acme.spacegame",
    "name": "Space Raiders",
    "scopes": ["identity", "membership.query", "presence.write"]
  }'
```

Du får {"status":"pending","request_id":"…","poll_after_ms":1000} tilbage, og spilleren ser dialogen. Poll derefter, indtil spilleren svarer (anmodningen udløber efter 3 minutter):

```bash
curl http://127.0.0.1:7440/mssgs/v1/authorize/<request_id>

# {"status":"approved","token":"…","scopes":["identity","presence.write"],"game_id":"com.acme.spacegame"}
```

### Tjek altid, hvad du faktisk fik

scopes i svaret kan være **kortere** end det, du bad om: spilleren kan frit fjerne fluebenet ved enkelte af dem. I eksemplet ovenfor blev membership.query afvist. Forgren på det, svaret siger, ikke på det, du bad om, ellers møder du en 403 MISSING_SCOPE , du ikke havde planlagt.

Gem tokenet, og send det som Authorization: Bearer <token> . Det overlever genstarter, så en spiller godkender dit spil én gang og ikke hver session. Genautoriserer du senere med scopes, der allerede er givet, får du det samme token tilbage med det samme uden nogen dialog.

## Scopes og privatliv

De fem scopes giver meget forskellige mængder væk. Det er ikke tilfældigt; det er hele designet. Bed om så lidt som muligt, og arbejd dig ned gennem tabellen.

| Scope | Hvad det tillader | Hvad spilleren giver fra sig |
| --- | --- | --- |
| presence.write | **Vis, hvad spilleren spiller** | Intet. Dette scope skriver kun; det læser slet ingen kontodata. |
| identity | **Hvem spilleren er** | user_guid, brugernavn, visningsnavn, avatar-URL. |
| staff | **Staff-/moderatorflag** | To booleans oven i identity. Separat, fordi et spil, der viser et navn, ikke har noget at gøre med, om spilleren modererer fællesskaber. |
| membership.query | **Tjek et fællesskab, du allerede kender** | For et server_guid, du angiver: ja/nej, dets navn og de roller, spilleren har dér. Intet om noget andet fællesskab. |
| servers.list | **Alle fællesskaber, spilleren er med i** | Hele listen: guids, navne, ikoner og roller. Det er det dyre: bed kun om det, hvis du virkelig har brug for det. |

### De fleste spil har brug for to af dem

identity og presence.write dækker "hvem er du" og "vis, hvad du spiller", og det er næsten alle integrationer. Tilføj membership.query , hvis du vil knytte en belønning til medlemskab af dit fællesskab. Du har næsten aldrig brug for servers.list , og spilleren ser det fremhævet med rødt.

## Tjek medlemskab

Dette er alternativet til "giv mig hele listen". Du angiver server_guid for dit eget fællesskab (som du allerede kender) og får et svar om netop det og intet andet.

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:7440/mssgs/v1/membership?server_guid=ca94ecc…f4g02"

# et medlem:
# {"server_guid":"ca94ecc…","member":true,"name":"Acme Fans","is_owner":false,
#  "roles":[{"guid":"0aa32…","name":"Pro"}]}

# ikke et medlem, og intet andet:
# {"server_guid":"…","member":false}
```

Et "nej" er præcis det og intet mere. Du må sende op til 10 guids per kald (gentag server_guid eller adskil dem med komma), hvilket returnerer et results -array. Gruppen @everyone er aldrig med i roles : den gælder for alle medlemmer, så den fortæller dig intet.

## Udgiv en spillestatus

Ét PUT sætter "Spiller …"-linjen under spillerens navn, overalt hvor spillerens fællesskaber ser spilleren.

```json
{
  "name":    "Space Raiders",
  "details": "Sector 7",
  "state":   "In a raid",
  "role":    "Gunner",
  "started_at": 1755859200000,
  "party":   { "size": 3, "max": 4, "kind": "party" },
  "join":    { "secret": "raid-42" }
}
```

Kun name er påkrævet. Svaret fortæller dig, hvor længe statussen lever, og hvor tit du skal sende et heartbeat:

```json
{ "ok": true, "expires_in_ms": 90000, "heartbeat_every_ms": 30000 }
```

### Heartbeat, ellers forsvinder statussen

En status uden livstegn i 90 sekunder ryddes automatisk. Det er bevidst: hvis dit spil crasher, står spilleren ikke som "spiller" i timevis. Send et POST /mssgs/v1/activity/heartbeat hvert 30. sekund og DELETE /mssgs/v1/activity ved en ren nedlukning.

### Antal spillere og rolle

party.kind bestemmer, hvilken sætning der vises, for de samme to tal betyder ikke det samme. Et hold på fire er ikke en server med fire spillere på.

| kind | Vises som | Til |
| --- | --- | --- |
| party (standard) | 3 af 4 i gruppen | et hold, en crew eller en gruppe |
| server | 4/100 spillere | en spilserver (FiveM, en fællesskabsserver) |
| lobby | 4/100 spillere | en lobby, før kampen starter |
| match | 4/100 spillere | en kamp eller runde, der er i gang |

role (op til 48 tegn) er det, spilleren spiller *som*: et job, en klasse eller en figur. Den får sit eget felt i stedet for endnu en sætning i state , fordi den vises som en etiket ved siden af antallet af spillere.

details og state er begrænset til 128 tegn hver, name til 64. Linjeskift og kontroltegn fjernes. En **ikon-URL understøttes bevidst ikke**: den ville blive hentet af hver klient, der viser linjen, og det gør en status til et fyrtårn, der melder hvert medlem af hvert fællesskab, spilleren er med i, tilbage til din server.

## Deltag nu-knappen

Læg en join -blok i din aktivitet, så får andre medlemmer en **Deltag nu**-knap ved siden af statussen. Der er to måder, og du kan kombinere dem.

### 1. En hemmelighed (til native spil)

Sæt {"join":{"secret":"raid-42"}} . Når nogen trykker på Deltag nu, leveres den hemmelighed til *deres egen* kopi af dit spil på deres egen maskine, matchet på det samme game_id . Der åbnes ingen URL, og ingen scheme-handler kaldes. Dit spil henter den med:

```json
{
  "events": [
    { "seq": 1, "type": "join", "secret": "raid-42",
      "from": { "user_guid": "62e377…", "username": "mssgs-test-1" } }
  ],
  "cursor": 1
}
```

Poll med ?since=<cursor> , så du ser hver hændelse én gang. Kører spillet hos den, der trykkede, ikke, leveres der intet, og det er en god grund til også at tilbyde en URL.

### 2. En https-URL (til webspil og lobbylinks)

Sæt {"join":{"url":"https://play.example.com/s/abc"}} , så åbner knappen det link. **Kun https accepteres.** Et brugerdefineret scheme ( steam:// , mygame:// , file:// ) afvises: den blok lander på hvert medlems skærm, og sådan en URL er en måde at få en andens maskine til at kalde en lokal handler med argumenter, du har valgt.

### Alt i join er offentligt

join-blokken sendes ud til alle, der kan se spillerens status; det er hele pointen med en Deltag nu-knap. Behandl den derfor som en lobbykode, ikke som et login. Læg aldrig noget i den, der skal forblive hemmeligt, og lad dine koder udløbe.

## FiveM

FiveM har ingen HTTP i sin Lua-runtime på klientsiden, så en resource taler med bridgen gennem **NUI**, en CEF-visning, som sender en Origin. Bridgen accepterer eksplicit de origins: https://cfx-nui-<resource> og den ældre nui://<resource> . Almindelige websider afvises stadig, og en side på det åbne web kan ikke udgive sig for at have den origin; browseren sætter den selv.

```lua
-- NUI-siden står for HTTP; Lua sender den kun dataene.
CreateThread(function()
  while true do
    SendNUIMessage({
      action  = 'mssgs:publish',
      players = GetActivePlayers and #GetActivePlayers() or 0,
      maxPlayers = GetConvarInt('sv_maxclients', 100),
      job     = exports['qb-core'] and 'Police' or nil
    })
    Wait(30000) -- heartbeat: statussen udløber efter 90 s
  end
end)
```

```javascript
const BASE = 'http://127.0.0.1:7440/mssgs/v1';   // prøv 7440-7443
let token = null;

async function authorize () {
  const res = await fetch(`${BASE}/authorize`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      game_id: 'fivem.lossantos.rp',
      name: 'Los Santos Roleplay',
      scopes: ['presence.write']          // mere er ikke nødvendigt her
    })
  });
  const started = await res.json();
  if (started.status === 'approved') { return started.token; }

  // Spilleren ser nu tilladelsesdialogen i mssgs.
  for (let i = 0; i < 180; i += 1) {
    await new Promise((r) => { setTimeout(r, 1000); });
    const poll = await (await fetch(`${BASE}/authorize/${started.request_id}`)).json();
    if (poll.status === 'approved') { return poll.token; }
    if (poll.status !== 'pending') { return null; }
  }
  return null;
}

window.addEventListener('message', async (event) => {
  if (event.data.action !== 'mssgs:publish') { return; }
  if (!token) { token = await authorize(); }
  if (!token) { return; }

  await fetch(`${BASE}/activity`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
    body: JSON.stringify({
      name: 'FiveM',
      details: 'Los Santos Roleplay',
      role: event.data.job,                       // "Police"
      party: { size: event.data.players, max: event.data.maxPlayers, kind: 'server' },
      join: { url: 'https://cfx.re/join/abc123' } // dit cfx.re-link
    })
  });
});
```

Resultatet: **Spiller FiveM · Los Santos Roleplay · 4/100 spillere · Police**, med en Deltag nu-knap, der åbner dit cfx.re-link.

### Bed kun om presence.write

En spillestatus har ikke brug for andet: det scope læser slet ingenting. Vil du knytte en belønning i spillet til medlemskab af dit mssgs-fællesskab, så tilføj membership.query og angiv dit eget server_guid; du lærer stadig intet om spillerens andre fællesskaber.

### En server, du spiller på, er ikke automatisk betroet

Enhver FiveM-server kan køre klient-resources, så enhver server, nogen går ind på, kan bede om tilladelse. Det er netop derfor, der sidder en dialog imellem, som navngiver resourcen: det er spilleren, der bestemmer, ikke serveren.

## Browser- og mobilspil: forbind gennem din backend

Et spil i en browserfane eller på en telefon kan ikke nå bridgen ovenfor. Den kører på spillerens desktop, og tre mure står i vejen: bridgen afviser enhver anmodning med en browser- Origin , Chrome sætter en tilladelsesprompt foran en offentlig side, der henter 127.0.0.1 , og Safari afviser helt, og en telefon har overhovedet ingen vej til en desktops loopback.

Så retningen vendes om. **Din egen backend ved allerede, hvem der spiller, og den fortæller det til mssgs**, for spillere, der har forbundet deres mssgs-konto med dit spil. Forbindelsen godkendes *i mssgs-appen*, aldrig i dit spil, og den opretter en forbindelse, aldrig en session: intet nedenfor kan logge nogen ind eller handle som spilleren. Din spilklient ser aldrig en nøgle og taler aldrig med mss.gs. Det første spil på denne vej er [CozyCity](https://cozycity.net), et bybyggerspil, der udkommer som en WebGL-side og en iPhone-app uden desktopbuild; eksemplerne nedenfor er dets egne.

### 1. Registrér dit spil

Registrér spillet på [Game SDK’ets registreringsside](https://mss.gs/da/docs/game-sdk/register): dit game_id (for eksempel com.deverence.cozycity ), det navn og ikon, spilleren ser på godkendelsesarket, og dine backend-hostnavne. Vi gennemgår det dér, og når det er godkendt, venter din **backend-nøgle** på samme side, vist én gang; vi gemmer kun et digest. Nøglen hører til på din server og ingen andre steder. Du kan rotere den dér når som helst, og den gamle forbliver gyldig i 24 timer, så et deploy kan rulle ud.

[Registrér dit spil](https://mss.gs/da/docs/game-sdk/register)

Navnet og ikonet på arket **kommer altid fra registreringen**, aldrig fra anmodningen. Ellers kunne et phishinglink klæde en forbindelsesanmodning ud som et hvilket som helst spil. Hostnavnene afgrænser, hvor en join.url må pege hen, se nedenfor.

### 2. Forbind en spiller

Spilleren vælger **Forbind mssgs** i dit spil. Dit spil spørger din backend, og din backend spørger os:

```bash
curl -X POST https://ams1-gateway.mss.gs/game-sdk/v1/link/start \
  -H "Authorization: Bearer $BACKEND_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "player_ref": "player-8812", "player_name": "René\u2019s city" }'

# {"link_code":"K7PQ2XM4","device_code":"…","qr_url":"https://mss.gs/gl/K7PQ2XM4",
#  "deep_link":"mssgs://link-game/K7PQ2XM4","expires_in":600,"interval":5}
```

player_ref er dit eget stabile id for spilleren (op til 128 tegn), ikke for en session eller en kamp; player_name (op til 64) er det, arket viser som "Spiller: …". Giv kun din spilklient link_code , qr_url og deep_link . device_code er dit polling-håndtag og bliver på serveren.

Dit spil viser så tre ting på én gang, for spilleren kan være hvor som helst:

- **QR-koden** for qr_url . En telefon med mssgs åbner den direkte i appens godkendelsesark. Uden appen lander den på en side på mss.gs, der viser koden og tilbyder download.

- **En "Åbn i mssgs"-knap** med deep_link , til en desktopbrowser ved siden af desktop-appen. Det er den eneste eksterne URL, dit spil nogensinde behøver at åbne.

- **Selve koden**, i to grupper af fire, til at skrive under **Indstillinger → Spilaktivitet → Forbind et spil**. Alfabetet har ingen 0/O eller 1/I, så det går sjældent galt, når den skrives.

Det, spilleren ser i mssgs, tegnet af appen ud fra registreringen:

### Forbind CozyCity til din mssgs-konto?

CozyCity vil kunne vise, hvad du spiller, som din mssgs-status. Spillet ser ikke dine beskeder, dine venner eller dine servere, og det kan ikke skrive som dig. Spiller: *Renés by* · **Forbind** / **Ikke nu**

Imens poller din backend hvert interval sekund (hurtigere svarer 429 SLOW_DOWN ), indtil statussen skifter. En kode virker én gang og udløber efter ti minutter:

```bash
curl -X POST https://ams1-gateway.mss.gs/game-sdk/v1/link/poll \
  -H "Authorization: Bearer $BACKEND_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "device_code": "…" }'

# {"status":"pending"}
# {"status":"denied"}      # spilleren valgte Ikke nu
# {"status":"expired"}
# {"status":"linked","link_guid":"…","user":{"user_guid":"62e377…"}}
```

Gem link_guid ved din spiller; fra nu af er det den adresse, du udgiver for. Svaret indeholder kun user_guid ; et username tilføjes kun, når din registrering har scopet identity.link , og mere end det er der ikke. En ny godkendelse for det samme player_ref **erstatter** den tidligere forbindelse, så én spiller i dit spil er én mssgs-konto. Den samme mssgs-konto kan forbindes med flere spil og med flere player_ref s i ét spil (en familie-iPad).

### 3. Udgiv spillestatussen

Den samme blok som på [bridgen](#activity), med de samme regler og de samme grænser, blot nu per forbindelse og med din backend-nøgle:

```json
{
  "activity": {
    "name":       "CozyCity",
    "details":    "Lantern Hollow",
    "state":      "Day 12 · 34 residents",
    "started_at": 1788901000000,
    "party":      { "size": 6, "max": 40, "kind": "server" },
    "join":       { "url": "https://cozycity.net/game/?share=…" }
  }
}
```

```bash
200 {"published":true,"changed":true}     # blokken blev ændret og sendt ud
200 {"published":true,"changed":false}    # identisk med det gemte; kun TTL blev fornyet
204                                        # gemt, men spilleren er ikke online i mssgs lige nu
410 {"error":"LINK_REVOKED"}               # spilleren afbrød forbindelsen: slip den
```

Behandl 200 og 204 ens: gemt. { "activity": null } rydder blokken, send det, når spilleren går. Én forskel fra bridgen: værten i join.url skal være en af dine registrerede backends (eller et subdomæne af en), ellers får du 400 INVALID_PAYLOAD . Så en backend kan ikke sætte en Deltag nu-knap på en spillers status, der fører et sted hen, hvor spilleren aldrig har spillet.

### Heartbeat hvert 60. sekund, TTL 120

En udgivet status lever **120 sekunder** uden en ny besked og forsvinder så af sig selv. Send derfor den samme blok igen hvert 60. sekund; en uændret blok koster intet og fornyer kun TTL. Stopper dit heartbeat, stopper "Spiller …"-linjen, og det er netop pointen.

Med hundredvis af spillere online sender du heartbeat i ét kald, op til 100 elementer ad gangen. Hvert element får sin egen status, så én spiller, der har afbrudt forbindelsen i mssgs, aldrig stopper de andre nioghalvfems:

```json
{ "items": [ { "link_guid": "…", "activity": { "name": "CozyCity", "details": "Lantern Hollow" } },
             { "link_guid": "…", "activity": null } ] }

// → 200 { "results": [ { "link_guid": "…", "status": 200, "changed": false },
//                      { "link_guid": "…", "status": 410 } ] }
```

### Sådan vises statussen

- Identisk med en bridge-status: **Spiller CozyCity · Lantern Hollow · 6/40 spillere**, med Deltag nu, når der er en join.url . Blokken stemples via: "backend" på serversiden, så en klient kan tilføje "Delt af spillets server".

- **Kun mens spilleren er online i mssgs.** Uden en åben mssgs-klient er kontoen offline og forbliver offline; din backend kan ikke få nogen til at se til stede ud. Det forhindrer også, at denne vej bliver et "sidder René ved sin computer"-fyrtårn.

- Forrang: **et spil i appen > et spil på bridgen > din backend**. Sætter spilleren sig til et parti skak inde i mssgs, mens din backend bliver ved med at sende heartbeat, vinder skakken, ikke den, der skrev sidst.

### Afbryd forbindelsen

Spilleren ser hver forbindelse under **Indstillinger → Spilaktivitet → Forbundne spil** med dit ikon og navn, spillernavnet fra dit spil, hvornår forbindelsen blev oprettet, og hvornår der sidst blev udgivet, samt en **Afbryd**-knap. Derefter svarer din næste udgivelse 410 LINK_REVOKED ; sådan finder dit spil ud af det. Slip link_guid , og tilbyd "Forbind mssgs" igen. Fra din side afslutter du en forbindelse med DELETE /game-sdk/v1/links/{link_guid} .

### Grænser

Per forbindelse tæller en *ændring* højst hvert 2. sekund; et uændret heartbeat er gratis. Per nøgle er der 600 anmodninger i minuttet, hvor batch-elementer tælles enkeltvis: heartbeat for 300 spillere hvert 60. sekund bruger 5 af de 600.

## Endpoint-reference: bridgen

Basis-URL http://127.0.0.1:<port> . Alt undtagen de tre første kræver Authorization: Bearer <token> .

| Metode | Sti | Scope | Hvad den gør |
| --- | --- | --- | --- |
| GET | /mssgs/v1/hello | intet | Er mssgs her, hvad taler det, og er nogen logget ind? Den eneste route, der ikke kræver et token, og den siger intet om spilleren. |
| POST | /mssgs/v1/authorize | intet | Bed spilleren om tilladelse. Viser en dialog i appen og returnerer et request_id, der skal polles. |
| GET | /mssgs/v1/authorize/:request_id | intet | pending, approved (med tokenet), denied eller expired. |
| GET | /mssgs/v1/me | identity | Den indloggede spiller. Tilføjer kun is_staff / is_moderator med staff-scopet. |
| GET | /mssgs/v1/membership | membership.query | Medlemskab af de server_guid-værdier, du sender (op til 10, gentaget eller kommasepareret). |
| GET | /mssgs/v1/servers | servers.list | Alle fællesskaber, spilleren er med i, med spillerens roller. Direkte beskeder er aldrig med. |
| PUT | /mssgs/v1/activity | presence.write | Udgiv "Spiller …"-blokken. Returnerer TTL, og hvor tit der skal sendes heartbeat. |
| POST | /mssgs/v1/activity/heartbeat | presence.write | Hold den udgivne aktivitet i live uden at sende den igen. |
| DELETE | /mssgs/v1/activity | presence.write | Ryd den med det samme, ved en ren nedlukning. |
| GET | /mssgs/v1/events | presence.write | Deltagelsesoverdragelser rettet mod dit spil. Poll med ?since=<cursor>. |
| GET | /mssgs/v1/session | intet | Hvad dette token har: game_id, givne scopes, og om nogen er logget ind. |
| DELETE | /mssgs/v1/session | intet | Giv tilladelsen tilbage. Samme virkning, som når spilleren tilbagekalder den i Indstillinger. |

## Endpoint-reference: forbundne backends

Basis-URL https://ams1-gateway.mss.gs . Hver route kræver Authorization: Bearer <backend key> og scopet activity.write på din registrering; svar sendes med Cache-Control: no-store . Kald dem fra din server, aldrig fra spilklienten.

| Metode | Sti | Hvad den gør |
| --- | --- | --- |
| POST | /game-sdk/v1/link/start | Start en forbindelse for en af dine spillere ({ player_ref, player_name? }). Returnerer link_code, device_code, qr_url, deep_link, expires_in og interval. |
| POST | /game-sdk/v1/link/poll | { device_code } → pending, denied, expired eller linked med link_guid og user. |
| DELETE | /game-sdk/v1/links/{link_guid} | Afslut en forbindelse fra din side. Spilleren kan gøre det samme fra Indstillinger. |
| PUT | /game-sdk/v1/links/{link_guid}/activity | Udgiv "Spiller …"-blokken for én spiller; { "activity": null } rydder den. |
| POST | /game-sdk/v1/activity/batch | Det samme for op til 100 spillere i ét kald. Hvert element svarer for sig. |

## Fejlkoder

Fejl kommer tilbage som {"error":"CODE","message":"…"} med en tilsvarende HTTP-status.

| Kode | Betydning |
| --- | --- |
| 401 UNAUTHORIZED | Manglende eller ukendt token; autorisér først. |
| 403 MISSING_SCOPE | Spilleren gav ikke den tilladelse. Spilleren kan have fjernet fluebenet ved den. |
| 403 ORIGIN_NOT_ALLOWED | Anmodningen havde en browser-Origin. Se "Kun native spil" nedenfor. |
| 409 NOT_SIGNED_IN | mssgs kører, men ingen er logget ind. |
| 429 RATE_LIMITED | Mere end 120 anmodninger i minuttet fra ét spil. |
| 400 INVALID_GAME_ID | game_id må kun bestå af bogstaver, tal, punktum, bindestreg eller understregning. |
| 400 TOO_MANY_GUIDS | Højst 10 server_guid-værdier per medlemskabskald. |

### Forbundne backends

Backend-routes bruger samme form. I en batch kommer statussen tilbage per element i results , så én afsluttet forbindelse aldrig får hele kaldet til at fejle.

| Kode | Betydning |
| --- | --- |
| 401 INVALID_BACKEND_KEY | Ukendt nøgle, eller en nøgle, der blev roteret ud for mere end 24 timer siden. |
| 403 SCOPE_NOT_GRANTED | Din registrering har ikke det scope, som routen kræver. |
| 400 INVALID_PAYLOAD | Ugyldig body, mere end 100 batch-elementer eller en join.url, hvis vært ikke er en af dine registrerede backends. |
| 400 INVALID_ACTIVITY | Intet brugbart navn tilbage efter normalisering. |
| 410 LINK_REVOKED | Forbindelsen er afsluttet, fra en af siderne. Slip den, og tilbyd "Forbind mssgs" igen. |
| 429 SLOW_DOWN | Du pollede link/poll hurtigere end interval. |
| 429 RATE_LIMITED | En ændring af én forbindelse inden for 2 sekunder af den forrige, eller mere end 600 anmodninger i minuttet på din nøgle. |
| 503 LINK_STORE_UNAVAILABLE | Midlertidigt på vores side. Prøv igen ved dit næste heartbeat. |

## Sikkerhed

### Kun native spil

Anmodninger med en websides Origin afvises med 403 ORIGIN_NOT_ALLOWED . At enhver webside kan registrere, at du kører mssgs, og fremkalde en tilladelsesdialog, er en flade for fingerprinting og phishing, ikke en funktion. Et native spil sender slet ingen Origin, så det påvirkes ikke, og et spils egen indlejrede browser tillades ved navn, se [FiveM](#fivem). Bygger du et browser- eller mobilspil, taler du ikke med bridgen: din egen backend udgiver for forbundne spillere, se [Browser- og mobilspil](#linked).

### Det, spilleren beholder kontrollen over

- Spilleren kan slå bridgen fra under **Indstillinger → Spilaktivitet**, og derefter kan intet spil overhovedet se mssgs.

- Hvert godkendt spil står dér med præcis de tilladelser, det har, hvornår det sidst var aktivt, og en **Fjern**-knap. Fjernelse sker med det samme: tokenet dør øjeblikkeligt.

- Et forbundet browser- eller mobilspil står under **Forbundne spil** med en **Afbryd**-knap. Også det sker med det samme: den backends næste udgivelse får en 410 .

- Bridgen lytter kun på 127.0.0.1, aldrig på netværket.

- Direkte beskeder afsløres aldrig, heller ikke med servers.list.

- Der er et budget på 120 anmodninger i minuttet per spil.

### Vær en god borger

- Bed om scopes, når du har brug for dem, ikke alle på én gang ved første opstart.

- Fungér uden mssgs: spilleren behøver ikke at have det.

- Ryd din status, når spillet stopper, i stedet for at vente på TTL.

- Behandl et afvist scope som et normalt udfald, ikke som en fejl.

## Byg videre
