API Reference

TheLoopChat's server exposes a chat HTTP API plus a WebSocket gateway, all on one port (:8080 by default):

Group Base path Auth Documented in
Chat API (v1) / (root paths) API key + user JWT or API secret API — Chat REST
WebSocket gateway /connect API key + user JWT (query params) API — WebSocket

Outbound integrations (FCM push, webhooks) are documented in API — Push and Webhooks.

Chat API authentication

Every chat request must identify the app with the x-api-key header. There are two auth modes:

Server auth — for customer backends (trusted, full admin over the app's data):

x-api-key: lck_...
x-api-secret: lcs_...

Client auth — for end-user devices (scoped to the authenticated user):

x-api-key: lck_...
authorization: Bearer <user JWT>

The user JWT is an HS256 token with a {"user_id": "<id>"} claim, signed with the app's api_secret — minted by your backend (e.g. @loopchat/node's createToken). Optional iat/exp claims are honored.

Dev tokens: a JWT-shaped string whose signature segment is literally devtoken (header {"alg":"HS256","typ":"JWT"}, payload {"user_id":"..."}, base64url, no padding). Accepted only when the app has dev mode enabled — for local development, never production.

App resolution also enforces suspension: requests for suspended apps fail with code 99, apps of suspended projects with code 98.

Plan limits

Effective limits = defaults ← project plan limits ← per-app permission_overrides (overrides win). Defaults: max_apps: 1, max_mau: 100, max_messages_month: 10000.

Enforcement is tolerant (Redis-backed counters; slight overrun is accepted): creating a new user checks max_mau (monthly-active HyperLogLog), sending a message checks max_messages_month, creating an app via the dashboard checks max_apps. Violations return HTTP 403 with code 97.

Error format

All errors — across every API group — share one body shape (see Protocol for the full code table):

{ "code": 17, "message": "user ada is not a member of messaging:general", "status": 403 }
HTTP Codes Typical causes
400 4 Validation failure (zod), malformed JSON
401 5 Missing/invalid API key, user token, or session
403 17, 97, 98, 99 Not a member / not allowed; plan limit; project or app suspended
404 16, 18, 19 Channel / user / message not found
500 1 Internal error

The most important one for chat clients: 403 + code 17 on watch means "not a member of an existing channel" — the SDKs expose this (LoopChatNetworkError.isNotMember) and the intended fix is a server-side idempotent addMembers call.

Conventions

  • JSON everywhere. Send content-type: application/json; empty bodies are tolerated (so DELETE with a JSON content-type header is fine).
  • Channel addressing: channels are identified by type + id; the cid is "type:id" (e.g. messaging:general). Both appear throughout payloads and events.
  • Message IDs are ULIDs — sortable by creation time; history pagination is ?before=<message id>.
  • Timestamps are ISO-8601 UTC strings.
  • Custom data: users, channels, and messages all carry a free-form custom object; SDKs surface it as custom (JS) or extraData (Dart).
  • Booleans in query strings are the literal strings true/false.
  • CORS is permissive with credentials, so the chat API can be called cross-origin from browser-based clients.

Health

GET /health{ "status": "ok" } — unauthenticated liveness probe.