raresats.net API
Guides View as Markdown

Chat & profiles

raresats.net has a chat, at /chat, and display names for addresses. Anyone can read. Posting and naming need a wallet that signs BIP-322 messages (Xverse, UniSat, or your own key): there are no accounts and no passwords, only signatures.

Topics #

The chat is one room for now: General. /chat shows its messages directly; /chat/general works too, and a link to any other topic (/chat/market, from the first weeks' topics) goes to /chat, its ?thread= and ?m= kept.

Topics stay in the API, so more can come back: when there is more than one, each has its page, /chat/<topic>, its own messages and threads, and the page lists them down the left (on a phone, /chat is the list). The first weeks' other topics (Market & prices, Palindromes and the rest) are archived: hidden and closed to posts. Their messages are kept, and a link to one still opens its thread, read only.

  • GET /api/chat/topics lists them in order, each with last_message_id and last_activity_at (just General for now). The page keeps the last message id you read in each topic (in your browser only) and marks a topic unread when its last_message_id is larger; the header's Chat link has a dot while one is.
  • GET /api/chat/messages?topic=market lists a topic's messages; POST /api/chat/messages takes {text, topic}. Without topic, both are General's (main, the one room before topics, is General too: its messages are General's now).
  • Only moderators create topics (POST /api/chat/topics) and archive them (DELETE /api/chat/topics/{id}: hidden and closed to new messages; the messages stay). A topic's id is its URL: 1 to 32 of a-z, 0-9 and inner hyphens. Creating one of the first weeks' archived topics brings it back, its messages with it.

Every message's time links to it: /chat?m=<id> (/chat/<topic>?m=<id> while there are topics) scrolls to the message and highlights it, and a reply's link, /chat?thread=<root>&m=<id>, opens its thread. ?thread=<id> alone opens a thread. A link naming the wrong topic finds the right one: GET /api/chat/messages/{id}/replies answers any message's thread with its topic.

Signing in #

  1. GET /api/session/nonce?address=<address> returns a nonce, usable once, for 10 minutes, by that address only.
  2. Sign raresats.net login <address> <nonce> <unix seconds> with the address (BIP-322 simple or full).
  3. POST /api/session with {address, message, signature}.

The answer sets the session cookie __Host-rs_chat: HttpOnly, Secure, SameSite=Lax, Path=/, for 30 days. The server keeps only a hash of its token. GET /api/session says who is signed in; DELETE /api/session signs out.

The site signs in with your wallet's ordinals address by default; the payment address works too. Whichever address signs is the one shown in chat.

Posting #

POST /api/chat/messages with {text, topic} and the cookie. Messages are plain text:

  • normalized to NFC; control, zero-width, bidi-override and private-use characters are dropped, and line breaks become spaces, so a message cannot spoof its direction or hide text;
  • 1 to 500 characters after that;
  • shown as text only: never HTML or Markdown, no images. Links are shown as links that open in a new tab, marked nofollow ugc, labelled with their real host. A sat number written sat 1953421509541, or a bare number of 10 to 16 digits up to the last sat, links to its sat page.

Limits: one message every 3 seconds and 20 a minute per address, and the same text twice within 2 minutes is refused (duplicate); the whole chat takes at most 5 messages a second (room_busy). Per IPv4 address or IPv6 /64: 20 sign-ins and 10 display-name changes an hour. Messages are kept for 30 days, at most 10,000 a topic.

Replies and threads #

Send {text, reply_to: <id>} to reply to a message; the reply is in its root's topic (a topic sent with it must be that one). Threads are one level deep: a reply to a reply joins its root's thread, and keeps the reply it answered as its quote (text only, cut at 140 characters). Every message has reply_to (its root, or null on a root), and a root has reply_count and last_reply_at.

  • GET /api/chat/messages lists a topic's roots only, each with its thread's counts.
  • GET /api/chat/messages/{id}/replies lists a thread: its root and its replies, newest first, paged like a topic.
  • A reply's answer, and its live chat event, carry the root's new counts as thread: {id, reply_count, last_reply_at}.

Deleting a root deletes its whole thread: replies read wrong without what they answered, and what a moderator deletes is often what they quote. Bans, mutes and deletions apply to replies like any message.

Reading and live updates #

GET /api/chat/messages pages newest first; pass the answer's next as before for older messages. New messages and replies, deletions, topics and name changes are pushed on the event stream as chat and profile events: {type: "message", topic, message, thread?}, {type: "delete", ids, threads, topics} (the roots whose replies went, with their new counts, and the topics touched) and {type: "topic", topic, archived}. A page showing another topic marks the message's topic unread.

Display names #

Sign raresats.net profile <address> display_name=<name> <unix seconds> and send it to Set a display name with {address, display_name, message, signature}. Each change is signed on its own: a chat session is not enough. An empty name clears it.

Rule
Length3 to 24 characters, trimmed, inner spaces collapsed
Charactersletters, digits, spaces and _ . -; starts and ends with a letter or digit
Uniquecompared without case, separators or look-alike digits: Rare_Hunter is rare hunter, B0B is bob
Not an addressnothing starting bc1, no base58 address-like strings, hex blobs or .. (a shortened address)
Not reservedraresats, admin, moderator, support, official, satoshi and the like, anywhere in the name
How oftenat most one change in 24 hours

A name given up stays its last owner's for 7 days.

Moderation #

Chat is on only where the operator turned it on with moderation: AI moderation, moderators (admins), or both. GET /api/session says whether the AI moderation is on (moderated).

AI moderation. Every message and every new display name is checked by a language model before it is posted or taken, against the chat's rules. Refused: harassment and hate, sexual content, threats and violence, doxxing and personal data, scams and phishing (fake support, "send BTC to ...", asking for a seed phrase or private key, wallet-drainer and claim links, posing as raresats.net staff), spam and flooding, malware links, illegal goods. Allowed: trading talk, prices, sat numbers, addresses and txids in context, links to well-known explorers and marketplaces, mild profanity and criticism.

AnswerMeaning
422 moderatedRefused; category says why. Nothing was posted.
403 mutedThree refusals in 10 minutes mute the address for an hour; six from one IPv4 address or IPv6 /48 within an hour pause that network. until is when it ends.
429 rate_limitedAt most 6 texts a minute are checked per address and per IPv4 address or IPv6 /48.
503 moderation_unavailableThe text could not be checked right now (timeout or model error). Nothing was posted: try again.
429 chat_busyToo many texts are being checked across the site at once; wait retry-after seconds.

Links must name their real host in plain ASCII: a host with letters from other scripts (a look-alike of a known site), punycode (xn--) or user@host is refused before the check (422 suspicious_link), and the page shows every link by its host name.

The check never fails open: a message that could not be checked is not posted. The rate limits come first, so a refused flood costs nothing. Moderators' own messages are not checked.

Moderators sign in like anyone else. Their sessions may delete messages (a thread's root with its replies), remove a display name (it is never given out again) and ban addresses: a banned address cannot post or set a display name, its name is removed, and its recent messages can be removed with the ban. Unbanning also ends a mute. Every moderator action is logged.