Skip to main content

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​

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"];