npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

simple_node_characterai

v1.2.2

Published

Simple Node.js library for interacting with Character.AI via HTTP and WebSocket with typescript

Readme

THIS PACKAGE HEAVILY INSPIRED BY node_characterai PACKAGE.

Character AI (Node) Package

Simple Node.js library for interacting with Character.AI via HTTP and WebSocket with typescript. It provides simple primitives to authenticate, create chats, send messages, and fetch chat turns. Perfect usage for route handlers in Node.js applications that need interactions with Character.AI in custom interface.

Features

  • Authenticate with a session token and initialize a WebSocket
  • Create one-on-one chats with characters
  • Create group chats with characters
  • Send messages and await final replies
  • Fetch chat turns (messages) with pagination
  • Minimal, typed API with clear data models

Installation

npm install simple_node_characterai

Quick Start

import { authenticate, createNewConversation, sendMessage, getMessages, getProfile } from 'simple_node_characterai';

(async () => {
  // 1) Authenticate with your session token (string without "Token " prefix is fine)
  await authenticate('YOUR_SESSION_TOKEN');

  // 2) Access the authenticated user profile
  const profile = getProfile();
  console.log('Logged in as:', profile.user.username);

  // 3) Create a new chat with a character
  const characterId = 'CHARACTER_ID';
  const addTurn = await createNewConversation(characterId);
  const chatId = addTurn.turn.turn_key.chat_id;

  // 4) Send a message and wait for the final candidate
  const response = await sendMessage('Hello!', characterId, chatId);
  console.log(response.turn.candidates[0].raw_content);

  // 5) Fetch latest turns (up to 50)
  const turns = await getMessages(chatId);
  console.log('Turns count:', turns.length);
})();

Example with Hono Route Handler

import { Hono } from 'hono'
import { authenticate, createNewConversation, sendMessage, getMessages, getProfile } from 'simple_node_characterai';

const app = new Hono();

(async () => {
  await authenticate(process.env.CHARACTERAI_TOKEN!);
})();

// Creates a new one-on-one conversation
// Body: { characterId: string } -> Response: { chatId: string }
app.post('/conversations', async (c) => {
  try {
    const body = await c.req.json();
    const characterId = body?.characterId;
    if (!characterId) return c.json({ error: 'characterId is required' }, 400);
    const addTurn = await createNewConversation(characterId);
    const chatId = addTurn.turn.turn_key.chat_id;
    return c.json({ chatId });
  } catch (e) {
    return c.json({ error: 'failed to create conversation' }, 500);
  }
});

// Sends a message to a conversation and returns the final content
// Body: { message: string, characterId: string, chatId: string }
// Make sure to store the characterId and chatId somewhere, like Database
// The messages field can be get in response.turn.candidates[0].raw_content
app.post('/messages', async (c) => {
  try {
    const body = await c.req.json();
    const message = body?.message;
    const characterId = body?.characterId;
    const chatId = body?.chatId;
    if (!message || !characterId || !chatId) {
      return c.json({ error: 'message, characterId, and chatId are required' }, 400);
    }
    const response = await sendMessage(message, characterId, chatId);
    const final = response.turn.candidates?.[0];
    return c.json({
      turn: response.turn,
      content: final?.raw_content ?? null
    });
  } catch (e) {
    return c.json({ error: 'failed to send message' }, 500);
  }
});

// Fetches up to 50 turns for the given chatId
// Optional query: ?token=NEXT_TOKEN for pagination
// Token will be available in meta field of response if there's more turns to fetch.
app.get('/messages/:chatId', async (c) => {
  try {
    const chatId = c.req.param('chatId');
    const token = c.req.query('token') ?? undefined;
    const messages = await getMessages(chatId, token);
    return c.json({ turns: messages.turns, token: messages.meta.next_token });
  } catch (e) {
    return c.json({ error: 'failed to fetch messages' }, 500);
  }
});

export default app

Group Chat Quick Start

Warning: Connection to room chat is cannot more than 1 connection. so you cannot mass use it.

import { 
  authenticate,
  createGroupChat
  listGroupChat,
  connectGroupChat,
  sendGroupMessage,
  generateTurnGroupMessage,
  disconnectGroupChat
} from 'simple_node_characterai';

(async () => {
  await authenticate('YOUR_SESSION_TOKEN');

  // 1) List Groups
  const listGroups = await listGroupChats();

  // 2) Create Group Chat
  const createGroupChat = await createGroupChat('My Group Chat', ['CHARACTER_ID_1', 'CHARACTER_ID_2']);

  // Assuming we pick the first room or use a known ID
  const roomId = 'ROOM_ID'; 

  // 3) Connect
  const connect = await connectGroupChat(roomId);

  // 4) Send Message
  const sendMessage = await sendGroupMessage('Hi!', roomId);
  console.log(JSON.stringify(sendMessage, null, 4));

  // 5) Generate Turn Message
  const generateResponse = await generateTurnGroupMessage(roomId);
  console.log(JSON.stringify(generateResponse.push, null, 4));

  // 6) Disconnect
  await disconnectGroupChat(roomId);
})();

Example with Hono Route Handler (Group Chat)

import { Hono } from 'hono'
import { 
  authenticate,
  listGroupChat,
  CreateGroupChat
  connectGroupChat,
  sendGroupMessage,
  generateTurnGroupMessage,
  disconnectGroupChat
} from 'simple_node_characterai';

