API Overview
The AllMyChats API is built using tRPC (TypeScript Remote Procedure Call) on top of TanStack Start, providing end-to-end type safety between the client and server. This means you get full TypeScript autocomplete and type checking for all API calls.
Base URL
/api/trpc
Key Features
- Type-Safe: Full TypeScript inference from server to client
- Auto-completion: IDE autocomplete for all API procedures
- Zero Runtime Overhead: No code generation required
- Batching: Automatic request batching for better performance
- Authentication: Better Auth integration for session-based auth
- Rate Limiting: Built-in rate limiting for API endpoints
Tech Stack
- tRPC v11: Type-safe RPC framework
- SuperJSON: Data transformer for dates, BigInt, and other special types
- Zod: Schema validation for all inputs and outputs
- Better Auth: Authentication and session management
Getting Started
Client Setup
The tRPC client is pre-configured in src/lib/trpc/client.ts:
import { trpc } from "~/lib/trpc/client";
// Use in React components
function MyComponent() {
const { data } = trpc.messages.list.useQuery({
limit: 50,
offset: 0
});
return <div>{data?.messages.length} messages</div>;
}
Vanilla Client (Server-Side)
For server-side calls or outside React components:
import { trpcClient } from "~/lib/trpc/client";
const result = await trpcClient.messages.list.query({
limit: 50,
offset: 0,
});
Authentication
All API endpoints (except those marked as public) require authentication. Authentication is handled via Better Auth session cookies.
Authentication Flow
- User logs in via
/api/auth/*endpoints - Session is stored in an HTTP-only cookie
- tRPC automatically includes the cookie in requests
- Server validates session and extracts
userId
Protected Procedures
Procedures that require authentication use protectedProcedure:
import { protectedProcedure } from "~/server/trpc";
export const myRouter = router({
// Requires authentication
secretData: protectedProcedure.query(({ ctx }) => {
return { userId: ctx.userId };
}),
});
Context Type
The tRPC context includes:
interface TRPCContext {
userId?: string; // User ID if authenticated
isAuthenticated: boolean; // Authentication status
headers: Headers; // Request headers
}
Rate Limiting
The API implements rate limiting to prevent abuse and ensure fair usage. Rate limits are applied per IP address and/or per user.
Rate Limit Categories
| Category | Limit | Window | Description |
|---|---|---|---|
AUTH | 5 requests | 15 minutes | Login, register, password reset |
API | 100 requests | 1 minute | General API endpoints |
UPLOAD | 10 requests | 1 hour | File upload operations |
AI_CATEGORIZE | 50 requests | 1 minute | AI categorization |
AI_SUMMARIZE | 50 requests | 1 minute | AI summarization |
GLOBAL | 1000 requests | 1 minute | Overall application |
Rate Limit Response
When rate limited, the API returns:
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please try again later.",
"retryAfter": 60,
"resetTime": "2025-01-18T12:00:00.000Z"
}
}
Response Headers
All API responses include rate limit headers:
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1737182400
Retry-After: 60
Error Handling
tRPC provides structured error responses with proper TypeScript types.
Error Format
{
error: {
code: "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "INTERNAL_SERVER_ERROR" | "BAD_REQUEST",
message: "Human-readable error message",
data?: {
zodError?: ZodError // Validation errors (if applicable)
}
}
}
Error Codes
| Code | Description | HTTP Status |
|---|---|---|
UNAUTHORIZED | Authentication required | 401 |
FORBIDDEN | Insufficient permissions | 403 |
NOT_FOUND | Resource not found | 404 |
BAD_REQUEST | Invalid input | 400 |
INTERNAL_SERVER_ERROR | Server error | 500 |
CONFLICT | Resource conflict | 409 |
PRECONDITION_FAILED | Service unavailable | 412 |
Handling Errors
const { data, error } = trpc.messages.getById.useQuery({ id: "123" });
if (error) {
switch (error.data?.code) {
case "NOT_FOUND":
console.log("Message not found");
break;
case "FORBIDDEN":
console.log("You don't have access");
break;
default:
console.log("Something went wrong");
}
}
Available API Procedures
Messages (messages)
Manage WhatsApp messages, including imports, updates, and categorization.
Queries
| Procedure | Description |
|---|---|
messages.list | List messages with pagination and filtering |
messages.getById | Get a single message by ID |
messages.search | Search messages by content |
messages.getCount | Get total message count |
messages.getCategories | Get all categories for a message |
Mutations
| Procedure | Description |
|---|---|
messages.create | Create a new message |
messages.update | Update a message |
messages.delete | Delete a message |
messages.markAsRead | Mark message as read |
messages.toggleStarred | Toggle starred status |
messages.archive | Archive a message |
messages.unarchive | Unarchive a message |
messages.setCategories | Set categories for a message (multi-category) |
messages.addCategory | Add a category to a message |
messages.removeCategory | Remove a category from a message |
messages.importWhatsApp | Import messages from WhatsApp export |
Categories (categories)
Manage message categories and hierarchies.
Queries
| Procedure | Description |
|---|---|
categories.list | List all categories |
categories.listWithStats | List categories with message counts |
categories.listRoot | List root categories (no parent) |
categories.listChildren | List child categories of a parent |
categories.getById | Get a single category with stats |
categories.getStatistics | Get comprehensive category statistics |
categories.getCount | Get category count |
Mutations
| Procedure | Description |
|---|---|
categories.create | Create a new category |
categories.update | Update a category |
categories.delete | Delete a category |
categories.updateSortOrder | Update category sort order |
categories.moveToParent | Move category to new parent |
Conversations (conversations)
Manage chat conversations and messages for Q&A features.
Queries
| Procedure | Description |
|---|---|
conversations.list | List conversations with pagination |
conversations.getById | Get a single conversation with stats |
conversations.getCount | Get conversation count |
conversations.getMessages | Get messages for a conversation |
conversations.getThreadMessages | Get thread messages (replies) |
Mutations
| Procedure | Description |
|---|---|
conversations.create | Create a new conversation |
conversations.update | Update a conversation |
conversations.delete | Delete a conversation |
conversations.archive | Archive a conversation |
conversations.unarchive | Unarchive a conversation |
conversations.sendMessage | Send a message in a conversation |
conversations.updateMessage | Update a chat message |
conversations.deleteMessage | Delete a chat message |
Search (search)
Comprehensive search functionality with tags and saved filters.
Queries
| Procedure | Description |
|---|---|
search.search | Perform a search (keyword, semantic, or hybrid) |
search.parseQuery | Parse a search query for preview/validation |
search.getSuggestions | Get search suggestions (recent/popular) |
search.getTags | Get all tags for the current user |
search.getMessageTags | Get tags for a message |
search.getSavedFilters | Get all saved filters |
search.getSavedFilter | Get a saved filter by ID |
search.getMostUsedFilters | Get most used saved filters |
search.getRecentlyUsedFilters | Get recently used saved filters |
search.getEmbeddingStats | Get embedding statistics |
Mutations
| Procedure | Description |
|---|---|
search.clearHistory | Clear search history |
search.createTag | Create a new tag |
search.updateTag | Update a tag |
search.deleteTag | Delete a tag |
search.addTagToMessage | Add tag to a message |
search.removeTagFromMessage | Remove tag from a message |
search.setMessageTags | Set all tags for a message |
search.bulkAddTag | Bulk add tag to multiple messages |
search.bulkRemoveTag | Bulk remove tag from multiple messages |
search.createSavedFilter | Create a saved filter |
search.updateSavedFilter | Update a saved filter |
search.deleteSavedFilter | Delete a saved filter |
search.applySavedFilter | Apply a saved filter (increments usage) |
search.reorderSavedFilters | Reorder saved filters |
search.generateEmbeddings | Generate embeddings for messages |
Categorization (categorization)
AI-powered message categorization using LLM providers.
Queries
| Procedure | Description |
|---|---|
categorization.getStats | Get categorization statistics |
categorization.getUncategorizedCount | Get uncategorized message count |
categorization.getMessagesNeedingReview | Get low-confidence messages |
categorization.getCategoryOptions | Get category options for the user |
categorization.getCategoriesWithStats | Get categories with message counts |
Mutations
| Procedure | Description |
|---|---|
categorization.categorizeMessage | Categorize a single message |
categorization.categorizeBulk | Categorize multiple messages |
categorization.categorizeUncategorized | Categorize all uncategorized messages |
categorization.suggestTaxonomy | Suggest a taxonomy based on messages |
categorization.approveCategorizaton | Approve a categorization (mark as reviewed) |
categorization.suggestCategoryAssignments | Suggest category assignments (preview) |
categorization.applyCategoryAssignments | Apply category assignments after approval |
Pipeline (pipeline)
Message processing pipeline orchestration for import, categorize, and summarize jobs.
Queries
| Procedure | Description |
|---|---|
pipeline.getJob | Get a pipeline job by ID |
pipeline.listJobs | List pipeline jobs with filtering |
pipeline.getActiveJobs | Get active pipeline jobs |
pipeline.getRecentCompleted | Get recent completed jobs |
pipeline.getFailedJobs | Get failed jobs |
pipeline.getStats | Get pipeline job statistics |
Mutations
| Procedure | Description |
|---|---|
pipeline.startImport | Start an import pipeline job |
pipeline.startCategorize | Start a categorization job |
pipeline.startSummarize | Start a summarization job |
pipeline.cancelJob | Cancel a running job |
pipeline.deleteJob | Delete a job |
pipeline.cleanup | Clean up old completed jobs |
Q&A (qa)
Context-aware question and answer functionality using LLM with retrieved message context.
Queries
| Procedure | Description |
|---|---|
qa.getStatus | Get Q&A service status and configuration |
qa.previewContext | Preview context that would be retrieved |
qa.getConversationHistory | Get conversation history for Q&A context |
Mutations
| Procedure | Description |
|---|---|
qa.ask | Ask a question with context-aware Q&A |
Feature Votes (featureVotes)
Feature request voting system.
Queries
| Procedure | Description |
|---|---|
featureVotes.list | List all feature requests with vote status |
featureVotes.getRemainingVotes | Get user's remaining votes |
featureVotes.getUserVotedIds | Get feature IDs the user has voted for |
Mutations
| Procedure | Description |
|---|---|
featureVotes.vote | Cast a vote for a feature request |
featureVotes.unvote | Remove a vote from a feature request |
Feedback (feedback)
User feedback submission and management.
Queries
| Procedure | Description |
|---|---|
feedback.list | Get all feedback for the current user |
feedback.getById | Get feedback by ID |
feedback.getCount | Get feedback count for the current user |
Mutations
| Procedure | Description |
|---|---|
feedback.submit | Submit feedback |
feedback.updateStatus | Update feedback status |
Code Examples
Basic Usage
import { trpc } from "~/lib/trpc/client";
function MessageList() {
// List messages with filtering
const { data, isLoading, error } = trpc.messages.list.useQuery({
limit: 50,
categoryId: "category-123",
isStarred: true,
sortBy: "messageTimestamp",
sortOrder: "desc"
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data?.messages.map((msg) => (
<li key={msg.id}>{msg.content}</li>
))}
</ul>
);
}
Mutations
import { trpc } from "~/lib/trpc/client";
import { useState } from "react";
function CreateMessage() {
const [content, setContent] = useState("");
const utils = trpc.useUtils();
const createMutation = trpc.messages.create.useMutation({
onSuccess: () => {
// Invalidate and refetch messages list
utils.messages.list.invalidate();
setContent("");
}
});
const handleSubmit = () => {
createMutation.mutate({
content,
senderName: "John Doe",
messageTimestamp: new Date()
});
};
return (
<div>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<button onClick={handleSubmit}>
{createMutation.isPending ? "Creating..." : "Create"}
</button>
</div>
);
}
Infinite Scroll
import { trpc } from "~/lib/trpc/client";
function InfiniteMessageList() {
const [limit, setLimit] = useState(50);
const { data, fetchNextPage, hasNextPage, isFetching } =
trpc.messages.list.useInfiniteQuery(
{ limit },
{
getNextPageParam: (lastPage) => {
if (lastPage.messages.length < limit) return undefined;
return lastPage.messages.length;
}
}
);
return (
<div>
{data?.pages.map((page) => (
<div key={page.offset}>
{page.messages.map((msg) => (
<div key={msg.id}>{msg.content}</div>
))}
</div>
))}
{hasNextPage && (
<button
onClick={() => fetchNextPage()}
disabled={isFetching}
>
{isFetching ? "Loading..." : "Load More"}
</button>
)}
</div>
);
}
Server-Side Usage
import { trpcClient } from "~/lib/trpc/client";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/messages")({
loader: async () => {
// Fetch data server-side
const messages = await trpcClient.messages.list.query({
limit: 50,
offset: 0,
});
return { messages };
},
});
Webhooks
The application supports webhooks for external services integration.
Stripe Webhook
Endpoint: /api/stripe/webhook
Method: POST
Handles Stripe payment events for subscription management.
Events Handled
| Event Type | Description |
|---|---|
checkout.session.completed | New subscription created |
customer.subscription.created | Subscription created |
customer.subscription.updated | Subscription updated |
customer.subscription.deleted | Subscription canceled |
Webhook Signature
The webhook verifies Stripe signatures for security:
const sig = request.headers.get("stripe-signature");
const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET);
Pagination
Most list endpoints support pagination via limit and offset parameters:
interface PaginationParams {
limit: number; // Number of items per page (1-100)
offset: number; // Number of items to skip
}
Response Format
interface PaginatedResponse<T> {
items: T[];
pagination: {
total: number; // Total number of items
limit: number; // Items per page
offset: number; // Current offset
hasMore: boolean; // Whether there are more items
};
}
Data Transformer (SuperJSON)
The API uses SuperJSON as the data transformer, which enables:
- Date serialization: Dates are properly serialized and deserialized
- BigInt support: Large numbers are handled correctly
- Map/Set support: Complex data structures work seamlessly
- Circular references: Handles circular references in objects
This means you don't need to manually parse dates or handle special types:
// Date is automatically a Date object on the client
const { data } = await trpc.messages.list.query();
console.log(data.messages[0].messageTimestamp instanceof Date); // true
API Versioning
The current API version is v1. Future versions will be introduced with breaking changes, and the old version will be maintained for a deprecation period.
Versioning Strategy
- tRPC: Type changes automatically enforce compatibility
- Deprecation: Old procedures marked with
@deprecatedJSDoc comments - Breaking Changes: Major version increments
- Backward Compatibility: Maintained when possible
Best Practices
1. Type Inference
Always use the auto-inferred types instead of manually typing:
// ❌ Don't do this
const data: MessageType = await trpc.messages.getById.query({ id: "123" });
// ✅ Do this - types are inferred automatically
const data = await trpc.messages.getById.query({ id: "123" });
2. Error Handling
Always handle errors properly:
const { data, error } = trpc.messages.getById.useQuery({ id: "123" });
if (error) {
// Handle error based on error code
return <ErrorMessage error={error} />;
}
3. Optimistic Updates
Use utils for optimistic updates:
const utils = trpc.useUtils();
const mutation = 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 }, newData);
// Return context with rollback function
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.getById.invalidate({ id: newData.id });
},
});
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.list, query: undefined },
{ trpc: trpc.conversations.list, query: { limit: 20 } },
],
});
Debugging
tRPC DevTools
Enable tRPC devtools in development:
// In development, tRPC devtools are available
// in the browser's React DevTools
Logging
All tRPC calls with duration > 1000ms are logged:
[tRPC] messages.list took 1234ms
Error Formatting
Zod validation errors are formatted for easy debugging:
{
error: {
data: {
zodError: {
fieldErrors: {
content: ["Content is required"];
}
}
}
}
}
Next Steps
-
Read architecture docs:
-
Get started with development: