Team Chat API
Team Chat is internal, employee-to-employee messaging inside your Orbit workspace — channels, threaded replies, huddles (ad-hoc voice/video rooms), and direct messages between operators. It is entirely separate from customer-facing conversations: nothing here reaches a customer, and no outbound customer voice or SMS is touched by any endpoint on this page. Base path:/api/v1/team-chat
Authentication: API key (X-API-Key) or session JWT. Posting or reading a channel requires channel membership; managing membership requires an owner or admin role on that channel. DM endpoints require the caller to be one of the two participants.
Channels
Channel messages
Search
GET /api/v1/team-chat/search ranks message bodies by relevance and paginates with an opaque cursor. It only ever searches channels you’re a member of — membership is the authorization boundary, so a channel you were removed from drops out of results with no separate check.
The cursor is opaque — pass
meta.pagination.cursor back verbatim as the cursor query param to fetch the next page, and do not parse or rebuild it. has_more: false (or a null cursor) means you have seen the full result set. Results are ordered by rank, then recency.Huddles
A huddle is an ad-hoc, low-friction voice/video room scoped to a channel — anyone in the channel can start one or drop into the live one.Direct messages
Members, threads, DMs, and huddles
Samples below state the response envelope for one call in each declared group.data is the payload shown under each example; every response also carries meta.request_id (echo it back to support — it is the fastest cross-reference) and meta.timestamp.
Add a member
POST /api/v1/team-chat/channels/{channelId}/members with { "user_id": "<workspace user id>" } adds one member. Only the channel’s owner or an admin can add or remove members; a caller without that role gets 403 NOT_A_MEMBER. Response 201:
201
Reply in a thread
POST /api/v1/team-chat/channels/{channelId}/messages/{messageId}/thread posts into the thread rooted at {messageId}. Threads are one level deep: if {messageId} is itself a reply, the new reply still points at that reply’s root, so parent_message_id always names the root message. Response 201:
201
GET on the same path returns the thread in reading order as { "root": <message>, "replies": [<message>...] }; a soft-deleted root renders root: null with the surviving replies.
List DM threads
GET /api/v1/team-chat/dms returns the threads you participate in, most-recent-message first. Response 200:
200
null — render the id tail then), their last inbound message preview (last_body, capped at 140 characters), and your capped unread_count. DM endpoints reject callers who are not one of the two participants.
Start or join a huddle
POST /api/v1/team-chat/channels/{channelId}/huddle starts the huddle (or rejoins the one already running) and returns a join hint to pass to your WebRTC client — this is an internal operator voice/video plane; nothing on this page exercises outbound customer voice or SMS. Response 201 (first start) or 200 (joining a live huddle):
201
started: true means you opened the room; started: false means you joined it. Present token around media_url as the client join hint — the server pre-creates the room with an empty-timeout so an abandoned huddle reclaims itself, and POST /huddle/leave ends it when the room empties.
Example — create a channel and post a message
id, name, description, created_by_user_id, created_at, updated_at, plus my_role — your role on the channel, owner for its creator. Message objects carry id, channel_id, sender_user_id, body, mentions, attachments, created_at, and edited_at; a top-level message has parent_message_id: null (replies point at their thread root). The workspace user id you see as created_by_user_id / sender_user_id qualifies as an owner_id / author_id wherever a client types that field — only members yield ids on this surface (403 NOT_A_MEMBER otherwise).
Walked mini-recipe — create, post, edit, and search
The four-step miniature below recreates acreate channel → post → edit own message → search it flow end to end; drop the drafts into the Task index in API recipes. Shape it as copyable steps and keep every request id so a page hop across the four calls stays traceable.
Create the channel — capture data.id (tch_…) for the two follows:
cURL
data.id (the message id):
cURL
PATCH; the sender-scoped WHERE clause is the authorization boundary, so another member gets 404 NOT_FOUND:
cURL
cURL
data (a flat array, data.results if your client normalizes it) in rank-descending order, and meta.pagination.cursor carries forward the next-page token. Reuse the cursor verbatim — parsing or rebuilding it voids the page; an unparsed/null cursor always starts at rank 1.
See also
- On-Call API — a common pairing: page a rotation, then coordinate the incident in a team-chat channel
- Inbox API — the customer-facing conversation surface this is intentionally separate from