Thread messaging message store

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

List messages

Any SMS handler can view all messages in the thread messaging store for the common resources they belong to, regardless of whether they are assigned to a specific thread. This enables handlers to access not only their own conversations but also those assigned to teammates, improving visibility, collaboration, and overall continuity of service.

The list message threads API supports multiple filters which can be used to optimize the response which returns only interested messages. For example, a handler can set the “threadStatus”=”Open” to list only messages from open threads, or set the “ownerExtensionIds” to a common resource ID (e.g. a call queue extension ID) to list only messages created for that common resource. This is useful when a handler is a member of multiple common resources.

The /restapi/v1.0/account/~/message-threads/messages API currently supports the following essential filters:

Filter name type Description Note
threadStatus String Filters threads based on their status: Open or Resolved. If no status is specified, all threads (both Open and Resolved) will be listed by default.
ownerExtensionIds Array of string Filters messages based on one or several common resources identified by the common resource IDs. If not specified, return messages from all common resources the current authenticated user is a SMS handler.
availability enum Can be specified to "Alive" and/or "Deleted".
messageIds Array of string Return messages matches the specified message Ids. If not specified, returning all messages from common resources the current authenticated user is a SMS handler.
creationTimeFrom string Start date/time for resulting messages created after the specified date/time. In UCT time. If not specified, the default value is the last 24 hours.
creationTimeTo string End date/time for resulting messages created before the specified date/time. In UCT time. If not specified, the default value is the current time.

Sample response

[
  {
    "id": "2904652345",
    "threadId": "ebf18942-9cd4-4980-9fa3-e1ddfec7c45a",
    "availability": "Alive",
    "creationTime": "2026-02-12T21:06:33.706Z",
    "lastModifiedTime": "2026-02-12T21:10:05.921Z",
    "direction": "Outbound",
    "messageStatus": "Delivered",
    "text": "Hi Bill, your order was shipped and here is the tracking number #1000203330.",
    "author": {
      "extensionId": "62295327016",
      "name": "Jenn G",
      "extensionType": "User"
    }
  },
  {
    "id": "2901714323",
    "threadId": "ebf18942-9cd4-4980-9fa3-e1ddfec7c45a",
    "availability": "Alive",
    "creationTime": "2026-02-12T17:19:56.266Z",
    "lastModifiedTime": "2026-02-12T17:19:56.266Z",
    "direction": "Inbound",
    "messageStatus": "Received",
    "text": "My order number is 123456, can you let me know the order status?"
  }
]

A thread message does not include the pair of sender and recipient phone numbers. Developers should use the threadId in each message record to connect messages in a thread to create a conversation. To detect the pair of phone numbers, developers should call the Read Message Thread API.

Delete messages

Any SMS handler can delete a message in the thread messaging store for the common resources they belong to, regardless of whether they are assigned to a specific thread. This provides flexibility in message management, allowing handlers to perform cleanup or moderation actions across shared resources without requiring explicit thread ownership.

To delete multiple messages in a single API call, developers can specify a list of message IDs to be deleted. Batching deletions this way reduces the number of API calls required and improves overall performance, making it especially useful when managing high volumes of messages.

It is the developer's responsibility to restrict message deletion if the application requires that only the assignee can delete messages from their threads. This access control logic must be implemented at the application level, as the API does not enforce ownership-based deletion restrictions by default. Developers should validate the requesting user's identity and thread assignment before invoking the delete operation.

Sample code

async function delete_messages(){
  try{
    let bodyParams = {
      ids: ["4344768583", "4353120793"]
    }
    let endpoint = "/restapi/v1.0/account/~/message-threads/messages"
    await platform.delete(endpoint, bodyParams)
    console.log("Message(s) deleted")
  }catch(e){
    console.log(e.message)
  }
}

Sync thread entries (messages)

Inbound and outbound messages are grouped into message threads based on a combination of the service and the customer phone number. The Sync Thread Entries API allows SMS handlers to retrieve and monitor message records within these threads, ensuring that applications remain up to date with the latest inbound and outbound message activity.

The Sync API supports two synchronization modes, defined by the syncType parameter: FSync (full synchronization) and ISync (incremental synchronization).

  • The FSync mode returns all message records that belong to currently active and open thread(s), providing an initial snapshot of thread messages.
  • The ISync mode returns only those message records whose properties have changed since the initial FSync call or the most recent ISync call, enabling efficient and incremental tracking of message updates.

When calling the API in FSync mode, developers can optionally specify the scope parameter to control which thread entries are included in the response, depending on the desired use case:

  • scope="Accessible" — Returns entries from all threads accessible to the current SMS handler.
  • scope="Unassigned" — Returns entries from accessible threads that are currently unassigned.
  • scope="AssignedToMe" — Returns entries from threads assigned to the current SMS handler.
  • scope="AssignedToMeAndUnassigned" — Returns entries from threads assigned to the current SMS handler or currently unassigned.
  • scope="Explicit" — Returns entries from specific threads identified by the threadIds parameter. When using this scope, a list of thread IDs must be provided.

