@myronsi/messenger-api 2.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2334 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: Messenger API
4
+ version: 2.0.0-alpha.1
5
+ summary: REST contract of the Messenger backend (v2).
6
+ description: |
7
+ Contract for the Go backend. The WebSocket protocol is described in `websocket/` and
8
+ `websocket.md`; the entity schemas below are shared by both.
9
+
10
+ ## Conventions
11
+
12
+ - Base path `/api/v2`. Breaking changes need a new MAJOR version with a new base path
13
+ (see `docs/api-compatibility.md`).
14
+ - All IDs are **strings** in JSON. Message IDs are 64-bit Snowflake IDs, larger than
15
+ JavaScript's safe integer limit (2^53), so they must never be parsed as numbers.
16
+ - Timestamps are RFC 3339 UTC (`2026-10-04T12:00:00Z`).
17
+ - Errors are `application/problem+json` (RFC 9457) with a stable `code`; clients switch on
18
+ `code`, never on `title` or `detail`.
19
+ - Lists use cursor pagination: `limit` plus `before`, `after` or `around`, and responses
20
+ return `next_cursor` (and `prev_cursor` for messages). Cursors are opaque to clients
21
+ except for message history, where they are message IDs.
22
+ - Enums can grow in a MINOR version; clients must handle unknown values with a fallback.
23
+ - Clients send `X-Client-Version` (app version) and `X-Client-Api-Version` (the contract
24
+ version they were built with). A different MAJOR or a version below
25
+ `min_client_api_version` gets `426` with `code: client_outdated`.
26
+ - Files are never referenced by a client-supplied URL: upload with `POST /attachments`,
27
+ then refer to the returned `attachment_id`.
28
+ license:
29
+ name: MIT
30
+ identifier: MIT
31
+ servers:
32
+ - url: /api/v2
33
+ description: Same origin as the web app (reverse proxy)
34
+ security:
35
+ - bearerAuth: []
36
+ tags:
37
+ - name: meta
38
+ description: Versions of the backend and the contract
39
+ - name: auth
40
+ description: Registration, login, tokens and account recovery
41
+ - name: me
42
+ description: The current user's profile, security and privacy
43
+ - name: users
44
+ description: Other users
45
+ - name: chats
46
+ description: Direct chats, pinning, read state and message history
47
+ - name: groups
48
+ description: Group chats and their members
49
+ - name: messages
50
+ description: Edit, delete and forward messages
51
+ - name: attachments
52
+ description: File and voice uploads
53
+ - name: requests
54
+ description: Approval requests (direct messages and group invites)
55
+ - name: websocket
56
+ description: Realtime connection (see `websocket.md`)
57
+
58
+ paths:
59
+ /meta:
60
+ get:
61
+ tags: [meta]
62
+ operationId: getMeta
63
+ summary: Backend and contract versions
64
+ description: No authentication. Cacheable for 60 seconds.
65
+ security: []
66
+ responses:
67
+ "200":
68
+ description: Versions
69
+ headers:
70
+ Cache-Control:
71
+ description: "`public, max-age=60`"
72
+ schema: { type: string }
73
+ content:
74
+ application/json:
75
+ schema: { $ref: "#/components/schemas/Meta" }
76
+ "429": { $ref: "#/components/responses/TooManyRequests" }
77
+
78
+ # ---------------------------------------------------------------- auth
79
+ /auth/register:
80
+ parameters:
81
+ - $ref: "#/components/parameters/ClientVersion"
82
+ - $ref: "#/components/parameters/ClientApiVersion"
83
+ post:
84
+ tags: [auth]
85
+ operationId: register
86
+ summary: Create an account
87
+ security: []
88
+ requestBody:
89
+ required: true
90
+ content:
91
+ application/json:
92
+ schema: { $ref: "#/components/schemas/RegisterRequest" }
93
+ responses:
94
+ "426": { $ref: "#/components/responses/ClientOutdated" }
95
+ "201":
96
+ description: Account created and signed in
97
+ headers:
98
+ Set-Cookie: { $ref: "#/components/headers/RefreshCookie" }
99
+ content:
100
+ application/json:
101
+ schema: { $ref: "#/components/schemas/RegisterResponse" }
102
+ "400": { $ref: "#/components/responses/BadRequest" }
103
+ "409": { $ref: "#/components/responses/Conflict" }
104
+ "422": { $ref: "#/components/responses/ValidationFailed" }
105
+ "429": { $ref: "#/components/responses/TooManyRequests" }
106
+ /auth/login:
107
+ parameters:
108
+ - $ref: "#/components/parameters/ClientVersion"
109
+ - $ref: "#/components/parameters/ClientApiVersion"
110
+ post:
111
+ tags: [auth]
112
+ operationId: login
113
+ summary: Sign in with username and password
114
+ description: Answers with a login challenge instead of tokens when two-factor authentication is enabled.
115
+ security: []
116
+ requestBody:
117
+ required: true
118
+ content:
119
+ application/json:
120
+ schema: { $ref: "#/components/schemas/LoginRequest" }
121
+ responses:
122
+ "426": { $ref: "#/components/responses/ClientOutdated" }
123
+ "200":
124
+ description: Signed in, or a two-factor challenge
125
+ headers:
126
+ Set-Cookie: { $ref: "#/components/headers/RefreshCookie" }
127
+ content:
128
+ application/json:
129
+ schema: { $ref: "#/components/schemas/LoginResponse" }
130
+ "400": { $ref: "#/components/responses/BadRequest" }
131
+ "401": { $ref: "#/components/responses/Unauthorized" }
132
+ "429": { $ref: "#/components/responses/TooManyRequests" }
133
+ /auth/login/2fa:
134
+ parameters:
135
+ - $ref: "#/components/parameters/ClientVersion"
136
+ - $ref: "#/components/parameters/ClientApiVersion"
137
+ post:
138
+ tags: [auth]
139
+ operationId: loginTwoFactor
140
+ summary: Finish a login with a two-factor code
141
+ security: []
142
+ requestBody:
143
+ required: true
144
+ content:
145
+ application/json:
146
+ schema: { $ref: "#/components/schemas/TwoFactorLoginRequest" }
147
+ responses:
148
+ "426": { $ref: "#/components/responses/ClientOutdated" }
149
+ "200":
150
+ description: Signed in
151
+ headers:
152
+ Set-Cookie: { $ref: "#/components/headers/RefreshCookie" }
153
+ content:
154
+ application/json:
155
+ schema: { $ref: "#/components/schemas/TokenResponse" }
156
+ "400": { $ref: "#/components/responses/BadRequest" }
157
+ "401": { $ref: "#/components/responses/Unauthorized" }
158
+ "429": { $ref: "#/components/responses/TooManyRequests" }
159
+ /auth/refresh:
160
+ parameters:
161
+ - $ref: "#/components/parameters/ClientVersion"
162
+ - $ref: "#/components/parameters/ClientApiVersion"
163
+ post:
164
+ tags: [auth]
165
+ operationId: refreshToken
166
+ summary: Get a new access token
167
+ description: Uses the HttpOnly refresh cookie and rotates it.
168
+ security:
169
+ - cookieAuth: []
170
+ responses:
171
+ "426": { $ref: "#/components/responses/ClientOutdated" }
172
+ "400": { $ref: "#/components/responses/BadRequest" }
173
+ "200":
174
+ description: New access token
175
+ headers:
176
+ Set-Cookie: { $ref: "#/components/headers/RefreshCookie" }
177
+ content:
178
+ application/json:
179
+ schema: { $ref: "#/components/schemas/TokenResponse" }
180
+ "401": { $ref: "#/components/responses/Unauthorized" }
181
+ "429": { $ref: "#/components/responses/TooManyRequests" }
182
+ /auth/logout:
183
+ parameters:
184
+ - $ref: "#/components/parameters/ClientVersion"
185
+ - $ref: "#/components/parameters/ClientApiVersion"
186
+ post:
187
+ tags: [auth]
188
+ operationId: logout
189
+ summary: End the current session
190
+ security:
191
+ - cookieAuth: []
192
+ - bearerAuth: []
193
+ responses:
194
+ "426": { $ref: "#/components/responses/ClientOutdated" }
195
+ "400": { $ref: "#/components/responses/BadRequest" }
196
+ "204":
197
+ description: Signed out; the refresh cookie is cleared
198
+ headers:
199
+ Set-Cookie: { $ref: "#/components/headers/RefreshCookie" }
200
+ "401": { $ref: "#/components/responses/Unauthorized" }
201
+ /auth/recover:
202
+ parameters:
203
+ - $ref: "#/components/parameters/ClientVersion"
204
+ - $ref: "#/components/parameters/ClientApiVersion"
205
+ post:
206
+ tags: [auth]
207
+ operationId: startRecovery
208
+ summary: Start account recovery (temporary)
209
+ description: Temporary port of the Python recovery flow; it is redesigned in release 1.1.
210
+ x-temporary: true
211
+ security: []
212
+ requestBody:
213
+ required: true
214
+ content:
215
+ application/json:
216
+ schema: { $ref: "#/components/schemas/RecoveryRequest" }
217
+ responses:
218
+ "426": { $ref: "#/components/responses/ClientOutdated" }
219
+ "200":
220
+ description: Recovery token
221
+ content:
222
+ application/json:
223
+ schema: { $ref: "#/components/schemas/RecoveryResponse" }
224
+ "400": { $ref: "#/components/responses/BadRequest" }
225
+ "404": { $ref: "#/components/responses/NotFound" }
226
+ "429": { $ref: "#/components/responses/TooManyRequests" }
227
+ /auth/reset-password:
228
+ parameters:
229
+ - $ref: "#/components/parameters/ClientVersion"
230
+ - $ref: "#/components/parameters/ClientApiVersion"
231
+ post:
232
+ tags: [auth]
233
+ operationId: resetPassword
234
+ summary: Set a new password with a recovery token (temporary)
235
+ x-temporary: true
236
+ security: []
237
+ requestBody:
238
+ required: true
239
+ content:
240
+ application/json:
241
+ schema: { $ref: "#/components/schemas/ResetPasswordRequest" }
242
+ responses:
243
+ "426": { $ref: "#/components/responses/ClientOutdated" }
244
+ "204":
245
+ description: Password changed; all sessions are revoked
246
+ "400": { $ref: "#/components/responses/BadRequest" }
247
+ "401": { $ref: "#/components/responses/Unauthorized" }
248
+ "422": { $ref: "#/components/responses/ValidationFailed" }
249
+ "429": { $ref: "#/components/responses/TooManyRequests" }
250
+
251
+ /ws/ticket:
252
+ parameters:
253
+ - $ref: "#/components/parameters/ClientVersion"
254
+ - $ref: "#/components/parameters/ClientApiVersion"
255
+ post:
256
+ tags: [websocket]
257
+ operationId: createWebSocketTicket
258
+ summary: Get a one-time ticket for the WebSocket
259
+ description: |
260
+ The ticket is valid for a few seconds and can be used once at
261
+ `/api/v2/ws?ticket=…`; it replaces tokens in the URL.
262
+ responses:
263
+ "426": { $ref: "#/components/responses/ClientOutdated" }
264
+ "400": { $ref: "#/components/responses/BadRequest" }
265
+ "201":
266
+ description: Ticket
267
+ content:
268
+ application/json:
269
+ schema: { $ref: "#/components/schemas/WebSocketTicket" }
270
+ "401": { $ref: "#/components/responses/Unauthorized" }
271
+ "429": { $ref: "#/components/responses/TooManyRequests" }
272
+
273
+ # ------------------------------------------------------------------ me
274
+ /me:
275
+ parameters:
276
+ - $ref: "#/components/parameters/ClientVersion"
277
+ - $ref: "#/components/parameters/ClientApiVersion"
278
+ get:
279
+ tags: [me]
280
+ operationId: getMe
281
+ summary: Current user
282
+ responses:
283
+ "426": { $ref: "#/components/responses/ClientOutdated" }
284
+ "400": { $ref: "#/components/responses/BadRequest" }
285
+ "200":
286
+ description: The current user
287
+ content:
288
+ application/json:
289
+ schema: { $ref: "#/components/schemas/Me" }
290
+ "401": { $ref: "#/components/responses/Unauthorized" }
291
+ patch:
292
+ tags: [me]
293
+ operationId: updateMe
294
+ summary: Update the profile
295
+ description: Replaces `PUT /me` and `POST /me/bio` of v1. Only the sent fields change.
296
+ requestBody:
297
+ required: true
298
+ content:
299
+ application/json:
300
+ schema: { $ref: "#/components/schemas/UpdateMeRequest" }
301
+ responses:
302
+ "426": { $ref: "#/components/responses/ClientOutdated" }
303
+ "200":
304
+ description: Updated user
305
+ content:
306
+ application/json:
307
+ schema: { $ref: "#/components/schemas/Me" }
308
+ "400": { $ref: "#/components/responses/BadRequest" }
309
+ "401": { $ref: "#/components/responses/Unauthorized" }
310
+ "422": { $ref: "#/components/responses/ValidationFailed" }
311
+ delete:
312
+ tags: [me]
313
+ operationId: deleteMe
314
+ summary: Delete the account
315
+ requestBody:
316
+ required: true
317
+ content:
318
+ application/json:
319
+ schema: { $ref: "#/components/schemas/PasswordConfirmation" }
320
+ responses:
321
+ "426": { $ref: "#/components/responses/ClientOutdated" }
322
+ "400": { $ref: "#/components/responses/BadRequest" }
323
+ "204":
324
+ description: Account deleted
325
+ "401": { $ref: "#/components/responses/Unauthorized" }
326
+ "403": { $ref: "#/components/responses/Forbidden" }
327
+ /me/avatar:
328
+ parameters:
329
+ - $ref: "#/components/parameters/ClientVersion"
330
+ - $ref: "#/components/parameters/ClientApiVersion"
331
+ put:
332
+ tags: [me]
333
+ operationId: setMyAvatar
334
+ summary: Set the avatar from an uploaded attachment
335
+ description: "Replaces both avatar routes of v1. Upload the image with `POST /attachments` (`purpose: avatar`) first."
336
+ requestBody:
337
+ required: true
338
+ content:
339
+ application/json:
340
+ schema: { $ref: "#/components/schemas/SetAvatarRequest" }
341
+ responses:
342
+ "426": { $ref: "#/components/responses/ClientOutdated" }
343
+ "200":
344
+ description: Updated user
345
+ content:
346
+ application/json:
347
+ schema: { $ref: "#/components/schemas/Me" }
348
+ "400": { $ref: "#/components/responses/BadRequest" }
349
+ "401": { $ref: "#/components/responses/Unauthorized" }
350
+ "404": { $ref: "#/components/responses/NotFound" }
351
+ /me/password:
352
+ parameters:
353
+ - $ref: "#/components/parameters/ClientVersion"
354
+ - $ref: "#/components/parameters/ClientApiVersion"
355
+ put:
356
+ tags: [me]
357
+ operationId: changePassword
358
+ summary: Change the password
359
+ description: Revokes all other sessions.
360
+ requestBody:
361
+ required: true
362
+ content:
363
+ application/json:
364
+ schema: { $ref: "#/components/schemas/ChangePasswordRequest" }
365
+ responses:
366
+ "426": { $ref: "#/components/responses/ClientOutdated" }
367
+ "204":
368
+ description: Password changed
369
+ "400": { $ref: "#/components/responses/BadRequest" }
370
+ "401": { $ref: "#/components/responses/Unauthorized" }
371
+ "422": { $ref: "#/components/responses/ValidationFailed" }
372
+ "429": { $ref: "#/components/responses/TooManyRequests" }
373
+ /me/security:
374
+ parameters:
375
+ - $ref: "#/components/parameters/ClientVersion"
376
+ - $ref: "#/components/parameters/ClientApiVersion"
377
+ get:
378
+ tags: [me]
379
+ operationId: getSecuritySettings
380
+ summary: Security settings
381
+ responses:
382
+ "426": { $ref: "#/components/responses/ClientOutdated" }
383
+ "400": { $ref: "#/components/responses/BadRequest" }
384
+ "200":
385
+ description: Settings
386
+ content:
387
+ application/json:
388
+ schema: { $ref: "#/components/schemas/SecuritySettings" }
389
+ "401": { $ref: "#/components/responses/Unauthorized" }
390
+ patch:
391
+ tags: [me]
392
+ operationId: updateSecuritySettings
393
+ summary: Change the session duration
394
+ requestBody:
395
+ required: true
396
+ content:
397
+ application/json:
398
+ schema: { $ref: "#/components/schemas/UpdateSecuritySettingsRequest" }
399
+ responses:
400
+ "426": { $ref: "#/components/responses/ClientOutdated" }
401
+ "400": { $ref: "#/components/responses/BadRequest" }
402
+ "200":
403
+ description: Settings
404
+ content:
405
+ application/json:
406
+ schema: { $ref: "#/components/schemas/SecuritySettings" }
407
+ "401": { $ref: "#/components/responses/Unauthorized" }
408
+ "422": { $ref: "#/components/responses/ValidationFailed" }
409
+ /me/sessions:
410
+ parameters:
411
+ - $ref: "#/components/parameters/ClientVersion"
412
+ - $ref: "#/components/parameters/ClientApiVersion"
413
+ get:
414
+ tags: [me]
415
+ operationId: listSessions
416
+ summary: Signed-in devices
417
+ responses:
418
+ "426": { $ref: "#/components/responses/ClientOutdated" }
419
+ "400": { $ref: "#/components/responses/BadRequest" }
420
+ "200":
421
+ description: Sessions of the current user
422
+ content:
423
+ application/json:
424
+ schema:
425
+ type: object
426
+ required: [items]
427
+ properties:
428
+ items:
429
+ type: array
430
+ items: { $ref: "#/components/schemas/Session" }
431
+ "401": { $ref: "#/components/responses/Unauthorized" }
432
+ delete:
433
+ tags: [me]
434
+ operationId: revokeOtherSessions
435
+ summary: Sign out everywhere except this device
436
+ responses:
437
+ "426": { $ref: "#/components/responses/ClientOutdated" }
438
+ "400": { $ref: "#/components/responses/BadRequest" }
439
+ "204":
440
+ description: Other sessions revoked
441
+ "401": { $ref: "#/components/responses/Unauthorized" }
442
+ /me/sessions/{session_id}:
443
+ parameters:
444
+ - $ref: "#/components/parameters/ClientVersion"
445
+ - $ref: "#/components/parameters/ClientApiVersion"
446
+ - $ref: "#/components/parameters/SessionId"
447
+ delete:
448
+ tags: [me]
449
+ operationId: revokeSession
450
+ summary: Sign out one device
451
+ responses:
452
+ "426": { $ref: "#/components/responses/ClientOutdated" }
453
+ "400": { $ref: "#/components/responses/BadRequest" }
454
+ "204":
455
+ description: Session revoked
456
+ "401": { $ref: "#/components/responses/Unauthorized" }
457
+ "404": { $ref: "#/components/responses/NotFound" }
458
+ /me/2fa/setup:
459
+ parameters:
460
+ - $ref: "#/components/parameters/ClientVersion"
461
+ - $ref: "#/components/parameters/ClientApiVersion"
462
+ post:
463
+ tags: [me]
464
+ operationId: setupTwoFactor
465
+ summary: Start two-factor setup
466
+ description: Returns the secret and an `otpauth://` URI; two-factor stays off until confirmed.
467
+ requestBody:
468
+ required: true
469
+ content:
470
+ application/json:
471
+ schema: { $ref: "#/components/schemas/PasswordConfirmation" }
472
+ responses:
473
+ "426": { $ref: "#/components/responses/ClientOutdated" }
474
+ "400": { $ref: "#/components/responses/BadRequest" }
475
+ "200":
476
+ description: Secret to add to an authenticator app
477
+ content:
478
+ application/json:
479
+ schema: { $ref: "#/components/schemas/TwoFactorSetup" }
480
+ "401": { $ref: "#/components/responses/Unauthorized" }
481
+ "403": { $ref: "#/components/responses/Forbidden" }
482
+ "409": { $ref: "#/components/responses/Conflict" }
483
+ /me/2fa/confirm:
484
+ parameters:
485
+ - $ref: "#/components/parameters/ClientVersion"
486
+ - $ref: "#/components/parameters/ClientApiVersion"
487
+ post:
488
+ tags: [me]
489
+ operationId: confirmTwoFactor
490
+ summary: Turn two-factor on with a code
491
+ requestBody:
492
+ required: true
493
+ content:
494
+ application/json:
495
+ schema: { $ref: "#/components/schemas/TwoFactorCode" }
496
+ responses:
497
+ "426": { $ref: "#/components/responses/ClientOutdated" }
498
+ "204":
499
+ description: Two-factor authentication enabled
500
+ "400": { $ref: "#/components/responses/BadRequest" }
501
+ "401": { $ref: "#/components/responses/Unauthorized" }
502
+ "429": { $ref: "#/components/responses/TooManyRequests" }
503
+ /me/2fa/disable:
504
+ parameters:
505
+ - $ref: "#/components/parameters/ClientVersion"
506
+ - $ref: "#/components/parameters/ClientApiVersion"
507
+ post:
508
+ tags: [me]
509
+ operationId: disableTwoFactor
510
+ summary: Turn two-factor off
511
+ requestBody:
512
+ required: true
513
+ content:
514
+ application/json:
515
+ schema: { $ref: "#/components/schemas/DisableTwoFactorRequest" }
516
+ responses:
517
+ "426": { $ref: "#/components/responses/ClientOutdated" }
518
+ "204":
519
+ description: Two-factor authentication disabled
520
+ "400": { $ref: "#/components/responses/BadRequest" }
521
+ "401": { $ref: "#/components/responses/Unauthorized" }
522
+ "403": { $ref: "#/components/responses/Forbidden" }
523
+ "429": { $ref: "#/components/responses/TooManyRequests" }
524
+ /me/privacy:
525
+ parameters:
526
+ - $ref: "#/components/parameters/ClientVersion"
527
+ - $ref: "#/components/parameters/ClientApiVersion"
528
+ get:
529
+ tags: [me]
530
+ operationId: getPrivacySettings
531
+ summary: Privacy settings and exceptions
532
+ responses:
533
+ "426": { $ref: "#/components/responses/ClientOutdated" }
534
+ "400": { $ref: "#/components/responses/BadRequest" }
535
+ "200":
536
+ description: Settings
537
+ content:
538
+ application/json:
539
+ schema: { $ref: "#/components/schemas/PrivacySettings" }
540
+ "401": { $ref: "#/components/responses/Unauthorized" }
541
+ patch:
542
+ tags: [me]
543
+ operationId: updatePrivacySettings
544
+ summary: Update privacy settings
545
+ requestBody:
546
+ required: true
547
+ content:
548
+ application/json:
549
+ schema: { $ref: "#/components/schemas/UpdatePrivacySettingsRequest" }
550
+ responses:
551
+ "426": { $ref: "#/components/responses/ClientOutdated" }
552
+ "400": { $ref: "#/components/responses/BadRequest" }
553
+ "200":
554
+ description: Settings
555
+ content:
556
+ application/json:
557
+ schema: { $ref: "#/components/schemas/PrivacySettings" }
558
+ "401": { $ref: "#/components/responses/Unauthorized" }
559
+ "422": { $ref: "#/components/responses/ValidationFailed" }
560
+ /me/privacy/exceptions/{setting_key}/{effect}:
561
+ parameters:
562
+ - $ref: "#/components/parameters/ClientVersion"
563
+ - $ref: "#/components/parameters/ClientApiVersion"
564
+ - name: setting_key
565
+ in: path
566
+ required: true
567
+ schema:
568
+ type: string
569
+ enum: [avatar_visibility, profile_visibility, presence_visibility, group_invites]
570
+ - name: effect
571
+ in: path
572
+ required: true
573
+ schema:
574
+ type: string
575
+ enum: [allow, deny]
576
+ put:
577
+ tags: [me]
578
+ operationId: replacePrivacyExceptions
579
+ summary: Replace the exception list of one setting
580
+ requestBody:
581
+ required: true
582
+ content:
583
+ application/json:
584
+ schema: { $ref: "#/components/schemas/PrivacyExceptionsRequest" }
585
+ responses:
586
+ "426": { $ref: "#/components/responses/ClientOutdated" }
587
+ "400": { $ref: "#/components/responses/BadRequest" }
588
+ "200":
589
+ description: Settings
590
+ content:
591
+ application/json:
592
+ schema: { $ref: "#/components/schemas/PrivacySettings" }
593
+ "401": { $ref: "#/components/responses/Unauthorized" }
594
+ "404": { $ref: "#/components/responses/NotFound" }
595
+ "422": { $ref: "#/components/responses/ValidationFailed" }
596
+ /me/blocked-users:
597
+ parameters:
598
+ - $ref: "#/components/parameters/ClientVersion"
599
+ - $ref: "#/components/parameters/ClientApiVersion"
600
+ get:
601
+ tags: [me]
602
+ operationId: listBlockedUsers
603
+ summary: Blocked users
604
+ parameters:
605
+ - $ref: "#/components/parameters/Limit"
606
+ - $ref: "#/components/parameters/After"
607
+ responses:
608
+ "426": { $ref: "#/components/responses/ClientOutdated" }
609
+ "400": { $ref: "#/components/responses/BadRequest" }
610
+ "200":
611
+ description: A page of blocked users
612
+ content:
613
+ application/json:
614
+ schema: { $ref: "#/components/schemas/UserPage" }
615
+ "401": { $ref: "#/components/responses/Unauthorized" }
616
+ /me/blocked-users/{user_id}:
617
+ parameters:
618
+ - $ref: "#/components/parameters/ClientVersion"
619
+ - $ref: "#/components/parameters/ClientApiVersion"
620
+ - $ref: "#/components/parameters/UserId"
621
+ put:
622
+ tags: [me]
623
+ operationId: blockUser
624
+ summary: Block a user
625
+ responses:
626
+ "426": { $ref: "#/components/responses/ClientOutdated" }
627
+ "400": { $ref: "#/components/responses/BadRequest" }
628
+ "204":
629
+ description: Blocked (also when already blocked)
630
+ "401": { $ref: "#/components/responses/Unauthorized" }
631
+ "404": { $ref: "#/components/responses/NotFound" }
632
+ delete:
633
+ tags: [me]
634
+ operationId: unblockUser
635
+ summary: Unblock a user
636
+ responses:
637
+ "426": { $ref: "#/components/responses/ClientOutdated" }
638
+ "400": { $ref: "#/components/responses/BadRequest" }
639
+ "204":
640
+ description: Unblocked (also when not blocked)
641
+ "401": { $ref: "#/components/responses/Unauthorized" }
642
+ "404": { $ref: "#/components/responses/NotFound" }
643
+
644
+ # --------------------------------------------------------------- users
645
+ /users:
646
+ parameters:
647
+ - $ref: "#/components/parameters/ClientVersion"
648
+ - $ref: "#/components/parameters/ClientApiVersion"
649
+ get:
650
+ tags: [users]
651
+ operationId: searchUsers
652
+ summary: Search users by username or display name
653
+ description: Users whose `search_visibility` is `nobody` are not returned.
654
+ parameters:
655
+ - name: q
656
+ in: query
657
+ required: true
658
+ schema: { type: string, minLength: 2, maxLength: 64 }
659
+ - $ref: "#/components/parameters/Limit"
660
+ - $ref: "#/components/parameters/After"
661
+ responses:
662
+ "426": { $ref: "#/components/responses/ClientOutdated" }
663
+ "400": { $ref: "#/components/responses/BadRequest" }
664
+ "200":
665
+ description: A page of users
666
+ content:
667
+ application/json:
668
+ schema: { $ref: "#/components/schemas/UserPage" }
669
+ "401": { $ref: "#/components/responses/Unauthorized" }
670
+ "422": { $ref: "#/components/responses/ValidationFailed" }
671
+ /usernames/{username}:
672
+ parameters:
673
+ - $ref: "#/components/parameters/ClientVersion"
674
+ - $ref: "#/components/parameters/ClientApiVersion"
675
+ get:
676
+ tags: [users]
677
+ operationId: getUserByUsername
678
+ summary: Profile by username
679
+ parameters:
680
+ - name: username
681
+ in: path
682
+ required: true
683
+ schema: { $ref: "#/components/schemas/Username" }
684
+ responses:
685
+ "426": { $ref: "#/components/responses/ClientOutdated" }
686
+ "400": { $ref: "#/components/responses/BadRequest" }
687
+ "200":
688
+ description: The user, as visible to the current user
689
+ content:
690
+ application/json:
691
+ schema: { $ref: "#/components/schemas/User" }
692
+ "401": { $ref: "#/components/responses/Unauthorized" }
693
+ "404": { $ref: "#/components/responses/NotFound" }
694
+ /users/{user_id}:
695
+ parameters:
696
+ - $ref: "#/components/parameters/ClientVersion"
697
+ - $ref: "#/components/parameters/ClientApiVersion"
698
+ - $ref: "#/components/parameters/UserId"
699
+ get:
700
+ tags: [users]
701
+ operationId: getUser
702
+ summary: Profile by ID
703
+ responses:
704
+ "426": { $ref: "#/components/responses/ClientOutdated" }
705
+ "400": { $ref: "#/components/responses/BadRequest" }
706
+ "200":
707
+ description: The user, as visible to the current user
708
+ content:
709
+ application/json:
710
+ schema: { $ref: "#/components/schemas/User" }
711
+ "401": { $ref: "#/components/responses/Unauthorized" }
712
+ "404": { $ref: "#/components/responses/NotFound" }
713
+ /users/{user_id}/avatar:
714
+ parameters:
715
+ - $ref: "#/components/parameters/ClientVersion"
716
+ - $ref: "#/components/parameters/ClientApiVersion"
717
+ - $ref: "#/components/parameters/UserId"
718
+ get:
719
+ tags: [users]
720
+ operationId: getUserAvatar
721
+ summary: Current avatar image
722
+ description: Respects the avatar visibility of the user; falls back to the default avatar.
723
+ responses:
724
+ "426": { $ref: "#/components/responses/ClientOutdated" }
725
+ "400": { $ref: "#/components/responses/BadRequest" }
726
+ "200":
727
+ description: Image
728
+ content:
729
+ image/*:
730
+ schema: { type: string, format: binary }
731
+ "401": { $ref: "#/components/responses/Unauthorized" }
732
+ "404": { $ref: "#/components/responses/NotFound" }
733
+ /users/{user_id}/avatars:
734
+ parameters:
735
+ - $ref: "#/components/parameters/ClientVersion"
736
+ - $ref: "#/components/parameters/ClientApiVersion"
737
+ - $ref: "#/components/parameters/UserId"
738
+ get:
739
+ tags: [users]
740
+ operationId: listUserAvatars
741
+ summary: Avatar history
742
+ parameters:
743
+ - $ref: "#/components/parameters/Limit"
744
+ - $ref: "#/components/parameters/After"
745
+ responses:
746
+ "426": { $ref: "#/components/responses/ClientOutdated" }
747
+ "400": { $ref: "#/components/responses/BadRequest" }
748
+ "200":
749
+ description: A page of previous avatars, newest first
750
+ content:
751
+ application/json:
752
+ schema:
753
+ type: object
754
+ required: [items, next_cursor]
755
+ properties:
756
+ items:
757
+ type: array
758
+ items: { $ref: "#/components/schemas/AvatarVersion" }
759
+ next_cursor: { $ref: "#/components/schemas/Cursor" }
760
+ "401": { $ref: "#/components/responses/Unauthorized" }
761
+ "404": { $ref: "#/components/responses/NotFound" }
762
+ /users/{user_id}/contact-name:
763
+ parameters:
764
+ - $ref: "#/components/parameters/ClientVersion"
765
+ - $ref: "#/components/parameters/ClientApiVersion"
766
+ - $ref: "#/components/parameters/UserId"
767
+ put:
768
+ tags: [users]
769
+ operationId: setContactName
770
+ summary: Set your own display name for a user
771
+ requestBody:
772
+ required: true
773
+ content:
774
+ application/json:
775
+ schema: { $ref: "#/components/schemas/ContactNameRequest" }
776
+ responses:
777
+ "426": { $ref: "#/components/responses/ClientOutdated" }
778
+ "400": { $ref: "#/components/responses/BadRequest" }
779
+ "200":
780
+ description: The user with the contact name applied
781
+ content:
782
+ application/json:
783
+ schema: { $ref: "#/components/schemas/User" }
784
+ "401": { $ref: "#/components/responses/Unauthorized" }
785
+ "404": { $ref: "#/components/responses/NotFound" }
786
+ "422": { $ref: "#/components/responses/ValidationFailed" }
787
+ delete:
788
+ tags: [users]
789
+ operationId: removeContactName
790
+ summary: Remove your display name for a user
791
+ responses:
792
+ "426": { $ref: "#/components/responses/ClientOutdated" }
793
+ "400": { $ref: "#/components/responses/BadRequest" }
794
+ "204":
795
+ description: Removed
796
+ "401": { $ref: "#/components/responses/Unauthorized" }
797
+ "404": { $ref: "#/components/responses/NotFound" }
798
+
799
+ # --------------------------------------------------------- attachments
800
+ /attachments:
801
+ parameters:
802
+ - $ref: "#/components/parameters/ClientVersion"
803
+ - $ref: "#/components/parameters/ClientApiVersion"
804
+ post:
805
+ tags: [attachments]
806
+ operationId: uploadAttachment
807
+ summary: Upload a file, image, audio or voice message
808
+ description: |
809
+ The server checks the content, not the file name or the client's content type. The
810
+ returned `attachment_id` is used in messages (`type: file` or `voice`) or for
811
+ avatars. Attachments that are not used within 24 hours are deleted.
812
+ requestBody:
813
+ required: true
814
+ content:
815
+ multipart/form-data:
816
+ schema: { $ref: "#/components/schemas/UploadAttachmentRequest" }
817
+ responses:
818
+ "426": { $ref: "#/components/responses/ClientOutdated" }
819
+ "201":
820
+ description: Uploaded
821
+ content:
822
+ application/json:
823
+ schema: { $ref: "#/components/schemas/Attachment" }
824
+ "400": { $ref: "#/components/responses/BadRequest" }
825
+ "401": { $ref: "#/components/responses/Unauthorized" }
826
+ "413": { $ref: "#/components/responses/PayloadTooLarge" }
827
+ "415": { $ref: "#/components/responses/UnsupportedMediaType" }
828
+ "429": { $ref: "#/components/responses/TooManyRequests" }
829
+ /attachments/{attachment_id}/content:
830
+ parameters:
831
+ - $ref: "#/components/parameters/ClientVersion"
832
+ - $ref: "#/components/parameters/ClientApiVersion"
833
+ - $ref: "#/components/parameters/AttachmentId"
834
+ get:
835
+ tags: [attachments]
836
+ operationId: getAttachmentContent
837
+ summary: Download an attachment
838
+ description: Only participants of the chat the attachment was sent to (and its uploader) can download it.
839
+ responses:
840
+ "426": { $ref: "#/components/responses/ClientOutdated" }
841
+ "400": { $ref: "#/components/responses/BadRequest" }
842
+ "200":
843
+ description: File content
844
+ content:
845
+ application/octet-stream:
846
+ schema: { type: string, format: binary }
847
+ "401": { $ref: "#/components/responses/Unauthorized" }
848
+ "403": { $ref: "#/components/responses/Forbidden" }
849
+ "404": { $ref: "#/components/responses/NotFound" }
850
+
851
+ # --------------------------------------------------------------- chats
852
+ /chats:
853
+ parameters:
854
+ - $ref: "#/components/parameters/ClientVersion"
855
+ - $ref: "#/components/parameters/ClientApiVersion"
856
+ get:
857
+ tags: [chats]
858
+ operationId: listChats
859
+ summary: My chats with last message and unread count
860
+ description: One request replaces `GET /chats/list/{username}` and `GET /groups/list/{username}`; pinned chats come first.
861
+ parameters:
862
+ - $ref: "#/components/parameters/Limit"
863
+ - $ref: "#/components/parameters/After"
864
+ responses:
865
+ "426": { $ref: "#/components/responses/ClientOutdated" }
866
+ "400": { $ref: "#/components/responses/BadRequest" }
867
+ "200":
868
+ description: A page of chats
869
+ content:
870
+ application/json:
871
+ schema: { $ref: "#/components/schemas/ChatPage" }
872
+ "401": { $ref: "#/components/responses/Unauthorized" }
873
+ post:
874
+ tags: [chats]
875
+ operationId: createChat
876
+ summary: Start a direct chat
877
+ description: |
878
+ Respects the other user's `direct_messages` privacy setting. When approval is needed
879
+ the answer is `202` with the pending request instead of a chat. Returns the existing
880
+ chat (`200`) if there already is one.
881
+ requestBody:
882
+ required: true
883
+ content:
884
+ application/json:
885
+ schema: { $ref: "#/components/schemas/CreateChatRequest" }
886
+ responses:
887
+ "426": { $ref: "#/components/responses/ClientOutdated" }
888
+ "200":
889
+ description: The chat already existed
890
+ content:
891
+ application/json:
892
+ schema: { $ref: "#/components/schemas/Chat" }
893
+ "201":
894
+ description: Chat created
895
+ content:
896
+ application/json:
897
+ schema: { $ref: "#/components/schemas/Chat" }
898
+ "202":
899
+ description: Waiting for the other user's approval
900
+ content:
901
+ application/json:
902
+ schema: { $ref: "#/components/schemas/ApprovalRequest" }
903
+ "400": { $ref: "#/components/responses/BadRequest" }
904
+ "401": { $ref: "#/components/responses/Unauthorized" }
905
+ "403": { $ref: "#/components/responses/Forbidden" }
906
+ "404": { $ref: "#/components/responses/NotFound" }
907
+ "429": { $ref: "#/components/responses/TooManyRequests" }
908
+ /chats/{chat_id}:
909
+ parameters:
910
+ - $ref: "#/components/parameters/ClientVersion"
911
+ - $ref: "#/components/parameters/ClientApiVersion"
912
+ - $ref: "#/components/parameters/ChatId"
913
+ get:
914
+ tags: [chats]
915
+ operationId: getChat
916
+ summary: One chat
917
+ responses:
918
+ "426": { $ref: "#/components/responses/ClientOutdated" }
919
+ "400": { $ref: "#/components/responses/BadRequest" }
920
+ "200":
921
+ description: The chat
922
+ content:
923
+ application/json:
924
+ schema: { $ref: "#/components/schemas/Chat" }
925
+ "401": { $ref: "#/components/responses/Unauthorized" }
926
+ "404": { $ref: "#/components/responses/NotFound" }
927
+ delete:
928
+ tags: [chats]
929
+ operationId: deleteChat
930
+ summary: Delete a direct chat for yourself
931
+ description: For groups use `DELETE /groups/{chat_id}` (owner) or `POST /groups/{chat_id}/leave`.
932
+ responses:
933
+ "426": { $ref: "#/components/responses/ClientOutdated" }
934
+ "400": { $ref: "#/components/responses/BadRequest" }
935
+ "204":
936
+ description: Deleted
937
+ "401": { $ref: "#/components/responses/Unauthorized" }
938
+ "404": { $ref: "#/components/responses/NotFound" }
939
+ /chats/{chat_id}/pin:
940
+ parameters:
941
+ - $ref: "#/components/parameters/ClientVersion"
942
+ - $ref: "#/components/parameters/ClientApiVersion"
943
+ - $ref: "#/components/parameters/ChatId"
944
+ put:
945
+ tags: [chats]
946
+ operationId: pinChat
947
+ summary: Pin a chat
948
+ responses:
949
+ "426": { $ref: "#/components/responses/ClientOutdated" }
950
+ "400": { $ref: "#/components/responses/BadRequest" }
951
+ "204":
952
+ description: Pinned
953
+ "401": { $ref: "#/components/responses/Unauthorized" }
954
+ "404": { $ref: "#/components/responses/NotFound" }
955
+ "409": { $ref: "#/components/responses/Conflict" }
956
+ delete:
957
+ tags: [chats]
958
+ operationId: unpinChat
959
+ summary: Unpin a chat
960
+ responses:
961
+ "426": { $ref: "#/components/responses/ClientOutdated" }
962
+ "400": { $ref: "#/components/responses/BadRequest" }
963
+ "204":
964
+ description: Unpinned
965
+ "401": { $ref: "#/components/responses/Unauthorized" }
966
+ "404": { $ref: "#/components/responses/NotFound" }
967
+ /chats/{chat_id}/read:
968
+ parameters:
969
+ - $ref: "#/components/parameters/ClientVersion"
970
+ - $ref: "#/components/parameters/ClientApiVersion"
971
+ - $ref: "#/components/parameters/ChatId"
972
+ post:
973
+ tags: [chats]
974
+ operationId: markChatRead
975
+ summary: Mark messages as read up to a message
976
+ description: Replaces the duplicate `/read` and `/read/` routes of v1.
977
+ requestBody:
978
+ required: true
979
+ content:
980
+ application/json:
981
+ schema: { $ref: "#/components/schemas/MarkReadRequest" }
982
+ responses:
983
+ "426": { $ref: "#/components/responses/ClientOutdated" }
984
+ "400": { $ref: "#/components/responses/BadRequest" }
985
+ "200":
986
+ description: New unread count
987
+ content:
988
+ application/json:
989
+ schema:
990
+ type: object
991
+ required: [unread_count]
992
+ properties:
993
+ unread_count: { type: integer, minimum: 0 }
994
+ "401": { $ref: "#/components/responses/Unauthorized" }
995
+ "404": { $ref: "#/components/responses/NotFound" }
996
+ /chats/{chat_id}/messages:
997
+ parameters:
998
+ - $ref: "#/components/parameters/ClientVersion"
999
+ - $ref: "#/components/parameters/ClientApiVersion"
1000
+ - $ref: "#/components/parameters/ChatId"
1001
+ get:
1002
+ tags: [chats]
1003
+ operationId: listMessages
1004
+ summary: Message history
1005
+ description: |
1006
+ Replaces `GET /messages/history/{chat_id}`. Without a cursor the newest messages are
1007
+ returned. Items are always ordered oldest to newest. Send at most one of `before`,
1008
+ `after` and `around` (message IDs).
1009
+ parameters:
1010
+ - $ref: "#/components/parameters/Limit"
1011
+ - name: before
1012
+ in: query
1013
+ description: Messages older than this message ID
1014
+ schema: { $ref: "#/components/schemas/Id" }
1015
+ - name: after
1016
+ in: query
1017
+ description: Messages newer than this message ID
1018
+ schema: { $ref: "#/components/schemas/Id" }
1019
+ - name: around
1020
+ in: query
1021
+ description: A window containing this message ID
1022
+ schema: { $ref: "#/components/schemas/Id" }
1023
+ responses:
1024
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1025
+ "400": { $ref: "#/components/responses/BadRequest" }
1026
+ "200":
1027
+ description: A page of messages
1028
+ content:
1029
+ application/json:
1030
+ schema: { $ref: "#/components/schemas/MessagePage" }
1031
+ "401": { $ref: "#/components/responses/Unauthorized" }
1032
+ "404": { $ref: "#/components/responses/NotFound" }
1033
+ "422": { $ref: "#/components/responses/ValidationFailed" }
1034
+ post:
1035
+ tags: [chats]
1036
+ operationId: sendMessage
1037
+ summary: Send a message over HTTP
1038
+ description: |
1039
+ Same as the WebSocket `message` event, for clients without a connection. Sending the
1040
+ same `client_temp_id` again returns the existing message instead of creating a copy.
1041
+ requestBody:
1042
+ required: true
1043
+ content:
1044
+ application/json:
1045
+ schema: { $ref: "#/components/schemas/SendMessageRequest" }
1046
+ responses:
1047
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1048
+ "201":
1049
+ description: Message created
1050
+ content:
1051
+ application/json:
1052
+ schema: { $ref: "#/components/schemas/Message" }
1053
+ "200":
1054
+ description: Already created for this `client_temp_id`
1055
+ content:
1056
+ application/json:
1057
+ schema: { $ref: "#/components/schemas/Message" }
1058
+ "400": { $ref: "#/components/responses/BadRequest" }
1059
+ "401": { $ref: "#/components/responses/Unauthorized" }
1060
+ "403": { $ref: "#/components/responses/Forbidden" }
1061
+ "404": { $ref: "#/components/responses/NotFound" }
1062
+ "422": { $ref: "#/components/responses/ValidationFailed" }
1063
+ "429": { $ref: "#/components/responses/TooManyRequests" }
1064
+ /chats/{chat_id}/messages/search:
1065
+ parameters:
1066
+ - $ref: "#/components/parameters/ClientVersion"
1067
+ - $ref: "#/components/parameters/ClientApiVersion"
1068
+ - $ref: "#/components/parameters/ChatId"
1069
+ get:
1070
+ tags: [chats]
1071
+ operationId: searchMessages
1072
+ summary: Search messages in a chat
1073
+ parameters:
1074
+ - name: q
1075
+ in: query
1076
+ required: true
1077
+ schema: { type: string, minLength: 2, maxLength: 128 }
1078
+ - $ref: "#/components/parameters/Limit"
1079
+ - $ref: "#/components/parameters/After"
1080
+ responses:
1081
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1082
+ "400": { $ref: "#/components/responses/BadRequest" }
1083
+ "200":
1084
+ description: Matches, newest first
1085
+ content:
1086
+ application/json:
1087
+ schema: { $ref: "#/components/schemas/MessageSearchPage" }
1088
+ "401": { $ref: "#/components/responses/Unauthorized" }
1089
+ "404": { $ref: "#/components/responses/NotFound" }
1090
+ "422": { $ref: "#/components/responses/ValidationFailed" }
1091
+ /chats/{chat_id}/media:
1092
+ parameters:
1093
+ - $ref: "#/components/parameters/ClientVersion"
1094
+ - $ref: "#/components/parameters/ClientApiVersion"
1095
+ - $ref: "#/components/parameters/ChatId"
1096
+ get:
1097
+ tags: [chats]
1098
+ operationId: listChatMedia
1099
+ summary: Photos or audio of a chat
1100
+ description: Replaces `GET /messages/photos/{chat_id}` and `GET /messages/audios/{chat_id}`.
1101
+ parameters:
1102
+ - name: kind
1103
+ in: query
1104
+ required: true
1105
+ schema: { $ref: "#/components/schemas/MediaKind" }
1106
+ - $ref: "#/components/parameters/Limit"
1107
+ - $ref: "#/components/parameters/After"
1108
+ responses:
1109
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1110
+ "400": { $ref: "#/components/responses/BadRequest" }
1111
+ "200":
1112
+ description: Messages with matching attachments, newest first
1113
+ content:
1114
+ application/json:
1115
+ schema: { $ref: "#/components/schemas/MessageSearchPage" }
1116
+ "401": { $ref: "#/components/responses/Unauthorized" }
1117
+ "404": { $ref: "#/components/responses/NotFound" }
1118
+ "422": { $ref: "#/components/responses/ValidationFailed" }
1119
+
1120
+ # ------------------------------------------------------------ messages
1121
+ /messages/{message_id}:
1122
+ parameters:
1123
+ - $ref: "#/components/parameters/ClientVersion"
1124
+ - $ref: "#/components/parameters/ClientApiVersion"
1125
+ - $ref: "#/components/parameters/MessageId"
1126
+ patch:
1127
+ tags: [messages]
1128
+ operationId: editMessage
1129
+ summary: Edit your text message
1130
+ requestBody:
1131
+ required: true
1132
+ content:
1133
+ application/json:
1134
+ schema: { $ref: "#/components/schemas/EditMessageRequest" }
1135
+ responses:
1136
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1137
+ "400": { $ref: "#/components/responses/BadRequest" }
1138
+ "200":
1139
+ description: Edited message
1140
+ content:
1141
+ application/json:
1142
+ schema: { $ref: "#/components/schemas/Message" }
1143
+ "401": { $ref: "#/components/responses/Unauthorized" }
1144
+ "403": { $ref: "#/components/responses/Forbidden" }
1145
+ "404": { $ref: "#/components/responses/NotFound" }
1146
+ "422": { $ref: "#/components/responses/ValidationFailed" }
1147
+ delete:
1148
+ tags: [messages]
1149
+ operationId: deleteMessage
1150
+ summary: Delete a message
1151
+ description: |
1152
+ `scope=me` hides it for the current user only (any message). `scope=everyone` removes
1153
+ it for all participants (own messages, and any message for group admins).
1154
+ parameters:
1155
+ - name: scope
1156
+ in: query
1157
+ required: true
1158
+ schema:
1159
+ type: string
1160
+ enum: [me, everyone]
1161
+ responses:
1162
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1163
+ "400": { $ref: "#/components/responses/BadRequest" }
1164
+ "204":
1165
+ description: Deleted
1166
+ "401": { $ref: "#/components/responses/Unauthorized" }
1167
+ "403": { $ref: "#/components/responses/Forbidden" }
1168
+ "404": { $ref: "#/components/responses/NotFound" }
1169
+ "422": { $ref: "#/components/responses/ValidationFailed" }
1170
+ /messages/{message_id}/forward:
1171
+ parameters:
1172
+ - $ref: "#/components/parameters/ClientVersion"
1173
+ - $ref: "#/components/parameters/ClientApiVersion"
1174
+ - $ref: "#/components/parameters/MessageId"
1175
+ post:
1176
+ tags: [messages]
1177
+ operationId: forwardMessage
1178
+ summary: Forward a message to other chats
1179
+ requestBody:
1180
+ required: true
1181
+ content:
1182
+ application/json:
1183
+ schema: { $ref: "#/components/schemas/ForwardMessageRequest" }
1184
+ responses:
1185
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1186
+ "400": { $ref: "#/components/responses/BadRequest" }
1187
+ "201":
1188
+ description: One new message per target chat
1189
+ content:
1190
+ application/json:
1191
+ schema:
1192
+ type: object
1193
+ required: [items]
1194
+ properties:
1195
+ items:
1196
+ type: array
1197
+ items: { $ref: "#/components/schemas/Message" }
1198
+ "401": { $ref: "#/components/responses/Unauthorized" }
1199
+ "403": { $ref: "#/components/responses/Forbidden" }
1200
+ "404": { $ref: "#/components/responses/NotFound" }
1201
+ "422": { $ref: "#/components/responses/ValidationFailed" }
1202
+ "429": { $ref: "#/components/responses/TooManyRequests" }
1203
+
1204
+ # ------------------------------------------------------------ requests
1205
+ /requests:
1206
+ parameters:
1207
+ - $ref: "#/components/parameters/ClientVersion"
1208
+ - $ref: "#/components/parameters/ClientApiVersion"
1209
+ get:
1210
+ tags: [requests]
1211
+ operationId: listApprovalRequests
1212
+ summary: Inbox of approval requests
1213
+ parameters:
1214
+ - $ref: "#/components/parameters/Limit"
1215
+ - $ref: "#/components/parameters/After"
1216
+ responses:
1217
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1218
+ "400": { $ref: "#/components/responses/BadRequest" }
1219
+ "200":
1220
+ description: A page of pending requests
1221
+ content:
1222
+ application/json:
1223
+ schema:
1224
+ type: object
1225
+ required: [items, next_cursor]
1226
+ properties:
1227
+ items:
1228
+ type: array
1229
+ items: { $ref: "#/components/schemas/ApprovalRequest" }
1230
+ next_cursor: { $ref: "#/components/schemas/Cursor" }
1231
+ "401": { $ref: "#/components/responses/Unauthorized" }
1232
+ /requests/{request_id}/approve:
1233
+ parameters:
1234
+ - $ref: "#/components/parameters/ClientVersion"
1235
+ - $ref: "#/components/parameters/ClientApiVersion"
1236
+ - $ref: "#/components/parameters/RequestId"
1237
+ post:
1238
+ tags: [requests]
1239
+ operationId: approveRequest
1240
+ summary: Approve a request
1241
+ responses:
1242
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1243
+ "400": { $ref: "#/components/responses/BadRequest" }
1244
+ "200":
1245
+ description: Approved; the chat or group membership now exists
1246
+ content:
1247
+ application/json:
1248
+ schema: { $ref: "#/components/schemas/Chat" }
1249
+ "401": { $ref: "#/components/responses/Unauthorized" }
1250
+ "403": { $ref: "#/components/responses/Forbidden" }
1251
+ "404": { $ref: "#/components/responses/NotFound" }
1252
+ "409": { $ref: "#/components/responses/Conflict" }
1253
+ /requests/{request_id}/reject:
1254
+ parameters:
1255
+ - $ref: "#/components/parameters/ClientVersion"
1256
+ - $ref: "#/components/parameters/ClientApiVersion"
1257
+ - $ref: "#/components/parameters/RequestId"
1258
+ post:
1259
+ tags: [requests]
1260
+ operationId: rejectRequest
1261
+ summary: Reject a request
1262
+ responses:
1263
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1264
+ "400": { $ref: "#/components/responses/BadRequest" }
1265
+ "204":
1266
+ description: Rejected
1267
+ "401": { $ref: "#/components/responses/Unauthorized" }
1268
+ "403": { $ref: "#/components/responses/Forbidden" }
1269
+ "404": { $ref: "#/components/responses/NotFound" }
1270
+ "409": { $ref: "#/components/responses/Conflict" }
1271
+
1272
+ # -------------------------------------------------------------- groups
1273
+ /groups:
1274
+ parameters:
1275
+ - $ref: "#/components/parameters/ClientVersion"
1276
+ - $ref: "#/components/parameters/ClientApiVersion"
1277
+ get:
1278
+ tags: [groups]
1279
+ operationId: listGroups
1280
+ summary: My groups
1281
+ parameters:
1282
+ - $ref: "#/components/parameters/Limit"
1283
+ - $ref: "#/components/parameters/After"
1284
+ responses:
1285
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1286
+ "400": { $ref: "#/components/responses/BadRequest" }
1287
+ "200":
1288
+ description: A page of group chats
1289
+ content:
1290
+ application/json:
1291
+ schema: { $ref: "#/components/schemas/ChatPage" }
1292
+ "401": { $ref: "#/components/responses/Unauthorized" }
1293
+ post:
1294
+ tags: [groups]
1295
+ operationId: createGroup
1296
+ summary: Create a group
1297
+ description: Members whose `group_invites` setting requires approval get an approval request instead of being added.
1298
+ requestBody:
1299
+ required: true
1300
+ content:
1301
+ application/json:
1302
+ schema: { $ref: "#/components/schemas/CreateGroupRequest" }
1303
+ responses:
1304
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1305
+ "201":
1306
+ description: Group created
1307
+ content:
1308
+ application/json:
1309
+ schema: { $ref: "#/components/schemas/Group" }
1310
+ "400": { $ref: "#/components/responses/BadRequest" }
1311
+ "401": { $ref: "#/components/responses/Unauthorized" }
1312
+ "422": { $ref: "#/components/responses/ValidationFailed" }
1313
+ "429": { $ref: "#/components/responses/TooManyRequests" }
1314
+ /groups/{chat_id}:
1315
+ parameters:
1316
+ - $ref: "#/components/parameters/ClientVersion"
1317
+ - $ref: "#/components/parameters/ClientApiVersion"
1318
+ - $ref: "#/components/parameters/ChatId"
1319
+ get:
1320
+ tags: [groups]
1321
+ operationId: getGroup
1322
+ summary: Group details and members
1323
+ responses:
1324
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1325
+ "400": { $ref: "#/components/responses/BadRequest" }
1326
+ "200":
1327
+ description: The group
1328
+ content:
1329
+ application/json:
1330
+ schema: { $ref: "#/components/schemas/Group" }
1331
+ "401": { $ref: "#/components/responses/Unauthorized" }
1332
+ "404": { $ref: "#/components/responses/NotFound" }
1333
+ patch:
1334
+ tags: [groups]
1335
+ operationId: updateGroup
1336
+ summary: Change name or description
1337
+ requestBody:
1338
+ required: true
1339
+ content:
1340
+ application/json:
1341
+ schema: { $ref: "#/components/schemas/UpdateGroupRequest" }
1342
+ responses:
1343
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1344
+ "400": { $ref: "#/components/responses/BadRequest" }
1345
+ "200":
1346
+ description: Updated group
1347
+ content:
1348
+ application/json:
1349
+ schema: { $ref: "#/components/schemas/Group" }
1350
+ "401": { $ref: "#/components/responses/Unauthorized" }
1351
+ "403": { $ref: "#/components/responses/Forbidden" }
1352
+ "404": { $ref: "#/components/responses/NotFound" }
1353
+ "422": { $ref: "#/components/responses/ValidationFailed" }
1354
+ delete:
1355
+ tags: [groups]
1356
+ operationId: deleteGroup
1357
+ summary: Delete the group (owner only)
1358
+ responses:
1359
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1360
+ "400": { $ref: "#/components/responses/BadRequest" }
1361
+ "204":
1362
+ description: Deleted for all members
1363
+ "401": { $ref: "#/components/responses/Unauthorized" }
1364
+ "403": { $ref: "#/components/responses/Forbidden" }
1365
+ "404": { $ref: "#/components/responses/NotFound" }
1366
+ /groups/{chat_id}/avatar:
1367
+ parameters:
1368
+ - $ref: "#/components/parameters/ClientVersion"
1369
+ - $ref: "#/components/parameters/ClientApiVersion"
1370
+ - $ref: "#/components/parameters/ChatId"
1371
+ put:
1372
+ tags: [groups]
1373
+ operationId: setGroupAvatar
1374
+ summary: Set the group avatar from an uploaded attachment
1375
+ requestBody:
1376
+ required: true
1377
+ content:
1378
+ application/json:
1379
+ schema: { $ref: "#/components/schemas/SetAvatarRequest" }
1380
+ responses:
1381
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1382
+ "400": { $ref: "#/components/responses/BadRequest" }
1383
+ "200":
1384
+ description: Updated group
1385
+ content:
1386
+ application/json:
1387
+ schema: { $ref: "#/components/schemas/Group" }
1388
+ "401": { $ref: "#/components/responses/Unauthorized" }
1389
+ "403": { $ref: "#/components/responses/Forbidden" }
1390
+ "404": { $ref: "#/components/responses/NotFound" }
1391
+ /groups/{chat_id}/participants:
1392
+ parameters:
1393
+ - $ref: "#/components/parameters/ClientVersion"
1394
+ - $ref: "#/components/parameters/ClientApiVersion"
1395
+ - $ref: "#/components/parameters/ChatId"
1396
+ post:
1397
+ tags: [groups]
1398
+ operationId: addGroupParticipant
1399
+ summary: Add a participant
1400
+ requestBody:
1401
+ required: true
1402
+ content:
1403
+ application/json:
1404
+ schema: { $ref: "#/components/schemas/AddParticipantRequest" }
1405
+ responses:
1406
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1407
+ "400": { $ref: "#/components/responses/BadRequest" }
1408
+ "200":
1409
+ description: Updated group (the user may still be pending approval)
1410
+ content:
1411
+ application/json:
1412
+ schema: { $ref: "#/components/schemas/Group" }
1413
+ "401": { $ref: "#/components/responses/Unauthorized" }
1414
+ "403": { $ref: "#/components/responses/Forbidden" }
1415
+ "404": { $ref: "#/components/responses/NotFound" }
1416
+ "409": { $ref: "#/components/responses/Conflict" }
1417
+ /groups/{chat_id}/participants/{user_id}:
1418
+ parameters:
1419
+ - $ref: "#/components/parameters/ClientVersion"
1420
+ - $ref: "#/components/parameters/ClientApiVersion"
1421
+ - $ref: "#/components/parameters/ChatId"
1422
+ - $ref: "#/components/parameters/UserId"
1423
+ patch:
1424
+ tags: [groups]
1425
+ operationId: updateGroupParticipantRole
1426
+ summary: Change a participant's role
1427
+ requestBody:
1428
+ required: true
1429
+ content:
1430
+ application/json:
1431
+ schema: { $ref: "#/components/schemas/UpdateRoleRequest" }
1432
+ responses:
1433
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1434
+ "400": { $ref: "#/components/responses/BadRequest" }
1435
+ "200":
1436
+ description: Updated group
1437
+ content:
1438
+ application/json:
1439
+ schema: { $ref: "#/components/schemas/Group" }
1440
+ "401": { $ref: "#/components/responses/Unauthorized" }
1441
+ "403": { $ref: "#/components/responses/Forbidden" }
1442
+ "404": { $ref: "#/components/responses/NotFound" }
1443
+ "422": { $ref: "#/components/responses/ValidationFailed" }
1444
+ delete:
1445
+ tags: [groups]
1446
+ operationId: removeGroupParticipant
1447
+ summary: Remove a participant
1448
+ responses:
1449
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1450
+ "400": { $ref: "#/components/responses/BadRequest" }
1451
+ "204":
1452
+ description: Removed
1453
+ "401": { $ref: "#/components/responses/Unauthorized" }
1454
+ "403": { $ref: "#/components/responses/Forbidden" }
1455
+ "404": { $ref: "#/components/responses/NotFound" }
1456
+ /groups/{chat_id}/transfer-owner:
1457
+ parameters:
1458
+ - $ref: "#/components/parameters/ClientVersion"
1459
+ - $ref: "#/components/parameters/ClientApiVersion"
1460
+ - $ref: "#/components/parameters/ChatId"
1461
+ post:
1462
+ tags: [groups]
1463
+ operationId: transferGroupOwnership
1464
+ summary: Make another member the owner
1465
+ requestBody:
1466
+ required: true
1467
+ content:
1468
+ application/json:
1469
+ schema: { $ref: "#/components/schemas/TransferOwnerRequest" }
1470
+ responses:
1471
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1472
+ "400": { $ref: "#/components/responses/BadRequest" }
1473
+ "200":
1474
+ description: Updated group
1475
+ content:
1476
+ application/json:
1477
+ schema: { $ref: "#/components/schemas/Group" }
1478
+ "401": { $ref: "#/components/responses/Unauthorized" }
1479
+ "403": { $ref: "#/components/responses/Forbidden" }
1480
+ "404": { $ref: "#/components/responses/NotFound" }
1481
+ /groups/{chat_id}/leave:
1482
+ parameters:
1483
+ - $ref: "#/components/parameters/ClientVersion"
1484
+ - $ref: "#/components/parameters/ClientApiVersion"
1485
+ - $ref: "#/components/parameters/ChatId"
1486
+ post:
1487
+ tags: [groups]
1488
+ operationId: leaveGroup
1489
+ summary: Leave the group
1490
+ description: The owner has to transfer ownership first.
1491
+ responses:
1492
+ "426": { $ref: "#/components/responses/ClientOutdated" }
1493
+ "400": { $ref: "#/components/responses/BadRequest" }
1494
+ "204":
1495
+ description: Left
1496
+ "401": { $ref: "#/components/responses/Unauthorized" }
1497
+ "404": { $ref: "#/components/responses/NotFound" }
1498
+ "409": { $ref: "#/components/responses/Conflict" }
1499
+
1500
+ components:
1501
+ securitySchemes:
1502
+ bearerAuth:
1503
+ type: http
1504
+ scheme: bearer
1505
+ bearerFormat: JWT
1506
+ description: Short-lived access token from login or refresh.
1507
+ cookieAuth:
1508
+ type: apiKey
1509
+ in: cookie
1510
+ name: refresh_token
1511
+ description: HttpOnly refresh cookie set by login; scoped to `/api/v2/auth`.
1512
+
1513
+ headers:
1514
+ RefreshCookie:
1515
+ description: HttpOnly, Secure, SameSite refresh cookie scoped to `/api/v2/auth`. Login and refresh set it; logout clears it (expired cookie).
1516
+ schema: { type: string }
1517
+
1518
+ parameters:
1519
+ Limit:
1520
+ name: limit
1521
+ in: query
1522
+ description: Page size
1523
+ schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
1524
+ After:
1525
+ name: after
1526
+ in: query
1527
+ description: Opaque `next_cursor` of the previous page
1528
+ schema: { $ref: "#/components/schemas/Cursor" }
1529
+ ChatId:
1530
+ name: chat_id
1531
+ in: path
1532
+ required: true
1533
+ schema: { $ref: "#/components/schemas/Id" }
1534
+ MessageId:
1535
+ name: message_id
1536
+ in: path
1537
+ required: true
1538
+ schema: { $ref: "#/components/schemas/Id" }
1539
+ UserId:
1540
+ name: user_id
1541
+ in: path
1542
+ required: true
1543
+ schema: { $ref: "#/components/schemas/Id" }
1544
+ RequestId:
1545
+ name: request_id
1546
+ in: path
1547
+ required: true
1548
+ schema: { $ref: "#/components/schemas/Id" }
1549
+ AttachmentId:
1550
+ name: attachment_id
1551
+ in: path
1552
+ required: true
1553
+ schema: { $ref: "#/components/schemas/Id" }
1554
+ SessionId:
1555
+ name: session_id
1556
+ in: path
1557
+ required: true
1558
+ schema: { $ref: "#/components/schemas/Id" }
1559
+
1560
+ ClientVersion:
1561
+ name: X-Client-Version
1562
+ in: header
1563
+ required: false
1564
+ description: Version of the client app, for logs and the per-client metric.
1565
+ schema: { type: string }
1566
+ ClientApiVersion:
1567
+ name: X-Client-Api-Version
1568
+ in: header
1569
+ required: false
1570
+ description: Contract version the client was built with (`API_VERSION` of `@myronsi/messenger-api`). A different MAJOR or a version below `min_client_api_version` is answered with `426`; a malformed value with `400` (`invalid_client_version`).
1571
+ schema: { type: string }
1572
+
1573
+ responses:
1574
+ ClientOutdated:
1575
+ description: "The `X-Client-Api-Version` header has another MAJOR version than the server or is below `min_client_api_version` (`client_outdated`). Not returned by `/meta`, which clients use to find out the current versions."
1576
+ content:
1577
+ application/problem+json:
1578
+ schema: { $ref: "#/components/schemas/Problem" }
1579
+ BadRequest:
1580
+ description: Malformed request, or a malformed `X-Client-Api-Version` header (`invalid_client_version`)
1581
+ content:
1582
+ application/problem+json:
1583
+ schema: { $ref: "#/components/schemas/Problem" }
1584
+ Unauthorized:
1585
+ description: Missing, invalid or expired credentials
1586
+ content:
1587
+ application/problem+json:
1588
+ schema: { $ref: "#/components/schemas/Problem" }
1589
+ Forbidden:
1590
+ description: Authenticated but not allowed
1591
+ content:
1592
+ application/problem+json:
1593
+ schema: { $ref: "#/components/schemas/Problem" }
1594
+ NotFound:
1595
+ description: Does not exist or is not visible to the current user
1596
+ content:
1597
+ application/problem+json:
1598
+ schema: { $ref: "#/components/schemas/Problem" }
1599
+ Conflict:
1600
+ description: The state of the resource does not allow this
1601
+ content:
1602
+ application/problem+json:
1603
+ schema: { $ref: "#/components/schemas/Problem" }
1604
+ PayloadTooLarge:
1605
+ description: Upload exceeds the size limit
1606
+ content:
1607
+ application/problem+json:
1608
+ schema: { $ref: "#/components/schemas/Problem" }
1609
+ UnsupportedMediaType:
1610
+ description: The content type is not allowed
1611
+ content:
1612
+ application/problem+json:
1613
+ schema: { $ref: "#/components/schemas/Problem" }
1614
+ ValidationFailed:
1615
+ description: Syntactically valid but rejected values; `errors` lists the fields
1616
+ content:
1617
+ application/problem+json:
1618
+ schema: { $ref: "#/components/schemas/Problem" }
1619
+ TooManyRequests:
1620
+ description: Rate limit exceeded
1621
+ headers:
1622
+ Retry-After:
1623
+ description: Seconds until the next attempt is allowed
1624
+ schema: { type: integer, minimum: 0 }
1625
+ content:
1626
+ application/problem+json:
1627
+ schema: { $ref: "#/components/schemas/Problem" }
1628
+
1629
+ schemas:
1630
+ # ---------------------------------------------------------- basics
1631
+ Id:
1632
+ type: string
1633
+ pattern: "^[0-9]{1,19}$"
1634
+ description: Decimal ID as a string (Snowflake IDs exceed 2^53).
1635
+ examples: ["7217400317439950848"]
1636
+ Cursor:
1637
+ type: [string, "null"]
1638
+ maxLength: 256
1639
+ description: Opaque cursor; `null` when there are no more results.
1640
+ Timestamp:
1641
+ type: string
1642
+ format: date-time
1643
+ Username:
1644
+ type: string
1645
+ pattern: "^[A-Za-z0-9_]{3,32}$"
1646
+ ClientTempId:
1647
+ type: string
1648
+ minLength: 1
1649
+ maxLength: 64
1650
+ description: Client-generated ID that makes sending idempotent and ties acks and errors to the send.
1651
+ ErrorCode:
1652
+ type: string
1653
+ description: |
1654
+ Stable machine-readable error code. New codes can appear in MINOR versions; unknown
1655
+ codes must be treated like `internal_error`.
1656
+ enum:
1657
+ - invalid_request
1658
+ - validation_failed
1659
+ - unauthenticated
1660
+ - invalid_credentials
1661
+ - two_factor_required
1662
+ - invalid_two_factor_code
1663
+ - token_expired
1664
+ - forbidden
1665
+ - not_found
1666
+ - conflict
1667
+ - already_exists
1668
+ - payload_too_large
1669
+ - unsupported_media_type
1670
+ - rate_limited
1671
+ - client_outdated
1672
+ - invalid_client_version
1673
+ - blocked_by_user
1674
+ - approval_required
1675
+ - internal_error
1676
+ Problem:
1677
+ type: object
1678
+ description: RFC 9457 problem details. `426 Upgrade Required` (`client_outdated`) uses the same format.
1679
+ required: [type, title, status, code]
1680
+ properties:
1681
+ type:
1682
+ type: string
1683
+ description: URI reference identifying the problem type (`about:blank` when `code` says it all).
1684
+ default: about:blank
1685
+ title: { type: string }
1686
+ status: { type: integer, minimum: 400, maximum: 599 }
1687
+ detail: { type: string }
1688
+ instance: { type: string }
1689
+ code: { $ref: "#/components/schemas/ErrorCode" }
1690
+ errors:
1691
+ type: array
1692
+ description: Field errors for `validation_failed`.
1693
+ items:
1694
+ type: object
1695
+ required: [field, message]
1696
+ properties:
1697
+ field:
1698
+ type: string
1699
+ description: JSON pointer into the request body, or the parameter name.
1700
+ message: { type: string }
1701
+ min_client_api_version:
1702
+ type: string
1703
+ description: Present when `code` is `client_outdated`.
1704
+ api_version:
1705
+ type: string
1706
+ description: Present when `code` is `client_outdated`.
1707
+ additionalProperties: true
1708
+ Meta:
1709
+ type: object
1710
+ required: [backend_version, commit, api_version, min_client_api_version]
1711
+ properties:
1712
+ backend_version: { type: string, examples: ["1.0.3"] }
1713
+ commit: { type: string, examples: ["abc1234"] }
1714
+ api_version: { type: string, examples: ["2.0.0"] }
1715
+ min_client_api_version: { type: string, examples: ["2.0.0"] }
1716
+
1717
+ # ------------------------------------------------------------ auth
1718
+ RegisterRequest:
1719
+ type: object
1720
+ required: [username, display_name, password]
1721
+ properties:
1722
+ username: { $ref: "#/components/schemas/Username" }
1723
+ display_name: { type: string, minLength: 1, maxLength: 64 }
1724
+ password: { type: string, minLength: 8, maxLength: 128, writeOnly: true }
1725
+ bio: { type: [string, "null"], maxLength: 500 }
1726
+ LoginRequest:
1727
+ type: object
1728
+ required: [username, password]
1729
+ properties:
1730
+ username: { $ref: "#/components/schemas/Username" }
1731
+ password: { type: string, maxLength: 128, writeOnly: true }
1732
+ TwoFactorLoginRequest:
1733
+ type: object
1734
+ required: [login_challenge, code]
1735
+ properties:
1736
+ login_challenge: { type: string }
1737
+ code: { type: string, pattern: "^[0-9]{6}$" }
1738
+ TokenResponse:
1739
+ type: object
1740
+ required: [access_token, token_type, expires_in]
1741
+ properties:
1742
+ access_token: { type: string }
1743
+ token_type: { type: string, const: Bearer }
1744
+ expires_in:
1745
+ type: integer
1746
+ minimum: 1
1747
+ description: Seconds until the access token expires
1748
+ RegisterResponse:
1749
+ allOf:
1750
+ - $ref: "#/components/schemas/TokenResponse"
1751
+ - type: object
1752
+ description: Recovery parts of the temporary recovery flow (redesigned in release 1.1).
1753
+ properties:
1754
+ device_part: { type: string }
1755
+ qr_part: { type: string }
1756
+ TwoFactorChallenge:
1757
+ type: object
1758
+ required: [two_factor_required, login_challenge]
1759
+ properties:
1760
+ two_factor_required: { type: boolean, const: true }
1761
+ login_challenge: { type: string }
1762
+ LoginResponse:
1763
+ oneOf:
1764
+ - $ref: "#/components/schemas/TokenResponse"
1765
+ - $ref: "#/components/schemas/TwoFactorChallenge"
1766
+ RecoveryRequest:
1767
+ type: object
1768
+ required: [username]
1769
+ properties:
1770
+ username: { $ref: "#/components/schemas/Username" }
1771
+ device_part: { type: string }
1772
+ qr_part: { type: string }
1773
+ RecoveryResponse:
1774
+ type: object
1775
+ required: [recovery_token]
1776
+ properties:
1777
+ recovery_token: { type: string }
1778
+ ResetPasswordRequest:
1779
+ type: object
1780
+ required: [recovery_token, new_password]
1781
+ properties:
1782
+ recovery_token: { type: string }
1783
+ new_password: { type: string, minLength: 8, maxLength: 128, writeOnly: true }
1784
+ WebSocketTicket:
1785
+ type: object
1786
+ required: [ticket, expires_in]
1787
+ properties:
1788
+ ticket: { type: string }
1789
+ expires_in:
1790
+ type: integer
1791
+ minimum: 1
1792
+ description: Seconds the ticket stays valid
1793
+
1794
+ # -------------------------------------------------------------- me
1795
+ PasswordConfirmation:
1796
+ type: object
1797
+ required: [password]
1798
+ properties:
1799
+ password: { type: string, maxLength: 128, writeOnly: true }
1800
+ ChangePasswordRequest:
1801
+ type: object
1802
+ required: [current_password, new_password]
1803
+ properties:
1804
+ current_password: { type: string, maxLength: 128, writeOnly: true }
1805
+ new_password: { type: string, minLength: 8, maxLength: 128, writeOnly: true }
1806
+ UpdateMeRequest:
1807
+ type: object
1808
+ minProperties: 1
1809
+ properties:
1810
+ display_name: { type: string, minLength: 1, maxLength: 64 }
1811
+ bio: { type: [string, "null"], maxLength: 500 }
1812
+ SetAvatarRequest:
1813
+ type: object
1814
+ required: [attachment_id]
1815
+ properties:
1816
+ attachment_id: { $ref: "#/components/schemas/Id" }
1817
+ SecuritySettings:
1818
+ type: object
1819
+ required: [session_duration_days, two_factor_enabled]
1820
+ properties:
1821
+ session_duration_days: { type: integer, minimum: 1, maximum: 365 }
1822
+ two_factor_enabled: { type: boolean }
1823
+ UpdateSecuritySettingsRequest:
1824
+ type: object
1825
+ required: [session_duration_days]
1826
+ properties:
1827
+ session_duration_days: { type: integer, minimum: 1, maximum: 365 }
1828
+ Session:
1829
+ type: object
1830
+ required: [id, created_at, last_used_at, is_current]
1831
+ properties:
1832
+ id: { $ref: "#/components/schemas/Id" }
1833
+ device:
1834
+ type: [string, "null"]
1835
+ description: Sanitised user agent
1836
+ created_at: { $ref: "#/components/schemas/Timestamp" }
1837
+ last_used_at: { $ref: "#/components/schemas/Timestamp" }
1838
+ expires_at: { $ref: "#/components/schemas/Timestamp" }
1839
+ is_current: { type: boolean }
1840
+ TwoFactorSetup:
1841
+ type: object
1842
+ required: [secret, otpauth_uri]
1843
+ properties:
1844
+ secret: { type: string }
1845
+ otpauth_uri: { type: string }
1846
+ TwoFactorCode:
1847
+ type: object
1848
+ required: [code]
1849
+ properties:
1850
+ code: { type: string, pattern: "^[0-9]{6}$" }
1851
+ DisableTwoFactorRequest:
1852
+ type: object
1853
+ required: [password, code]
1854
+ properties:
1855
+ password: { type: string, maxLength: 128, writeOnly: true }
1856
+ code: { type: string, pattern: "^[0-9]{6}$" }
1857
+ Visibility:
1858
+ type: string
1859
+ description: Who may see the item. `*_except` scopes use the exception lists.
1860
+ enum: [everyone, contacts, everyone_except, nobody_except, nobody]
1861
+ DirectMessagePolicy:
1862
+ type: string
1863
+ enum: [everyone, shared_chats, wait_approval]
1864
+ GroupInvitePolicy:
1865
+ type: string
1866
+ enum: [everyone, contacts, everyone_except, nobody_except, nobody, wait_approval]
1867
+ SearchVisibility:
1868
+ type: string
1869
+ enum: [everyone, nobody]
1870
+ PrivacyExceptions:
1871
+ type: object
1872
+ description: User IDs per setting; `allow` applies to `nobody_except`, `deny` to `everyone_except`.
1873
+ additionalProperties:
1874
+ type: object
1875
+ required: [allow, deny]
1876
+ properties:
1877
+ allow:
1878
+ type: array
1879
+ items: { $ref: "#/components/schemas/Id" }
1880
+ deny:
1881
+ type: array
1882
+ items: { $ref: "#/components/schemas/Id" }
1883
+ PrivacySettings:
1884
+ type: object
1885
+ required:
1886
+ - avatar_visibility
1887
+ - profile_visibility
1888
+ - presence_visibility
1889
+ - read_receipts_enabled
1890
+ - direct_messages
1891
+ - group_invites
1892
+ - search_visibility
1893
+ - exceptions
1894
+ properties:
1895
+ avatar_visibility: { $ref: "#/components/schemas/Visibility" }
1896
+ profile_visibility: { $ref: "#/components/schemas/Visibility" }
1897
+ presence_visibility: { $ref: "#/components/schemas/Visibility" }
1898
+ read_receipts_enabled: { type: boolean }
1899
+ direct_messages: { $ref: "#/components/schemas/DirectMessagePolicy" }
1900
+ group_invites: { $ref: "#/components/schemas/GroupInvitePolicy" }
1901
+ search_visibility: { $ref: "#/components/schemas/SearchVisibility" }
1902
+ exceptions: { $ref: "#/components/schemas/PrivacyExceptions" }
1903
+ UpdatePrivacySettingsRequest:
1904
+ type: object
1905
+ minProperties: 1
1906
+ properties:
1907
+ avatar_visibility: { $ref: "#/components/schemas/Visibility" }
1908
+ profile_visibility: { $ref: "#/components/schemas/Visibility" }
1909
+ presence_visibility: { $ref: "#/components/schemas/Visibility" }
1910
+ read_receipts_enabled: { type: boolean }
1911
+ direct_messages: { $ref: "#/components/schemas/DirectMessagePolicy" }
1912
+ group_invites: { $ref: "#/components/schemas/GroupInvitePolicy" }
1913
+ search_visibility: { $ref: "#/components/schemas/SearchVisibility" }
1914
+ PrivacyExceptionsRequest:
1915
+ type: object
1916
+ required: [user_ids]
1917
+ properties:
1918
+ user_ids:
1919
+ type: array
1920
+ maxItems: 500
1921
+ items: { $ref: "#/components/schemas/Id" }
1922
+
1923
+ # ----------------------------------------------------------- users
1924
+ User:
1925
+ type: object
1926
+ description: A user as visible to the requesting user; hidden fields are `null`.
1927
+ required: [id, username, display_name, avatar_url, bio, is_online, last_seen, is_deleted]
1928
+ properties:
1929
+ id: { $ref: "#/components/schemas/Id" }
1930
+ username: { $ref: "#/components/schemas/Username" }
1931
+ display_name: { type: string }
1932
+ contact_name:
1933
+ type: [string, "null"]
1934
+ description: The requesting user's own name for this user, if set.
1935
+ avatar_url:
1936
+ type: [string, "null"]
1937
+ description: Path of the avatar, always same-origin (`/api/v2/users/{id}/avatar`).
1938
+ bio: { type: [string, "null"] }
1939
+ is_online: { type: boolean }
1940
+ last_seen:
1941
+ oneOf:
1942
+ - $ref: "#/components/schemas/Timestamp"
1943
+ - type: "null"
1944
+ is_deleted: { type: boolean }
1945
+ Me:
1946
+ allOf:
1947
+ - $ref: "#/components/schemas/User"
1948
+ - type: object
1949
+ required: [created_at]
1950
+ properties:
1951
+ created_at: { $ref: "#/components/schemas/Timestamp" }
1952
+ UserPage:
1953
+ type: object
1954
+ required: [items, next_cursor]
1955
+ properties:
1956
+ items:
1957
+ type: array
1958
+ items: { $ref: "#/components/schemas/User" }
1959
+ next_cursor: { $ref: "#/components/schemas/Cursor" }
1960
+ AvatarVersion:
1961
+ type: object
1962
+ required: [id, url, created_at]
1963
+ properties:
1964
+ id: { $ref: "#/components/schemas/Id" }
1965
+ url: { type: string }
1966
+ created_at: { $ref: "#/components/schemas/Timestamp" }
1967
+ ContactNameRequest:
1968
+ type: object
1969
+ required: [contact_name]
1970
+ properties:
1971
+ contact_name: { type: string, minLength: 1, maxLength: 64 }
1972
+
1973
+ # ----------------------------------------------------- attachments
1974
+ AttachmentKind:
1975
+ type: string
1976
+ enum: [image, audio, voice, video, file]
1977
+ UploadPurpose:
1978
+ type: string
1979
+ enum: [message, avatar]
1980
+ UploadAttachmentRequest:
1981
+ type: object
1982
+ required: [file, purpose]
1983
+ properties:
1984
+ file:
1985
+ type: string
1986
+ format: binary
1987
+ purpose: { $ref: "#/components/schemas/UploadPurpose" }
1988
+ kind: { $ref: "#/components/schemas/AttachmentKind" }
1989
+ duration_ms:
1990
+ type: integer
1991
+ minimum: 0
1992
+ description: Length of a voice message
1993
+ waveform:
1994
+ type: array
1995
+ description: Amplitude samples (0-255) of a voice message
1996
+ maxItems: 128
1997
+ items: { type: integer, minimum: 0, maximum: 255 }
1998
+ Attachment:
1999
+ type: object
2000
+ required: [id, kind, filename, content_type, size, url]
2001
+ properties:
2002
+ id: { $ref: "#/components/schemas/Id" }
2003
+ kind: { $ref: "#/components/schemas/AttachmentKind" }
2004
+ filename: { type: string }
2005
+ content_type:
2006
+ type: string
2007
+ description: Detected by the server
2008
+ size:
2009
+ type: integer
2010
+ minimum: 0
2011
+ description: Bytes
2012
+ url:
2013
+ type: string
2014
+ description: Same-origin path (`/api/v2/attachments/{id}/content`) set by the server.
2015
+ readOnly: true
2016
+ width: { type: [integer, "null"], minimum: 1 }
2017
+ height: { type: [integer, "null"], minimum: 1 }
2018
+ thumbnail_url: { type: [string, "null"], readOnly: true }
2019
+ duration_ms: { type: [integer, "null"], minimum: 0 }
2020
+ waveform:
2021
+ type: [array, "null"]
2022
+ items: { type: integer, minimum: 0, maximum: 255 }
2023
+ MediaKind:
2024
+ type: string
2025
+ enum: [image, audio]
2026
+
2027
+ # -------------------------------------------------------- messages
2028
+ MessageType:
2029
+ type: string
2030
+ enum: [text, file, voice, system]
2031
+ Reaction:
2032
+ type: object
2033
+ required: [emoji, user_id]
2034
+ properties:
2035
+ emoji: { type: string, minLength: 1, maxLength: 32 }
2036
+ user_id: { $ref: "#/components/schemas/Id" }
2037
+ ReadReceipt:
2038
+ type: object
2039
+ required: [user_id, read_at]
2040
+ properties:
2041
+ user_id: { $ref: "#/components/schemas/Id" }
2042
+ read_at: { $ref: "#/components/schemas/Timestamp" }
2043
+ ForwardedFrom:
2044
+ type: object
2045
+ required: [message_id, sender_id, sender_name]
2046
+ properties:
2047
+ message_id: { $ref: "#/components/schemas/Id" }
2048
+ sender_id:
2049
+ oneOf:
2050
+ - $ref: "#/components/schemas/Id"
2051
+ - type: "null"
2052
+ sender_name: { type: string }
2053
+ Message:
2054
+ type: object
2055
+ required:
2056
+ - id
2057
+ - chat_id
2058
+ - type
2059
+ - sender
2060
+ - content
2061
+ - attachment
2062
+ - reply_to
2063
+ - forwarded_from
2064
+ - reactions
2065
+ - read_by
2066
+ - created_at
2067
+ - edited_at
2068
+ - is_deleted
2069
+ properties:
2070
+ id: { $ref: "#/components/schemas/Id" }
2071
+ chat_id: { $ref: "#/components/schemas/Id" }
2072
+ type: { $ref: "#/components/schemas/MessageType" }
2073
+ sender:
2074
+ description: "`null` for system messages."
2075
+ oneOf:
2076
+ - $ref: "#/components/schemas/User"
2077
+ - type: "null"
2078
+ content:
2079
+ type: [string, "null"]
2080
+ maxLength: 4096
2081
+ description: Text, or the caption of a file; `null` when deleted.
2082
+ attachment:
2083
+ oneOf:
2084
+ - $ref: "#/components/schemas/Attachment"
2085
+ - type: "null"
2086
+ reply_to:
2087
+ oneOf:
2088
+ - $ref: "#/components/schemas/Id"
2089
+ - type: "null"
2090
+ forwarded_from:
2091
+ oneOf:
2092
+ - $ref: "#/components/schemas/ForwardedFrom"
2093
+ - type: "null"
2094
+ reactions:
2095
+ type: array
2096
+ items: { $ref: "#/components/schemas/Reaction" }
2097
+ read_by:
2098
+ type: array
2099
+ items: { $ref: "#/components/schemas/ReadReceipt" }
2100
+ created_at: { $ref: "#/components/schemas/Timestamp" }
2101
+ edited_at:
2102
+ oneOf:
2103
+ - $ref: "#/components/schemas/Timestamp"
2104
+ - type: "null"
2105
+ is_deleted: { type: boolean }
2106
+ client_temp_id: { $ref: "#/components/schemas/ClientTempId" }
2107
+ MessagePage:
2108
+ type: object
2109
+ required: [items, next_cursor, prev_cursor]
2110
+ properties:
2111
+ items:
2112
+ type: array
2113
+ description: Oldest to newest
2114
+ items: { $ref: "#/components/schemas/Message" }
2115
+ next_cursor:
2116
+ description: |
2117
+ Continues in the direction of the request: pass it as `before` (older) for the
2118
+ default, `before` and `around` requests, or as `after` (newer) for `after` requests.
2119
+ oneOf:
2120
+ - $ref: "#/components/schemas/Id"
2121
+ - type: "null"
2122
+ prev_cursor:
2123
+ description: The opposite direction (`after` for the default request, `before` for an `after` request).
2124
+ oneOf:
2125
+ - $ref: "#/components/schemas/Id"
2126
+ - type: "null"
2127
+ SearchHit:
2128
+ type: object
2129
+ required: [message]
2130
+ properties:
2131
+ message: { $ref: "#/components/schemas/Message" }
2132
+ highlight:
2133
+ type: [string, "null"]
2134
+ description: Plain-text excerpt; never HTML.
2135
+ MessageSearchPage:
2136
+ type: object
2137
+ required: [items, next_cursor]
2138
+ properties:
2139
+ items:
2140
+ type: array
2141
+ items: { $ref: "#/components/schemas/SearchHit" }
2142
+ next_cursor: { $ref: "#/components/schemas/Cursor" }
2143
+ SendMessageRequest:
2144
+ type: object
2145
+ required: [client_temp_id, type]
2146
+ properties:
2147
+ client_temp_id: { $ref: "#/components/schemas/ClientTempId" }
2148
+ type:
2149
+ type: string
2150
+ enum: [text, file, voice]
2151
+ content:
2152
+ type: [string, "null"]
2153
+ maxLength: 4096
2154
+ attachment_id:
2155
+ description: Required and not null for `file` and `voice`; never a URL.
2156
+ oneOf:
2157
+ - $ref: "#/components/schemas/Id"
2158
+ - type: "null"
2159
+ reply_to:
2160
+ oneOf:
2161
+ - $ref: "#/components/schemas/Id"
2162
+ - type: "null"
2163
+ if:
2164
+ properties:
2165
+ type: { enum: [file, voice] }
2166
+ then:
2167
+ required: [attachment_id]
2168
+ properties:
2169
+ attachment_id: { $ref: "#/components/schemas/Id" }
2170
+ else:
2171
+ required: [content]
2172
+ properties:
2173
+ content: { type: string, minLength: 1, maxLength: 4096 }
2174
+ attachment_id: { not: {} }
2175
+ EditMessageRequest:
2176
+ type: object
2177
+ required: [content]
2178
+ properties:
2179
+ content: { type: string, minLength: 1, maxLength: 4096 }
2180
+ ForwardMessageRequest:
2181
+ type: object
2182
+ required: [chat_ids]
2183
+ properties:
2184
+ chat_ids:
2185
+ type: array
2186
+ minItems: 1
2187
+ maxItems: 20
2188
+ uniqueItems: true
2189
+ items: { $ref: "#/components/schemas/Id" }
2190
+ MarkReadRequest:
2191
+ type: object
2192
+ required: [message_id]
2193
+ properties:
2194
+ message_id:
2195
+ description: Everything up to and including this message is read.
2196
+ $ref: "#/components/schemas/Id"
2197
+
2198
+ # ------------------------------------------------- chats and groups
2199
+ ChatType:
2200
+ type: string
2201
+ enum: [direct, group]
2202
+ GroupRole:
2203
+ type: string
2204
+ enum: [owner, admin, member]
2205
+ Chat:
2206
+ type: object
2207
+ required:
2208
+ - id
2209
+ - type
2210
+ - name
2211
+ - avatar_url
2212
+ - peer
2213
+ - is_pinned
2214
+ - unread_count
2215
+ - last_message
2216
+ - created_at
2217
+ properties:
2218
+ id: { $ref: "#/components/schemas/Id" }
2219
+ type: { $ref: "#/components/schemas/ChatType" }
2220
+ name:
2221
+ type: string
2222
+ description: Display name of the peer or the group
2223
+ avatar_url: { type: [string, "null"] }
2224
+ peer:
2225
+ description: The other user of a direct chat; `null` for groups.
2226
+ oneOf:
2227
+ - $ref: "#/components/schemas/User"
2228
+ - type: "null"
2229
+ is_pinned: { type: boolean }
2230
+ unread_count: { type: integer, minimum: 0 }
2231
+ last_message:
2232
+ oneOf:
2233
+ - $ref: "#/components/schemas/Message"
2234
+ - type: "null"
2235
+ my_role:
2236
+ description: Only for groups.
2237
+ oneOf:
2238
+ - $ref: "#/components/schemas/GroupRole"
2239
+ - type: "null"
2240
+ created_at: { $ref: "#/components/schemas/Timestamp" }
2241
+ ChatPage:
2242
+ type: object
2243
+ required: [items, next_cursor]
2244
+ properties:
2245
+ items:
2246
+ type: array
2247
+ items: { $ref: "#/components/schemas/Chat" }
2248
+ next_cursor: { $ref: "#/components/schemas/Cursor" }
2249
+ CreateChatRequest:
2250
+ type: object
2251
+ required: [user_id]
2252
+ properties:
2253
+ user_id: { $ref: "#/components/schemas/Id" }
2254
+ initial_message:
2255
+ type: [string, "null"]
2256
+ maxLength: 4096
2257
+ GroupMember:
2258
+ type: object
2259
+ required: [user, role, joined_at]
2260
+ properties:
2261
+ user: { $ref: "#/components/schemas/User" }
2262
+ role: { $ref: "#/components/schemas/GroupRole" }
2263
+ joined_at: { $ref: "#/components/schemas/Timestamp" }
2264
+ Group:
2265
+ type: object
2266
+ required: [id, name, description, avatar_url, owner_id, my_role, members, created_at]
2267
+ properties:
2268
+ id: { $ref: "#/components/schemas/Id" }
2269
+ name: { type: string, minLength: 1, maxLength: 64 }
2270
+ description: { type: [string, "null"], maxLength: 500 }
2271
+ avatar_url: { type: [string, "null"] }
2272
+ owner_id: { $ref: "#/components/schemas/Id" }
2273
+ my_role: { $ref: "#/components/schemas/GroupRole" }
2274
+ members:
2275
+ type: array
2276
+ items: { $ref: "#/components/schemas/GroupMember" }
2277
+ created_at: { $ref: "#/components/schemas/Timestamp" }
2278
+ CreateGroupRequest:
2279
+ type: object
2280
+ required: [name, member_ids]
2281
+ properties:
2282
+ name: { type: string, minLength: 1, maxLength: 64 }
2283
+ description: { type: [string, "null"], maxLength: 500 }
2284
+ member_ids:
2285
+ type: array
2286
+ maxItems: 200
2287
+ uniqueItems: true
2288
+ items: { $ref: "#/components/schemas/Id" }
2289
+ UpdateGroupRequest:
2290
+ type: object
2291
+ minProperties: 1
2292
+ properties:
2293
+ name: { type: string, minLength: 1, maxLength: 64 }
2294
+ description: { type: [string, "null"], maxLength: 500 }
2295
+ AddParticipantRequest:
2296
+ type: object
2297
+ required: [user_id]
2298
+ properties:
2299
+ user_id: { $ref: "#/components/schemas/Id" }
2300
+ UpdateRoleRequest:
2301
+ type: object
2302
+ required: [role]
2303
+ properties:
2304
+ role:
2305
+ type: string
2306
+ enum: [admin, member]
2307
+ TransferOwnerRequest:
2308
+ type: object
2309
+ required: [user_id]
2310
+ properties:
2311
+ user_id: { $ref: "#/components/schemas/Id" }
2312
+
2313
+ # -------------------------------------------------------- requests
2314
+ ApprovalRequestType:
2315
+ type: string
2316
+ enum: [direct_message, group_invite]
2317
+ ApprovalRequestStatus:
2318
+ type: string
2319
+ enum: [pending, approved, rejected]
2320
+ ApprovalRequest:
2321
+ type: object
2322
+ required: [id, type, status, requester, created_at]
2323
+ properties:
2324
+ id: { $ref: "#/components/schemas/Id" }
2325
+ type: { $ref: "#/components/schemas/ApprovalRequestType" }
2326
+ status: { $ref: "#/components/schemas/ApprovalRequestStatus" }
2327
+ requester: { $ref: "#/components/schemas/User" }
2328
+ group_name:
2329
+ type: [string, "null"]
2330
+ description: Only for `group_invite`
2331
+ preview:
2332
+ type: [string, "null"]
2333
+ description: First message of a direct-message request
2334
+ created_at: { $ref: "#/components/schemas/Timestamp" }