---
title: "Game SDK: показывайте, во что играют игроки в mssgs"
description: "Публикуйте из своей игры статус «Играет» с кнопкой «Присоединиться» и проверяйте членство в сообществе. mssgs Game SDK для нативных, браузерных и мобильных игр."
canonical: https://docs.mss.gs/ru/game-sdk
language: ru
---

# Показывайте, во что играет человек

Пусть ваша игра сообщает mssgs, чем занят игрок. Друзья видят «Играет» под его именем, открывают подробности и нажимают «Присоединиться», чтобы попасть в ту же игру. Ваша игра также может проверить, состоит ли игрок в вашем сообществе.

## Что можно сделать

- **Публикуйте игровой статус** Игра, чем игрок занят, его роль и насколько заполнена группа.

- **Добавьте кнопку «Присоединиться»** Друзья одним нажатием попадают в ту же игру, на тот же сервер или в то же лобби.

- **Проверяйте членство** Узнайте, состоит ли игрок в вашем сообществе и с какими ролями.

- **Компьютер, браузер или телефон** Нативные игры используют локальный мост; браузерные и мобильные игры работают через ваш бэкенд.

В приложении

Статус под именем и подробности, которые он открывает. Игра опубликовала один блок JSON; всё остальное делает приложение.

- [Обзор](#overview)

- [Как найти клиент](#discover)

- [Запрос разрешения](#authorize)

- [Игровой статус](#activity)

- [Кнопка «Присоединиться»](#join)

- [Браузер и телефон](#linked)

- [Справочник](#reference)

## Обзор

Настольное приложение mssgs запускает небольшой **локальный HTTP-мост**, с которым общается игра на том же компьютере. Ваша игра никогда не обращается к нашим серверам, никогда не видит пароль или токен аккаунта и никогда не может писать от имени игрока. Она общается с копией mssgs, в которой игрок уже вошёл в аккаунт, и эта копия решает, что отвечать.

Что с его помощью можно делать:

- Определить, что mssgs установлен и кто-то вошёл в аккаунт.

- Узнать, кто игрок: user_guid, имя пользователя, аватар.

- Спросить «состоит ли этот игрок в сообществе X?» и какая у него там роль.

- Опубликовать статус «Играет в …» с кнопкой **«Присоединиться»** для остальных.

- Получить передачу приглашения, когда кто-то нажимает эту кнопку.

### Два способа подключения

**Нативная настольная игра** общается с локальным мостом; именно это описывают следующие разделы. Игра **в браузере или на телефоне** до этого моста добраться не может. За такие игры публикует ваш собственный бэкенд, для игроков, которые связали свой аккаунт mssgs через QR-код или восьмисимвольный код: см. [Браузерные и мобильные игры](#linked), где первым примером служит [CozyCity](https://cozycity.net). Сам игровой статус в обоих случаях представляет собой один и тот же блок.

### Минимальное раскрытие по умолчанию

Области доступа намеренно неравноценны. Если вам нужно знать только «состоит ли этот человек в нашем сообществе», запросите membership.query и сами укажите server_guid: вы получите «да» или «нет» и роли игрока там, и ничего не узнаете об остальных его сообществах. Полный список скрыт за отдельной, более высокой областью доступа, которую игрок должен одобрить отдельно.

## Как найти клиент

Мост слушает только 127.0.0.1 , на первом свободном порту из небольшого диапазона. Перебирайте их по порядку, пока какой-нибудь не ответит: **7440, 7441, 7442, 7443**. Сборки mssgs для разработки слушают вместо этого **7540–7543**, так что тестовая сборка никогда не отвечает на вызовы настоящей игры.

Токен не нужен, и ответ ничего не говорит об игроке, только о том, что mssgs здесь и вошёл ли кто-нибудь в аккаунт.

```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"]
}
```

Прежде чем идти дальше, проверьте product === "mssgs" и api . Если ни один из четырёх портов не отвечает, mssgs не запущен. Просто предложите обычный сценарий игры, не заставляя игрока ждать.

## Запрос разрешения

Всё, кроме /hello , требует токена, а токен появляется только после того, как игрок одобрил вашу игру в диалоге внутри приложения. Запрашивайте только те области доступа, которые действительно используете: игрок видит каждую отдельно, с пояснением, и может снять галочку с любой из них.

```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"]
  }'
```

Вы получите {"status":"pending","request_id":"…","poll_after_ms":1000} , а игрок увидит диалог. Затем опрашивайте, пока он не ответит (запрос истекает через 3 минуты):

```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"}
```

### Всегда проверяйте, что вы на самом деле получили

Список scopes в ответе может быть **короче**, чем вы запросили: игрок вправе снять галочку с отдельных областей. В примере выше в membership.query было отказано. Опирайтесь на то, что сказано в ответе, а не на то, что вы запросили, иначе столкнётесь с незапланированным 403 MISSING_SCOPE .

Сохраните токен и отправляйте его как Authorization: Bearer <token> . Он переживает перезапуски, так что игрок одобряет вашу игру один раз, а не в каждой сессии. Если позже вы снова пройдёте авторизацию с уже выданными областями доступа, вы сразу получите тот же токен без диалога.

## Области доступа и приватность

Пять областей доступа раскрывают очень разный объём данных. Это не случайность, а весь замысел. Запрашивайте как можно меньше, двигаясь по этой таблице сверху вниз.

| Область доступа | Что она разрешает | Чем игрок за это платит |
| --- | --- | --- |
| presence.write | **Показывать, во что играет игрок** | Ничем. Эта область доступа только пишет; она вообще не читает данных аккаунта. |
| identity | **Кто игрок** | user_guid, имя пользователя, отображаемое имя, URL аватара. |
| staff | **Флаги сотрудника и модератора** | Два логических значения поверх identity. Отдельно, потому что игре, которая показывает имя, незачем знать, что игрок модерирует сообщества. |
| membership.query | **Проверять сообщество, которое вы уже знаете** | Для указанного вами server_guid: да или нет, его название и роли игрока в нём. Ничего о других сообществах. |
| servers.list | **Все сообщества игрока** | Полный список: guid, названия, значки и роли. Это самая дорогая область: запрашивайте её, только если она вам действительно нужна. |

### Большинству игр нужны две из них

identity и presence.write покрывают «кто вы» и «покажите, во что вы играете», а это почти любая интеграция. Добавьте membership.query , если хотите привязать награду к членству в вашем сообществе. servers.list вам почти никогда не понадобится, и игрок видит его выделенным красным.

## Проверка членства

Это альтернатива подходу «дайте мне весь список». Вы указываете server_guid своего собственного сообщества (который вы и так знаете) и получаете ответ только о нём.

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

# участник:
# {"server_guid":"ca94ecc…","member":true,"name":"Acme Fans","is_owner":false,
#  "roles":[{"guid":"0aa32…","name":"Pro"}]}

# не участник, и больше ничего:
# {"server_guid":"…","member":false}
```

«Нет» означает ровно это и ничего больше. За один вызов можно передать до 10 guid (повторите server_guid или перечислите их через запятую), и тогда вернётся массив results . Группа @everyone никогда не попадает в roles : она верна для каждого участника, поэтому ни о чём не говорит.

## Публикация игрового статуса

Один PUT помещает строку «Играет в …» под именем игрока везде, где его видят его сообщества.

```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" }
}
```

Обязательно только поле name . Ответ сообщает, сколько живёт статус и как часто отправлять heartbeat:

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

### Heartbeat, иначе статус исчезнет

Статус без признаков жизни в течение 90 секунд удаляется автоматически. Это сделано намеренно: если игра упадёт, игрок не останется «играющим» несколько часов. Отправляйте POST /mssgs/v1/activity/heartbeat каждые 30 секунд и DELETE /mssgs/v1/activity при штатном завершении.

### Число игроков и роль

party.kind определяет, какая фраза будет показана, потому что одни и те же два числа означают разное. Отряд из четырёх человек не то же самое, что сервер, на котором четыре игрока.

| kind | Как отображается | Для чего |
| --- | --- | --- |
| party (по умолчанию) | В группе: 3 из 4 | отряд, команда или группа |
| server | 4/100 игроков | игровой сервер (FiveM, сервер сообщества) |
| lobby | 4/100 игроков | лобби до начала матча |
| match | 4/100 игроков | идущий матч или раунд |

role (до 48 символов) означает, *кем* играет игрок: профессия, класс или персонаж. Для неё есть отдельное поле, а не ещё одна фраза в state , потому что она показывается как метка рядом с числом игроков.

details и state ограничены 128 символами каждое, name ограничено 64. Переводы строк и управляющие символы удаляются. **URL значка намеренно не поддерживается**: его загружал бы каждый клиент, отображающий эту строку, и статус превратился бы в маячок, сообщающий вашему серверу о каждом участнике каждого сообщества, где состоит игрок.

## Кнопка «Присоединиться»

Добавьте в активность блок join , и другие участники увидят рядом со статусом кнопку **«Присоединиться»**. Есть два способа, и их можно сочетать.

### 1. Секрет (для нативных игр)

Задайте {"join":{"secret":"raid-42"}} . Когда кто-то нажимает «Присоединиться», этот секрет доставляется *его собственной* копии вашей игры, на его собственном компьютере, с сопоставлением по тому же game_id . Никакой URL не открывается, и никакой обработчик схемы не вызывается. Игра забирает секрет так:

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

Опрашивайте с ?since=<cursor> , чтобы видеть каждое событие один раз. Если игра нажавшего не запущена, ничего не доставляется, и это хороший повод дополнительно предложить URL.

### 2. URL с https (для веб-игр и ссылок на лобби)

Задайте {"join":{"url":"https://play.example.com/s/abc"}} , и кнопка откроет эту ссылку. **Принимается только https .** Собственная схема ( steam:// , mygame:// , file:// ) отклоняется: этот блок попадает на экран каждого участника, а такой URL позволил бы заставить чужой компьютер вызвать локальный обработчик с выбранными вами аргументами.

### Всё в join публично

Блок join рассылается всем, кто может видеть статус игрока; в этом и состоит смысл кнопки «Присоединиться». Поэтому относитесь к нему как к коду лобби, а не как к учётным данным. Никогда не кладите туда ничего, что должно оставаться секретом, и ограничивайте срок действия своих кодов.

## FiveM

В клиентской среде Lua у FiveM нет HTTP, поэтому ресурс общается с мостом через **NUI**, представление CEF, которое отправляет Origin. Мост явно принимает такие источники: https://cfx-nui-<resource> и более старый nui://<resource> . Обычные веб-страницы по-прежнему отклоняются, и страница в открытом интернете не может выдать себя за такой источник: браузер выставляет его сам.

```lua
-- HTTP выполняет страница NUI; Lua только передаёт ей данные.
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: статус истекает через 90 с
  end
end)
```

```javascript
const BASE = 'http://127.0.0.1:7440/mssgs/v1';   // перебираем 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']          // больше здесь ничего не нужно
    })
  });
  const started = await res.json();
  if (started.status === 'approved') { return started.token; }

  // Теперь игрок видит в 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' } // ваша ссылка cfx.re
    })
  });
});
```

Результат: **Играет в FiveM · Los Santos Roleplay · Игроков: 4/100 · Police**, с кнопкой «Присоединиться», которая открывает вашу ссылку cfx.re.

### Запрашивайте только presence.write

Игровому статусу больше ничего не нужно: эта область доступа вообще ничего не читает. Если хотите привязать внутриигровую награду к членству в вашем сообществе mssgs, добавьте membership.query и укажите свой server_guid; о других сообществах игрока вы по-прежнему ничего не узнаете.

### Сервер, на котором вы играете, не становится доверенным автоматически

Любой сервер FiveM может запускать клиентские ресурсы, поэтому любой сервер, на который кто-то зашёл, может запросить разрешение. Именно поэтому между ними стоит диалог с названием ресурса: решает игрок, а не сервер.

## Браузерные и мобильные игры: связывание через ваш бэкенд

Игра во вкладке браузера или на телефоне не может достучаться до описанного выше моста. Он работает на компьютере игрока, и между ними три стены: мост отклоняет каждый запрос с браузерным Origin , Chrome показывает запрос разрешения, когда публичная страница обращается к 127.0.0.1 , а Safari отказывает сразу, у телефона же вообще нет пути к loopback-интерфейсу компьютера.

Поэтому направление меняется. **Ваш собственный бэкенд уже знает, кто играет, и сообщает об этом mssgs** для игроков, которые связали свой аккаунт mssgs с вашей игрой. Связь одобряется *в приложении mssgs*, а не в вашей игре, и создаёт именно связь, а не сессию: ничто из описанного ниже не может никого авторизовать или действовать от имени игрока. Клиент вашей игры никогда не видит ключа и никогда не обращается к mss.gs. Первая игра на этом пути: [CozyCity](https://cozycity.net), градостроительный симулятор, который выходит как страница WebGL и приложение для iPhone без настольной версии; примеры ниже взяты из неё.

### 1. Зарегистрируйте игру

Зарегистрируйте игру на [странице регистрации Game SDK](https://mss.gs/ru/docs/game-sdk/register): свой game_id (например, com.deverence.cozycity ), название и значок, которые игрок видит в окне подтверждения, и имена хостов своего бэкенда. Мы проверим её там, и после одобрения ваш **ключ бэкенда** будет ждать на той же странице, показанный один раз; мы храним только его дайджест. Ключ должен лежать на вашем сервере и больше нигде. Там же его можно заменить в любой момент, а старый остаётся действительным 24 часа, чтобы деплой успел пройти.

[Зарегистрировать игру](https://mss.gs/ru/docs/game-sdk/register)

Название и значок в окне **всегда берутся из регистрации**, а не из запроса. Иначе фишинговая ссылка могла бы выдать запрос на связывание за любую игру. Имена хостов ограничивают, куда может вести join.url , см. ниже.

### 2. Свяжите игрока

Игрок выбирает в вашей игре **«Подключить mssgs»**. Игра обращается к вашему бэкенду, а бэкенд к нам:

```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 представляет собой ваш собственный стабильный id этого игрока (до 128 символов), а не сессии или матча; player_name (до 64) показывается в окне как «Игрок: …». Передавайте клиенту игры только link_code , qr_url и deep_link . device_code служит вашим дескриптором для опроса и остаётся на сервере.

