Connector protocol
A connector is a program (game plugin, addon or mod) that keeps Inari informed of where players are. It talks to Inari over the same WebSocket endpoint as browsers: /ws.
Messages
Section titled “Messages”Every message is a JSON text frame with the same envelope:
{ "kind": "auth", "data": {}, "transaction_uuid": "6f1c0a9e-3a8b-4c53-a9a1-5e2c0f2d1b77"}| Field | Description |
|---|---|
kind |
Message type, in snake_case. |
data |
Payload, depends on kind. |
transaction_uuid |
Chosen by you. The response to a request carries the same value, so you can match them. |
Invalid JSON or unknown messages are silently ignored.
Must be the first message. It can only be sent once per connection.
Request
{ "kind": "auth", "data": { "is_connector": true, "auth_key": "<API_KEY>", "channel": "default" }, "transaction_uuid": "..."}| Field | Type | Description |
|---|---|---|
is_connector |
boolean |
true for a connector. |
auth_key |
string |
The server’s API_KEY. |
channel |
string |
Channel to manage. |
Response
{ "kind": "auth", "data": { "max_distance": 20, "app_version": "1.0.0" }, "transaction_uuid": "..."}sync_connector_state
Section titled “sync_connector_state”Send the full list of players of your game server. Call it regularly, about every 300 ms: it drives who hears whom. Players of the channel that are not in the list are not linked to a character.
Request
{ "kind": "sync_connector_state", "data": { "clients": [ { "code": "a1b2c3d4", "display_name": "Steve", "position": [120.5, 64.0, -30.2], "head": [1, 0, 0], "is_force_muted": false, "group": "team-red", "actions": { "toggle_muted": false, "toggle_deafened": false } } ] }, "transaction_uuid": "..."}| Field | Type | Required | Description |
|---|---|---|---|
code |
string |
yes | The code the player used to join. |
position |
[x, y, z] |
yes | Position of the character, in your game’s units. Compared with MAX_DISTANCE. |
head |
[x, y, z] |
yes | Direction the player is looking at, used to place the voices around the listener. Must be a unit vector (length 1, each component between -1 and 1), like Minecraft Bedrock’s getViewDirection(). For example [1, 0, 0] looks along the X axis. |
is_force_muted |
boolean |
yes | When true, nobody hears this player. |
display_name |
string |
no | Name shown on the web page. |
group |
string |
no | Only players of the same group hear each other. Defaults to default. |
actions.toggle_muted |
boolean |
no | true toggles the player’s muted state. |
actions.toggle_deafened |
boolean |
no | true toggles the player’s deafened state. |
Response
{ "kind": "sync_connector_state", "data": { "connections": [ { "code": "a1b2c3d4", "display_name": "Steve", "is_connector": false, "is_force_muted": false, "is_muted": false, "is_deafened": false } ] }, "transaction_uuid": "..."}connections lists everyone connected to the channel, which lets you show a player’s muted/deafened status in game.
Errors
Section titled “Errors”Errors use the error kind and the transaction_uuid of the failed request.
{ "kind": "error", "data": { "message": "UNAUTHORIZED", "details": "The API key provided is invalid.", "code": 401 }, "transaction_uuid": "..."}| Code | message |
Cause |
|---|---|---|
400 |
BAD_REQUEST |
Invalid client code (shorter than 6 characters or already used). |
401 |
UNAUTHORIZED |
Invalid API key, or a non-connector sent a connector message. |
409 |
ALREADY_CONNECTED |
auth was already sent on this connection. |
Minimal example
Section titled “Minimal example”import { WebSocket } from "ws";import { v4 as uuid } from "uuid";
const ws = new WebSocket("wss://inari.example.com/ws");const send = (kind: string, data: unknown) => ws.send(JSON.stringify({ kind, data, transaction_uuid: uuid() }));
ws.on("open", () => { send("auth", { is_connector: true, auth_key: process.env.INARI_API_KEY, channel: "default" });
setInterval(() => { send("sync_connector_state", { clients: getPlayers() }); // your game data }, 300);});