TypeScript SDK
@clickclack/sdk-ts is the framework-neutral client. It wraps the HTTP API and the realtime WebSocket without any Svelte dependency, so bots, CLIs, and non-Svelte frontends can use it directly.
Source: packages/sdk-ts/src/index.ts.
#Install (workspace)
The SDK is published from the monorepo. Inside this repo, depend on it as @clickclack/sdk-ts via pnpm workspaces. External consumers will install it once it's published.
#Quick start
import { ClickClackClient } from "@clickclack/sdk-ts";
const client = new ClickClackClient({
baseUrl: "http:
token: process.env.CLICKCLACK_TOKEN,
userId: process.env.CLICKCLACK_USER_ID,
});
const me = await client.me();
await client.updateMe({ display_name: "Peter Steinberger", handle: "@steipete" });
const workspaces = await client.workspaces.list();
const channels = await client.channels.list(workspaces[0].id);
const message = await client.channels.sendMessage(channels[0].id, {
body: "click clack",
nonce: crypto.randomUUID(),
});
await client.channels.markRead(channels[0].id, message.channel_seq ?? 0);
#Auth
The client sends, in this order:
Authorization: Bearer <token>iftokenwas set orauth.consumeMagicLinkX-ClickClack-User: <userId>ifuserIdwas set. Use this only for local
succeeded (it stores the returned session token).
development/test impersonation; hosted bots should use bearer tokens.
Helpers:
client.auth.requestMagicLink({ email }); // POST /api/auth/magic/request
client.auth.consumeMagicLink(token); // POST /api/auth/magic/consume; sets bearer
client.auth.setToken(token); // store an externally-issued token
client.auth.githubStartUrl(); // build the OAuth start URL for the browser
See features/auth.md.
#Surface
| Group | Methods |
|---|---|
me(), updateMe() | get or edit the current user's profile |
workspaces | list, create |
topics | list, create |
bots | list, create, listTokens, createToken, revokeToken |
apps | list, install, revoke |
slashCommands | list, create, revoke |
eventSubscriptions | list, create, revoke, deliveries |
auditLog | list |
connectedAccounts | list, create, revoke |
channels | list, create, update, messages, sendMessage, markRead |
messages | get, update, delete |
threads | get, reply |
search(workspaceId, q) | full-text search |
uploads | create(workspaceId, file), attach(messageId, uploadId) |
dms | list, create, messages, sendMessage, markRead |
events | publishEphemeral, subscribe |
#Realtime subscription
const socket = client.events.subscribe({
workspaceId,
afterCursor: lastSeenCursor,
onEvent(event) {
// event.type, event.payload, event.cursor
},
onClose() {
// backoff and resubscribe with the latest cursor
},
});
subscribe returns a raw WebSocket. Call .close() to disconnect. See features/realtime.md for cursor recovery rules.
#Generated types
packages/sdk-ts/src/generated/openapi.d.ts is generated from packages/protocol/openapi.yaml. Re-export at the top of index.ts:
export type { components, paths } from "./generated/openapi";
Use the friendly hand-written types (User, Workspace, Message, etc.) for app code; reach into components["schemas"] only when you need the exact OpenAPI shape.
#Bot accounts
Hosted bots should use bot tokens, not human session tokens. Create one from the admin CLI:
clickclack admin bot create \
--workspace wsp_... \
--name "OpenClaw Service" \
--handle openclaw \
--scopes bot:write \
--plain
The returned ccb_... token goes into CLICKCLACK_TOKEN.
Human-session clients can also manage bot lifecycle through the SDK:
const { bot, bot_token } = await client.bots.create(workspaceId, {
display_name: "OpenClaw Service",
handle: "openclaw",
token_name: "prod",
scopes: ["bot:write"],
});
const tokens = await client.bots.listTokens(bot.id);
await client.bots.revokeToken(tokens[0].id);
Only create and createToken responses include the one-time raw bot_token.token. List calls return metadata only.
The SDK also exports ClickClackBot, a tiny runner around the same client plus the realtime WebSocket:
import { ClickClackBot } from "@clickclack/sdk-ts";
const bot = new ClickClackBot({
baseUrl: "http:
token: process.env.CLICKCLACK_TOKEN,
workspaceId: process.env.CLICKCLACK_WORKSPACE_ID!,
onEvent(event, client) {
if (event.type !== "message.created") return;
const channelId = event.channel_id;
if (channelId) void client.channels.sendMessage(channelId, { body: "ack" });
},
});
bot.start();
Persist event.cursor after each handled event and reconnect with afterCursor for exactly-once-ish processing. Ignore events whose payload.author_id matches the bot's own user ID to avoid loops.
See features/bots.md and bot-installs.md.
#Example package
examples/bot-ts is a minimal one-shot bot that sends a single message:
CLICKCLACK_URL=http://localhost:8080 \
CLICKCLACK_TOKEN=ccb_... \
CLICKCLACK_CHANNEL_ID=chn_... \
CLICKCLACK_TEXT="clack from bot" \
pnpm --filter @clickclack/example-bot start