Patterns

Common patterns for pagination, error handling, and real-world usage

Pagination

channelMessages supports cursor-based pagination using fromOrder and around. Messages are ordered by a sort key — pass the last message’s order value to load the next page.

import { setup, query } from '@start-chat/sdk'
await setup({ apiKey: 'sk_live_your_api_key' })
const SERVER = 'srv_...'
const CHANNEL = 'ch_...'
// first page
let messages = await query.channelMessages({
serverId: SERVER,
channelId: CHANNEL,
limit: 20,
})
// load older messages
if (messages.length > 0) {
const oldest = messages[messages.length - 1]
const olderMessages = await query.channelMessages({
serverId: SERVER,
channelId: CHANNEL,
fromOrder: oldest.order,
around: 'before',
limit: 20,
})
}

Filtering

Filter messages by type using channelFilter:

// only thread messages
const threads = await query.channelMessages({
serverId: SERVER,
channelId: CHANNEL,
channelFilter: 'thread',
})
// only pinned messages
const pinned = await query.channelMessages({
serverId: SERVER,
channelId: CHANNEL,
channelFilter: 'pinned',
})
// only app-generated messages
const appMessages = await query.channelMessages({
serverId: SERVER,
channelId: CHANNEL,
channelFilter: 'app',
})

Error handling

Setup errors

try {
await setup({ apiKey: 'sk_live_your_api_key' })
} catch (e) {
if (e.message === 'Invalid API key') {
// key doesn't exist or was revoked
}
// network errors, server unreachable, etc.
}

Query errors

Queries throw on permission or not-found errors:

try {
const channel = await query.channelById({ channelId: 'ch_doesntexist' })
} catch (e) {
// channel not found or no access
}

Mutation errors

Mutations validate required fields before sending:

try {
await mutate.message.send({ channelId: 'ch_...', content: '' })
} catch (e) {
// "content is required" — validation happens client-side
}

Typing indicators

Show when your bot is “typing” before sending a response:

import { setup, mutate } from '@start-chat/sdk'
await setup({ apiKey: 'sk_live_your_api_key' })
// start typing
const indicator = await mutate.indicator.insert({
serverId: 'srv_...',
channelId: 'ch_...',
type: 'typing',
})
// do work...
await someSlowOperation()
// send the message
await mutate.message.send({
channelId: 'ch_...',
content: 'Here is the result!',
})
// stop typing
await mutate.indicator.delete({ id: indicator.id })

Threads

Create a thread and reply to it:

// create a thread
await mutate.thread.insert({
channelId: 'ch_...',
serverId: 'srv_...',
creatorId: 'usr_...',
title: 'Discussion about feature X',
})
// send a message to the thread
await mutate.message.send({
channelId: 'ch_...',
threadId: 'thr_...',
content: 'First reply in the thread',
})
// read thread messages
const thread = await query.threadWithMessages({
threadId: 'thr_...',
serverId: 'srv_...',
})

Reactions

// add a reaction
await mutate.reaction.insert({
messageId: 'msg_...',
content: '👍',
})
// remove a reaction
await mutate.reaction.delete({ id: 'reaction_id' })

Pins

// pin a message
await mutate.pin.insert({
messageId: 'msg_...',
channelId: 'ch_...',
})
// list pinned messages
const pinned = await query.channelMessages({
serverId: 'srv_...',
channelId: 'ch_...',
channelFilter: 'pinned',
})

Local development

Use sk_dev with a local server to skip API key setup:

await setup({
apiKey: 'sk_dev',
baseUrl: 'http://localhost:7878',
})
// authenticated as admin, full access
const servers = await query.userServers()

This auto-authenticates as the admin user. Use it for local testing — never in production.