const app = new Hono();

(async () => {
  await authenticate(process.env.CHARACTERAI_TOKEN!);
})();

// List all group chats
app.get('/group', async (c) => {
  try {
    const list = await listGroupChat();
    return c.json(list);
  } catch (e) {
    return c.json({ error: 'Failed to list groups' }, 500);
  }
});

// Create a group chat
// Body: {title: string, characterIds: string | string[]}
// Store the id somewhere
app.post('/group', async (c) => {
  try {
    const body = await c.req.json();
    const title = body?.title;
    const characterIds = body?.characterIds;
    if (!title || !characterIds) return c.json({ error: 'title and characterIds are required' }, 400);
    const createGroupChat = await createGroupChat(title, characterIds);
    return c.json(createGroupChat);
  } catch (e) {
    return c.json({ error: 'Failed to create group chat' }, 500);
  }
});

// Connect to a group chat room
// Params: { roomId: string }
app.post('/group/:roomId/connect', async (c) => {
  try {
    const roomId = c.req.param('roomId');
    if (!roomId) return c.json({ error: 'roomId is required' }, 400);
    const connect = await connectGroupChat(roomId);
    return c.json(connect);
  } catch (e) {
    return c.json({ error: 'Failed to connect' }, 500);
  }
});

// Send a message to a group chat
// Body: { message: string }
// After sending a message, you must generate a turn to get the response, this message function is return your message, not the character reply itself
app.post('/group/:roomId/message', async (c) => {
  try {
    const roomId = c.req.param('roomId');
    if (!roomId) return c.json({ error: 'roomId is required' }, 400);
    const body = await c.req.json();
    const message = body?.message;
    if (!message) return c.json({ error: 'Message is required' }, 400);
    const sendMessage = await sendGroupMessage(message, roomId);
    return c.json(sendMessage);
  } catch (e) {
    return c.json({ error: 'Failed to send message' }, 500);
  }
});

// Generate a turn in the group chat after sending a message
app.post('/group/:roomId/generate', async (c) => {
  try {
    const roomId = c.req.param('roomId');
    if (!roomId) return c.json({ error: 'roomId is required' }, 400);
    const generateResponse = await generateTurnGroupMessage(roomId);
    return c.json(generateResponse);
  } catch (e) {
    return c.json({ error: 'Failed to generate turn' }, 500);
  }
});

// Disconnect a group chat room
// Params: { roomId: string }
app.post('/group/:roomId/disconnect', async (c) => {
  try {
    const roomId = c.req.param('roomId');
    if (!roomId) return c.json({ error: 'roomId is required' }, 400);
    const disconnect = await disconnectGroupChat(roomId);
    return c.json(disconnect);
  } catch (e) {
    return c.json({ error: 'Failed to disconnect' }, 500);
  }
});

export default app

Find Authorization Token

  1. Logged in to character.ai
  2. Make sure to select any character first
  3. Open the developer tools F12, FN + F12, CTRL + SHIFT + I
  4. Go to Application tab
  5. Navigate to Cookies section, and select https://character.ai cookies
  6. Look up for HTTP_AUTHORIZATION token with string that starts with Token
  7. If token doesn't present, refresh the page and see the token again
  8. Copy the value

Sometimes the token will show up for a minutes and dissapear.

Find Character ID and Chat ID

Logged in to character.ai, and select a character you want to chat, then look up at URL, the URL contains the Character and Chat ID with following detail:

https://character.ai/chat/{characterId}?hist={chatId}

API Reference

Auth

authenticate(sessionToken: string): Promise

  • Authenticates the user, validates the token, loads the profile, and opens the WebSocket.
  • Accepts either raw token or "Token XXX" format.

getProfile(): Profile

  • Returns the in-memory authenticated user profile.

Chat

createNewConversation(characterId: string): Promise

  • Creates a new one-on-one chat with the specified character and resolves when the greeting turn arrives.
  • Returns metadata and the initial turn.

sendMessage(message: string, characterId: string, chatId: string): Promise

  • Sends a user message to an existing chat.
  • Resolves when the model returns a final candidate for the turn.

getMessages(chatId: string, token?: string): Promise<Turn[]>

  • Retrieves up to 50 turns for the given chat.
  • If a token is provided, fetches the next page of results using next_token.

Group Chat

listGroupChat(): Promise<{rooms: GroupChat[]}>

  • Lists all group chats (rooms) the user is a member of.

createGroupChat(title: string, characterIds: string | string[]): Promise

  • Creates a new group chat room with specified characters.

connectGroupChat(roomId: string): Promise

  • Connects to a specific group chat room via WebSocket.

disconnectGroupChat(roomId: string): Promise

  • Disconnects from a specific group chat room via WebSocket.

sendGroupMessage(message: string, chatId: string): Promise

  • Sends a message to a group chat room.

generateTurnGroupMessage(chatId: string): Promise

  • Triggers the characters in the group chat to generate a turn/reply.

Notes

  • This package is intended for personal use.
  • Avoid logging tokens, cookies, or PII; keep sensitive information in memory.
  • When building client-side applications, follow CORS restrictions; this package is designed for server-side Node usage.

Development

Still in development but ready to use for basic chatting usage, more feature will be added in the future.

Support

If you like this package, support me with stars in github, its help me so much