NinjaChatter automation guide

NinjaChatter provides room-scoped chat, bot publishing, and read-only live streams. An AI agent can use the same HTTP API as any other integration; no model account or special AI endpoint is required.

Connect an agent to a room

  1. Have the room owner select a room in the dashboard, open Integrations, and create a send-only bot key. Copy the key when it appears; it cannot be retrieved later.
  2. Store the key in your backend's secret configuration. Bind the room and key in application code; expose only message text to the model's publishing tool. Send-only keys cannot delete messages, moderate, change settings, or mint privileged tokens. The separate legacy ingress key still has broader integration privileges.
  3. Give the model a narrowly scoped tool such as send_room_message(text), using type: "chat" and payload.isBot: true. Use a recognizable display name. Avoid automatic replies to every event, especially your own bot's events.
  4. Treat incoming chat text, names, URLs, and metadata as untrusted user content. Keep them separate from system instructions and credentials, and require the operator's policy to authorize moderation or external actions.

The example was tested with Node.js 22. Download room-publisher.mjs into your backend and import it:

import { createRoomPublisher } from './room-publisher.mjs';

const sendRoomMessage = createRoomPublisher({
  room: process.env.NINJACHATTER_ROOM,
  apiKey: process.env.NINJACHATTER_BOT_KEY,
});

// Connect this function to your agent's tool handler. Room and key stay server-side.
const result = await sendRoomMessage({ text: 'Your task is complete.' });
// result.status is "accepted" or "filtered"; accepted is not a viewer receipt.

Never embed either key in a public page, an iframe URL, chat content, or the model's prompt. For a viewing UI, use an overlay or the documented guest/OAuth join flow. Human provider verification still requires the normal OAuth interaction.

Manage a send-only key

Each room can have one active send-only key. It remains valid until replaced, revoked, or the room is deleted; it does not expire automatically. Creating another immediately replaces the previous key. Rotation of legacy ingress keys and webhook secrets is independent.

The owner can use GET, POST, and DELETE /rooms/{room}/bot-key with Authorization: Bearer <owner JWT> to check status, create/replace, and revoke respectively. No request body is needed. Status and revocation return { "active": true/false }; creation also returns key once. Responses have Cache-Control: no-store. Only a SHA-256 hash is stored. Do not give an agent the owner JWT or key-management tools.

Revocation is checked against shared storage on every ingress request. It blocks subsequent authentication; a request already authorized may finish. A restored backup may restore the credential state captured in that backup, so revoke/replace keys after a recovery if necessary.

Send-only keys accept only type: "chat", optional id (1–128 UTF-8 bytes), and payload containing nonblank text (at most 4,096 Unicode characters), optional nonblank displayName (at most 80 characters), and optional isBot: true. Other fields, event types, and identity overrides are rejected with 400. The server assigns a stable room bot identity and bot role. Content filtering and existing ingress limits still apply. The backend publisher below supports both key types without changes.

Publish with HTTP

POST /rooms/{room}/ingress accepts a JSON envelope. The entire UTF-8 request body must be no more than 65,536 bytes. For visible bot messages use:

{
  "type": "chat",
  "id": "your-correlation-id",
  "payload": {
    "text": "Your task is complete.",
    "displayName": "Assistant",
    "isBot": true
  }
}

Choose one authentication method:

A Node.js signing example:

import { createHmac } from 'node:crypto';
const body = JSON.stringify({ type: 'chat', payload: { text: 'Hello', isBot: true } });
const signature = createHmac('sha256', process.env.NINJACHATTER_WEBHOOK_SECRET)
  .update(body, 'utf8').digest('base64');
// Send exactly body, with Content-Type: application/json and X-Chat-Signature: signature.

A successful response contains message_id and, if supplied, external_id. For a chat publication, message_id: "filtered" with no external_id means the room's content filter blocked publication. If your supplied ID is literally filtered, the accepted response also includes external_id: "filtered"; that is not a filter rejection. An ID you supply is a correlation value, not an idempotency key: repeating a request can broadcast it again. A successful response acknowledges the broker publication; it does not prove that a viewer received or displayed it.

Read live chat

GET /rooms/{room}/public returns public settings without secrets. enabled and allow_viewers must both be true for public read-only streams. allow_guest controls guest chat joins separately.

GET /stream/{room}/sse is a long-lived text/event-stream response with chat envelopes in data: fields and comment keep-alives. It emits chat only; it is not a moderation or deletion feed. Use the documented WebSocket integration when those events are needed. No token is needed for an enabled public viewer stream. Do not enable public viewers on a private room merely to connect a bot; use an authorized WebSocket integration instead.

There is no durable history guarantee or Last-Event-ID resumption. Events during outages or disconnection can be lost. Reconnect reads with bounded exponential backoff and jitter; stop on permission failures. Consumers that fall behind may be disconnected.

Handle failures deliberately

Application errors normally return { "error": "..." }; proxy and framework errors may be plain text or HTML. Check HTTP status and content type before decoding JSON. Never log authorization headers.

GET /readyz returns 200 with ok when dependencies are ready. It is a readiness check, not proof of end-to-end message delivery.

Host slow mode

Pro hosts can open Dashboard → Room Settings → Slow mode (seconds), choose a delay from 1 to 300 seconds, and save. Set it to 0 and save to turn it off. Through the owner API, update features.slow_mode_secs with PUT /rooms/{room}.

The cooldown applies per viewer to chat messages and is shared across app nodes and connections. Chat displays the remaining wait after a throttled send. Bot/webhook ingress keeps its existing rate limits. Slow mode can make busy chat easier to follow, but it does not cap the room's total publication rate when many different viewers send messages.