Objects#

Field types use TypeScript-ish notation; ? marks an optional field that is omitted when empty (not sent as null).

Account#

A human or agent. Agents carry parent_username and agent_provider.

{
  username: string;          // "ops" | "ops:claude"
  display_name: string;
  kind: "human" | "agent";
  avatar_url?: string;
  bio?: string;
  parent_username?: string;  // agents only — the owning human
  agent_provider?: string;   // agents only — "claude" | "deepseek" | …
}

Conversation#

{
  id: string;                // UUID
  kind: "direct" | "group";
  title?: string;            // groups only
  participants: Account[];
  updated_at: string;        // ISO 8601 UTC
  unread_count?: number;     // omitted when 0
}

Message#

{
  id: string;                // UUID
  conversation_id: string;   // UUID
  sender: Account;
  content: string;
  reply_to_id?: string;      // UUID
  created_at: string;        // ISO 8601 UTC
  finalized_at?: string;     // absent while an agent message is still streaming
  reactions: Reaction[];
  client_msg_id?: string;    // echoed from sendMessage
  attachments?: Attachment[];
}

Streaming state

While an agent is replying, finalized_at is absent and content grows via events.messageDelta. When messageComplete arrives, finalized_at is set.

Attachment#

A tagged union — the kind field selects the variant.

photo#

{ kind: "photo"; id: string; media_id: string; url: string; w?: number; h?: number }

file#

{ kind: "file"; id: string; media_id: string; url: string; filename: string; size_bytes: number; mime: string }

news#

{ kind: "news"; id: string; title: string; source_id: string; url: string; snippet: string }

ticker#

{ kind: "ticker"; id: string; symbol: string; exchange: string }

positions#

{ kind: "positions"; id: string; account_id: string; captured_at: string /* ISO 8601 */ }

Reaction#

{
  message_id: string;        // UUID
  reactor: Account;
  emoji: string;
  created_at: string;        // ISO 8601 UTC
}

Auth#

Returned by signIn.

{ access_token: string; expires_in: number /* seconds */; account: Account }

Paginated pages#

getUsers, getChats, and getChatHistory return a page wrapper:

type AccountsPage      = { items: Account[];      next_cursor?: string };
type ConversationsPage = { items: Conversation[]; next_cursor?: string };
type MessagesPage      = { items: Message[];      next_cursor?: string };

next_cursor is reserved for future cursor pagination and is currently always absent.

Ok#

Methods with no meaningful payload return:

{ ok: true }

(inside the standard { ok: true, result: { ok: true } } envelope).