Keys
Sign in with your QuxCloud account and open the console: your first workspace is made for you, on the Free plan. Create a key there. It looks like qcx_live_…, is shown once, and authenticates as your whole workspace, so it lives on your server, in an environment variable.
Every request carries it: Authorization: Bearer qcx_live_…. The base URL is https://qux.chat/api/chat.
Your first conversation
curl https://qux.chat/api/chat/conversations \
-H "Authorization: Bearer $QUXCHAT_API_KEY" \
-H "content-type: application/json" \
-d '{"kind":"support","createdBy":"agent:sam",
"participants":[{"actorId":"agent:sam","role":"admin"},{"actorId":"customer:ada"}]}'Actors are your own ids (customer:ada, agent:sam): qux.chat keeps no user table of yours. Your server says who is acting; the key says which workspace.
The SDK
@quxkit/chat is a typed TypeScript client for this API, with no dependencies, for Node 18+ and edge runtimes. It installs from our registry, npm.quxkit.ai, with a registry token from the console’s Keys page (it starts qcr_live_, and can only install packages):
# .npmrc
registry=https://npm.quxkit.ai/
//npm.quxkit.ai/:_authToken=${QUXKIT_NPM_TOKEN}
pnpm add @quxkit/chatSet its baseUrl to qux.chat:
import { ChatClient } from '@quxkit/chat';
const chat = new ChatClient({
apiKey: process.env.QUXCHAT_API_KEY!, // qcx_live_…, from the console
baseUrl: 'https://qux.chat/api/chat',
});
const msg = await chat.messages.send(conversationId, {
body: 'Hello',
senderActorId: 'customer:ada',
idempotencyKey: 'order-1042-hello',
});An idempotency key is unique across the workspace: the same key, conversation and body returns the original message instead of a second one.
Realtime
Every conversation has an event feed. Read it with a cursor and nothing is ever missed between two reads; ask it to wait and it answers the moment something happens.
// Long-poll: every event after a cursor, oldest first, gap-free.
// An empty answer after the wait means nothing happened; ask again with the same cursor.
let since = '';
for (;;) {
const q = new URLSearchParams({ wait: '25000', ...(since ? { since } : {}) });
const r = await fetch(`https://qux.chat/api/chat/conversations/${id}/events?${q}`, {
headers: { authorization: `Bearer ${key}` },
});
const { events, nextCursor } = await r.json();
for (const e of events) handle(e);
if (nextCursor) since = nextCursor;
}Webhooks
Subscribe an endpoint of yours with POST /webhooks. Every delivery is signed (Standard Webhooks), retried with backoff, and verifiable in one call with the SDK’s verifyWebhook.
Calls and media
Voice and video calls ring the people you name, connect browser to browser for small calls and through an SFU for groups, and have a lobby: a guest knocks, someone in the call admits them. Media uploads go straight to storage on a presigned link and attach to a message by id.
Browsers
The API sends no CORS headers, on purpose: a key is your workspace and must never reach a browser. Your browser talks to your server, which takes the user from your session, adds the key and forwards. The SDK’s turnProxy is that route for calls.
Endpoints
| Route | What it does |
|---|---|
| GET /conversations?actorId=… | One actor’s conversations, newest first, with unread counts |
| POST /conversations | Create a conversation with its participants |
| GET /conversations/:id/messages | History, newest first; page with `before` |
| POST /conversations/:id/messages | Send, with an idempotency key |
| POST /conversations/:id/read | Mark read up to a message |
| GET /conversations/:id/participants | Who is in it |
| GET /conversations/:id/events?since=&wait= | The realtime feed (long-poll) |
| GET /conversations/:id/stream | The same feed as server-sent events (beta) |
| GET · POST /webhooks | List and add webhook subscriptions |
| POST /calls | Start a call: rings the invitees |
| POST /calls/:id/join · leave · knock · admit | Answer, hang up, and the guest lobby |
| POST /calls/:id/sfu/… | A group call’s media path through the SFU |
| POST /media | A presigned upload; then POST /media/:id/complete |
Moving to QuxCloud
qux.chat runs QuxCloud’s chat API: the same routes, the same qcx keys and the same data model. On QuxCloud the base URL is https://cloud.quxkit.ai/api/chat (the SDK’s default). Nothing else in your code changes.