Skip to main content

Overview

The Hazel Chat API is built on Effect RPC, providing a type-safe, functional approach to API communication. The RPC system offers automatic serialization, error handling, and seamless integration between frontend and backend.

Key Features

  • Type-Safe: Full TypeScript type safety from client to server
  • Effect-Based: Built on Effect for composable, functional error handling
  • Real-Time Ready: HTTP and WebSocket support for live updates
  • Automatic Serialization: NDJSON protocol for efficient data transfer
  • Authentication: WorkOS-based authentication with session management
  • Rate Limited: Built-in rate limiting (60 requests/minute per user)

Architecture

The API uses the Effect RPC framework with the following architecture:
Client (Web/Desktop/Bot)

RPC Protocol Layer (HTTP/WebSocket)

Authentication Middleware

RPC Handlers

Business Logic (Policies + Repositories)

Database

Base URL

http://localhost:3003

RPC Endpoint

All RPC calls are made to the /rpc endpoint:
POST {BASE_URL}/rpc

Available RPC Groups

The API is organized into the following RPC groups:
GroupDescription
MessageRpcsCreate, update, and delete messages
ChannelRpcsManage channels, DMs, and threads
OrganizationRpcsOrganization management
UserRpcsUser profile and settings
NotificationRpcsNotification preferences
InvitationRpcsWorkspace invitations
MessageReactionRpcsMessage reactions (emoji)
TypingIndicatorRpcsReal-time typing indicators
ChannelMemberRpcsChannel membership
PinnedMessageRpcsPin important messages
AttachmentRpcsFile uploads and attachments
BotRpcsBot management and commands
ChatSyncRpcsExternal chat integration sync

Type-Safe Client Usage

Web/Desktop Client

The frontend uses an Effect-based RPC client with automatic type inference:
import { HazelRpcClient } from "~/lib/services/common/rpc-atom-client"
import { Effect } from "effect"

// Create a message
const createMessage = Effect.gen(function* () {
  const client = yield* HazelRpcClient
  
  const result = yield* client["message.create"]({
    channelId: "channel-id-here",
    content: "Hello, world!",
    attachmentIds: []
  })
  
  return result.data // Fully typed Message object
})

Bot SDK Client

Bots use a simplified HTTP-based RPC client:
import { makeBotRpcClient } from "@hazel/bot-sdk"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const client = yield* makeBotRpcClient({
    backendUrl: "https://api.hazel.sh",
    botToken: process.env.BOT_TOKEN
  })
  
  // Send a message
  const result = yield* client["message.create"]({
    channelId: "channel-id",
    content: "Message from bot",
    attachmentIds: []
  })
  
  return result
})
All RPC methods return an Effect that must be executed using Effect’s runtime. Errors are typed and handled functionally.

Rate Limiting

The API enforces rate limits to ensure fair usage:
  • Message Operations: 60 requests per minute per user
  • Per-user tracking: Rate limits are applied per authenticated user
  • Response headers: Rate limit information included in error responses
When rate limited, you’ll receive a RateLimitExceededError with retry information:
{
  _tag: "RateLimitExceededError",
  message: "Rate limit exceeded",
  retryAfterMs: 5000,
  limit: 60,
  remaining: 0
}
Implement exponential backoff when handling rate limit errors to avoid being temporarily blocked.

Response Format

All successful RPC responses include:
  1. Data: The requested resource(s)
  2. Transaction ID: For optimistic updates
{
  data: {
    id: "msg_123",
    content: "Hello, world!",
    channelId: "ch_456",
    authorId: "user_789",
    createdAt: "2024-03-04T10:30:00Z",
    // ... other fields
  },
  transactionId: "txn_abc123"
}
Transaction IDs enable optimistic UI updates. The client can immediately show changes and reconcile them when the server response arrives.

Common RPC Methods

Message Operations

// Create message
client["message.create"]({
  channelId: string,
  content: string,
  attachmentIds?: string[]
})

// Update message
client["message.update"]({
  id: string,
  content?: string
})

// Delete message
client["message.delete"]({
  id: string
})

Channel Operations

// Create channel
client["channel.create"]({
  name: string,
  type: "public" | "private" | "thread",
  organizationId: string,
  addAllMembers?: boolean
})

// Create DM
client["channel.createDm"]({
  participantIds: string[],
  type: "direct" | "single",
  organizationId: string
})

// Create thread
client["channel.createThread"]({
  messageId: string,
  organizationId?: string
})

// Generate AI thread name
client["channel.generateName"]({
  channelId: string
})

Next Steps

Authentication

Learn how to authenticate API requests with WorkOS

Error Handling

Understand error types and how to handle them

Build docs developers (and LLMs) love