Skip to main content

Quick Start

This guide walks you through the essentials of working with the Osmium Chat Protocol. You’ll learn how to read the proto definitions, establish a connection, authenticate, and send your first message.

Prerequisites

Before you begin, you’ll need:
  • A Protocol Buffers compiler (protoc) installed
  • A WebSocket or TCP client library for your language
  • Basic understanding of Protocol Buffers
The Osmium Chat Protocol uses proto3 syntax. All proto files are located in the source/ directory of the repository.

Step 1: Reading the Proto Files

1

Locate the proto files

All protocol definitions are in the source/ directory. The main entry point is core.proto, which imports all other packages.Key files to start with:
  • core.proto - ClientMessage, ServerMessage, RPCResult, Initialize
  • auth.proto - Authentication and authorization
  • messages.proto - Sending and managing messages
  • refs.proto - Chat references (channels, users, groups)
  • types.proto - Shared data types (User, Message, Community, etc.)
  • updates.proto - Real-time update messages
2

Generate client code

Use protoc to generate client code for your language. Here’s an example for Go:
protoc --go_out=./generated \
  --go_opt=paths=source_relative \
  source/*.proto
For JavaScript/TypeScript:
protoc --js_out=import_style=commonjs:./generated \
  --ts_out=./generated \
  source/*.proto
3

Understand the message structure

Every client request is wrapped in a ClientMessage:
message ClientMessage {
  uint32 id = 1;           // Unique request ID
  oneof message {
    Initialize core_initialize = 19;
    auth.Authorize auth_authorize = 20;
    messages.SendMessage messages_send_message = 4;
    // ... 80+ other RPC methods
  }
}
Every server response is wrapped in a ServerMessage:
message ServerMessage {
  uint32 id = 1;           // Server message ID
  oneof message {
    Update update = 2;     // Real-time update
    RPCResult result = 3;  // RPC response
  }
}

Step 2: Connect and Initialize

Establish a connection and send your first RPC call.
1

Connect to the server

Connect to the Osmium server using WebSocket or TCP. For WebSocket:
const ws = new WebSocket('wss://your-server.com/gateway');

ws.onmessage = (event) => {
  const serverMessage = ServerMessage.decode(event.data);
  handleServerMessage(serverMessage);
};
2

Send Initialize request

Before any other operations, you must initialize the connection:
const clientMessage = {
  id: 1,  // Unique request ID
  message: {
    core_initialize: {
      client_id: 1,                    // Client type identifier
      device_type: "web",              // Platform (web, ios, android, etc.)
      device_version: "Chrome 120",    // Device/browser version
      app_version: "1.0.0",            // Your app version
      no_subscribe: false              // Set true to disable updates
    }
  }
};

const encoded = ClientMessage.encode(clientMessage).finish();
ws.send(encoded);
3

Handle the Initialized response

The server responds with available entrypoints:
function handleServerMessage(serverMessage) {
  if (serverMessage.message.result) {
    const result = serverMessage.message.result;
    
    if (result.req_id === 1) {  // Matches our Initialize request
      if (result.result.initialized) {
        console.log('Connection initialized!');
        console.log('Entrypoints:', result.result.initialized.entrypoints);
        // Now you can authenticate
      }
    }
  }
}
The req_id in the response matches the id from your request. This is how you correlate responses to requests.

Step 3: Authenticate

After initialization, authenticate to access protected operations.
1

Sign in with credentials

Use auth_sign_in to authenticate with email and password:
const signInMessage = {
  id: 2,  // New unique request ID
  message: {
    auth_sign_in: {
      email: "[email protected]",
      password: "your_secure_password"
    }
  }
};

const encoded = ClientMessage.encode(signInMessage).finish();
ws.send(encoded);
2

Handle authentication response

The server returns an Authorization with your token and user info:
function handleServerMessage(serverMessage) {
  if (serverMessage.message.result) {
    const result = serverMessage.message.result;
    
    if (result.req_id === 2) {  // Matches our SignIn request
      if (result.result.error) {
        console.error('Auth failed:', result.result.error);
      } else if (result.result.authorization) {
        const auth = result.result.authorization;
        
        // Save the token for future sessions
        localStorage.setItem('auth_token', auth.token);
        
        console.log('Authenticated as:', auth.user.name);
        console.log('User ID:', auth.user.id);
        console.log('Session ID:', auth.session_id);
        
        // Now you can make authenticated requests
      }
    }
  }
}
3

Using a saved token

For subsequent sessions, use auth_authorize with your saved token:
const authorizeMessage = {
  id: 2,
  message: {
    auth_authorize: {
      token: localStorage.getItem('auth_token')
    }
  }
};

ws.send(ClientMessage.encode(authorizeMessage).finish());
Authorization returns the same Authorization response as sign in, refreshing your user data.

Step 4: Send Your First Message

Now that you’re authenticated, send a message to a channel.
1

Construct the message

Use messages_send_message with a ChatRef to specify the destination:
const sendMessage = {
  id: 3,  // New unique request ID
  message: {
    messages_send_message: {
      chat_ref: {
        channel: {
          community_id: 123456789,  // Target community
          channel_id: 987654321     // Target channel
        }
      },
      message: "Hello, Osmium! 👋"
    }
  }
};

ws.send(ClientMessage.encode(sendMessage).finish());
You can also send direct messages by using user in the ChatRef instead of channel.
2

Handle the sent message response

The server responds with the message ID:
if (result.req_id === 3) {  // Matches our SendMessage request
  if (result.result.sent_message) {
    const messageId = result.result.sent_message.message_id;
    console.log('Message sent with ID:', messageId);
  }
}
3

Send a direct message

To send a DM, use a UserRef instead:
const dmMessage = {
  id: 4,
  message: {
    messages_send_message: {
      chat_ref: {
        user: {
          user_id: 111111111  // Recipient's user ID
        }
      },
      message: "Hey, how are you?"
    }
  }
};

ws.send(ClientMessage.encode(dmMessage).finish());

Step 5: Handle Real-Time Updates

The server pushes real-time updates to keep your client synchronized.
1

Recognize update messages

Updates arrive as ServerMessage with the update field set:
function handleServerMessage(serverMessage) {
  if (serverMessage.message.update) {
    handleUpdate(serverMessage.message.update);
  } else if (serverMessage.message.result) {
    handleRPCResult(serverMessage.message.result);
  }
}
2

Handle message created updates

When someone sends a message, you receive an UpdateMessageCreated:
function handleUpdate(update) {
  if (update.update.message_created) {
    const messageCreated = update.update.message_created;
    const msg = messageCreated.message;
    
    console.log('New message in channel:', msg.chat_ref.channel.channel_id);
    console.log('From user:', msg.author_id);
    console.log('Content:', msg.message);
    console.log('Unread count:', messageCreated.channel_unread_count);
    
    // Update your UI with the new message
    displayMessage(msg);
  }
}
3

Handle other common updates

The protocol includes many update types:
function handleUpdate(update) {
  // Typing indicators
  if (update.update.chat_typing) {
    const typing = update.update.chat_typing;
    showTypingIndicator(typing.user_id, typing.typing);
  }
  
  // User status changes
  if (update.update.user_status) {
    const status = update.update.user_status;
    updateUserStatus(status.user_id, status.status.status);
  }
  
  // Message edited
  if (update.update.message_edited) {
    const edited = update.update.message_edited;
    updateMessage(edited.message);
  }
  
  // Message deleted
  if (update.update.message_deleted) {
    const deleted = update.update.message_deleted;
    removeMessage(deleted.message_id);
  }
}

Complete Example

Here’s a complete working example putting it all together:
class OsmiumClient {
  constructor(serverUrl) {
    this.ws = new WebSocket(serverUrl);
    this.requestId = 0;
    this.pendingRequests = new Map();
    
    this.ws.onmessage = (event) => {
      const serverMessage = ServerMessage.decode(new Uint8Array(event.data));
      this.handleServerMessage(serverMessage);
    };
  }
  
  sendRequest(message) {
    const id = ++this.requestId;
    const clientMessage = { id, message };
    const encoded = ClientMessage.encode(clientMessage).finish();
    
    return new Promise((resolve, reject) => {
      this.pendingRequests.set(id, { resolve, reject });
      this.ws.send(encoded);
    });
  }
  
  handleServerMessage(serverMessage) {
    if (serverMessage.message.result) {
      const result = serverMessage.message.result;
      const pending = this.pendingRequests.get(result.req_id);
      
      if (pending) {
        this.pendingRequests.delete(result.req_id);
        
        if (result.result.error) {
          pending.reject(result.result.error);
        } else {
          pending.resolve(result.result);
        }
      }
    } else if (serverMessage.message.update) {
      this.handleUpdate(serverMessage.message.update);
    }
  }
  
  handleUpdate(update) {
    // Emit events or call callbacks based on update type
    if (update.update.message_created) {
      this.onMessageCreated?.(update.update.message_created);
    }
    // ... handle other updates
  }
  
  async initialize() {
    return await this.sendRequest({
      core_initialize: {
        client_id: 1,
        device_type: "web",
        device_version: navigator.userAgent,
        app_version: "1.0.0",
        no_subscribe: false
      }
    });
  }
  
  async signIn(email, password) {
    return await this.sendRequest({
      auth_sign_in: { email, password }
    });
  }
  
  async authorize(token) {
    return await this.sendRequest({
      auth_authorize: { token }
    });
  }
  
  async sendMessage(chatRef, message) {
    return await this.sendRequest({
      messages_send_message: { chat_ref: chatRef, message }
    });
  }
}

// Usage
const client = new OsmiumClient('wss://your-server.com/gateway');

client.onMessageCreated = (messageCreated) => {
  console.log('New message:', messageCreated.message.message);
};

// Initialize and authenticate
await client.initialize();
const auth = await client.signIn('[email protected]', 'password');
console.log('Logged in as:', auth.authorization.user.name);

// Send a message
await client.sendMessage(
  { channel: { community_id: 123456789, channel_id: 987654321 } },
  "Hello from Osmium!"
);

Error Handling

Always handle errors in RPC responses:
try {
  const result = await client.sendMessage(chatRef, message);
} catch (error) {
  if (error.error_code === 403) {
    console.error('Permission denied:', error.error_message);
  } else if (error.error_code === 404) {
    console.error('Channel not found:', error.error_message);
  } else {
    console.error('Error:', error.error_message);
  }
}
Common error codes:
  • 400 - Bad request (invalid parameters)
  • 401 - Unauthorized (not authenticated)
  • 403 - Forbidden (no permission)
  • 404 - Not found (resource doesn’t exist)
  • 429 - Rate limited

Next Steps

Protocol Overview

Dive deeper into the protocol architecture and message structure

Messages API

Learn about message editing, deletion, history, and advanced features

Communities

Explore community management, channels, roles, and permissions

Updates Reference

Complete reference for all real-time update types

Tips for Development

1

Use unique request IDs

Always increment your request ID counter to avoid conflicts. The ID is used to match responses to requests.
2

Keep your token secure

Store authentication tokens securely and never expose them in logs or client-side code that could be inspected.
3

Handle disconnections gracefully

Implement reconnection logic and re-authenticate when the WebSocket connection drops.
4

Subscribe to updates selectively

If you don’t need real-time updates (e.g., for bots or scripts), set no_subscribe: true in Initialize to reduce bandwidth.

Build docs developers (and LLMs) love