Note

The scope parameter is supported only when calling the API in FSync mode. For ISync calls, the scope is already embedded in the sync token returned by the previous synchronization request.

To use the Sync API effectively, developers should first call the API with syncType=FSync to retrieve the initial set of message records. The response includes a sync token, which must be saved by the application. In subsequent calls, developers use this sync token with syncType=ISync to retrieve only new message records and the message records that have changed since the last synchronization.

let entriesSyncToken = ""
async function full_sync_thread_messages(){
  try{
    let queryParams = {
      scope: "Accessible",
      syncType: "FSync"
    }
    let endpoint = '/restapi/v1.0/account/~/message-threads/entries/sync'
    var resp = await platform.get(endpoint, queryParams)
    var jsonObj = await resp.json()
    console.log("Full sync message data: ", JSON.stringify(jsonObj, null, 4))
    entriesSyncToken = jsonObj.syncInfo.syncToken
  }catch(e){
    console.log(e.message)
  }
}

async function incremental_sync_thread_messages(){
  if (entriesSyncToken == ""){
    await full_sync_thread_messages()
    return
  }
  try{
    let queryParams = {
      syncType: "ISync",
      syncToken: entriesSyncToken
    }
    let endpoint = '/restapi/v1.0/account/~/message-threads/entries/sync'
    var resp = await platform.get(endpoint, queryParams)
    var jsonObj = await resp.json()
    console.log("Incremental message sync data: ", JSON.stringify(jsonObj, null, 4))
    entriesSyncToken = jsonObj.syncInfo.syncToken
  }catch(e){
    console.log(e.message)
  }
}

Render and download message attachments

If a message contains attachment(s), the attachment's metadata is included in the message object under the attachments array as shown in the sample message below:

{
  "id": "4345841054",
  "threadId": "cb1bbb90-8091-4dce-a70d-5491c109546f",
  "availability": "Alive",
  "creationTime": "2026-05-18T19:24:30.975Z",
  "lastModifiedTime": "2026-05-18T19:34:33.902Z",
  "direction": "Inbound",
  "messageStatus": "Received",
  "text": "Here is the image of the damaged part.",
  "attachments": [
    {
      "size": 65012,
      "contentType": "image/png",
      "id": "488010059",
      "contentUri": "https://media.ringcentral.com/restapi/v1.0/account/80964XXXX/message-threads/messages/4345841054/content/488010059",
      "filename": "image-001.png"
    }
  ]
}

To render attachment(s) directly in a web application UI from the remote server, developers can use the contentUri value and include the user’s valid access token when requesting the resource.

For example, to display the image from the sample attachment above, retrieve the user’s access token, construct the authenticated content URI, and assign it to the image element’s src attribute.

<img src='https://media.ringcentral.com/restapi/v1.0/account/80964XXXX/message-threads/messages/4345841054/content/488010059?access_token=valid-user-access-token'></img>

To download attachments and save them locally, developers can send an HTTP GET request to the content URI to retrieve the binary content and save it to a file.

Sample code

The sample code below download a message attachment(s) and save to a local file.

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

// Instantiate the SDK and get the platform instance
var rcsdk = new RC({
    server: "https://platform.ringcentral.com",
    clientId: "RC_APP_CLIENT_ID",
    clientSecret: "RC_APP_CLIENT_SECRET"
});
var platform = rcsdk.platform();

/* Authenticate a user using a personal JWT token */
platform.login({ jwt: "RC_USER_JWT" })

platform.on(platform.events.loginSuccess, function(e){
    list_messages()
});

platform.on(platform.events.loginError, function(e){
    console.log("Unable to authenticate to platform. Check credentials.", e.message)
    process.exit(1)
});

/*
  Read thread messages
*/
async function list_messages(){
  try{
    let queryParams = {
      // threadStatus: "Open",
      // threadId: "...",
      // ownerExtensionIds: ["..."],
      // messageIds: ["..."],
      // creationTimeFrom: "...",
      // creationTimeTo: "...",
      perPage: 10
    }

    let endpoint = `/restapi/v1.0/account/~/message-threads/messages`
    var resp = await platform.get(endpoint, queryParams)
    var jsonObj = await resp.json()
    for (let msg of jsonObj.records){
      // Parse and handle the message data

      // Check if this message has attachment(s)
      if (msg.attachments.length)
        await get_message_attachments(msg.attachments)
    }
  }catch(e){
    console.log(await e.message)
  }
}

/*
  Download attachment(s) and save to a local file
*/
async function get_message_attachments(attachments){
  let fs = require('fs')
  try {
    for (var attachment of attachments){
      var fileName = attachment.filename
      let resp = await platform.get(attachment.contentUri)
      let buffer = await resp.buffer()
      fs.writeFileSync(fileName, buffer)
    }
  }catch(e){
    console.log(e.message)
  }
}
import json
from ringcentral import SDK

