1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
|
# the hi api
## The basics
The `hi` API is exposed as HTTP endpoints that accept JSON and return JSON on success, with a few exceptions noted below.
On errors, the response body is freeform text and is meant to be shown to the user, logged, or otherwise handled. Programmatic action should rely on the status code, as documented.
Requests that require a JSON body must include a `content-type: application/json` header. For requests that take a JSON body, if the body does not match the required schema, the endpoint will return a 422 Unprocessable Entity response, instead of the responses documented for that endpoint.
## Client initialization
Clients will generally need some information about the session in order to present a coherent view to the user, including the session's login identity.
### `GET /api/boot`
Returns information needed to boot the client. Also the recommended way to check whether the current `identity` cookie is valid, and what login it authenticates.
#### On success
```json
{
"login": {
"name": "example username",
"id": "L1234abcd",
},
"resume_point": "1312",
"channels": [
{
"name": "nonsense and such",
"id": "C1234abcd",
"messages": [
{
"at": "2024-09-27T23:19:10.208147Z",
"sender": {
"id": "L1234abcd",
"name": "example username"
},
"id": "M1312acab",
"body": "beep"
}
]
}
]
}
```
## Authentication
Other than where noted below, all endpoints require authentication.
To authenticate a request, send a `cookie: identity=YOUR TOKEN HERE` header in the request. Tokens can be obtained via the `/api/auth/login` endpoint. This authentication protocol is intended to integrate with browsers and browser-like environments, where the browser handles cookie headers automatically.
If the token is not valid or has expired, then `hi` will send back a 401 Unauthorized response, instead of the responses documented for the endpoint the request was intended for.
### `POST /api/auth/login`
Authenticates the user by login name and password, creating a login if none exists. **This endpoint does not require an `identity` cookie.**
#### Request
```json
{
"name": "example username",
"password": "the plaintext password",
}
```
#### On success
This endpoint returns a 204 No Content response on success, with a `Set-Cookie` header setting the `identity` cookie to a newly created token for this login. This cookie must be presented in future requests, and will authenticate the associated login.
The cookie will expire if it is not used regularly. (As of this writing, identity cookies expire seven days after their last use, but this time period may change.)
#### Authentication failures
If the login already exists, and the provided password is different from the one used to create the login, then this will return a 401 Unauthorized response.
### `POST /api/auth/logout`
Invalidates the identity token, logging the user out.
#### Request
```json
{}
```
#### On success
This endpoint returns a 204 No Content response on success, with a `Set-Cookie` header that clears the `identity` cookie. The cookie provided in the request is also invalidated, and will not authenticate future requests even if the client fails to process the `Set-Cookie` response header.
## Working with channels
Channels are the containers for conversations. The API supports listing channels, creating new channels, and send messages to an existing channel.
### `POST /api/channels`
Creates a channel.
#### Request
```json
{
"name": "a unique channel name"
}
```
#### On succeess
```json
{
"name": "a unique channel name",
"id": "C9876cyyz"
}
```
#### On duplicate channel name
Channel names must be unique. If a channel with the same name already exists, this will return a 400 Bad Request error.
## Events
The API delivers events to clients to update them on other clients' actions and messages. While there is no specific delivery deadline, messages are delivered as soon as possible on a best-effort basis, and the event system allows clients to replay events or resume interrupted streams, to allow recovery if a message is lost.
### `POST /api/channels/:channel`
Sends a chat message to a channel. It will be relayed to clients subscribed to the channel's events, and recorded for replay.
The `:channel` placeholder must be a channel ID, as returned by `GET /api/channels` or `POST /api/channels`.
Chat messages expire after 90 days and can no longer be retrieved at that time.
#### Request
```json
{
"message": "my amazing thoughts, by bob"
}
```
#### On success
Once the message is accepted, this will return a 202 Accepted response. The message will be delivered to subscribers asynchronously, as soon as is feasible.
#### Invalid channel ID
If the channel ID is not valid, this will return a 404 Not Found response.
### `DELETE /api/channels/:channel`
Deletes a channel (and all messages in it).
The `:channel` placeholder must be a channel ID, as returned by `GET /api/channels` or `POST /api/channels`.
#### On success
This will return a 202 Accepted response on success, and delete the channel.
#### Invalid channel ID
If the channel ID is not valid, this will return a 404 Not Found response.
### `DELETE /api/messages/:message`
Deletes a message.
The `:message` placeholder must be a message ID, as returned from the event stream or from a list of messages.
#### On success
This will return a 202 Accepted response on success, and delete the message.
#### Invalid message ID
If the message ID is not valid, this will return a 404 Not Found response.
### `GET /api/events`
Subscribes to events. This endpoint returns an `application/event-stream` response, and is intended for use with the `EventSource` browser API. Events will be delivered on this stream as they occur, and the request will remain open to deliver events.
The returned stream may terminate, to limit the number of outstanding messages held by the server. Clients can and should repeat the request, using the `Last-Event-Id` header to resume from where they left off. Events will be replayed from that point, and the stream will resume.
#### Query parameters
This endpoint accepts an optional `resume_point` query parameter. If provided, the value must be the value obtained from the `/api/boot` method. This parameter start the returned stream immediately after the `resume_point`.
#### Request headers
This endpoint accepts an optional `Last-Event-Id` header for resuming an interrupted stream. If this header is provided, it must be set to the `id` field sent with the last event the client has processed. When `Last-Event-Id` is sent, the response will resume immediately after the corresponding event. This header takes precedence over the `resume_point` query parameter; if neither is provided, then event playback starts at the beginning of time (_you have been warned_).
If you're using a browser's `EventSource` API, this is handled for you automatically.
The event IDs `hi` sends in `application/event-stream` encoding are ephemeral, and can only be reused within the brief intervals required to reconnect to the event stream. Do not store them, and do not parse them. The message data's `"id"` field is the durable identifier for each message.
#### On success
The returned event stream is a sequence of events:
```json
id: 1233
data: {
data: "type": "created",
data: "at": "2024-09-27T23:18:10.208147Z",
data: "id": "C9876cyyz",
data: "name": "example channel 2"
data: }
id: 1234
data: {
data: "type": "message",
data: "at": "2024-09-27T23:19:10.208147Z",
data: "channel": {
data: "id": "C9876cyyz",
data: "name": "example channel 2"
data: },
data: "sender": {
data: "id": "L1234abcd",
data: "name": "example username"
data: },
data: "id": "M1312acab",
data: "body": "beep"
data: }
id: 1235
data: {
data: "at": "2024-09-28T02:44:27.077355Z",
data: "channel": {
data: "id": "C9876cyyz",
data: "name": "example channel 2"
data: },
data: "type": "message_deleted",
data: "id": "M1312acab"
data: }
id: 1236
data: {
data: "at": "2024-09-28T03:40:25.384318Z",
data: "type": "deleted",
data: "id": "C9876cyyz"
data: }
```
|