Chat messages
Messages are filed under topics (just General for now), each with threads one level deep: a reply to a reply joins its root's thread, in its topic. Without a topic, a request is General's. Messages and replies arrive live as chat events on the event stream, each naming its topic. Where the AI moderation is on, every message is checked before it is posted: Moderation.
List topics #
GET/api/chat/topics
The live topics, in their order, each with its newest message's id and time: a page keeps the last id it read per topic and shows the others as unread. New and archived topics come as chat events {type: "topic", topic, archived}.
Example request #
curl 'https://raresats.net/api/chat/topics'const response = await fetch('https://raresats.net/api/chat/topics');
const data = await response.json();import requests
data = requests.get("https://raresats.net/api/chat/topics").json()Response 200 #
| Field | Type | Description |
|---|---|---|
topics | array of object | |
topics[].id | string | 1-32 of a-z, 0-9 and inner hyphens. |
topics[].name | string | 1-40 characters, plain text. |
topics[].description | string | At most 140 characters, plain text; may be empty. |
topics[].position | integer | Its place in the list. |
topics[].created_at | integer | Unix seconds. |
topics[].last_message_id | integer | null | The newest live message's id (a reply counts); null when it has none. A page that keeps the last id it read per topic shows a topic as unread when this is larger. |
topics[].last_activity_at | integer | null | That message's time, unix seconds; null when it has none. |
{
"topics": [
{
"id": "general",
"name": "General",
"description": "Anything rare-sat: say gm, ask, share a find.",
"position": 0,
"created_at": 1790900000,
"last_message_id": 1044,
"last_activity_at": 1790949460
},
{
"id": "market",
"name": "Market & prices",
"description": "Listings, offers, floors and what things sell for.",
"position": 1,
"created_at": 1790900000,
"last_message_id": 1042,
"last_activity_at": 1790949400
}
]
}Errors #
{error, code}; see Errors for every code.
| Status | Code | Meaning |
|---|---|---|
| 404 | chat_disabled | Chat and display names are off on this server. |
| 405 | method_not_allowed | Wrong method for the route; the allow header lists the right ones. |
Create a topic (admin) #
POST/api/chat/topics
An admin session creates a topic, last in the list. Only admins may: topic names are not run through the AI moderation (an admin's text never is), but follow a message's text rules (NFC, no control, zero-width or bidi characters) and its link rule. An id is never reused, an archived topic's included, except a retired one's (a topic the defaults once seeded and no longer name): it comes back as the new topic, its messages with it. At most 50 topics. Pages are told with a chat event {type: "topic", topic, archived: false}. Logged.
Request body #
content-type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | 1-32 of a-z, 0-9 and inner hyphens; not main. |
name | string | yes | 1-40 characters. |
description | string | null | no | At most 140 characters. |
Example request #
curl -X POST 'https://raresats.net/api/chat/topics' \
-H 'content-type: application/json' \
-d '{
"id": "runes",
"name": "Runes",
"description": "Runes on rare sats, and what they do to a sat'\''s value."
}'const response = await fetch('https://raresats.net/api/chat/topics', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
"id": "runes",
"name": "Runes",
"description": "Runes on rare sats, and what they do to a sat's value."
})
});
const data = await response.json();import requests
data = requests.post("https://raresats.net/api/chat/topics", json={
"id": "runes",
"name": "Runes",
"description": "Runes on rare sats, and what they do to a sat's value."
}).json()Response 200 #
| Field | Type | Description |
|---|---|---|
topic | object | A chat topic (a channel). Its id is its slug: the page is /chat/<id>. |
topic.id | string | 1-32 of a-z, 0-9 and inner hyphens. |
topic.name | string | 1-40 characters, plain text. |
topic.description | string | At most 140 characters, plain text; may be empty. |
topic.position | integer | Its place in the list. |
topic.created_at | integer | Unix seconds. |
topic.last_message_id | integer | null | The newest live message's id (a reply counts); null when it has none. A page that keeps the last id it read per topic shows a topic as unread when this is larger. |
topic.last_activity_at | integer | null | That message's time, unix seconds; null when it has none. |
Errors #
{error, code}; see Errors for every code.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_json | The body is not valid JSON. |
| 400 | bad_request | The body is not a JSON object, or the engine refused a malformed request (e.g. a purchase submitted without its signed setup PSBT). |
| 400 | bad_topic | A topic id that is not one: 1-32 of a-z, 0-9 and inner hyphens (and, to create one, not main). On a reply: a topic other than its root's. |
| 401 | no_session | No chat session: sign in (POST /api/session) and send its cookie. |
| 403 | cross_origin | A POST from a browser on another site (a foreign Origin, or sec-fetch-site: cross-site). Server-side clients send no Origin and are not affected. |
| 403 | not_admin | Only a chat admin (SUPPLY_CHAT_ADMINS) may do that. |
| 404 | chat_disabled | Chat and display names are off on this server. |
| 405 | method_not_allowed | Wrong method for the route; the allow header lists the right ones. |
| 409 | topic_exists | A topic (or an archived one) has that id already. |
| 413 | too_large | The body is over 256 KiB. |
| 415 | not_json | A POST without content-type: application/json. |
| 422 | bad_topic_text | A topic's name must be 1-40 characters, its description at most 140. |
| 422 | suspicious_link | A link in the message names a host that is not plain ASCII: look-alike letters from other scripts, punycode (xn--), or user@host. Write the link with its real host name. |
| 422 | too_many_topics | At most 50 chat topics: archive one first. |
| 429 | rate_limited | Over 30 POSTs a minute from your IPv4 address or IPv6 /64 (in chat, counted per IPv4 address or IPv6 /64: 30 writes and 20 login nonces a minute, 20 sign-ins and 10 display-name changes an hour; one message every 3 s and 20 a minute per address); wait retry-after seconds. With the AI moderation on, at most 6 messages and names a minute are checked per address and per IPv4 address or IPv6 /48. |
Archive a topic (admin) #
DELETE/api/chat/topics/{id}
An admin session archives a topic: it leaves the list and takes no new messages or replies (404 unknown_topic); its messages stay in the store. General cannot be archived. Pages are told with a chat event {type: "topic", topic, archived: true}. Logged.
Path parameters #
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The topic's id. |
Example request #
curl -X DELETE 'https://raresats.net/api/chat/topics/off-topic' -H 'cookie: __Host-rs_chat=<token>'const response = await fetch('https://raresats.net/api/chat/topics/off-topic', { method: 'DELETE', credentials: 'include' });
const data = await response.json();Response 200 #
| Field | Type | Description |
|---|---|---|
topic | object | A chat topic (a channel). Its id is its slug: the page is /chat/<id>. |
topic.id | string | 1-32 of a-z, 0-9 and inner hyphens. |
topic.name | string | 1-40 characters, plain text. |
topic.description | string | At most 140 characters, plain text; may be empty. |
topic.position | integer | Its place in the list. |
topic.created_at | integer | Unix seconds. |
topic.last_message_id | integer | null | The newest live message's id (a reply counts); null when it has none. A page that keeps the last id it read per topic shows a topic as unread when this is larger. |
topic.last_activity_at | integer | null | That message's time, unix seconds; null when it has none. |
archived | true |
Errors #
{error, code}; see Errors for every code.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_path | The path is not valid percent-encoding. |
| 400 | bad_topic | A topic id that is not one: 1-32 of a-z, 0-9 and inner hyphens (and, to create one, not main). On a reply: a topic other than its root's. |
| 401 | no_session | No chat session: sign in (POST /api/session) and send its cookie. |
| 403 | cross_origin | A POST from a browser on another site (a foreign Origin, or sec-fetch-site: cross-site). Server-side clients send no Origin and are not affected. |
| 403 | not_admin | Only a chat admin (SUPPLY_CHAT_ADMINS) may do that. |
| 404 | chat_disabled | Chat and display names are off on this server. |
| 404 | unknown_topic | No such chat topic, or an archived one (which takes no new messages). |
| 405 | method_not_allowed | Wrong method for the route; the allow header lists the right ones. |
| 422 | not_archivable | General cannot be archived. |
| 429 | rate_limited | Over 30 POSTs a minute from your IPv4 address or IPv6 /64 (in chat, counted per IPv4 address or IPv6 /64: 30 writes and 20 login nonces a minute, 20 sign-ins and 10 display-name changes an hour; one message every 3 s and 20 a minute per address); wait retry-after seconds. With the AI moderation on, at most 6 messages and names a minute are checked per address and per IPv4 address or IPv6 /48. |
List messages #
GET/api/chat/messages
A topic's messages (the threads' roots), newest first, with each author's current display name and each thread's reply count and last reply time. Without topic, General's (main, the one room before topics, names General too). Replies are on their thread: List a thread. Page back with before. Messages are kept 30 days, at most 10,000 a topic.
Query parameters #
| Parameter | Type | Required | Description |
|---|---|---|---|
topic | string | no | A topic id (List topics); default general. Default: general. |
before | integer | no | A message id: only older ones (the previous page's next). (min 1) |
limit | integer | no | Messages per page, 1-100. Default: 50. (min 1, max 100) |
Example request #
curl 'https://raresats.net/api/chat/messages?topic=market&limit=50'const response = await fetch('https://raresats.net/api/chat/messages?topic=market&limit=50');
const data = await response.json();import requests
data = requests.get("https://raresats.net/api/chat/messages", params={"topic": "market", "limit": 50}).json()Response 200 #
| Field | Type | Description |
|---|---|---|
topic | string | The topic. |
room | string | Deprecated: the same as topic. |
messages | array of object | The topic's threads' roots, newest first (replies are on their thread). |
messages[].id | integer | Increasing; the paging cursor. |
messages[].topic | string | The topic it is in (a reply: its root's). |
messages[].room | string | Deprecated: the same as topic (the one room before topics was main, now general). |
messages[].address | string | The author. |
messages[].display_name | string | null | The author's display name now. |
messages[].admin | boolean | The author is a chat admin. |
messages[].text | string | Plain text (NFC, no control, zero-width or bidi characters, one line, 1-500 characters). Show it as text, never as HTML. |
messages[].created_at | integer | Unix seconds. |
messages[].reply_to | integer | null | The thread's root, on a reply; null on a root (a message of the topic). |
messages[].reply_count | integer | On a root: its live replies (0 on a reply). |
messages[].last_reply_at | integer | null | On a root: its newest reply's time, unix seconds; null without replies. |
messages[].quote | object | null | On a reply: the message it answered (the root, or a reply in the thread), while it is live; text only, cut at 140 characters. Null on a root. |
messages[].quote.id | integer | |
messages[].quote.address | string | |
messages[].quote.display_name | string | null | |
messages[].quote.text | string | |
next | integer | null | Pass as before for the older page; null at the start. |
{
"topic": "market",
"room": "market",
"messages": [
{
"id": 1042,
"topic": "market",
"room": "market",
"address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
"display_name": "Rare Hunter",
"admin": false,
"text": "gm, any block 9 for sale?",
"created_at": 1790949400,
"reply_to": null,
"reply_count": 2,
"last_reply_at": 1790949460,
"quote": null
}
],
"next": 1042
}Errors #
{error, code}; see Errors for every code.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_paging | limit or offset out of range. |
| 400 | bad_topic | A topic id that is not one: 1-32 of a-z, 0-9 and inner hyphens (and, to create one, not main). On a reply: a topic other than its root's. |
| 404 | chat_disabled | Chat and display names are off on this server. |
| 404 | unknown_topic | No such chat topic, or an archived one (which takes no new messages). |
| 405 | method_not_allowed | Wrong method for the route; the allow header lists the right ones. |
List a thread #
GET/api/chat/messages/{id}/replies
A thread: its root (with its reply count) and its replies, newest first, each with its author's current display name and the message it answered (quote). Page back with before. The id of a reply names its thread too. The answer names the thread's topic: a link to a message (/chat/<topic>?m=<id>) finds its page and thread from it.
Path parameters #
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | yes | The thread's root (or one of its replies). |
Query parameters #
| Parameter | Type | Required | Description |
|---|---|---|---|
before | integer | no | A reply id: only older ones (the previous page's next). (min 1) |
limit | integer | no | Replies per page, 1-100. Default: 50. (min 1, max 100) |
Example request #
curl 'https://raresats.net/api/chat/messages/1042/replies?limit=50'const response = await fetch('https://raresats.net/api/chat/messages/1042/replies?limit=50');
const data = await response.json();import requests
data = requests.get("https://raresats.net/api/chat/messages/1042/replies", params={"limit": 50}).json()Response 200 #
| Field | Type | Description |
|---|---|---|
topic | string | The thread's topic (its root's): a link to a reply finds its page. |
room | string | Deprecated: the same as topic. |
root | object | A message, or a reply in a thread (one level: a reply to a reply joins its root's thread). |
root.id | integer | Increasing; the paging cursor. |
root.topic | string | The topic it is in (a reply: its root's). |
root.room | string | Deprecated: the same as topic (the one room before topics was main, now general). |
root.address | string | The author. |
root.display_name | string | null | The author's display name now. |
root.admin | boolean | The author is a chat admin. |
root.text | string | Plain text (NFC, no control, zero-width or bidi characters, one line, 1-500 characters). Show it as text, never as HTML. |
root.created_at | integer | Unix seconds. |
root.reply_to | integer | null | The thread's root, on a reply; null on a root (a message of the topic). |
root.reply_count | integer | On a root: its live replies (0 on a reply). |
root.last_reply_at | integer | null | On a root: its newest reply's time, unix seconds; null without replies. |
root.quote | object | null | On a reply: the message it answered (the root, or a reply in the thread), while it is live; text only, cut at 140 characters. Null on a root. |
root.quote.id | integer | |
root.quote.address | string | |
root.quote.display_name | string | null | |
root.quote.text | string | |
messages | array of object | The replies, newest first. |
messages[].id | integer | Increasing; the paging cursor. |
messages[].topic | string | The topic it is in (a reply: its root's). |
messages[].room | string | Deprecated: the same as topic (the one room before topics was main, now general). |
messages[].address | string | The author. |
messages[].display_name | string | null | The author's display name now. |
messages[].admin | boolean | The author is a chat admin. |
messages[].text | string | Plain text (NFC, no control, zero-width or bidi characters, one line, 1-500 characters). Show it as text, never as HTML. |
messages[].created_at | integer | Unix seconds. |
messages[].reply_to | integer | null | The thread's root, on a reply; null on a root (a message of the topic). |
messages[].reply_count | integer | On a root: its live replies (0 on a reply). |
messages[].last_reply_at | integer | null | On a root: its newest reply's time, unix seconds; null without replies. |
messages[].quote | object | null | On a reply: the message it answered (the root, or a reply in the thread), while it is live; text only, cut at 140 characters. Null on a root. |
messages[].quote.id | integer | |
messages[].quote.address | string | |
messages[].quote.display_name | string | null | |
messages[].quote.text | string | |
next | integer | null | Pass as before for older replies; null at the start. |
{
"topic": "market",
"room": "market",
"root": {
"id": 1042,
"topic": "market",
"room": "market",
"address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
"display_name": "Rare Hunter",
"admin": false,
"text": "gm, any block 9 for sale?",
"created_at": 1790949400,
"reply_to": null,
"reply_count": 2,
"last_reply_at": 1790949460,
"quote": null
},
"messages": [
{
"id": 1044,
"topic": "market",
"room": "market",
"address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
"display_name": null,
"admin": false,
"text": "I have two, 0.0021 each",
"created_at": 1790949460,
"reply_to": 1042,
"reply_count": 0,
"last_reply_at": null,
"quote": {
"id": 1042,
"address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
"display_name": "Rare Hunter",
"text": "gm, any block 9 for sale?"
}
}
],
"next": null
}Errors #
{error, code}; see Errors for every code.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_paging | limit or offset out of range. |
| 400 | bad_path | The path is not valid percent-encoding. |
| 404 | chat_disabled | Chat and display names are off on this server. |
| 404 | not_found | No such listing or offer. In chat: no such message. A display name to remove that does not exist. |
| 405 | method_not_allowed | Wrong method for the route; the allow header lists the right ones. |
Post a message #
POST/api/chat/messages
Posts as the session's address (the __Host-rs_chat cookie of Sign in), in a topic (topic, default general) or, with reply_to, as a reply in its root's topic: one level of threads, so a reply to a reply joins its root's thread (and quotes the reply it answered). Plain text: normalized to NFC, control, zero-width and bidi characters dropped, line breaks made spaces; 1-500 characters. One message every 3 s and 20 a minute per address; the same text twice within 2 minutes is refused; the whole chat takes at most 5 messages a second (429 room_busy). A link must name a plain ASCII host (422 suspicious_link). Then, where the AI moderation is on (the session's moderated), the text is checked before it is posted: refused, 422 moderated with its category; when it cannot be checked, 503 moderation_unavailable and nothing is posted (try again); 3 refusals in 10 minutes mute the address for an hour, and 6 from one IPv4 address or IPv6 /48 within an hour pause that network (403 muted); at most 6 texts a minute are checked per address and per network (429 rate_limited). Delivered live as a chat event on /api/events, which names the topic; a reply's answer and event carry its root's new thread counts.
Request body #
content-type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
text | string | yes | The message. |
topic | string | null | no | The topic's id; absent or null: general. With reply_to it may be left out; given, it must be the root's topic. |
reply_to | integer | null | no | Reply to this message (a root, or a reply: the reply joins its root's thread, in its topic). Absent or null: a new message in the topic. |
Example request #
curl -X POST 'https://raresats.net/api/chat/messages' \
-H 'content-type: application/json' \
-d '{"text": "I have two, 0.0021 each", "topic": "market", "reply_to": 1042}'const response = await fetch('https://raresats.net/api/chat/messages', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({"text": "I have two, 0.0021 each", "topic": "market", "reply_to": 1042})
});
const data = await response.json();import requests
data = requests.post("https://raresats.net/api/chat/messages", json={
"text": "I have two, 0.0021 each",
"topic": "market",
"reply_to": 1042
}).json()Response 200 #
| Field | Type | Description |
|---|---|---|
message | object | A message, or a reply in a thread (one level: a reply to a reply joins its root's thread). |
message.id | integer | Increasing; the paging cursor. |
message.topic | string | The topic it is in (a reply: its root's). |
message.room | string | Deprecated: the same as topic (the one room before topics was main, now general). |
message.address | string | The author. |
message.display_name | string | null | The author's display name now. |
message.admin | boolean | The author is a chat admin. |
message.text | string | Plain text (NFC, no control, zero-width or bidi characters, one line, 1-500 characters). Show it as text, never as HTML. |
message.created_at | integer | Unix seconds. |
message.reply_to | integer | null | The thread's root, on a reply; null on a root (a message of the topic). |
message.reply_count | integer | On a root: its live replies (0 on a reply). |
message.last_reply_at | integer | null | On a root: its newest reply's time, unix seconds; null without replies. |
message.quote | object | null | On a reply: the message it answered (the root, or a reply in the thread), while it is live; text only, cut at 140 characters. Null on a root. |
message.quote.id | integer | |
message.quote.address | string | |
message.quote.display_name | string | null | |
message.quote.text | string | |
thread | object | On a reply: its root's thread now. |
thread.id | integer | The root. |
thread.reply_count | integer | |
thread.last_reply_at | integer | null |
Errors #
{error, code}; see Errors for every code.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_json | The body is not valid JSON. |
| 400 | bad_request | The body is not a JSON object, or the engine refused a malformed request (e.g. a purchase submitted without its signed setup PSBT). |
| 400 | bad_topic | A topic id that is not one: 1-32 of a-z, 0-9 and inner hyphens (and, to create one, not main). On a reply: a topic other than its root's. |
| 401 | no_session | No chat session: sign in (POST /api/session) and send its cookie. |
| 403 | banned | The address is banned from chat: it may not post or set a display name. |
| 403 | cross_origin | A POST from a browser on another site (a foreign Origin, or sec-fetch-site: cross-site). Server-side clients send no Origin and are not affected. |
| 403 | muted | The address is muted (by the AI moderation after repeated refusals: 3 in 10 minutes), or its network (an IPv4 address or IPv6 /48) is paused after 6 refusals within an hour, until until, unix seconds: it may not post or set a display name until then. |
| 404 | chat_disabled | Chat and display names are off on this server. |
| 404 | not_found | No such listing or offer. In chat: no such message. A display name to remove that does not exist. |
| 404 | unknown_topic | No such chat topic, or an archived one (which takes no new messages). |
| 405 | method_not_allowed | Wrong method for the route; the allow header lists the right ones. |
| 409 | duplicate | The same message (ignoring case) from the same address within 2 minutes. |
| 413 | too_large | The body is over 256 KiB. |
| 415 | not_json | A POST without content-type: application/json. |
| 422 | empty_text | Nothing is left of the message once control, zero-width and bidi characters are dropped and spaces collapsed. |
| 422 | moderated | The AI moderation refused the chat message or display name; category says why (the message is not posted, the name not taken). Three refusals in 10 minutes mute the address for an hour (until). |
| 422 | suspicious_link | A link in the message names a host that is not plain ASCII: look-alike letters from other scripts, punycode (xn--), or user@host. Write the link with its real host name. |
| 422 | text_too_long | A chat message is at most 500 characters. |
| 429 | chat_busy | Too many chat messages and names are being checked across the site right now (over the global cap of model calls a minute); wait retry-after seconds. |
| 429 | rate_limited | Over 30 POSTs a minute from your IPv4 address or IPv6 /64 (in chat, counted per IPv4 address or IPv6 /64: 30 writes and 20 login nonces a minute, 20 sign-ins and 10 display-name changes an hour; one message every 3 s and 20 a minute per address); wait retry-after seconds. With the AI moderation on, at most 6 messages and names a minute are checked per address and per IPv4 address or IPv6 /48. |
| 429 | room_busy | Over 5 chat messages a second across the whole chat; wait retry-after seconds. |
| 503 | moderation_unavailable | The AI moderation could not check the text right now (timeout or model error): nothing was posted. Try again in a moment. |
{
"error": "Couldn't check your message right now: try again in a moment",
"code": "moderation_unavailable"
}Delete a message (admin) #
DELETE/api/chat/messages/{id}
An admin session deletes a message. Deleting a thread's root deletes its replies too (deleted lists them all). Pages are told with a chat event {type: "delete", ids, threads, topics}. Logged.
Path parameters #
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | yes | The message id. |
Example request #
curl -X DELETE 'https://raresats.net/api/chat/messages/1042' -H 'cookie: __Host-rs_chat=<token>'const response = await fetch('https://raresats.net/api/chat/messages/1042', { method: 'DELETE', credentials: 'include' });
const data = await response.json();Response 200 #
| Field | Type | Description |
|---|---|---|
deleted | array of integer |
Errors #
{error, code}; see Errors for every code.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_path | The path is not valid percent-encoding. |
| 401 | no_session | No chat session: sign in (POST /api/session) and send its cookie. |
| 403 | cross_origin | A POST from a browser on another site (a foreign Origin, or sec-fetch-site: cross-site). Server-side clients send no Origin and are not affected. |
| 403 | not_admin | Only a chat admin (SUPPLY_CHAT_ADMINS) may do that. |
| 404 | chat_disabled | Chat and display names are off on this server. |
| 404 | not_found | No such listing or offer. In chat: no such message. A display name to remove that does not exist. |
| 405 | method_not_allowed | Wrong method for the route; the allow header lists the right ones. |
| 429 | rate_limited | Over 30 POSTs a minute from your IPv4 address or IPv6 /64 (in chat, counted per IPv4 address or IPv6 /64: 30 writes and 20 login nonces a minute, 20 sign-ins and 10 display-name changes an hour; one message every 3 s and 20 a minute per address); wait retry-after seconds. With the AI moderation on, at most 6 messages and names a minute are checked per address and per IPv4 address or IPv6 /48. |
List bans (admin) #
GET/api/chat/bans
The banned addresses, newest first. An admin session only.
Example request #
curl 'https://raresats.net/api/chat/bans'const response = await fetch('https://raresats.net/api/chat/bans');
const data = await response.json();import requests
data = requests.get("https://raresats.net/api/chat/bans").json()Response 200 #
| Field | Type | Description |
|---|---|---|
bans | array of object | |
bans[].address | string | |
bans[].reason | string | null | |
bans[].banned_by | string | The admin's address, or moderation for an automatic mute. |
bans[].created_at | integer | |
bans[].expires_at | integer | null | When a mute ends, unix seconds; null: banned until unbanned. |
Errors #
{error, code}; see Errors for every code.
| Status | Code | Meaning |
|---|---|---|
| 401 | no_session | No chat session: sign in (POST /api/session) and send its cookie. |
| 403 | not_admin | Only a chat admin (SUPPLY_CHAT_ADMINS) may do that. |
| 404 | chat_disabled | Chat and display names are off on this server. |
| 405 | method_not_allowed | Wrong method for the route; the allow header lists the right ones. |
Ban or unban (admin) #
POST/api/chat/bans
An admin session bans (banned: true) or unbans an address. A banned address cannot post or set a display name, and a ban removes its display name for good. delete_recent also deletes its messages of the last 24 hours. Logged.
Request body #
content-type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
address | string | yes | |
banned | boolean | yes | |
reason | string | null | no | At most 200 characters. |
delete_recent | boolean | no | Also delete its messages of the last 24 hours. |
Example request #
curl -X POST 'https://raresats.net/api/chat/bans' \
-H 'content-type: application/json' \
-d '{
"address": "bc1qm34lsc65zpw79lxes69zkqmk6ee3ewf0j77s3h",
"banned": true,
"reason": "spam",
"delete_recent": true
}'const response = await fetch('https://raresats.net/api/chat/bans', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
"address": "bc1qm34lsc65zpw79lxes69zkqmk6ee3ewf0j77s3h",
"banned": true,
"reason": "spam",
"delete_recent": true
})
});
const data = await response.json();import requests
data = requests.post("https://raresats.net/api/chat/bans", json={
"address": "bc1qm34lsc65zpw79lxes69zkqmk6ee3ewf0j77s3h",
"banned": True,
"reason": "spam",
"delete_recent": True
}).json()Response 200 #
| Field | Type | Description |
|---|---|---|
address | string | |
banned | boolean | |
ban | object | null | |
deleted | array of integer | The messages deleted with it. |
Errors #
{error, code}; see Errors for every code.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_address | An address is not a mainnet address (or not one at all). |
| 400 | bad_json | The body is not valid JSON. |
| 400 | bad_request | The body is not a JSON object, or the engine refused a malformed request (e.g. a purchase submitted without its signed setup PSBT). |
| 401 | no_session | No chat session: sign in (POST /api/session) and send its cookie. |
| 403 | cross_origin | A POST from a browser on another site (a foreign Origin, or sec-fetch-site: cross-site). Server-side clients send no Origin and are not affected. |
| 403 | not_admin | Only a chat admin (SUPPLY_CHAT_ADMINS) may do that. |
| 404 | chat_disabled | Chat and display names are off on this server. |
| 405 | method_not_allowed | Wrong method for the route; the allow header lists the right ones. |
| 413 | too_large | The body is over 256 KiB. |
| 415 | not_json | A POST without content-type: application/json. |
| 422 | not_bannable | An admin cannot be banned. |
| 429 | rate_limited | Over 30 POSTs a minute from your IPv4 address or IPv6 /64 (in chat, counted per IPv4 address or IPv6 /64: 30 writes and 20 login nonces a minute, 20 sign-ins and 10 display-name changes an hour; one message every 3 s and 20 a minute per address); wait retry-after seconds. With the AI moderation on, at most 6 messages and names a minute are checked per address and per IPv4 address or IPv6 /48. |