Chat

Peer-to-peer messaging.

Peer-to-peer messaging. Chat never touches the ledger: a message is a signed envelope carrying its own anti-spam proof-of-work, relayed directly to the node the recipient is registered on in the directory, and held only by the two parties' nodes.

Sending is unauthenticated but always requires a signed envelope — a node will not sign on a caller's behalf. Reading is the other way round: GET /chat/messages and a per-account GET /chat/events require an ownership proof, obtained by signing a challenge from GET /chat/auth/challenge. The unfiltered event firehose is operator-only and needs the admin bearer token.

GET
/chat/auth/challenge
Get chat read challengeIssues a short-lived challenge to sign as proof that you own the account whose chat you want to read. Required by `GET /chat/messages` and by `GET /chat/events` for a named account. The flow is: call this endpoint, hex-decode the returned `challenge`, compute `sha256("xe/chat-read-auth/v1" || 0x00 || challengeBytes)`, sign those 32 bytes with the account's ed25519 key, then pass `account`, `challenge` and the hex-encoded signature as `sig` on the read request. A challenge is bound to the node that issued it, lives 120 seconds, and is single-use — fetch a fresh one per read. The endpoint itself is unauthenticated: a challenge is worthless without the account's private key.
GET
/chat/contacts
List chat contactsEvery account this node currently holds chat messages for, sorted. Node-local, not a chain read.
GET
/chat/events
Subscribe to chat (SSE)Server-Sent Events stream of chat messages as this node stores them. With `account` set, the stream is filtered to messages where that account is the sender or the recipient, and needs the same ownership proof as `GET /chat/messages`. With `account` omitted the stream is the node's unfiltered firehose, which is operator-only and requires the admin bearer token. Because `EventSource` cannot set headers, the proof travels in query parameters. The response headers and a `: connected` comment are flushed as soon as the stream opens, before any message arrives, so `onopen` fires on an idle stream and proxies can see the `text/event-stream` content type. A `: ping` comment follows every 20 seconds to keep idle connections alive. Both are SSE comments and are ignored by `EventSource`. Messages arrive as unnamed `data:` events — there is no `event:` field, so listen on `onmessage`.
GET
/chat/messages
List chat messagesReturns the messages this node holds for an account, in arrival order (oldest first). Reading an account's history requires an ownership proof — fetch a challenge from `GET /chat/auth/challenge`, sign it with the account key, and pass `challenge` and `sig` alongside `account`. The store is node-local and bounded: only messages that passed through this node are here, and the oldest are dropped once the per-account cap is reached. It is not a chain read and not authoritative history.
POST
/chat/send
Send chat messageRelays a pre-signed chat envelope to the node the recipient is registered on. The endpoint is unauthenticated but never signs on the caller's behalf: `from` and `signature` must both be present, and the node verifies the signature, the canonical `id`, the timestamp freshness window and the anti-spam proof-of-work before accepting. Only the two parties' nodes store the message — there is no chain record and no gossip to the wider network.