Затем игра показывает сразу три вещи, потому что игрок может находиться где угодно:

- **QR-код** для qr_url . Телефон с mssgs открывает его сразу в окне подтверждения приложения. Без приложения он попадает на страницу mss.gs, которая показывает код и предлагает скачать приложение.

- **Кнопку «Открыть в mssgs»** с deep_link для браузера на компьютере, где рядом установлено настольное приложение. Это единственный внешний URL, который вашей игре когда-либо нужно открыть.

- **Сам код**, двумя группами по четыре символа, чтобы ввести его в разделе **Настройки → Игровая активность → Связать игру**. В алфавите нет 0/O и 1/I, так что ошибиться при вводе трудно.

Что игрок видит в mssgs; приложение строит это окно по данным регистрации:

### Подключить CozyCity к твоему аккаунту mssgs?

CozyCity сможет показывать в твоём статусе mssgs, во что ты играешь. Игра не увидит твои сообщения, друзей и серверы и не сможет писать от твоего имени. Игрок: *René's city* · **Подключить** / **Не сейчас**

Тем временем ваш бэкенд опрашивает каждые interval секунд (на более частые запросы приходит 429 SLOW_DOWN ), пока статус не сменится. Код работает один раз и истекает через десять минут:

```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"}      # игрок выбрал «Не сейчас»
# {"status":"expired"}
# {"status":"linked","link_guid":"…","user":{"user_guid":"62e377…"}}
```