#
# Read thread messages
#
def list_messages():
    try:
        query_params = {
            # "threadStatus": "Open",
            # "threadId": "...",
            # "ownerExtensionIds": ["..."],
            # "messageIds": ["..."],
            # "creationTimeFrom": "...",
            # "creationTimeTo": "...",
            "perPage": 10
        }

        endpoint = "/restapi/v1.0/account/~/message-threads/messages"

        resp = platform.get(endpoint, query_params)
        jsonObj = resp.json_dict()
        for msg in jsonObj['records']:
            # Parse and handle the message data

            # Check if this message has attachment(s)
            if msg.get("attachments"):
                get_message_attachments(msg["attachments"])
    except Exception as e:
        print(str(e))
#
# Download attachment(s) and save to a local file
#
def get_message_attachments(attachments):
    for attachment in attachments:
        fileName = str(attachment["filename"])
        try:
            res = platform.get(attachment["contentUri"])
            file = open(("%s" % (fileName)),'wb')
            file.write(res.body())
            file.close()
        except ApiException as e:
            print (e.getMessage())

# Authenticate a user using a personal JWT token
def login():
    try:
      platform.login( jwt= "RC_USER_JWT" )
      list_messages()
    except Exception as e:
      print ("Unable to authenticate to platform. Check credentials." + str(e))

# Instantiate the SDK and get the platform instance
rcsdk = SDK("RC_APP_CLIENT_ID", "RC_APP_CLIENT_SECRET", "https://platform.ringcentral.com")
platform = rcsdk.platform()

login()
<?php
require('vendor/autoload.php');

// Instantiate the SDK and get the platform instance
$rcsdk = new RingCentral\SDK\SDK( "RC_APP_CLIENT_ID", "RC_APP_CLIENT_SECRET", "https://platform.ringcentral.com" );
$platform = $rcsdk->platform();

/* Authenticate a user using a personal JWT token */
try {
  $platform->login(["jwt" => "RC_USER_JWT"]);
}catch (\RingCentral\SDK\Http\ApiException $e) {
  exit("Unable to authenticate to platform. Check credentials. " . $e->message . PHP_EOL);
}
list_messages();

/*
  Read thread messages
*/
function list_messages() {
    global $platform;
    try {
        $queryParams = [
            // 'threadStatus' => 'Open',
            // 'threadId' => '...',
            // 'ownerExtensionIds' => ['...'],
            // 'messageIds' => ['...'],
            // 'creationTimeFrom' => '...',
            // 'creationTimeTo' => '...',
            'perPage' => 10
        ];

        $endpoint = '/restapi/v1.0/account/~/message-threads/messages';
        $response = $platform->get($endpoint, $queryParams);
        $jsonObj = $response->json();

        foreach ($jsonObj->records as $msg) {
            // Parse and handle the message data

            // Check if this message has attachment(s)
            if (!empty($msg->attachments)) {
                get_message_attachments($msg->attachments);
            }
        }
    } catch (Exception $e) {
        echo $e->getMessage();
    }
}

/*
 Download attachment(s) and save to a local file
*/
function get_message_attachments($attachments) {
  global $platform;
  try {
    foreach ($attachments as $attachment){
      $res = $platform->get($attachment->contentUri);
      file_put_contents($attachment->filename, $res->raw());
    }
  }catch (ApiException $e) {
    $message = $e->getMessage();
    print 'Expected HTTP Error: ' . $message . PHP_EOL;
  }
}
?>
require 'ringcentral'

#
# Read thread messages
#
def list_messages()
  begin
    query_params = {
      # threadStatus: "Open",
      # threadId: "...",
      # ownerExtensionIds: ["..."],
      # messageIds: ["..."],
      # creationTimeFrom: "...",
      # creationTimeTo: "...",
      perPage: 100
    }

    endpoint = "/restapi/v1.0/account/~/message-threads/messages"

    resp = $platform.get(endpoint, query_params)
    for msg in resp.body['records'] do
      # Parse and handle the message data

      # Check if this message has attachment(s)
      if msg["attachments"] && !msg["attachments"].empty?
        get_message_attachments(msg["attachments"])
      end
    end
  rescue StandardError => e
    puts e.message
  end
end

#
# Download attachment(s) and save to a local file
#
def get_message_attachments(attachments)
  begin
    for attachment in attachments
      fileName = attachment['filename']
      begin
        res = $platform.get(attachment['contentUri'])
        file = File.open(fileName, "w")
        file.write(res.body)
      rescue IOError => e
        puts e.getMessage()
      end
    end
  rescue StandardError => e
    puts (e)
  end
end

# Authenticate a user using a personal JWT token
def login()
  begin
    $platform.authorize( jwt: "RC_USER_JWT" )
    list_messages()
  rescue StandardError => e
    puts ("Unable to authenticate to platform. Check credentials." + e.to_s)
  end
end

# Instantiate the SDK and get the platform instance
$platform = RingCentral.new( "RC_APP_CLIENT_ID", "RC_APP_CLIENT_SECRET", "https://platform.ringcentral.com" )

login()