Overview | Chat Projects
This topic covers the scripting API for chat projects. The chat-specific data lives on a separate object model accessed through ChatProjectItem, while the standard document loop still applies for iterating and committing changes.
Common Use Cases
Building custom chat connectors — ReadySuite has built-in connectors for common platforms, but scripts can import data from any chat format. A script reads the source data (CSV, JSON, database, proprietary export), creates participants, conversations, and events through the API, and produces a fully structured chat project. This is the primary way to add support for new or proprietary messaging formats.
Enriching and overlaying existing chat data — Scripts can iterate over an existing chat project to classify messages, flag content, assign importance, update metadata, or populate custom fields. Because each chat event is backed by a DocumentItem, you can combine standard field operations with chat-specific modifications in the same document loop.
Data cleanup and validation — Scripts can audit chat data for integrity issues like missing senders, orphaned threads, or incomplete metadata, and fix or report problems automatically.
Accessing the Chat Project
Use ProjectItem.GetChatProject() to access the chat API. Always check IsChatProject first — calling GetChatProject() on a document project throws a NotSupportedException:
var project = script.GetProject(); if (project.IsChatProject) { var chatProject = project.GetChatProject(); // Work with chat data }
Data Model
Chat projects are organized as a hierarchy:
ChatProjectItem
├── Participants (ChatParticipantItem)
└── Sources (ChatSourceItem)
└── Conversations (ChatConversationItem)
└── Events (ChatEventItem) ── tied to a DocumentItem
├── Reactions (ChatEventReactionItem)
├── Attachments (ChatEventAttachmentItem)
├── Edits (ChatEventEditItem)
└── Read Receipts (ChatEventReadReceiptItem)
Participants are global to the project — the same participant can appear in multiple conversations across multiple sources.
Sources are containers of conversations. A project may have one or many sources (e.g. one per data export or platform).
Conversations represent a channel or direct message thread. Each conversation has a type, platform, and its own set of participants.
Events are the individual messages, joins, leaves, or other activities. Each event is tied to a DocumentItem, so you can still use the document loop to iterate them.
Participants
Participants represent the people in a chat project. Each has a unique ID across the entire project.
Creating Participants
var chatProject = script.GetProject().GetChatProject(); var alice = chatProject.CreateParticipant( id: "user-001", displayName: "Alice Smith", email: "alice@example.com"); var bob = chatProject.CreateParticipant( id: "user-002", displayName: "Bob Jones");
All parameters are optional — omitting id auto-generates a GUID.
Retrieving Participants
var participants = chatProject.GetParticipants(); var alice = chatProject.GetParticipant("user-001");
ChatParticipantItem Properties
| Property | Type | Description |
|---|---|---|
| Id | string | Unique identifier |
| DisplayName | string? | Display name |
| string? | Email address | |
| AvatarPath | string? | Path to avatar image |
| OtherProperties | IDictionary<string, string> | Custom metadata |
Creating a Source and Conversation
var source = chatProject.CreateChatSource(); var conversation = source.CreateConversation( id: "general", displayName: "General", platform: "Slack", type: ChatConversationItemType.Channel);
Conversation Types
| Value | Description |
|---|---|
| Channel | A channel conversation (e.g. Slack channel, Teams channel) |
| Direct | A direct or group message |
| Other | Other or unknown type |
Adding Participants to a Conversation
Conversations track which participants are involved. Add participants to the Participants collection:
conversation.Participants.Add(alice); conversation.Participants.Add(bob);
Setting a Custodian
Each conversation can have a custodian — the person whose perspective the conversation is captured from:
conversation.Custodian = alice;
ChatConversationItem Properties
| Property | Type | Description |
|---|---|---|
| Id | string | Unique within the source |
| DisplayName | string? | Conversation name |
| Platform | string | Platform identifier (e.g. "Slack", "Teams") |
| Type | ChatConversationItemType | Channel, Direct, or Other |
| Participants | ICollection<ChatParticipantItem> | Conversation participants |
| Custodian | ChatParticipantItem? | Custodian of the conversation |
| OtherProperties | IDictionary<string, string> | Custom metadata |
Events
Events are the core data in a chat project — messages, joins, leaves, and other activity. Each event is tied to a DocumentItem.
Reading Events from Documents
When iterating documents in a chat project, use GetChatEvent() to access the event tied to each document:
while (script.ReadDocument()) { var document = script.Document; var chatEvent = document.GetChatEvent(); if (chatEvent != null) { var message = chatEvent.Message; var sender = chatEvent.Sender; var timestamp = chatEvent.Timestamp; } }
GetChatEvent() returns null if the document has no associated chat event.
Creating Events
To create a new event, call CreateChatEvent() on a document. The event is automatically tied to that document:
var newDoc = script.GetNewDocument("MSG-0001"); script.CreateDocument(newDoc); var chatEvent = newDoc.CreateChatEvent( conversation: conversation, sender: alice, timestamp: DateTime.UtcNow); chatEvent.Message = "Hello, world!"; script.UpdateDocument(newDoc);
A document can only have one chat event — calling CreateChatEvent() on a document that already has one throws an InvalidOperationException.
Modifying Events
Event properties are mutable. Update them directly and call UpdateDocument():
while (script.ReadDocument()) { var document = script.Document; var chatEvent = document.GetChatEvent(); if (chatEvent != null && chatEvent.Type == ChatEventItemType.Message) { chatEvent.Importance = ChatEventItemImportance.High; script.UpdateDocument(document); } }
ChatEventItem Properties
| Property | Type | Description |
|---|---|---|
| Id | string | Unique event identifier |
| Conversation | ChatConversationItem | The conversation this event belongs to |
| Parent | ChatEventItem? | Parent event (for threaded replies) |
| Message | string? | Message content |
| Type | ChatEventItemType | Message, Disclaimer, Join, Leave, or Other |
| Direction | ChatEventItemDirection | Incoming, Outgoing, or Other |
| Sender | ChatParticipantItem? | Who sent the event |
| Recipients | IReadOnlyCollection<ChatParticipantItem> | All conversation participants |
| Timestamp | DateTime | When the event was sent |
| Importance | ChatEventItemImportance | Normal, High, or Other |
| IsDeleted | bool | Whether the event was deleted |
Event Types
| Value | Description |
|---|---|
| Message | A chat message |
| Disclaimer | A disclaimer event |
| Join | A participant joined |
| Leave | A participant left |
| Other | Other or unknown event |
Threaded Replies
Events can be nested using the Parent property. This is used for threaded replies (e.g. Slack threads):
var reply = newDoc.CreateChatEvent(conversation, bob, DateTime.UtcNow); reply.Message = "This is a reply."; reply.Parent = parentEvent;
The parent event must belong to the same conversation. Changing an event's conversation resets Parent to null.
Reactions
Events can have emoji reactions:
var reaction = chatEvent.CreateReaction("👍"); reaction.Count = 3; reaction.Participants.Add(alice); reaction.Participants.Add(bob);
var reactions = chatEvent.GetReactions();
chatEvent.RemoveReaction(reaction);
chatEvent.ClearReactions();
Attachments
Events can have file attachments or external resource links:
// Local file attachment var attachment = chatEvent.CreateAttachment("C:\\files\\report.pdf"); // External resource (URL or network path) var extAttachment = chatEvent.CreateExternalResourceAttachment("https://example.com/file.pdf");
The copyFileToChatSource parameter (default true) controls whether the file is copied into the chat source directory:
var attachment = chatEvent.CreateAttachment(filePath, copyFileToChatSource: false);
ChatEventAttachmentItem Properties
| Property | Type | Description |
|---|---|---|
| Id | string | Unique identifier |
| DisplayName | string? | Display name |
| Path | string? | Local file path (null for external resources) |
| IsExternalResource | bool | Whether this is an external resource |
| ExternalResourcePath | string? | External resource path |
Attachments can be copied or moved:
attachment.CopyTo("C:\\output\\report.pdf"); attachment.MoveTo("C:\\output\\report.pdf");
Edit History
Track message edits:
var edit = chatEvent.CreateEdit( timestamp: DateTime.UtcNow, newMessage: "Updated message", oldMessage: "Original message", participant: alice);
var edits = chatEvent.GetEdits();
chatEvent.ClearEdits();
Read Receipts
Track when participants read or received a message:
var receipt = chatEvent.CreateReadReceipt(
participant: bob,
timestamp: DateTime.UtcNow,
type: ChatEventReadReceiptItemType.Read);
| Receipt Type | Description |
|---|---|
| Read | The message was read |
| Delivered | The message was delivered but may not be read |
| Other | Other or unknown type |
Custom Metadata
Most chat types expose an OtherProperties dictionary for storing platform-specific or custom metadata that doesn't fit the standard schema:
participant.OtherProperties["sourceSystem"] = "LegacyChat v2.1"; conversation.OtherProperties["originalId"] = "slack-C123456";
OtherProperties is available on ChatParticipantItem, ChatConversationItem, ChatEventReactionItem, ChatEventAttachmentItem, ChatEventEditItem, and ChatEventReadReceiptItem.
Validation Rules
The chat API enforces several constraints:
- When setting an event's Conversation, the Sender must be a participant of the target conversation
- Changing an event's Conversation resets Parent to null
- A Parent event must belong to the same conversation as the child
- Participants referenced in reactions, edits, and read receipts must be participants of the event's conversation
- A document can only have one chat event
Custom Connector (Importing Chat Data)
This example builds a chat project from scratch — the pattern used when writing a custom connector for a format ReadySuite doesn't natively support. The script reads source data, creates the project structure, and ties each message to a document:
public override void Run(IScriptContext script) { base.Run(script); var chatProject = script.GetProject().GetChatProject(); // Create participants var alice = chatProject.CreateParticipant( id: "user-001", displayName: "Alice Smith", email: "alice@example.com"); var bob = chatProject.CreateParticipant( id: "user-002", displayName: "Bob Jones"); // Create source and conversation var source = chatProject.CreateChatSource(); var conversation = source.CreateConversation( id: "general", displayName: "General", platform: "CustomChat", type: ChatConversationItemType.Channel); conversation.Participants.Add(alice); conversation.Participants.Add(bob); conversation.Custodian = alice; // Create a document and event for each message var newDoc = script.GetNewDocument("MSG-0001"); script.CreateDocument(newDoc); var chatEvent = newDoc.CreateChatEvent(conversation, alice, DateTime.UtcNow); chatEvent.Message = "Hello, team!"; chatEvent.Direction = ChatEventItemDirection.Outgoing; script.UpdateDocument(newDoc); }
Enriching Existing Chat Data
This example iterates over an existing chat project and flags messages that match a keyword — a common overlay pattern for classification or review workflows:
public override void Run(IScriptContext script) { base.Run(script); var project = script.GetProject(); if (!project.IsChatProject) { script.AddWarning("This script only works with chat projects."); return; } while (script.ReadDocument()) { var document = script.Document; var chatEvent = document.GetChatEvent(); if (chatEvent == null || chatEvent.Type != ChatEventItemType.Message) continue; if (!string.IsNullOrEmpty(chatEvent.Message) && chatEvent.Message.Contains(options.Keyword, StringComparison.OrdinalIgnoreCase)) { chatEvent.Importance = ChatEventItemImportance.High; document.SetFieldData(options.FlagField, "true"); script.UpdateDocument(document); } } }