Сохраните link_guid для своего игрока; с этого момента это адрес, для которого вы публикуете. В ответе есть только user_guid ; username добавляется, только если в вашей регистрации есть область доступа identity.link , и больше ничего. Повторное одобрение для того же player_ref **заменяет** прежнюю связь, так что одному игроку вашей игры соответствует один аккаунт mssgs. Один аккаунт mssgs может быть связан с несколькими играми и с несколькими player_ref одной игры (семейный iPad).

### 3. Публикуйте игровой статус

Тот же блок, что и на [мосту](#activity), с теми же правилами и ограничениями, только теперь для каждой связи и с вашим ключом бэкенда:

```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}     # блок изменился и был разослан
200 {"published":true,"changed":false}    # совпадает с сохранённым; обновлён только TTL
204                                        # сохранён, но игрок сейчас не в сети в mssgs
410 {"error":"LINK_REVOKED"}               # игрок отключил игру: удалите связь
```

Обрабатывайте 200 и 204 одинаково: сохранено. { "activity": null } очищает блок, отправьте его, когда игрок уходит. Одно отличие от моста: хост в join.url должен быть одним из ваших зарегистрированных бэкендов (или их поддоменом), иначе вы получите 400 INVALID_PAYLOAD . Так бэкенд не может поставить в статус игрока кнопку «Присоединиться», ведущую туда, где этот игрок никогда не играл.

### Heartbeat каждые 60 секунд, TTL 120

Опубликованный статус живёт **120 секунд** без нового сообщения, а затем исчезает сам. Поэтому повторяйте тот же блок каждые 60 секунд; неизменённый блок ничего не стоит и только обновляет TTL. Если heartbeat прекращается, пропадает и строка «Играет в …», в этом весь смысл.

Когда в сети сотни игроков, отправляйте heartbeat одним вызовом, до 100 элементов за раз. Каждый элемент получает собственный статус, так что один игрок, отключивший игру в mssgs, никогда не остановит остальных девяносто девять:

```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 } ] }
```

### Как показывается статус

- Так же, как статус с моста: **Играет в CozyCity · Lantern Hollow · Игроков: 6/40**, с кнопкой «Присоединиться», если есть join.url . Сервер помечает блок как via: "backend" , так что клиент может добавить «Передаёт сервер игры».

- **Только пока игрок в сети в mssgs.** Если ни один клиент mssgs не открыт, аккаунт не в сети и остаётся не в сети; ваш бэкенд не может создать видимость присутствия. Это же не даёт этому пути превратиться в маячок «сидит ли René за компьютером».

- Приоритет: **игра внутри приложения > игра через мост > ваш бэкенд**. Если игрок садится за шахматы внутри mssgs, пока ваш бэкенд продолжает отправлять heartbeat, побеждают шахматы, а не тот, кто написал последним.

### Отключение

Игрок видит каждую связь в разделе **Настройки → Игровая активность → Связанные игры**: ваш значок и название, имя игрока из вашей игры, когда связь была создана и когда игра последний раз публиковала статус, а также кнопку **«Отключить»**. После этого ваша следующая публикация получит ответ 410 LINK_REVOKED ; так ваша игра об этом узнаёт. Удалите link_guid и снова предложите «Подключить mssgs». Со своей стороны связь можно завершить через DELETE /game-sdk/v1/links/{link_guid} .

### Ограничения

Для одной связи *изменение* учитывается не чаще раза в 2 секунды; неизменённый heartbeat бесплатен. На один ключ приходится 600 запросов в минуту, элементы пакета считаются по отдельности: heartbeat для 300 игроков каждые 60 секунд расходует 5 из 600.

## Справочник эндпоинтов: мост

Базовый URL http://127.0.0.1:<port> . Всё, кроме первых трёх, требует Authorization: Bearer <token> .

| Метод | Путь | Scope | Что делает |
| --- | --- | --- | --- |
| GET | /mssgs/v1/hello | нет | Есть ли здесь mssgs, какую версию API он поддерживает и вошёл ли кто-нибудь в аккаунт. Единственный маршрут без токена, и он ничего не сообщает об игроке. |
| POST | /mssgs/v1/authorize | нет | Запросить разрешение у игрока. Открывает диалог в приложении и возвращает request_id для опроса. |
| GET | /mssgs/v1/authorize/:request_id | нет | pending, approved (с токеном), denied или expired. |
| GET | /mssgs/v1/me | identity | Игрок, вошедший в аккаунт. Добавляет is_staff / is_moderator только при области доступа staff. |
| GET | /mssgs/v1/membership | membership.query | Членство в переданных значениях server_guid (до 10, повторяющимся параметром или через запятую). |
| GET | /mssgs/v1/servers | servers.list | Все сообщества игрока с его ролями. Личные сообщения никогда не включаются. |
| PUT | /mssgs/v1/activity | presence.write | Опубликовать блок «Играет в …». Возвращает TTL и частоту heartbeat. |
| POST | /mssgs/v1/activity/heartbeat | presence.write | Поддерживать опубликованную активность, не отправляя её заново. |
| DELETE | /mssgs/v1/activity | presence.write | Сразу очистить её при штатном завершении. |
| GET | /mssgs/v1/events | presence.write | Передачи приглашений, адресованные вашей игре. Опрашивайте с ?since=<cursor>. |
| GET | /mssgs/v1/session | нет | Что содержит этот токен: game_id, выданные области доступа, вошёл ли кто-нибудь в аккаунт. |
| DELETE | /mssgs/v1/session | нет | Вернуть разрешение. Тот же эффект, что и отзыв игроком в Настройках. |

## Справочник эндпоинтов: связанные бэкенды

Базовый URL https://ams1-gateway.mss.gs . Каждый маршрут требует Authorization: Bearer <backend key> и области доступа activity.write в вашей регистрации; ответы отправляются с Cache-Control: no-store . Вызывайте их со своего сервера, никогда из клиента игры.

| Метод | Путь | Что делает |
| --- | --- | --- |
| POST | /game-sdk/v1/link/start | Начать связывание для одного из ваших игроков ({ player_ref, player_name? }). Возвращает link_code, device_code, qr_url, deep_link, expires_in и interval. |
| POST | /game-sdk/v1/link/poll | { device_code } → pending, denied, expired или linked с link_guid и user. |
| DELETE | /game-sdk/v1/links/{link_guid} | Завершить связь со своей стороны. Игрок может сделать то же самое в Настройках. |
| PUT | /game-sdk/v1/links/{link_guid}/activity | Опубликовать блок «Играет в …» для одного игрока; { "activity": null } очищает его. |
| POST | /game-sdk/v1/activity/batch | То же самое для до 100 игроков одним вызовом. Каждый элемент получает собственный ответ. |

## Коды ошибок

Ошибки возвращаются как {"error":"CODE","message":"…"} с соответствующим HTTP-статусом.

| Код | Значение |
| --- | --- |
| 401 UNAUTHORIZED | Токен отсутствует или неизвестен; сначала пройдите авторизацию. |
| 403 MISSING_SCOPE | Игрок не выдал это разрешение. Возможно, он снял с него галочку. |
| 403 ORIGIN_NOT_ALLOWED | Запрос содержал браузерный Origin. См. «Только нативные игры» ниже. |
| 409 NOT_SIGNED_IN | mssgs запущен, но никто не вошёл в аккаунт. |
| 429 RATE_LIMITED | Больше 120 запросов в минуту от одной игры. |
| 400 INVALID_GAME_ID | game_id может содержать только буквы, цифры, точку, дефис или подчёркивание. |
| 400 TOO_MANY_GUIDS | Не больше 10 значений server_guid за один вызов membership. |

### Связанные бэкенды

Маршруты бэкенда используют тот же формат. Внутри пакета статус возвращается для каждого элемента в results , так что одна завершённая связь никогда не проваливает весь вызов.

| Код | Значение |
| --- | --- |
| 401 INVALID_BACKEND_KEY | Неизвестный ключ или ключ, заменённый больше 24 часов назад. |
| 403 SCOPE_NOT_GRANTED | В вашей регистрации нет области доступа, нужной этому маршруту. |
| 400 INVALID_PAYLOAD | Некорректное тело запроса, больше 100 элементов пакета или join.url, хост которого не входит в ваши зарегистрированные бэкенды. |
| 400 INVALID_ACTIVITY | После нормализации не осталось пригодного name. |
| 410 LINK_REVOKED | Связь завершена, с одной из сторон. Удалите её и снова предложите «Подключить mssgs». |
| 429 SLOW_DOWN | Вы опрашивали link/poll чаще, чем interval. |
| 429 RATE_LIMITED | Изменение одной связи в течение 2 секунд после предыдущего или больше 600 запросов в минуту на ваш ключ. |
| 503 LINK_STORE_UNAVAILABLE | Временная проблема на нашей стороне. Повторите при следующем heartbeat. |

## Безопасность

### Только нативные игры

Запросы с Origin веб-страницы отклоняются с 403 ORIGIN_NOT_ALLOWED . Если бы любая веб-страница могла обнаружить, что у вас запущен mssgs, и вызвать диалог разрешения, это была бы поверхность для фингерпринтинга и фишинга, а не возможность. Нативная игра вообще не отправляет Origin, поэтому её это не касается, а встроенный браузер игры разрешён явно по имени, см. [FiveM](#fivem). Если вы делаете браузерную или мобильную игру, вы не обращаетесь к мосту: за связанных игроков публикует ваш собственный бэкенд, см. [Браузерные и мобильные игры](#linked).

### Что остаётся под контролем игрока

- Игрок может выключить мост в разделе **Настройки → Игровая активность**, после чего ни одна игра вообще не видит mssgs.

- Там же перечислена каждая одобренная игра с точным списком её разрешений, временем последней активности и кнопкой **«Убрать»**. Удаление срабатывает сразу: токен немедленно перестаёт действовать.

- Связанная браузерная или мобильная игра указана в разделе **Связанные игры** с кнопкой **«Отключить»**. Отключение тоже срабатывает сразу: следующая публикация этого бэкенда получает 410 .

- Мост слушает только 127.0.0.1 и никогда не слушает сеть.

- Личные сообщения никогда не раскрываются, даже с servers.list.

- На каждую игру действует лимит 120 запросов в минуту.

### Как вести себя корректно

- Запрашивайте области доступа, когда они нужны, а не все сразу при первом запуске.

- Работайте и без mssgs: он не обязан быть у игрока.

- Очищайте статус, когда игра заканчивается, не дожидаясь TTL.

- Считайте отклонённую область доступа нормальным исходом, а не ошибкой.

## Что дальше
