Without a relay, a party’s conversation sits in a room none of the players in the match can see. These two endpoints close that loop.
In-game chat → the site
/api/integration/v1/chatIntegrationCHAT_WRITERelay a line of in-game chat.
{
"partyId": "4471",
"player": { "name": "Ashley", "steamId": "76561198000000000" },
"body": "nice shot",
"ts": 1753822850
}
Omit partyId to post into the server’s own in-game relay room instead.
That form requires a server-scoped credential.
The body is plain text and is escaped on the way in
It is rendered through the same Markdown pipeline as the rest of the site, and anybody on your server can type into your chat box — so a player must not be able to post an image, an embed or a heading here by typing one in-game.
An author we cannot match keeps their in-game name and links nowhere. A message is never rendered as though a site member said it unless we actually resolved one.
The site → in-game
/api/integration/v1/chat/outboundIntegrationCHAT_READLines typed on the site that should be injected into the game.
GET /api/integration/v1/chat/outbound?since=8813&limit=50
{
"ok": true,
"messages": [
{
"id": "8814",
"at": "2026-07-29T20:41:02.000Z",
"author": "Ashley",
"authorId": "clx…",
"body": "rotating to B",
"muted": false
}
],
"newest": "8814"
}
Server-scoped credentials only. It serves the server’s own relay room plus the room of any live party on that server.
The cursor is an id, not a timestamp
Poll with the highest id you have already injected. Two messages can share a
millisecond, and a timestamp cursor would either skip or re-deliver a line.
Calling it with no since returns the most recent page, so a freshly
restarted mod does not replay the whole history into the game.
What comes back
Only site-authored messages. Lines that came from the game are excluded — echoing them would loop — and so are system lines, which are our own bookkeeping and mean nothing in-game. Deleted and hidden messages are excluded too.
author is the writer’s site name, so you can label the line in-game as
coming from the website rather than from a player standing next to them.
muted: true means the author was not in the party when they wrote it. The site
renders those dimmed, and you may want to skip or de-emphasise them too.
Telling the site what commands you accept
/api/integration/v1/chat/commandsIntegrationCHAT_WRITEWhat this server accepts as console commands, so the site’s chat composer can
offer them when a member types /. Server-scoped credentials only.
The list comes from the server because only the server has it. A member typing
/ into a relayed game chat is typing at a machine we do not control, whose
command table depends on which game is loaded and which modules an operator
installed — and a hard-coded list here would be stale the first time either
changed.
- commandsarrayrequired
Up to 256. Each has a
name(one word, no prefix on it), and optionallyusage(<map_id>,[reason]), a one-linedescription,chatAllowed, andpermission.- prefixstring
The prefix members type in front of them — for the menu’s label only. The site’s own trigger is
/and stays/whatever this says: it is what a member’s keyboard does, not what your server parses. A server whose prefix is!still gets a/menu here and receives the line it expects, because the relay writes the prefix on the way in.
Nothing you send here is a permission
Your server decides what a given member may run, every time, against its own
admin file. permission and chatAllowed are text for the menu. A report
claiming a command needs nothing therefore grants nothing — and that is the
property that makes accepting this list from an external program safe at all.
The payload is absolute, not incremental: it replaces what we hold. A command an operator removed has to disappear from the menu, and a merge has no way to say so. An empty list is how a server turns the menu off.
Two things happen on our side that are worth knowing. Names are deduplicated
case-insensitively before the write, so a payload naming map and Map does
not fail the whole batch — it would otherwise read to an operator as “the site
refuses my command list” rather than “you listed one twice”. And the replace is
one transaction, so a server reporting while a member has the menu open can
never show them an empty list followed by a full one.
An app-scoped credential is refused with a 403: it covers every server the
app has, and a command list posted under one would have no server to belong to.
Retention
Relayed chat has no per-report ceiling, and that is not an oversight: relaying is what the endpoint is for, so the only honest bound on it is your request rate.
It is bounded from the other end instead — a pruning job trims relayed lines on a retention window, 30 days by default.
Do not treat the chat relay as durable storage
If a line matters beyond the room it was said in, keep your own copy.
