tRPC Endpoints Reference
This document provides a complete reference of all tRPC procedures organized by router, including input/output types, usage examples, and best practices.
Table of Contents
- Messages Router
- Categories Router
- Conversations Router
- Search Router
- Categorization Router
- Pipeline Router
- Q&A Router
- Feature Votes Router
- Feedback Router
Messages Router
Base Path: messages
Manage WhatsApp messages, including imports, updates, and categorization.
Queries
messages.list
List messages with pagination and filtering.
Input:
interface ListMessagesInput {
limit?: number; // Default: 50, Min: 1, Max: 100
offset?: number; // Default: 0, Min: 0
categoryId?: string; // Filter by single category
categoryIds?: string[]; // Filter by multiple categories
isStarred?: boolean; // Filter starred status
isArchived?: boolean; // Filter archived status
isRead?: boolean; // Filter read status
messageType?:
| "text"
| "image"
| "video"
| "audio"
| "document"
| "sticker"
| "location"
| "contact";
sentiment?: "positive" | "negative" | "neutral";
senderName?: string; // Filter by sender name
search?: string; // Search in content
startDate?: Date; // Filter messages after this date
endDate?: Date; // Filter messages before this date
sortBy?: "messageTimestamp" | "createdAt" | "senderName";
sortOrder?: "asc" | "desc";
}
Output:
interface ListMessagesOutput {
messages: Message[];
pagination: {
total: number;
limit: number;
offset: number;
hasMore: boolean;
};
}
interface Message {
id: string;
userId: string;
content: string;
senderName: string;
senderPhone: string | null;
messageTimestamp: Date;
externalId: string | null;
categoryId: string | null;
messageType: MessageType;
sentiment: SentimentType | null;
mediaUrl: string | null;
mediaKey: string | null;
metadata: string | null;
isRead: boolean;
isStarred: boolean;
isArchived: boolean;
createdAt: Date;
updatedAt: Date;
}
Example:
const { data } = trpc.messages.list.useQuery({
limit: 50,
offset: 0,
categoryIds: ["cat-1", "cat-2"],
isStarred: true,
sortBy: "messageTimestamp",
sortOrder: "desc",
});
messages.getById
Get a single message by ID.
Input:
interface GetMessageByIdInput {
id: string;
}
Output: Message
Example:
const { data } = trpc.messages.getById.useQuery({ id: "msg-123" });
messages.search
Search messages by content.
Input:
interface SearchMessagesInput {
query: string; // Min: 1 character
limit?: number; // Min: 1, Max: 100
}
Output: Message[]
Example:
const { data } = trpc.messages.search.useQuery({
query: "meeting tomorrow",
limit: 20,
});
messages.getCount
Get total message count for the user.
Input: undefined
Output:
interface MessageCountOutput {
count: number;
}
Example:
const { data } = trpc.messages.getCount.useQuery();
console.log(data.count); // Total messages
messages.getCategories
Get all categories for a message (multi-category support).
Input:
interface GetMessageCategoriesInput {
messageId: string;
}
Output: Category[]
Example:
const { data } = trpc.messages.getCategories.useQuery({
messageId: "msg-123",
});
Mutations
messages.create
Create a new message.
Input:
interface CreateMessageInput {
content: string; // Min: 1, Max: 10000
senderName: string; // Min: 1, Max: 255
senderPhone?: string | null; // Max: 50
messageTimestamp: Date;
externalId?: string | null; // Max: 255
categoryId?: string | null;
messageType?: MessageType; // Default: "text"
sentiment?: SentimentType | null;
mediaUrl?: string | null;
mediaKey?: string | null;
metadata?: string | null;
isRead?: boolean; // Default: false
isStarred?: boolean; // Default: false
isArchived?: boolean; // Default: false
}
Output: Message
Example:
const createMutation = trpc.messages.create.useMutation();
createMutation.mutate({
content: "Hello, world!",
senderName: "John Doe",
messageTimestamp: new Date(),
messageType: "text",
});
messages.update
Update a message.
Input:
interface UpdateMessageInput {
id: string;
content?: string; // Min: 1, Max: 10000
categoryId?: string | null;
sentiment?: SentimentType | null;
isRead?: boolean;
isStarred?: boolean;
isArchived?: boolean;
}
Output: Message
Example:
const updateMutation = trpc.messages.update.useMutation();
updateMutation.mutate({
id: "msg-123",
isRead: true,
isStarred: true,
});
messages.delete
Delete a message.
Input:
interface DeleteMessageInput {
id: string;
}
Output:
interface DeleteMessageOutput {
success: boolean;
}
Example:
const deleteMutation = trpc.messages.delete.useMutation();
deleteMutation.mutate({ id: "msg-123" });
messages.markAsRead
Mark message as read.
Input:
interface MarkAsReadInput {
id: string;
}
Output: Message
Example:
const mutation = trpc.messages.markAsRead.useMutation();
mutation.mutate({ id: "msg-123" });
messages.toggleStarred
Toggle starred status.
Input:
interface ToggleStarredInput {
id: string;
}
Output: Message
Example:
const mutation = trpc.messages.toggleStarred.useMutation();
mutation.mutate({ id: "msg-123" });
messages.archive
Archive a message.
Input:
interface ArchiveInput {
id: string;
}
Output: Message
Example:
const mutation = trpc.messages.archive.useMutation();
mutation.mutate({ id: "msg-123" });
messages.unarchive
Unarchive a message.
Input:
interface UnarchiveInput {
id: string;
}
Output: Message
Example:
const mutation = trpc.messages.unarchive.useMutation();
mutation.mutate({ id: "msg-123" });
messages.setCategories
Set categories for a message (replaces all existing categories).
Input:
interface SetCategoriesInput {
messageId: string;
categoryIds: string[]; // Array of category IDs
}
Output:
interface SetCategoriesOutput {
success: boolean;
}
Example:
const mutation = trpc.messages.setCategories.useMutation();
mutation.mutate({
messageId: "msg-123",
categoryIds: ["cat-1", "cat-2", "cat-3"],
});
messages.addCategory
Add a category to a message.
Input:
interface AddCategoryInput {
messageId: string;
categoryId: string;
}
Output:
interface AddCategoryOutput {
success: boolean;
}
Example:
const mutation = trpc.messages.addCategory.useMutation();
mutation.mutate({
messageId: "msg-123",
categoryId: "cat-1",
});
messages.removeCategory
Remove a category from a message.
Input:
interface RemoveCategoryInput {
messageId: string;
categoryId: string;
}
Output:
interface RemoveCategoryOutput {
success: boolean;
}
Example:
const mutation = trpc.messages.removeCategory.useMutation();
mutation.mutate({
messageId: "msg-123",
categoryId: "cat-1",
});
messages.importWhatsApp
Import messages from WhatsApp text export.
Input:
interface ImportWhatsAppInput {
text: string; // WhatsApp export text content
skipDuplicates?: boolean; // Default: true
}
Output:
interface ImportWhatsAppOutput {
success: boolean;
imported: number;
duplicates: number;
errors: number;
totalParsed: number;
parseErrors: number;
stats: {
totalMessages: number;
byType: Record<string, number>;
bySender: Record<string, number>;
dateRange: {
first: Date;
last: Date;
};
};
errorDetails: Array<{
message: string;
error: string;
}>;
}
Example:
const mutation = trpc.messages.importWhatsApp.useMutation();
// Read file and import
const fileContent = await file.text();
mutation.mutate({
text: fileContent,
skipDuplicates: true,
});
Categories Router
Base Path: categories
Manage message categories and hierarchies.
Queries
categories.list
List all categories for the user.
Input: undefined
Output: Category[]
Example:
const { data } = trpc.categories.list.useQuery();
categories.listWithStats
List all categories with message count statistics.
Input: undefined
Output:
interface CategoryWithStats extends Category {
messageCount: number;
childCount: number;
}
interface Category {
id: string;
userId: string;
name: string;
description: string | null;
color: string | null;
icon: string | null;
parentId: string | null;
sortOrder: number;
isSystem: boolean;
createdAt: Date;
updatedAt: Date;
}
Example:
const { data } = trpc.categories.listWithStats.useQuery();
data?.forEach((cat) => {
console.log(`${cat.name}: ${cat.messageCount} messages`);
});
categories.listRoot
Get root categories (no parent).
Input: undefined
Output: Category[]
Example:
const { data } = trpc.categories.listRoot.useQuery();
categories.listChildren
Get child categories of a parent.
Input:
interface ListChildrenInput {
parentId: string;
}
Output: Category[]
Example:
const { data } = trpc.categories.listChildren.useQuery({
parentId: "cat-parent-123",
});
categories.getById
Get a single category by ID with stats.
Input:
interface GetCategoryByIdInput {
id: string;
}
Output: CategoryWithStats
Example:
const { data } = trpc.categories.getById.useQuery({ id: "cat-123" });
categories.getStatistics
Get comprehensive statistics for all categories.
Input: undefined
Output:
interface CategoryStatistics {
totalCategories: number;
rootCategories: number;
childCategories: number;
systemCategories: number;
customCategories: number;
maxDepth: number;
categoriesByDepth: Record<number, number>;
topCategories: Array<{
categoryId: string;
categoryName: string;
messageCount: number;
percentage: number;
}>;
uncategorizedCount: number;
}
Example:
const { data } = trpc.categories.getStatistics.useQuery();
console.log(`Total categories: ${data?.totalCategories}`);
categories.getCount
Get category count for the user.
Input: undefined
Output:
interface CategoryCountOutput {
count: number;
}
Example:
const { data } = trpc.categories.getCount.useQuery();
Mutations
categories.create
Create a new category.
Input:
interface CreateCategoryInput {
name: string; // Min: 1, Max: 100
description?: string | null; // Max: 500
color?: string | null; // Hex color format: #RRGGBB
icon?: string | null; // Max: 50
parentId?: string | null;
sortOrder?: number; // Min: 0, Default: 0
}
Output: Category
Example:
const mutation = trpc.categories.create.useMutation();
mutation.mutate({
name: "Work",
description: "Work-related messages",
color: "#3b82f6",
icon: "briefcase",
});
categories.update
Update a category.
Input:
interface UpdateCategoryInput {
id: string;
name?: string; // Min: 1, Max: 100
description?: string | null; // Max: 500
color?: string | null; // Hex color format: #RRGGBB
icon?: string | null; // Max: 50
sortOrder?: number; // Min: 0
}
Output: Category
Example:
const mutation = trpc.categories.update.useMutation();
mutation.mutate({
id: "cat-123",
name: "Updated Work",
color: "#22c55e",
});
categories.delete
Delete a category.
Input:
interface DeleteCategoryInput {
id: string;
}
Output:
interface DeleteCategoryOutput {
success: boolean;
}
Note: System categories cannot be deleted.
Example:
const mutation = trpc.categories.delete.useMutation();
mutation.mutate({ id: "cat-123" });
categories.updateSortOrder
Update category sort order.
Input:
interface UpdateSortOrderInput {
id: string;
sortOrder: number; // Min: 0
}
Output: Category
Example:
const mutation = trpc.categories.updateSortOrder.useMutation();
mutation.mutate({
id: "cat-123",
sortOrder: 5,
});
categories.moveToParent
Move category to a new parent.
Input:
interface MoveToParentInput {
id: string;
parentId: string | null; // null for root level
}
Output: Category
Note: Cannot create circular references.
Example:
const mutation = trpc.categories.moveToParent.useMutation();
// Move to root level
mutation.mutate({
id: "cat-123",
parentId: null,
});
// Move under another category
mutation.mutate({
id: "cat-123",
parentId: "cat-parent-456",
});
Conversations Router
Base Path: conversations
Manage chat conversations and messages for Q&A features.
Queries
conversations.list
List conversations with pagination.
Input:
interface ListConversationsInput {
limit?: number; // Default: 50, Min: 1, Max: 100
offset?: number; // Default: 0, Min: 0
isArchived?: boolean;
sortOrder?: "asc" | "desc";
}
Output:
interface ListConversationsOutput {
conversations: Conversation[];
pagination: {
total: number;
limit: number;
offset: number;
hasMore: boolean;
};
}
interface Conversation {
id: string;
userId: string;
title: string;
description: string | null;
isArchived: boolean;
createdAt: Date;
updatedAt: Date;
}
Example:
const { data } = trpc.conversations.list.useQuery({
limit: 20,
isArchived: false,
sortOrder: "desc",
});
conversations.getById
Get a single conversation by ID with stats.
Input:
interface GetConversationByIdInput {
id: string;
}
Output:
interface ConversationWithStats extends Conversation {
messageCount: number;
lastMessageAt: Date | null;
}
Example:
const { data } = trpc.conversations.getById.useQuery({
id: "conv-123",
});
conversations.getCount
Get conversation count for the user.
Input: undefined
Output:
interface ConversationCountOutput {
count: number;
}
Example:
const { data } = trpc.conversations.getCount.useQuery();
conversations.getMessages
Get messages for a conversation.
Input:
interface GetMessagesInput {
conversationId: string;
limit?: number; // Min: 1, Max: 100
offset?: number; // Min: 0
}
Output: ChatMessage[]
Example:
const { data } = trpc.conversations.getMessages.useQuery({
conversationId: "conv-123",
limit: 50,
});
conversations.getThreadMessages
Get thread messages (replies to a specific message).
Input:
interface GetThreadMessagesInput {
parentMessageId: string;
}
Output: ChatMessage[]
Example:
const { data } = trpc.conversations.getThreadMessages.useQuery({
parentMessageId: "msg-123",
});
Mutations
conversations.create
Create a new conversation.
Input:
interface CreateConversationInput {
title: string; // Min: 1, Max: 255
description?: string; // Max: 500
}
Output: Conversation
Example:
const mutation = trpc.conversations.create.useMutation();
mutation.mutate({
title: "Q&A about Messages",
description: "Questions about my WhatsApp messages",
});
conversations.update
Update a conversation.
Input:
interface UpdateConversationInput {
id: string;
title?: string; // Min: 1, Max: 255
description?: string | null; // Max: 500
}
Output: Conversation
Example:
const mutation = trpc.conversations.update.useMutation();
mutation.mutate({
id: "conv-123",
title: "Updated Title",
});
conversations.delete
Delete a conversation.
Input:
interface DeleteConversationInput {
id: string;
}
Output:
interface DeleteConversationOutput {
success: boolean;
}
Example:
const mutation = trpc.conversations.delete.useMutation();
mutation.mutate({ id: "conv-123" });
conversations.archive
Archive a conversation.
Input:
interface ArchiveConversationInput {
id: string;
}
Output: Conversation
Example:
const mutation = trpc.conversations.archive.useMutation();
mutation.mutate({ id: "conv-123" });
conversations.unarchive
Unarchive a conversation.
Input:
interface UnarchiveConversationInput {
id: string;
}
Output: Conversation
Example:
const mutation = trpc.conversations.unarchive.useMutation();
mutation.mutate({ id: "conv-123" });
conversations.sendMessage
Send a message in a conversation.
Input:
interface SendMessageInput {
conversationId: string;
content: string; // Min: 1, Max: 10000
role?: "user" | "assistant" | "system"; // Default: "user"
parentMessageId?: string; // For threaded replies
metadata?: string;
}
Output: ChatMessage
Example:
const mutation = trpc.conversations.sendMessage.useMutation();
mutation.mutate({
conversationId: "conv-123",
content: "What messages do I have about meetings?",
role: "user",
});
conversations.updateMessage
Update a chat message.
Input:
interface UpdateChatMessageInput {
id: string;
content: string; // Min: 1, Max: 10000
}
Output: ChatMessage
Example:
const mutation = trpc.conversations.updateMessage.useMutation();
mutation.mutate({
id: "chat-msg-123",
content: "Updated message content",
});
conversations.deleteMessage
Delete a chat message.
Input:
interface DeleteChatMessageInput {
id: string;
}
Output:
interface DeleteChatMessageOutput {
success: boolean;
}
Example:
const mutation = trpc.conversations.deleteMessage.useMutation();
mutation.mutate({ id: "chat-msg-123" });
Search Router
Base Path: search
Comprehensive search functionality with tags and saved filters.
Queries
search.search
Perform a search (keyword, semantic, or hybrid).
Input:
interface SearchInput {
query: string;
filters?: SearchFilters;
config?: SearchConfig;
limit?: number; // Default: 50, Min: 1, Max: 100
offset?: number; // Default: 0, Min: 0
saveToHistory?: boolean; // Default: true
}
interface SearchFilters {
categoryId?: string;
categoryIds?: string[];
startDate?: Date;
endDate?: Date;
senderName?: string;
senderNames?: string[];
messageType?: MessageType;
sentiment?: SentimentType;
isStarred?: boolean;
isRead?: boolean;
isArchived?: boolean;
tagIds?: string[];
hasAnyTag?: boolean;
source?: string;
}
interface SearchConfig {
mode?: "keyword" | "semantic" | "hybrid";
enableHighlighting?: boolean;
snippetLength?: number; // Min: 50, Max: 500
maxResults?: number; // Min: 1, Max: 200
boostRecentMessages?: boolean;
semanticWeight?: number; // Min: 0, Max: 1
}
Output:
interface SearchResult {
messages: Array<{
message: Message;
relevanceScore: number;
highlight?: {
before: string;
match: string;
after: string;
};
}>;
pagination: {
total: number;
limit: number;
offset: number;
hasMore: boolean;
};
queryInfo: {
originalQuery: string;
parsedQuery: ParsedQuery;
searchMode: SearchMode;
filtersApplied: string[];
};
}
Example:
const { data } = trpc.search.search.useQuery({
query: "meeting tomorrow",
filters: {
categoryIds: ["cat-work"],
isStarred: true,
},
config: {
mode: "hybrid",
enableHighlighting: true,
},
limit: 20,
});
search.parseQuery
Parse a search query for preview/validation.
Input:
interface ParseQueryInput {
query: string;
}
Output:
interface ParsedQuery {
keywords: string[];
filters: SearchFilters;
operators: Array<{
type: "AND" | "OR" | "NOT";
position: number;
}>;
errors: string[];
}
Example:
const { data } = trpc.search.parseQuery.useQuery({
query: "from:John meeting category:work",
});
search.getSuggestions
Get search suggestions (recent/popular).
Input:
interface GetSuggestionsInput {
prefix?: string;
}
Output:
interface SearchSuggestion {
text: string;
type: "recent" | "popular";
count?: number;
}
Example:
const { data } = trpc.search.getSuggestions.useQuery({
prefix: "meet",
});
search.getTags
Get all tags for the current user.
Input: undefined
Output: Tag[]
Example:
const { data } = trpc.search.getTags.useQuery();
search.getMessageTags
Get tags for a message.
Input:
interface GetMessageTagsInput {
messageId: string;
}
Output: Tag[]
Example:
const { data } = trpc.search.getMessageTags.useQuery({
messageId: "msg-123",
});
search.getSavedFilters
Get all saved filters for the current user.
Input: undefined
Output: SavedFilter[]
Example:
const { data } = trpc.search.getSavedFilters.useQuery();
search.getSavedFilter
Get a saved filter by ID.
Input:
interface GetSavedFilterInput {
id: string;
}
Output: SavedFilter
Example:
const { data } = trpc.search.getSavedFilter.useQuery({
id: "filter-123",
});
search.getMostUsedFilters
Get most used saved filters.
Input:
interface GetMostUsedFiltersInput {
limit?: number; // Default: 5, Min: 1, Max: 20
}
Output: SavedFilter[]
Example:
const { data } = trpc.search.getMostUsedFilters.useQuery({
limit: 10,
});
search.getRecentlyUsedFilters
Get recently used saved filters.
Input:
interface GetRecentlyUsedFiltersInput {
limit?: number; // Default: 5, Min: 1, Max: 20
}
Output: SavedFilter[]
Example:
const { data } = trpc.search.getRecentlyUsedFilters.useQuery({
limit: 5,
});
search.getEmbeddingStats
Get embedding statistics for the current user.
Input: undefined
Output:
interface EmbeddingStats {
embeddedCount: number;
pendingCount: number | "some";
}
Example:
const { data } = trpc.search.getEmbeddingStats.useQuery();
console.log(`${data.embeddedCount} messages embedded`);
Mutations
search.clearHistory
Clear search history.
Input: undefined
Output:
interface ClearHistoryOutput {
deleted: number;
}
Example:
const mutation = trpc.search.clearHistory.useMutation();
mutation.mutate();
search.createTag
Create a new tag.
Input:
interface CreateTagInput {
name: string; // Min: 1, Max: 50
color?: string;
description?: string; // Max: 200
}
Output: Tag
Example:
const mutation = trpc.search.createTag.useMutation();
mutation.mutate({
name: "important",
color: "#ef4444",
description: "Important messages",
});
search.updateTag
Update a tag.
Input:
interface UpdateTagInput {
id: string;
name?: string; // Min: 1, Max: 50
color?: string;
description?: string; // Max: 200
}
Output: Tag
Example:
const mutation = trpc.search.updateTag.useMutation();
mutation.mutate({
id: "tag-123",
name: "urgent",
color: "#f97316",
});
search.deleteTag
Delete a tag.
Input:
interface DeleteTagInput {
id: string;
}
Output:
interface DeleteTagOutput {
success: boolean;
}
Example:
const mutation = trpc.search.deleteTag.useMutation();
mutation.mutate({ id: "tag-123" });
search.addTagToMessage
Add tag to a message.
Input:
interface AddTagToMessageInput {
messageId: string;
tagId: string;
}
Output: MessageTag
Example:
const mutation = trpc.search.addTagToMessage.useMutation();
mutation.mutate({
messageId: "msg-123",
tagId: "tag-456",
});
search.removeTagFromMessage
Remove tag from a message.
Input:
interface RemoveTagFromMessageInput {
messageId: string;
tagId: string;
}
Output:
interface RemoveTagFromMessageOutput {
success: boolean;
}
Example:
const mutation = trpc.search.removeTagFromMessage.useMutation();
mutation.mutate({
messageId: "msg-123",
tagId: "tag-456",
});
search.setMessageTags
Set all tags for a message (replace existing).
Input:
interface SetMessageTagsInput {
messageId: string;
tagIds: string[];
}
Output:
interface SetMessageTagsOutput {
success: boolean;
}
Example:
const mutation = trpc.search.setMessageTags.useMutation();
mutation.mutate({
messageId: "msg-123",
tagIds: ["tag-1", "tag-2", "tag-3"],
});
search.bulkAddTag
Bulk add tag to multiple messages.
Input:
interface BulkAddTagInput {
messageIds: string[];
tagId: string;
}
Output:
interface BulkAddTagOutput {
count: number;
}
Example:
const mutation = trpc.search.bulkAddTag.useMutation();
mutation.mutate({
messageIds: ["msg-1", "msg-2", "msg-3"],
tagId: "tag-123",
});
search.bulkRemoveTag
Bulk remove tag from multiple messages.
Input:
interface BulkRemoveTagInput {
messageIds: string[];
tagId: string;
}
Output:
interface BulkRemoveTagOutput {
count: number;
}
Example:
const mutation = trpc.search.bulkRemoveTag.useMutation();
mutation.mutate({
messageIds: ["msg-1", "msg-2", "msg-3"],
tagId: "tag-123",
});
search.createSavedFilter
Create a saved filter.
Input:
interface CreateSavedFilterInput {
name: string; // Min: 1, Max: 100
description?: string; // Max: 500
filters: SavedFilterConfig;
searchMode?: SearchMode;
includeQuery?: string;
excludeQuery?: string;
isDefault?: boolean;
color?: string;
icon?: string;
}
interface SavedFilterConfig {
categoryId?: string;
messageType?: MessageType;
sentiment?: SentimentType;
senderName?: string;
isStarred?: boolean;
isRead?: boolean;
isArchived?: boolean;
startDate?: string;
endDate?: string;
tagIds?: string[];
}
Output: SavedFilter
Example:
const mutation = trpc.search.createSavedFilter.useMutation();
mutation.mutate({
name: "Important Work Messages",
description: "Starred work messages from last month",
filters: {
categoryId: "cat-work",
isStarred: true,
},
searchMode: "hybrid",
});
search.updateSavedFilter
Update a saved filter.
Input:
interface UpdateSavedFilterInput {
id: string;
name?: string; // Min: 1, Max: 100
description?: string; // Max: 500
filters?: SavedFilterConfig;
searchMode?: SearchMode;
includeQuery?: string;
excludeQuery?: string;
isDefault?: boolean;
color?: string;
icon?: string;
}
Output: SavedFilter
Example:
const mutation = trpc.search.updateSavedFilter.useMutation();
mutation.mutate({
id: "filter-123",
name: "Updated Filter Name",
filters: {
isStarred: true,
isRead: false,
},
});
search.deleteSavedFilter
Delete a saved filter.
Input:
interface DeleteSavedFilterInput {
id: string;
}
Output:
interface DeleteSavedFilterOutput {
success: boolean;
}
Example:
const mutation = trpc.search.deleteSavedFilter.useMutation();
mutation.mutate({ id: "filter-123" });
search.applySavedFilter
Apply a saved filter (increments usage count).
Input:
interface ApplySavedFilterInput {
id: string;
}
Output: SavedFilter
Example:
const mutation = trpc.search.applySavedFilter.useMutation();
mutation.mutate({ id: "filter-123" });
search.reorderSavedFilters
Reorder saved filters.
Input:
interface ReorderSavedFiltersInput {
orders: Array<{
id: string;
sortOrder: number;
}>;
}
Output:
interface ReorderSavedFiltersOutput {
success: boolean;
}
Example:
const mutation = trpc.search.reorderSavedFilters.useMutation();
mutation.mutate({
orders: [
{ id: "filter-1", sortOrder: 0 },
{ id: "filter-2", sortOrder: 1 },
{ id: "filter-3", sortOrder: 2 },
],
});
search.generateEmbeddings
Generate embeddings for messages that don't have them.
Input:
interface GenerateEmbeddingsInput {
limit?: number; // Default: 100, Min: 1, Max: 500
}
Output:
interface GenerateEmbeddingsOutput {
success: number;
failed: number;
message: string;
}
Example:
const mutation = trpc.search.generateEmbeddings.useMutation();
mutation.mutate({
limit: 200,
});
Categorization Router
Base Path: categorization
AI-powered message categorization using LLM providers.
Queries
categorization.getStats
Get categorization statistics.
Input: undefined
Output:
interface CategorizationStats {
totalMessages: number;
categorizedMessages: number;
uncategorizedMessages: number;
categorizationPercentage: number;
averageConfidence: number;
byCategory: Array<{
categoryId: string;
categoryName: string;
count: number;
percentage: number;
avgConfidence: number;
}>;
recentCategorizations: Array<{
messageId: string;
categoryId: string;
categorizedAt: Date;
confidence: number;
}>;
}
Example:
const { data } = trpc.categorization.getStats.useQuery();
categorization.getUncategorizedCount
Get uncategorized message count.
Input:
interface GetUncategorizedCountInput {
includeArchived?: boolean; // Default: false
}
Output: number
Example:
const { data } = trpc.categorization.getUncategorizedCount.useQuery({
includeArchived: false,
});
categorization.getMessagesNeedingReview
Get low-confidence messages.
Input:
interface GetMessagesNeedingReviewInput {
limit?: number; // Default: 50, Min: 1, Max: 100
offset?: number; // Default: 0, Min: 0
}
Output:
interface MessagesNeedingReviewOutput {
messages: Message[];
count: number;
}
Example:
const { data } = trpc.categorization.getMessagesNeedingReview.useQuery({
limit: 20,
});
categorization.getCategoryOptions
Get category options for the user.
Input: undefined
Output: Category[]
Example:
const { data } = trpc.categorization.getCategoryOptions.useQuery();
categorization.getCategoriesWithStats
Get categories with message counts.
Input: undefined
Output: CategoryWithStats[]
Example:
const { data } = trpc.categorization.getCategoriesWithStats.useQuery();
Mutations
categorization.categorizeMessage
Categorize a single message.
Input:
interface CategorizeMessageInput {
messageId: string;
options?: CategorizationOptions;
fallbackStrategy?: FallbackStrategy;
}
interface CategorizationOptions {
confidenceThreshold?: number; // Min: 0, Max: 1, Default: 0.6
includeReasoning?: boolean; // Default: true
includeSuggestions?: boolean; // Default: true
temperature?: number; // Min: 0, Max: 1, Default: 0.1
provider?: "claude" | "openai" | "glm";
model?: string;
}
type FallbackStrategy =
| "assign_uncategorized"
| "mark_for_review"
| "retry_with_context"
| "use_best_guess";
Output:
interface CategorizeMessageOutput {
success: boolean;
result: {
messageId: string;
categoryIds: string[]; // Multi-category support
categoryNames: string[];
confidence: number;
reasoning: string;
requiresReview: boolean;
suggestedCategories: Array<{
categoryId: string;
categoryName: string;
confidence: number;
}>;
processingTimeMs: number;
};
usage: {
inputTokens: number;
outputTokens: number;
totalCost: number;
};
}
Example:
const mutation = trpc.categorization.categorizeMessage.useMutation();
mutation.mutate({
messageId: "msg-123",
options: {
confidenceThreshold: 0.7,
includeReasoning: true,
provider: "claude",
},
fallbackStrategy: "mark_for_review",
});
categorization.categorizeBulk
Categorize multiple messages.
Input:
interface CategorizeBulkInput {
messageIds: string[]; // Min: 1, Max: 100
options?: CategorizationOptions;
fallbackStrategy?: FallbackStrategy;
batchSize?: number; // Min: 1, Max: 20, Default: 10
}
Output:
interface CategorizeBulkOutput {
success: boolean;
totalMessages: number;
categorizedCount: number;
lowConfidenceCount: number;
failedCount: number;
skippedCount: number;
results: Array<{
messageId: string;
categoryIds: string[];
categoryNames: string[];
confidence: number;
reasoning: string;
requiresReview: boolean;
suggestedCategories: SuggestedCategory[];
}>;
errors: Array<{
messageId: string;
error: string;
}>;
processingTimeMs: number;
usage: {
inputTokens: number;
outputTokens: number;
totalCost: number;
};
}
Example:
const mutation = trpc.categorization.categorizeBulk.useMutation();
mutation.mutate({
messageIds: ["msg-1", "msg-2", "msg-3"],
options: {
confidenceThreshold: 0.6,
},
batchSize: 10,
});
categorization.categorizeUncategorized
Categorize all uncategorized messages.
Input:
interface CategorizeUncategorizedInput {
limit?: number; // Default: 50, Min: 1, Max: 100
options?: CategorizationOptions;
fallbackStrategy?: FallbackStrategy;
}
Output: CategorizeBulkOutput
Example:
const mutation = trpc.categorization.categorizeUncategorized.useMutation();
mutation.mutate({
limit: 100,
fallbackStrategy: "mark_for_review",
});
categorization.suggestTaxonomy
Suggest a taxonomy based on messages.
Input:
interface SuggestTaxonomyInput {
sampleSize?: number; // Min: 5, Max: 50, Default: 20
taxonomyName?: string; // Max: 100
}
Output:
interface SuggestTaxonomyOutput {
success: boolean;
taxonomy: {
id: string;
name: string;
description: string;
categories: Array<{
id: string;
name: string;
description: string;
keywords: string[];
parentId?: string;
}>;
};
usage: {
inputTokens: number;
outputTokens: number;
totalCost: number;
};
}
Example:
const mutation = trpc.categorization.suggestTaxonomy.useMutation();
mutation.mutate({
sampleSize: 30,
taxonomyName: "My Custom Taxonomy",
});
categorization.approveCategorizaton
Approve a categorization (mark as reviewed).
Input:
interface ApproveCategorizationInput {
messageId: string;
categoryId?: string;
}
Output:
interface ApproveCategorizationOutput {
success: boolean;
messageId: string;
categoryId: string | null;
}
Example:
const mutation = trpc.categorization.approveCategorizaton.useMutation();
mutation.mutate({
messageId: "msg-123",
categoryId: "cat-work",
});
categorization.suggestCategoryAssignments
Suggest category assignments (preview only, doesn't save).
Input:
interface SuggestCategoryAssignmentsInput {
messageIds: string[]; // Min: 1, Max: 50
}
Output:
interface SuggestCategoryAssignmentsOutput {
success: boolean;
totalMessages: number;
suggestions: Array<{
messageId: string;
categoryIds: string[];
categoryNames: string[];
confidence: number;
reasoning: string;
requiresReview: boolean;
suggestedCategories: SuggestedCategory[];
}>;
processingTimeMs: number;
}
Example:
const mutation = trpc.categorization.suggestCategoryAssignments.useMutation();
mutation.mutate({
messageIds: ["msg-1", "msg-2", "msg-3"],
});
categorization.applyCategoryAssignments
Apply category assignments after user approval.
Input:
interface ApplyCategoryAssignmentsInput {
assignments: Array<{
messageId: string;
categoryIds: string[]; // Multi-category support
}>;
}
Output:
interface ApplyCategoryAssignmentsOutput {
success: boolean;
appliedCount: number;
}
Example:
const mutation = trpc.categorization.applyCategoryAssignments.useMutation();
mutation.mutate({
assignments: [
{ messageId: "msg-1", categoryIds: ["cat-1", "cat-2"] },
{ messageId: "msg-2", categoryIds: ["cat-3"] },
{ messageId: "msg-3", categoryIds: ["cat-1", "cat-4"] },
],
});
Pipeline Router
Base Path: pipeline
Message processing pipeline orchestration for import, categorize, and summarize jobs.
Queries
pipeline.getJob
Get a pipeline job by ID.
Input:
interface GetJobInput {
jobId: string;
}
Output:
interface PipelineJob {
id: string;
userId: string;
jobType: "import" | "categorize" | "summarize" | "recategorize";
status: "pending" | "processing" | "completed" | "failed" | "partial";
progress: {
current: number;
total: number;
percentage: number;
};
stages: PipelineStage[];
result: PipelineResult | null;
error: string | null;
metadata: Record<string, unknown>;
createdAt: Date;
updatedAt: Date;
completedAt: Date | null;
}
interface PipelineStage {
name: string;
status: "pending" | "processing" | "completed" | "failed" | "skipped";
progress: number;
startedAt?: Date;
completedAt?: Date;
error?: string;
}
Example:
const { data } = trpc.pipeline.getJob.useQuery({
jobId: "job-123",
});
pipeline.listJobs
List pipeline jobs with filtering.
Input:
interface ListJobsInput {
jobType?: "import" | "categorize" | "summarize" | "recategorize";
status?: "pending" | "processing" | "completed" | "failed" | "partial";
limit?: number; // Default: 50, Min: 1, Max: 100
offset?: number; // Default: 0, Min: 0
}
Output: PipelineJob[]
Example:
const { data } = trpc.pipeline.listJobs.useQuery({
jobType: "categorize",
status: "completed",
limit: 20,
});
pipeline.getActiveJobs
Get active pipeline jobs.
Input: undefined
Output: PipelineJob[]
Example:
const { data } = trpc.pipeline.getActiveJobs.useQuery();
pipeline.getRecentCompleted
Get recent completed jobs.
Input: undefined
Output: PipelineJob[] (last 10)
Example:
const { data } = trpc.pipeline.getRecentCompleted.useQuery();
pipeline.getFailedJobs
Get failed jobs.
Input: undefined
Output: PipelineJob[] (last 10)
Example:
const { data } = trpc.pipeline.getFailedJobs.useQuery();
pipeline.getStats
Get pipeline job statistics.
Input: undefined
Output:
interface PipelineStats {
totalJobs: number;
byStatus: Record<string, number>;
byType: Record<string, number>;
avgProcessingTime: number;
successRate: number;
recentActivity: Array<{
date: Date;
count: number;
}>;
}
Example:
const { data } = trpc.pipeline.getStats.useQuery();
Mutations
pipeline.startImport
Start an import pipeline job.
Input:
interface StartImportInput {
fileContent: string; // WhatsApp export text content
skipCategorization?: boolean; // Default: false
skipSummarization?: boolean; // Default: false
config?: PipelineConfig;
}
interface PipelineConfig {
categorizationBatchSize?: number; // Min: 1, Max: 50
summarizationBatchSize?: number; // Min: 1, Max: 100
confidenceThreshold?: number; // Min: 0, Max: 1
fallbackStrategy?: "mark_for_review" | "assign_uncategorized" | "skip";
maxRetries?: number; // Min: 0, Max: 10
continueOnError?: boolean;
stageTimeout?: number; // Min: 1000, Max: 600000 (ms)
}
Output:
interface StartJobOutput {
success: boolean;
jobId: string;
status: JobStatus;
summary: string;
}
Example:
const mutation = trpc.pipeline.startImport.useMutation();
const fileContent = await file.text();
mutation.mutate({
fileContent,
skipCategorization: false,
skipSummarization: true,
config: {
categorizationBatchSize: 20,
confidenceThreshold: 0.7,
},
});
pipeline.startCategorize
Start a categorization pipeline job.
Input:
interface StartCategorizeInput {
messageIds: string[]; // Min: 1, Max: 1000
config?: PipelineConfig;
}
Output: StartJobOutput
Example:
const mutation = trpc.pipeline.startCategorize.useMutation();
mutation.mutate({
messageIds: ["msg-1", "msg-2", "msg-3"],
config: {
batchSize: 25,
fallbackStrategy: "mark_for_review",
},
});
pipeline.startSummarize
Start a summarization pipeline job.
Input:
interface StartSummarizeInput {
messageIds: string[]; // Min: 5, Max: 1000
config?: PipelineConfig;
}
Output: StartJobOutput
Example:
const mutation = trpc.pipeline.startSummarize.useMutation();
mutation.mutate({
messageIds: ["msg-1", "msg-2" /* ... */],
config: {
summarizationBatchSize: 50,
},
});
pipeline.cancelJob
Cancel a running pipeline job.
Input:
interface CancelJobInput {
jobId: string;
}
Output:
interface CancelJobOutput {
success: boolean;
job: PipelineJob;
}
Example:
const mutation = trpc.pipeline.cancelJob.useMutation();
mutation.mutate({ jobId: "job-123" });
pipeline.deleteJob
Delete a pipeline job.
Input:
interface DeleteJobInput {
jobId: string;
}
Output:
interface DeleteJobOutput {
success: boolean;
}
Example:
const mutation = trpc.pipeline.deleteJob.useMutation();
mutation.mutate({ jobId: "job-123" });
pipeline.cleanup
Clean up old completed jobs.
Input:
interface CleanupJobsInput {
olderThanDays?: number; // Default: 30, Min: 1, Max: 365
}
Output:
interface CleanupJobsOutput {
success: boolean;
deletedCount: number;
}
Example:
const mutation = trpc.pipeline.cleanup.useMutation();
mutation.mutate({
olderThanDays: 60,
});
Q&A Router
Base Path: qa
Context-aware question and answer functionality using LLM with retrieved message context.
Queries
qa.getStatus
Get Q&A service status and configuration.
Input: undefined
Output:
interface QAStatus {
available: boolean;
config: {
defaultMaxMessages: number;
defaultMinRelevance: number;
defaultSearchMode: SearchMode;
maxHistoryTurns: number;
};
}
Example:
const { data } = trpc.qa.getStatus.useQuery();
if (data.available) {
console.log("Q&A service is available");
}
qa.previewContext
Preview context that would be retrieved for a question.
Input:
interface PreviewContextInput {
question: string; // Min: 1, Max: 2000
contextOptions?: ContextRetrievalOptions;
}
interface ContextRetrievalOptions {
maxMessages?: number; // Min: 1, Max: 50
minRelevance?: number; // Min: 0, Max: 1
searchMode?: SearchMode;
categoryIds?: string[];
senderNames?: string[];
startDate?: Date;
endDate?: Date;
sentiment?: SentimentType;
starredOnly?: boolean;
}
Output:
interface PreviewContextOutput {
messages: Array<{
id: string;
senderName: string;
content: string;
timestamp: Date;
relevanceScore: number;
reason: string;
}>;
totalConsidered: number;
retrievalMethod: SearchMode;
retrievalTimeMs: number;
}
Example:
const { data } = trpc.qa.previewContext.useQuery({
question: "What did John say about the meeting?",
contextOptions: {
maxMessages: 10,
senderNames: ["John Doe"],
searchMode: "semantic",
},
});
qa.getConversationHistory
Get conversation history for Q&A context.
Input:
interface GetConversationHistoryInput {
conversationId: string;
maxTurns?: number; // Min: 1, Max: 20, Default: 5
}
Output:
interface ConversationHistoryOutput {
turns: Array<{
role: "user" | "assistant";
content: string;
timestamp: Date;
}>;
}
Example:
const { data } = trpc.qa.getConversationHistory.useQuery({
conversationId: "conv-123",
maxTurns: 10,
});
Mutations
qa.ask
Ask a question with context-aware Q&A.
Input:
interface AskQuestionInput {
question: string; // Min: 1, Max: 2000
conversationId?: string;
contextOptions?: ContextRetrievalOptions;
includeSources?: boolean; // Default: true
maxTokens?: number; // Min: 100, Max: 4096
saveToConversation?: boolean; // Default: true
}
Output:
interface AnswerOutput {
answer: string;
confidence: number;
sources: Array<{
messageId: string;
senderName: string;
timestamp: Date;
snippet: string;
relevanceScore: number;
}>;
suggestedFollowUps: string[];
contextStats: {
messagesUsed: number;
totalConsidered: number;
retrievalMethod: SearchMode;
retrievalTimeMs: number;
};
usage: {
inputTokens: number;
outputTokens: number;
totalCost: number;
};
totalTimeMs: number;
}
Example:
const mutation = trpc.qa.ask.useMutation();
mutation.mutate({
question: "What were the main topics discussed in my work messages?",
conversationId: "conv-123",
contextOptions: {
categoryIds: ["cat-work"],
maxMessages: 20,
searchMode: "hybrid",
},
includeSources: true,
maxTokens: 2000,
});
Feature Votes Router
Base Path: featureVotes
Feature request voting system.
Queries
featureVotes.list
List all feature requests with user's vote status.
Input: undefined
Output:
interface FeatureRequestWithVoteStatus {
id: string;
title: string;
description: string;
status: "planned" | "in_progress" | "completed" | "declined";
voteCount: number;
userVoted: boolean;
createdAt: Date;
updatedAt: Date;
}
Example:
const { data } = trpc.featureVotes.list.useQuery();
featureVotes.getRemainingVotes
Get user's remaining votes.
Input: undefined
Output:
interface RemainingVotesOutput {
remaining: number;
total: number;
used: number;
}
Example:
const { data } = trpc.featureVotes.getRemainingVotes.useQuery();
console.log(`You have ${data.remaining} votes remaining`);
featureVotes.getUserVotedIds
Get feature IDs the user has voted for.
Input: undefined
Output: string[]
Example:
const { data } = trpc.featureVotes.getUserVotedIds.useQuery();
Mutations
featureVotes.vote
Cast a vote for a feature request.
Input:
interface VoteInput {
featureRequestId: string;
}
Output:
interface VoteOutput {
success: boolean;
remainingVotes: number;
}
Example:
const mutation = trpc.featureVotes.vote.useMutation();
mutation.mutate({
featureRequestId: "feat-123",
});
featureVotes.unvote
Remove a vote from a feature request.
Input:
interface UnvoteInput {
featureRequestId: string;
}
Output:
interface UnvoteOutput {
success: boolean;
remainingVotes: number;
}
Example:
const mutation = trpc.featureVotes.unvote.useMutation();
mutation.mutate({
featureRequestId: "feat-123",
});
Feedback Router
Base Path: feedback
User feedback submission and management.
Queries
feedback.list
Get all feedback for the current user.
Input: undefined
Output: Feedback[]
Example:
const { data } = trpc.feedback.list.useQuery();
feedback.getById
Get feedback by ID.
Input:
interface GetFeedbackByIdInput {
id: string;
}
Output: Feedback | null
Example:
const { data } = trpc.feedback.getById.useQuery({
id: "feedback-123",
});
feedback.getCount
Get feedback count for the current user.
Input: undefined
Output:
interface FeedbackCountOutput {
count: number;
}
Example:
const { data } = trpc.feedback.getCount.useQuery();
Mutations
feedback.submit
Submit feedback.
Input:
interface SubmitFeedbackInput {
category: "bug" | "feature" | "ux" | "other";
priority: "critical" | "high" | "medium" | "low";
subject?: string;
description: string; // Min: 1
screenshot?: string;
errorLogs?: string;
pageUrl?: string;
userAgent?: string;
}
Output:
interface SubmitFeedbackOutput {
success: boolean;
feedback: Feedback;
}
interface Feedback {
id: string;
userId: string;
category: FeedbackCategory;
priority: FeedbackPriority;
subject: string | null;
description: string;
screenshot: string | null;
errorLogs: string | null;
pageUrl: string | null;
userAgent: string | null;
status: "open" | "in_progress" | "resolved";
createdAt: Date;
updatedAt: Date;
}
Example:
const mutation = trpc.feedback.submit.useMutation();
mutation.mutate({
category: "bug",
priority: "high",
subject: "Issue with message import",
description: "The import fails when processing large files",
pageUrl: window.location.href,
});
feedback.updateStatus
Update feedback status.
Input:
interface UpdateFeedbackStatusInput {
id: string;
status: "open" | "in_progress" | "resolved";
}
Output: Feedback
Example:
const mutation = trpc.feedback.updateStatus.useMutation();
mutation.mutate({
id: "feedback-123",
status: "resolved",
});
Best Practices
1. Error Handling
Always handle errors properly with specific error codes:
const { data, error } = trpc.messages.getById.useQuery({ id: "msg-123" });
if (error) {
switch (error.data?.code) {
case "NOT_FOUND":
return <MessageNotFound />;
case "FORBIDDEN":
return <AccessDenied />;
case "UNAUTHORIZED":
return <PleaseLogin />;
default:
return <GenericError />;
}
}
2. Optimistic Updates
Use optimistic updates for better UX:
const utils = trpc.useUtils();
const updateMutation = trpc.messages.update.useMutation({
onMutate: async (newData) => {
// Cancel outgoing refetches
await utils.messages.getById.cancel({ id: newData.id });
// Snapshot previous value
const previousData = utils.messages.getById.getData({ id: newData.id });
// Optimistically update
utils.messages.getById.setData({ id: newData.id }, (old) => ({
...old,
...newData,
}));
return { previousData };
},
onError: (err, newData, context) => {
// Rollback on error
if (context?.previousData) {
utils.messages.getById.setData({ id: newData.id }, context.previousData);
}
},
onSuccess: () => {
// Refetch after success
utils.messages.list.invalidate();
},
});
3. Type Inference
Let TypeScript infer types automatically:
// ✅ Good - types inferred
const { data } = trpc.messages.list.useQuery({ limit: 50 });
// ❌ Bad - manual typing
const { data }: { data: ListMessagesOutput } = trpc.messages.list.useQuery({ limit: 50 });
4. Batch Queries
Use useQueries for parallel queries:
import { useQueries } from "@trpc/react-query";
const results = useQueries({
queries: [
{ trpc: trpc.messages.list, query: { limit: 50 } },
{ trpc: trpc.categories.listWithStats },
{ trpc: trpc.categorization.getStats },
],
});
5. Infinite Query
Use infinite query for pagination:
const { data, fetchNextPage, hasNextPage, isFetching } = trpc.messages.list.useInfiniteQuery(
{ limit: 50 },
{
getNextPageParam: (lastPage) => {
if (lastPage.messages.length < 50) return undefined;
return lastPage.messages.length;
},
}
);
6. Mutation Callbacks
Handle mutation lifecycle properly:
const mutation = trpc.messages.create.useMutation({
onMutate: (variables) => {
// Show loading indicator
toast.loading("Creating message...");
},
onSuccess: (data, variables) => {
// Show success message
toast.success("Message created!");
// Invalidate related queries
utils.messages.list.invalidate();
utils.messages.getCount.invalidate();
},
onError: (error, variables) => {
// Show error message
toast.error(`Failed: ${error.message}`);
},
onSettled: (data, error, variables) => {
// Hide loading indicator
toast.dismiss();
},
});
7. Server-Side Usage
For server-side rendering or API routes:
import { trpcClient } from "~/lib/trpc/client";
export const loader = async ({ request }) => {
const messages = await trpcClient.messages.list.query({
limit: 50,
offset: 0,
});
return { messages };
};
Type Exports
All types are exported from the tRPC router:
import type { AppRouter } from "~/server/routers";
// Access router types
type Message = AppRouter["messages"]["getById"]["_output"];
type ListMessagesInput = AppRouter["messages"]["list"]["_input"];
Related Documentation
- API Overview - High-level API documentation
- Architecture Overview - System architecture
- Authentication - Auth implementation
- LLM Provider Guide - LLM integration