@@ -1,166 +1,98 @@
# Message s chemas
# Message S chemas
This directory holds JSON Schema file s (draft 2020-12) for the m essages that th e
neoirc HTTP API carries. `message.json` describes the fields that every message
can have. `commands/` has files for some commands. `numerics/` has files for
some of the numeric replies. A numeric reply is a server reply named by a
three-digit number, such as `001` . `README.md` at the repository root documents
the protocol.
JSON Schema definition s (draft 2020-12) for the neoirc protocol. M essages us e
**IRC command names and numeric reply codes ** (RFC 1459/2812) encoded as JSON
over HTTP.
The server can also run an IRC listener for ordinary IRC clients. The IRC
listener sends plain IRC lines, which these files do not describe.
## Envelope
## Message fields
Every message is one JSON object, which `message.json` calls the envelope. These
are the fields it can have:
- `command` : the IRC command name, such as `PRIVMSG` , or the name of a numeric
reply, such as `RPL_WELCOME` . Always present.
- `code` : the number of a numeric reply, such as `1` for `RPL_WELCOME` . Absent
on other messages.
- `id` : the message's UUID, a unique ID that the server assigns.
- `from` : the sender's nick, or the server name on a server message.
- `to` : the target, such as a channel name starting with `#` or a nick.
- `params` : an array of strings with the message's other parameters, in order.
`KICK` events and many numeric replies have it. The HTTP API ignores `params`
sent by a client.
- `body` : an array of strings. It is never a bare string. In a `PRIVMSG` or
`NOTICE` , each string is one line of text.
- `ts` : the time the server stored the message. It is an RFC 3339 time in UTC,
such as `2026-10-27T12:00:00.123Z` .
- `meta` : an object of extra data, such as a hashcash stamp in `meta.hashcash` .
A hashcash stamp is a short string that takes a computer some work to make. A
`PRIVMSG` to a channel with mode `+H` needs one. The HTTP API relays the
`meta` of a `PRIVMSG` or `NOTICE` unchanged. It drops the `meta` of every
other command.
Here is one `PRIVMSG` as the IRC listener sends it and as the HTTP API sends it.
The JSON leaves out `id` , `ts` and `meta` :
Every message is a JSON object with a `command` field. The format maps directly
to IRC wire format:
```
IRC: :alice!alice@* PRIVMSG #genera l :hello world
JSON: {"command": "PRIVMSG", "from": "al ice ", "to": "#genera l", "body": ["hello world"]}
IRC: :nick PRIVMSG #channe l :hello world
JSON: {"command": "PRIVMSG", "from": "n ick ", "to": "#channe l", "body": ["hello world"]}
IRC: :server 353 nick = #channel :user1 @op1 +voice1
JSON: {"command": "353", "to": "nick", "params": ["=", "#channel"], "body": ["user1 @op1 +voice1"]}
Multiline: {"command": "PRIVMSG", "to": "#ch", "body": ["line 1", "line 2"]}
Structured: {"command": "PUBKEY", "body": {"alg": "ed25519", "key": "base64..."}}
```
In the JSON, a numeric reply has its name in `command` and its number in `code` .
This `353` reply lists the members of `#general` . The JSON again leaves out
`id` , `ts` and `meta` :
Common fields (see `message.json` for full schema):
```
{"command": "RPL_NAMREPLY", "code": 353, "from": "neoirc", "to": "alice",
"params": ["=", "#general"], "body": ["@op1!op1@host1 alice!alice@host2"]}
```
| Field | Type | Description |
| --------- | --------------- | ---------------------------------------------------- |
| `id` | string (uuid) | Server-assigned message UUID |
| `command` | string | IRC command or 3-digit numeric code |
| `from` | string | Source nick or server name (IRC prefix) |
| `to` | string | Target: #channel or nick |
| `params` | string[] | Middle parameters (mainly for numerics) |
| `body` | array \| object | Structured body — never a raw string (see below) |
| `ts` | string | ISO 8601 timestamp (server-assigned, not in raw IRC) |
| `meta` | object | Extensible metadata (signatures, hashes, etc.) |
A `PRIVMSG` whose `body` has two strings is a two-line message:
**Structured bodies: ** `body` is always an array of strings (for text) or an
object (for structured data like PUBKEY). Never a raw string. This enables:
```
{"command": "PRIVMSG", "to": "#general", "body": ["line one", "line two"]}
```
- Multiline messages without escape sequences
- Deterministic canonicalization via RFC 8785 JCS for signing
- Structured data where needed
## File s
## Command s
C ommands, in `commands/` :
IRC c ommands used for client↔server and server↔server communication.
- `PRIVMSG.json` : a message to a channel or a user.
- `NOTICE.json` : a notice, which is a message that a client must not answer
automatically.
- `JOIN.json` : a user joined a channel.
- `PART.json` : a user left a channel.
- `QUIT.json` : a user left the server.
- `NICK.json` : a user changed nick.
- `TOP IC.json`: a channel topic was set.
- `MODE.json` : a channel mode was changed.
- `KICK.json` : a channel operator removed a user from a channel.
- `PING.json` : a check that the server still answers.
- `PO NG.json` : the answer to a `PING` .
- `PUBKEY.json` : an announcement of a user's public signing key. The server has
no such command. It answers a `PUBKEY` with `ERR_UNKNOWNCOMMAND` (`421` ).
| Command | File | RFC | Description |
| --------- | ----------------------- | ----------- | -------------------------- |
| `PRIVMSG` | `commands/PRIVMSG.json` | 1459 §4.4.1 | Message to channel or user |
| `NOTICE` | `commands/NOTICE.json` | 1459 §4.4.2 | Notice (no auto-reply) |
| `JOIN` | `commands/JOIN.json` | 1459 §4.2.1 | Join a channel |
| `PART` | `commands/PART.json` | 1459 §4.2.2 | Leave a channel |
| `QUIT` | `commands/QUIT.json` | 1459 §4.1.6 | User disconnected |
| `NICK` | `commands/N ICK .json` | 1459 §4.1.2 | Change nickname |
| `TOPIC` | `commands/TOPIC.json` | 1459 §4.2.4 | Get/set channel topic |
| `MODE` | `commands/MODE.json` | 1459 §4.2.3 | Set channel/user modes |
| `KICK` | `commands/KICK.json` | 1459 §4.2.8 | Kick user from channel |
| `PI NG` | `commands/PING.json` | 1459 §4.6.2 | Keepalive |
| `PONG` | `commands/PONG.json` | 1459 §4.6.3 | Keepalive response |
| `PUBKEY` | `commands/PUBKEY.json` | (extension) | Announce/relay signing key |
Numeric r eplies, in `numerics/` :
## Numeric R eplies
- `001.json` : `RPL_WELCOME` , which wel com es a new user .
- `002.json` : `RPL_YOURHOST` , the server's name and version.
- `003.json` : `RPL_CREATED` , which says when the server was created.
- `004.json` : `RPL_MYINFO` , the server's version and its lists of modes.
- `322.json` : `RPL_LIST` , one channel in the answer to `LIST` .
- `323.json` : `RPL_LISTEND` , the end of the answer to `LIST` .
- `332.json` : `RPL_TOPIC` , a channel's topic.
- `353.json` : `RPL_NAMREPLY` , a channel's members.
- `366.json` : `RPL_ENDOFNAMES` , the end of the member list.
- `372.json` : `RPL_MOTD` , one line of the message of the day.
- `375.json` : `RPL_MOTDSTART` , the start of the message of the day.
- `376.json` : `RPL_ENDOFMOTD` , the end of the message of the day.
- `401.json` : `ERR_NOSUCHNICK` , the nick does not exist.
- `403.json` : `ERR_NOSUCHCHANNEL` , the channel does not exist.
- `433.json` : `ERR_NICKNAMEINUSE` , the nick is taken.
- `442.json` : `ERR_NOTONCHANNEL` , the user is not on that channel.
- `482.json` : `ERR_CHANOPRIVSNEEDED` , the user is not a channel operator.
Three-digit cod es for server responses, per IRC convention .
## Where the files differ from the HTTP API
### Success / Informational (0xx– 3xx)
Where a schema file and the HTTP API disagree, the HTTP API is right:
| Code | Name | File | Description |
| ----- | -------------- | ------------------- | ------------------------------ |
| `001` | RPL_WELCOME | `numerics/001.json` | Welcome after session creation |
| `002` | RPL_YOURHOST | `numerics/002.json` | Server host info |
| `003` | RPL_CREATED | `numerics/003.json` | Server creation date |
| `004` | RPL_MYINFO | `numerics/004.json` | Server info and modes |
| `322` | RPL_LIST | `numerics/322.json` | Channel list entry |
| `323` | RPL_LISTEND | `numerics/323.json` | End of channel list |
| `332` | RPL_TOPIC | `numerics/332.json` | Channel topic |
| `353` | RPL_NAMREPLY | `numerics/353.json` | Channel member list |
| `366` | RPL_ENDOFNAMES | `numerics/366.json` | End of NAMES list |
| `372` | RPL_MOTD | `numerics/372.json` | MOTD line |
| `375` | RPL_MOTDSTART | `numerics/375.json` | Start of MOTD |
| `376` | RPL_ENDOFMOTD | `numerics/376.json` | End of MOTD |
- `message.json` and the files in `numerics/` put the three-digit code in
`command` . They have no `code` field. The HTTP API puts the reply's name in
`command` and its number in `code` .
- `message.json` allows an object as `body` . The HTTP API reads only arrays of
strings. A `PRIVMSG` with an object as `body` gets `ERR_NOTEXTTOSEND` (`412` ).
- `message.json` says `to` is a channel or a nick. A `WALLOPS` message has `*`
there.
- `message.json` allows `meta` on every message. The HTTP API keeps a client's
`meta` only on a `PRIVMSG` or `NOTICE` .
- `message.json` describes signature fields in `meta` . The server never checks a
signature.
- `message.json` does not describe `meta.hashcash` . The HTTP API reads it in a
`PRIVMSG` to a channel with mode `+H` .
- `JOIN.json` has no `body` . A client's `JOIN` can carry the channel key in
`body` . When a user joins, the `JOIN` event repeats the channel name in
`body` .
- `JOIN.json` allows a channel name of any length. The server allows at most 63
characters after the `#` . It answers a longer name with `ERR_NOSUCHCHANNEL`
(`403` ).
- `KICK.json` puts the kicked nick in `params` . The HTTP API reads a client's
`KICK` from `body` : the nick, then an optional reason.
- `MODE.json` puts the mode change in `params` . The HTTP API uses `body`
instead. A client sends the change and any value as separate strings, such as
`"+o"` and `"bob"` . In a `MODE` event, `body` holds the change as one string,
such as `"+o bob"` , or as two strings, such as `"+k"` and `"secret"` .
- `NOTICE.json` allows `*` as `to` . The server answers a `NOTICE` to `*` with
`ERR_NOSUCHNICK` (`401` ).
- `NOTICE.json` has an example notice from the server to `*` . The server sends
no such notice.
- `PING.json` and `PONG.json` describe a token that the `PONG` repeats. The HTTP
API ignores the `body` of a `PING` . Its answer is the HTTP response
`{"command": "PONG", "from": "<server name>"}` .
- `PING.json` and `PONG.json` also describe messages between servers. The server
does not connect to other IRC servers.
- `PUBKEY.json` describes a command the server does not have.
- `QUIT.json` says that `QUIT` comes only from the server. A client can send it
too, to end its session. A client's `QUIT` can carry a reason in `body` .
- `TOPIC.json` says a `TOPIC` without a `body` queries the topic. The HTTP API
answers such a `TOPIC` with `ERR_NEEDMOREPARAMS` (`461` ).
- `TOPIC.json` says an empty array clears the topic. The HTTP API answers `[]`
with `ERR_NEEDMOREPARAMS` (`461` ). A `body` of `[""]` clears the topic.
- `003.json` describes the server's creation date. The HTTP API sends the date
the server last started.
- `004.json` has example mode lists. The HTTP API sends an empty string as the
user modes and `ikmnostl` as the channel modes.
- `322.json` says `LIST` gets one reply per channel. The server leaves out a
secret channel, one with mode `+s` , unless the user is on it.
- `332.json` says `RPL_TOPIC` answers a `TOPIC` query. The HTTP API has no such
query. It sends `RPL_TOPIC` after a client sets the topic, and after a `JOIN`
to a channel that has one.
- `353.json` says `body` lists nicks. The HTTP API lists `nick!user@host` in
place of each nick, such as `@op1!op1@host1` .
- `353.json` says the first parameter marks a channel as public, private or
secret. The server always sends `=` , the mark for a public channel.
- `376.json` gives the text as `End of /MOTD command` . The HTTP API adds a full
stop.
- `401.json` gives the text as `No such nick/channel` . The server sends
`No such nick` in answer to a `PRIVMSG` or `NOTICE` to an unknown nick.
- `433.json` has an example with `*` as `to` . The HTTP API sends `433` only in
answer to a `NICK` , with the user's nick as `to` .
### Errors (4xx)
The HTTP API also accepts some messages that the files do not allow, such as a
`JOIN` whose `to` has no `#` .
| Code | Name | File | Description |
| ----- | -------------------- | ------------------- | ----------------------- |
| `401` | ERR_NOSUCHNICK | `numerics/401.json` | No such nick/channel |
| `403` | ERR_NOSUCHCHANNEL | `numerics/403.json` | No such channel |
| `433` | ERR_NICKNAMEINUSE | `numerics/433.json` | Nickname already in use |
| `442` | ERR_NOTONCHANNEL | `numerics/442.json` | Not on that channel |
| `482` | ERR_CHANOPRIVSNEEDED | `numerics/482.json` | Not channel operator |
## Federation (S2S)
Server-to-server messages use the same command format. Federated servers relay
messages with an additional `origin` field in `meta` to track the source server.
The PING/PONG commands serve as inter-server keepalives. State sync after link
establishment uses a burst of JOIN, NICK, TOPIC, and MODE commands.