Posting thread messages via the API

Last updated: 2026-06-29Contributors
Edit this page

Team Messaging posts can be grouped into threads. A threaded reply is still a post in the same chat, but the post response includes thread metadata that lets your app find the parent post and retrieve the other posts in the thread.

Use threaded posts when a bot or app needs to keep follow-up messages attached to a specific conversation inside a chat.

Creating a thread reply

Create the first reply to a post by passing the parent post ID in the parentPostId request property when you call Create Post.

{
  "text": "First reply in the thread",
  "parentPostId": "1234567890"
}

If parentPostId identifies a reply that is already in a thread, the new post is added to the original parent post's thread.

Adding a reply to a thread

If your app already has a thread ID, you can add a reply by passing threadId instead.

{
  "text": "Reply using a thread ID",
  "threadId": "9876543210"
}

Use one of parentPostId or threadId for a threaded reply. To create a regular top-level post, omit both fields.

Post responses from Create Post, Get Post, Update Post, and List Posts can include these thread fields:

Field Description
isParent Returned for a post that is the parent post of a thread.
parentPostId The parent post ID when the post belongs to a thread.
threadId The thread ID shared by posts in the same thread.

Listing posts in a thread

Call List Thread Posts to fetch posts that belong to a specific thread.

GET /team-messaging/v1/chats/{chatId}/threads/{threadId}/posts?recordCount=30

The response uses the same Team Messaging pagination model as other list endpoints. If the response contains navigation.nextPageToken or navigation.prevPageToken, pass the token as the pageToken query parameter in the next request.

GET /team-messaging/v1/chats/{chatId}/threads/{threadId}/posts?pageToken={pageToken}

Example

The following example finds the authenticated user's personal chat, creates a parent post, creates a threaded reply using parentPostId, and then lists posts from the returned threadId.

const RC = require('@ringcentral/sdk').SDK
require('dotenv').config();

var rcsdk = new RC({
    'server':       process.env.RC_SERVER_URL,
    'clientId':     process.env.RC_APP_CLIENT_ID,
    'clientSecret': process.env.RC_APP_CLIENT_SECRET
});
var platform = rcsdk.platform();
platform.login({ 'jwt':  process.env.RC_USER_JWT })

platform.on(platform.events.loginSuccess, () => {
  create_threaded_posts()
})

async function create_threaded_posts() {
  try {
    let chatId = await get_personal_chat_id()
    let parentPost = await create_post(chatId, {
      text: "Thread parent post"
    })
    console.log("Parent post ID: " + parentPost.id)

    let reply = await create_post(chatId, {
      text: "First reply in the thread",
      parentPostId: parentPost.id
    })
    console.log("Reply post ID: " + reply.id)
    console.log("Thread ID: " + reply.threadId)

    let threadPosts = await list_thread_posts(chatId, reply.threadId)
    console.log("Posts in thread:")
    threadPosts.records.forEach((post) => {
      console.log(`- ${post.id}: ${post.text}`)
    })
  } catch (e) {
    console.log(e.message)
  }
}

async function get_personal_chat_id() {
  let endpoint = "/team-messaging/v1/chats"
  let resp = await platform.get(endpoint, { type: 'Personal' })
  let jsonObj = await resp.json()
  if (jsonObj.records.length === 0) {
    throw new Error("Personal chat not found")
  }
  return jsonObj.records[0].id
}

async function create_post(chatId, bodyParams) {
  let endpoint = `/team-messaging/v1/chats/${chatId}/posts`
  let resp = await platform.post(endpoint, bodyParams)
  return await resp.json()
}

async function list_thread_posts(chatId, threadId) {
  let endpoint = `/team-messaging/v1/chats/${chatId}/threads/${threadId}/posts`
  let resp = await platform.get(endpoint, { recordCount: 30 })
  return await resp.json()
}