# Chat sessions

Reading chat is public. Posting needs a **session**, opened by one BIP-322 signature: the walkthrough is in the [Chat & profiles guide](https://raresats.net/docs/guide-chat.md).

## Get a login nonce

`GET /api/session/nonce`

A nonce for an address to sign in with: usable once, for 10 minutes, by that address only. Same-origin, and limited to 20 a minute per IPv4 address or IPv6 /64.

#### Query parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `address` | string | yes | The address that will sign. |

#### Example request

```bash title="cURL"
curl 'https://raresats.net/api/session/nonce?address=bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh'
```
```js title="JavaScript"
const response = await fetch('https://raresats.net/api/session/nonce?address=bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh');
const data = await response.json();
```
```python title="Python"
import requests

data = requests.get("https://raresats.net/api/session/nonce", params={"address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh"}).json()
```

#### Response `200`

| Field | Type | Description |
| --- | --- | --- |
| `address` | string | The address, normalized. |
| `nonce` | string | 32 hex characters, usable once. |
| `expires_at` | integer | When the nonce expires, unix seconds (10 minutes). |
| `message` | string | The message to sign, with `<unix seconds>` for the current time. |

```json title="Response"
{
  "address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
  "nonce": "9f2c41d07a5be3816c0d4f27e9a1b358",
  "expires_at": 1790950000,
  "message": "raresats.net login bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh 9f2c41d07a5be3816c0d4f27e9a1b358 <unix seconds>"
}
```

#### Errors

`{error, code}`; see [Errors](https://raresats.net/docs/errors.md) for every code.

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad_address` | An address is not a mainnet address (or not one at all). |
| 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. |
| 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. |
| 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. |

## Sign in to chat

`POST /api/session`

Opens a chat session from a BIP-322 signature of `raresats.net login <address> <nonce> <unix seconds>`, the nonce from [Get a login nonce](https://raresats.net/docs/chat-sessions.md#get-a-login-nonce). Sets the session cookie `__Host-rs_chat` (HttpOnly, Secure, SameSite=Lax, Path=/, 30 days). The nonce and the message are each usable once; the message at most 10 minutes old. At most 20 sign-ins an hour per IPv4 address or IPv6 /64.

#### Request body

`content-type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `address` | string | yes | The address signing in (ordinals or payment). |
| `message` | string | yes | `raresats.net login <address> <nonce> <unix seconds>`, at most 10 minutes old. |
| `signature` | string | yes | BIP-322 signature of `message` by `address` (base64; simple or full). |

#### Example request

```bash title="cURL"
curl -X POST 'https://raresats.net/api/session' \
  -H 'content-type: application/json' \
  -d '{
  "address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
  "message": "raresats.net login bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh 9f2c41d07a5be3816c0d4f27e9a1b358 1790949400",
  "signature": "AUBGQvdc2H+7HTo1njZ1vlDxoX6ncoehAGbaHcAY1FtfSNg7lmo8Ih3WGInhAH/snOUIb8DkwRSQxc2dOu911Z9E"
}'
```
```js title="JavaScript"
const response = await fetch('https://raresats.net/api/session', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    "address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
    "message": "raresats.net login bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh 9f2c41d07a5be3816c0d4f27e9a1b358 1790949400",
    "signature": "AUBGQvdc2H+7HTo1njZ1vlDxoX6ncoehAGbaHcAY1FtfSNg7lmo8Ih3WGInhAH/snOUIb8DkwRSQxc2dOu911Z9E"
  })
});
const data = await response.json();
```
```python title="Python"
import requests

data = requests.post("https://raresats.net/api/session", json={
    "address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
    "message": "raresats.net login bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh 9f2c41d07a5be3816c0d4f27e9a1b358 1790949400",
    "signature": "AUBGQvdc2H+7HTo1njZ1vlDxoX6ncoehAGbaHcAY1FtfSNg7lmo8Ih3WGInhAH/snOUIb8DkwRSQxc2dOu911Z9E"
}).json()
```

#### Response `200`

| Field | Type | Description |
| --- | --- | --- |
| `enabled` | boolean | Chat and display names are on (always true: off, the route is a 404). |
| `max_length` | integer | The longest message, in characters. |
| `address` | string \| null | The signed-in address. |
| `admin` | boolean | The address is a chat admin. |
| `display_name` | string \| null | Its display name. |
| `expires_at` | integer \| null | When the session ends, unix seconds. |
| `banned` | boolean | The address may not post: banned, or muted until `banned_until`. |
| `server_time` | integer | The server's clock, unix seconds: date signed messages by it. |
| `banned_until` | integer \| null | When a mute ends, unix seconds (the AI moderation mutes an address for an hour after 3 refusals in 10 minutes); null when not muted or banned for good. |
| `moderated` | boolean | Messages and display names are checked by the AI moderation before they are posted. |

#### Errors

`{error, code}`; see [Errors](https://raresats.net/docs/errors.md) 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_message` | The cancel message is not `raresats.net cancel <ids> <unix seconds>` or names other listings than `ids`; or the registration message is not `raresats.net affiliate <address> <unix seconds>` for `address`. |
| 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, 422 | `bad_signature` | 400: `signature` is missing or too long. 422: a signature does not verify (cancel or registration message, listing or purchase input). |
| 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. |
| 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 | `replayed` | That cancel or registration message was used already. |
| 413 | `too_large` | The body is over 256 KiB. |
| 415 | `not_json` | A POST without `content-type: application/json`. |
| 422 | `bad_nonce` | The login nonce was not handed out for this address, has expired (10 minutes) or was used: ask for a new one. |
| 422 | `future_message` | The cancel or registration message is dated more than 2 minutes ahead: check the clock. |
| 422 | `stale_message` | The cancel or registration message is more than 10 minutes old: sign a new one. |
| 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. |

```json title="Error (422)"
{
  "error": "that nonce was not handed out for this address, has expired or was used: ask for a new one",
  "code": "bad_nonce"
}
```

## Get the session

`GET /api/session`

The session of the request's `__Host-rs_chat` cookie: the signed-in address, its name and whether it is an admin or banned; `address` null when signed out. A browser's page uses it to know whether chat is on (off: 404 `chat_disabled`).

#### Example request

```bash title="cURL"
curl 'https://raresats.net/api/session'
```
```js title="JavaScript"
const response = await fetch('https://raresats.net/api/session');
const data = await response.json();
```
```python title="Python"
import requests

data = requests.get("https://raresats.net/api/session").json()
```

#### Response `200`

| Field | Type | Description |
| --- | --- | --- |
| `enabled` | boolean | Chat and display names are on (always true: off, the route is a 404). |
| `max_length` | integer | The longest message, in characters. |
| `address` | string \| null | The signed-in address. |
| `admin` | boolean | The address is a chat admin. |
| `display_name` | string \| null | Its display name. |
| `expires_at` | integer \| null | When the session ends, unix seconds. |
| `banned` | boolean | The address may not post: banned, or muted until `banned_until`. |
| `server_time` | integer | The server's clock, unix seconds: date signed messages by it. |
| `banned_until` | integer \| null | When a mute ends, unix seconds (the AI moderation mutes an address for an hour after 3 refusals in 10 minutes); null when not muted or banned for good. |
| `moderated` | boolean | Messages and display names are checked by the AI moderation before they are posted. |

#### Errors

`{error, code}`; see [Errors](https://raresats.net/docs/errors.md) 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. |

## Sign out

`DELETE /api/session`

Ends the cookie's session and clears the cookie. Answers the signed-out session.

#### Example request

```bash title="cURL"
curl -X DELETE 'https://raresats.net/api/session' -H 'cookie: __Host-rs_chat=<token>'
```
```js title="JavaScript"
const response = await fetch('https://raresats.net/api/session', { method: 'DELETE', credentials: 'include' });
const data = await response.json();
```

#### Response `200`

| Field | Type | Description |
| --- | --- | --- |
| `enabled` | boolean | Chat and display names are on (always true: off, the route is a 404). |
| `max_length` | integer | The longest message, in characters. |
| `address` | string \| null | The signed-in address. |
| `admin` | boolean | The address is a chat admin. |
| `display_name` | string \| null | Its display name. |
| `expires_at` | integer \| null | When the session ends, unix seconds. |
| `banned` | boolean | The address may not post: banned, or muted until `banned_until`. |
| `server_time` | integer | The server's clock, unix seconds: date signed messages by it. |
| `banned_until` | integer \| null | When a mute ends, unix seconds (the AI moderation mutes an address for an hour after 3 refusals in 10 minutes); null when not muted or banned for good. |
| `moderated` | boolean | Messages and display names are checked by the AI moderation before they are posted. |

#### Errors

`{error, code}`; see [Errors](https://raresats.net/docs/errors.md) for every code.

| Status | Code | Meaning |
| --- | --- | --- |
| 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. |
| 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. |
| 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. |
