summary refs log tree commit diff
path: root/docs/client-server/specification.rst
blob: 7df2bb14c50d630e4c133dbb6dce041c3776566b (plain) (blame)
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
=========================
Synapse Client-Server API
=========================

The following specification outlines how a client can send and receive data from 
a home server.

[[TODO(kegan): 4/7/14 Grilling
- Mechanism for getting historical state changes (e.g. topic updates) - add 
  query param flag?
- Generic mechanism for linking first class events (e.g. feedback) with other s
  first class events (e.g. messages)?
- Generic mechanism for updating 'stuff about the room' (e.g. favourite coffee) 
  AND specifying clobbering rules (clobber/add to list/etc)?
- How to ensure a consistent view for clients paginating through room lists? 
  They aren't really ordered in any way, and if you're paginating
  through them, how can you show them a consistent result set? Temporary 'room 
  list versions' akin to event version? How does that work?
]]

[[TODO(kegan):
Outstanding problems / missing spec:
- Push
- Typing notifications
]]

Terminology
-----------
Stream Tokens: 
An opaque token used to make further streaming requests. When using any 
pagination streaming API, responses will contain a start and end stream token. 
When reconnecting to the stream, these tokens can be used to tell the server 
where the client got up to in the stream.

Event ID:
Every event that comes down the event stream or that is returned from the REST
API has an associated event ID (event_id). This ID will be the same between the 
REST API and the event stream, so any duplicate events can be clobbered 
correctly without knowing anything else about the event.

Message ID:
The ID of a message sent by a client in a room. Clients send IMs to each other 
in rooms. Each IM sent by a client must have a unique message ID which is unique
for that particular client.

User ID:
The @username:host style ID of the client. When registering for an account, the 
client specifies their username. The user_id is this username along with the 
home server's unique hostname. When federating between home servers, the user_id
is used to uniquely identify users across multiple home servers.

Room ID:
The room_id@host style ID for the room. When rooms are created, the client either
specifies or is allocated a room ID. This room ID must be used to send messages 
in that room. Like with clients, there may be multiple rooms with the same ID 
across multiple home servers. The room_id is used to uniquely identify a room 
when federating.

Global message ID:
The globally unique ID for a message. This ID is formed from the msg_id, the 
client's user_id and the room_id. This uniquely identifies any 
message. It is represented with '-' as the delimeter between IDs. The 
global_msg_id is of the form: room_id-user_id-msg_id


REST API and the Event Stream
-----------------------------
Clients send data to the server via a RESTful API. They can receive data via 
this API or from an event stream. An event stream is a special path which 
streams all events the client may be interested in. This makes it easy to 
immediately receive updates from the REST API. All data is represented as JSON.

Pagination streaming API
========================
Clients are often interested in very large datasets. The data itself could
be 1000s of messages in a given room, 1000s of rooms in a public room list, or
1000s of events (presence, typing, messages, etc) in the system. It is not
practical to send vast quantities of data to the client every time they
request a list of public rooms for example. There needs to be a way to show a
subset of this data, and apply various filters to it. This is what the pagination
streaming API is. This API defines standard request/response parameters which 
can be used when navigating this stream of data.

Pagination Request Query Parameters
-----------------------------------
Clients may wish to paginate results from the event stream, or other sources of 
information where the amount of information may be a problem,
e.g. in a room with 10,000s messages. The pagination query parameters provide a 
way to navigate a 'window' around a large set of data. These
parameters are only valid for GET requests.
       
        S e r v e r - s i d e   d a t a
 |-------------------------------------------------|
START      ^               ^                      END
           |_______________|
                   |
            Client-extraction

'START' and 'END' are magic token values which specify the start and end of the 
dataset respectively.

Query parameters:
  from : $streamtoken - The opaque token to start streaming from.
  to : $streamtoken - The opaque token to end streaming at. Typically,
       clients will not know the item of data to end at, so this will usually be 
       START or END.
  limit : integer - An integer representing the maximum number of items to 
          return.

For example, the event stream has events E1 -> E15. The client wants the last 5 
events and doesn't know any previous events:

S                                                    E
|-E1-E2-E3-E4-E5-E6-E7-E8-E9-E10-E11-E12-E13-E14-E15-|
|                               |                    |
|                          _____|                    |
|__________________       |       ___________________|
                   |      |      |
 GET /events?to=START&limit=5&from=END
 Returns:
   E15,E14,E13,E12,E11


Another example: a public room list has rooms R1 -> R17. The client is showing 5 
rooms at a time on screen, and is on page 2. They want to
now show page 3 (rooms R11 -> 15):

S                                                           E
|  0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16 | stream token
|-R1-R2-R3-R4-R5-R6-R7-R8-R9-R10-R11-R12-R13-R14-R15-R16-R17| room
                  |____________| |________________|
                        |                |
                    Currently            |
                    viewing              |
                                         |
                         GET /rooms/list?from=9&to=END&limit=5
                         Returns: R11,R12,R13,R14,R15
                         
Note that tokens are treated in an *exclusive*, not inclusive, manner. The end 
token from the intial request was '9' which corresponded to R10. When the 2nd
request was made, R10 did not appear again, even though from=9 was specified. If
you know the token, you already have the data.

Pagination Response
-------------------
Responses to pagination requests MUST follow the format:
{
  "chunk": [ ... , Responses , ... ],
  "start" : $streamtoken,
  "end" : $streamtoken
}
Where $streamtoken is an opaque token which can be used in another query to
get the next set of results. The "start" and "end" keys can only be omitted if
the complete dataset is provided in "chunk".

If the client wants earlier results, they should use from=$start_streamtoken,
to=START. Likewise, if the client wants later results, they should use
from=$end_streamtoken, to=END.

Unless specified, the default pagination parameters are from=START, to=END, 
without a limit set. This allows you to hit an API like
/events without any query parameters to get everything.

The Event Stream
----------------
The event stream returns events using the pagination streaming API. When the 
client disconnects for a while and wants to reconnect to the event stream, they 
should specify from=$end_streamtoken. This lets the server know where in the 
event stream the client is. These tokens are completely opaque, and the client 
cannot infer anything from them.

  GET /events?from=$LAST_STREAM_TOKEN
  REST Path: /events
  Returns (success): A JSON array of Event Data.
  Returns (failure): An Error Response

LAST_STREAM_TOKEN is the last stream token obtained from the event stream. If the 
client is connecting for the first time and does not know any stream tokens,
they can use "START" to request all events from the start. For more information 
on this, see "Pagination Request Query Parameters".

The event stream supports shortpoll and longpoll with the "timeout" query
parameter. This parameter specifies the number of milliseconds the server should
hold onto the connection waiting for incoming events. If no events occur in this
period, the connection will be closed and an empty chunk will be returned. To
use shortpoll, specify "timeout=0".

Event Data
----------
This is a JSON object which looks like:
{
  "event_id" : $EVENT_ID,
  "type" : $EVENT_TYPE,
  $URL_ARGS,
  "content" : {
    $EVENT_CONTENT
  }
}

EVENT_ID
  An ID identifying this event. This is so duplicate events can be suppressed on
  the client.

EVENT_TYPE
  The namespaced event type (m.*)

URL_ARGS
  Path specific data from the REST API.

EVENT_CONTENT
  The event content, matching the REST content PUT previously.

Events are differentiated via the event type "type" key. This is the type of 
event being received. This can be expanded upon by using different namespaces. 
Every event MUST have a 'type' key.

Most events will have a corresponding REST URL. This URL will generally have 
data in it to represent the resource being modified,
e.g. /rooms/$room_id. The event data will contain extra top-level keys to expose 
this information to clients listening on an event
stream. The event content maps directly to the contents submitted via the REST 
API.

For example:
  Event Type: m.example.room.members
  REST Path: /examples/room/$room_id/members/$user_id
  REST Content: { "membership" : "invited" }
  
is represented in the event stream as:

{
  "event_id" : "e_some_event_id",
  "type" : "m.example.room.members",
  "room_id" : $room_id,
  "user_id" : $user_id,
  "content" : {
    "membership" : "invited"
  }
}

As convention, the URL variable "$varname" will map directly onto the name 
of the JSON key "varname".

Error Responses
---------------
If the client sends an invalid request, the server MAY respond with an error 
response. This is of the form:
{
  "error" : "string",
  "errcode" : "string"
}
The 'error' string will be a human-readable error message, usually a sentence
explaining what went wrong. 

The 'errcode' string will be a unique string which can be used to handle an 
error message e.g. "M_FORBIDDEN". These error codes should have their namespace 
first in ALL CAPS, followed by a single _. For example, if there was a custom
namespace com.mydomain.here, and a "FORBIDDEN" code, the error code should look
like "COM.MYDOMAIN.HERE_FORBIDDEN". There may be additional keys depending on 
the error, but the keys 'error' and 'errcode' will always be present. 

Some standard error codes are below:

M_FORBIDDEN:
Forbidden access, e.g. bad access token, failed login.

M_BAD_JSON:
Request contained valid JSON, but it was malformed in some way, e.g. missing
required keys, invalid values for keys.

M_NOT_JSON:
Request did not contain valid JSON.

M_NOT_FOUND:
No resource was found for this request.

Some requests have unique error codes:

M_USER_IN_USE:
Encountered when trying to register a user ID which has been taken.

M_ROOM_IN_USE:
Encountered when trying to create a room which has been taken.

M_BAD_PAGINATION:
Encountered when specifying bad pagination values to a Pagination Streaming API.


========
REST API
========

All content must be application/json. Some keys are required, while others are 
optional. Unless otherwise specified,
all HTTP PUT/POST/DELETEs will return a 200 OK with an empty response body on 
success, and a 4xx/5xx with an optional Error Response on failure. When sending 
data, if there are no keys to send, an empty JSON object should be sent.

All POST/PUT/GET/DELETE requests MUST have an 'access_token' query parameter to 
allow the server to authenticate the client. All
POST requests MUST be submitted as application/json. 

All paths MUST be namespaced by the version of the API being used. This should
be:

/matrix/client/api/v1

All REST paths in this section MUST be prefixed with this. E.g.
  REST Path: /rooms/$room_id
  Absolute Path: /matrix/client/api/v1/rooms/$room_id

Registration
============
Clients must register with the server in order to use the service. After 
registering, the client will be given an
access token which must be used in ALL requests as a query parameter 
'access_token'.

Registering for an account
--------------------------
  POST /register
  With: A JSON object containing the key "user_id" which contains the desired 
        user_id, or an empty JSON object to have the server allocate a user_id 
        automatically.
  Returns (success): 200 OK with a JSON object:
                     {
                       "user_id" : "string [user_id]",
                       "access_token" : "string"
                     }
  Returns (failure): An Error Response. M_USER_IN_USE if the user ID is taken.
                     

Unregistering an account
------------------------
  POST /unregister
  With query parameters: access_token=$ACCESS_TOKEN
  Returns (success): 200 OK
  Returns (failure): An Error Response.
  
  
Logging in to an existing account
=================================
If the client has already registered, they need to be able to login to their
account. The home server may provide many different ways of logging in, such
as user/password auth, login via a social network (OAuth), login by confirming 
a token sent to their email address, etc. This section does NOT define how home 
servers should authorise their users who want to login to their existing 
accounts. This section defines the standard interface which implementations 
should follow so that ANY client can login to ANY home server.

The login process breaks down into the following:
  1: Get login process info.
  2: Submit the login stage credentials.
  3: Get access token or be told the next stage in the login process and repeat 
     step 2.
     
Getting login process info:
  GET /login
  Returns (success): 200 OK with LoginInfo.
  Returns (failure): An Error Response.
  
Submitting the login stage credentials:
  POST /login
  With: LoginSubmission
  Returns (success): 200 OK with LoginResult
  Returns (failure): An Error Response
  
Where LoginInfo is a JSON object which MUST have a "type" key which denotes the 
login type. If there are multiple login stages, this object MUST also contain a 
"stages" key, which has a JSON array of login types denoting all the steps in 
order to login, including the first stage which is in "type". This allows the 
client to make an informed decision as to whether or not they can natively
handle the entire login process, or whether they should fallback (see below).

Where LoginSubmission is a JSON object which MUST have a "type" key.

Where LoginResult is a JSON object which MUST have either a "next" key OR an
"access_token" key, depending if the login process is over or not. This object
MUST have a "session" key if multiple POSTs need to be sent to /login.

Fallback
--------
If the client does NOT know how to handle the given type, they should:
  GET /login/fallback
This MUST return an HTML page which can perform the entire login process.

Password-based
--------------
Type: "m.login.password"
LoginSubmission:
{
  "type": "m.login.password",
  "user": <user_id>,
  "password": <password>
}

Example:
Assume you are @bob:matrix.org and you wish to login on another mobile device.
First, you GET /login which returns:
{
  "type": "m.login.password"
}
Your client knows how to handle this, so your client prompts the user to enter
their username and password. This is then submitted:
{
  "type": "m.login.password",
  "user": "@bob:matrix.org",
  "password": "monkey"
}
The server checks this, finds it is valid, and returns:
{
  "access_token": "abcdef0123456789"
}

OAuth2-based
------------
Type: "m.login.oauth2"
This is a multi-stage login.

LoginSubmission:
{
  "type": "m.login.oauth2",
  "user": <user_id>
}
Returns:
{
  "uri": <Authorization Request uri OR service selection uri>
}

The home server acts as a 'confidential' Client for the purposes of OAuth2.

If the uri is a "sevice selection uri", it is a simple page which prompts the 
user to choose which service to authorize with. On selection of a service, they
link through to Authorization Request URIs. If there is only 1 service which the
home server accepts when logging in, this indirection can be skipped and the
"uri" key can be the Authorization Request URI. 

The client visits the Authorization Request URI, which then shows the OAuth2 
Allow/Deny prompt. Hitting 'Allow' returns the redirect URI with the auth code. 
Home servers can choose any path for the redirect URI. The client should visit 
the redirect URI, which will then finish the OAuth2 login process, granting the 
home server an access token for the chosen service. When the home server gets 
this access token, it knows that the cilent has authed with the 3rd party, and 
so can return a LoginResult.

The OAuth redirect URI (with auth code) MUST return a LoginResult.
    
Example:
Assume you are @bob:matrix.org and you wish to login on another mobile device.
First, you GET /login which returns:
{
  "type": "m.login.oauth2"
}
Your client knows how to handle this, so your client prompts the user to enter
their username. This is then submitted:
{
  "type": "m.login.oauth2",
  "user": "@bob:matrix.org"
}
The server only accepts auth from Google, so returns the Authorization Request
URI for Google:
{
  "uri": "https://accounts.google.com/o/oauth2/auth?response_type=code&
  client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos"
}
The client then visits this URI and authorizes the home server. The client then
visits the REDIRECT_URI with the auth code= query parameter which returns:
{
  "access_token": "0123456789abcdef"
}

Email-based (code)
------------------
Type: "m.login.email.code"
This is a multi-stage login.

First LoginSubmission:
{
  "type": "m.login.email.code",
  "user": <user_id>
  "email": <email address>
}
Returns:
{
  "session": <session id>
}

The email contains a code which must be sent in the next LoginSubmission:
{
  "type": "m.login.email.code",
  "session": <session id>,
  "code": <code in email sent>
}
Returns:
{
  "access_token": <access token>
}

Example:
Assume you are @bob:matrix.org and you wish to login on another mobile device.
First, you GET /login which returns:
{
  "type": "m.login.email.code"
}
Your client knows how to handle this, so your client prompts the user to enter
their email address. This is then submitted:
{
  "type": "m.login.email.code",
  "user": "@bob:matrix.org",
  "email": "bob@mydomain.com"
}
The server confirms that bob@mydomain.com is linked to @bob:matrix.org, then 
sends an email to this address and returns:
{
  "session": "ewuigf7462"
}
The client's screen changes to a code submission page. The email arrives and it 
says something to the effect of "please enter 2348623 into the app". This is
the submitted along with the session:
{
  "type": "m.login.email.code",
  "session": "ewuigf7462",
  "code": "2348623"
}
The server accepts this and returns:
{
  "access_token": "abcdef0123456789"
}

Email-based (url)
-----------------
Type: "m.login.email.url"
This is a multi-stage login.

First LoginSubmission:
{
  "type": "m.login.email.url",
  "user": <user_id>
  "email": <email address>
}
Returns:
{
  "session": <session id>
}

The email contains a URL which must be clicked. After it has been clicked, the
client should perform a request:
{
  "type": "m.login.email.code",
  "session": <session id>
}
Returns:
{
  "access_token": <access token>
}

Example:
Assume you are @bob:matrix.org and you wish to login on another mobile device.
First, you GET /login which returns:
{
  "type": "m.login.email.url"
}
Your client knows how to handle this, so your client prompts the user to enter
their email address. This is then submitted:
{
  "type": "m.login.email.url",
  "user": "@bob:matrix.org",
  "email": "bob@mydomain.com"
}
The server confirms that bob@mydomain.com is linked to @bob:matrix.org, then 
sends an email to this address and returns:
{
  "session": "ewuigf7462"
}
The client then starts polling the server with the following:
{
  "type": "m.login.email.url",
  "session": "ewuigf7462"
}
(Alternatively, the server could send the device a push notification when the
email has been validated). The email arrives and it contains a URL to click on.
The user clicks on the which completes the login process with the server. The
next time the client polls, it returns:
{
  "access_token": "abcdef0123456789"
}

N-Factor auth
-------------
Multiple login stages can be combined with the "next" key in the LoginResult.

Example:
A server demands an email.code then password auth before logging in. First, the
client performs a GET /login which returns:
{
  "type": "m.login.email.code",
  "stages": ["m.login.email.code", "m.login.password"]
}
The client performs the email login (See "Email-based (code)"), but instead of
returning an access_token, it returns:
{
  "next": "m.login.password"
}
The client then presents a user/password screen and the login continues until
this is complete (See "Password-based"), which then returns the "access_token".

Rooms
=====
A room is a conceptual place where users can send and receive messages. Rooms 
can be created, joined and left. Messages are sent
to a room, and all participants in that room will receive the message. Rooms are 
uniquely identified via the room_id.

Creating a room (with a room ID)
--------------------------------
  Event Type: m.room.create [TODO(kegan): Do we generate events for this?]
  REST Path: /rooms/$room_id
  Valid methods: PUT
  Required keys: None.
  Optional keys:
    visibility : [public|private] - Set whether this room shows up in the public 
    room list.
  Returns:
    On Failure: MAY return a suggested alternative room ID if this room ID is 
    taken.
    {
      suggested_room_id : $new_room_id
      error : "Room already in use."
      errcode : "M_ROOM_IN_USE"
    }
    

Creating a room (without a room ID)
-----------------------------------
  Event Type: m.room.create [TODO(kegan): Do we generate events for this?]
  REST Path: /rooms
  Valid methods: POST
  Required keys: None.
  Optional keys:
    visibility : [public|private] - Set whether this room shows up in the public 
    room list.
  Returns:
    On Success: The allocated room ID. Additional information about the room
    such as the visibility MAY be included as extra keys in this response.
    {
      room_id : $room_id
    }

Setting the topic for a room
----------------------------
  Event Type: m.room.topic
  REST Path: /rooms/$room_id/topic
  Valid methods: GET/PUT
  Required keys: 
    topic : $topicname - Set the topic to $topicname in room $room_id.


See a list of public rooms
--------------------------
  REST Path: /public/rooms?pagination_query_parameters
  Valid methods: GET
  This API can use pagination query parameters.
  Returns:
    {
      "chunk" : JSON array of RoomInfo JSON objects - Required.
      "start" : "string (start token)" - See Pagination Response.
      "end" : "string (end token)" - See Pagination Response.
      "total" : integer - Optional. The total number of rooms.
    }

RoomInfo: Information about a single room.
  Servers MUST send the key: room_id
  Servers MAY send the keys: topic, num_members
  {
    "room_id" : "string",
    "topic" : "string",
    "num_members" : integer
  }

Room Members
============

Invite/Joining/Leaving a room
-----------------------------
  Event Type: m.room.member
  REST Path: /rooms/$room_id/members/$user_id/state
  Valid methods: PUT/GET/DELETE
  Required keys:
    membership : [join|invite] - The membership state of $user_id in room 
                                 $room_id.

Where:
  join - Indicate you ($user_id) are joining the room $room_id.
  invite - Indicate that $user_id has been invited to room $room_id.

User $user_id can leave room $room_id by DELETEing this path.

Checking the user list of a room
--------------------------------
  REST Path: /rooms/$room_id/members/list
  This API can use pagination query parameters.
  Valid methods: GET
  Returns:
    A pagination response with chunk data as m.room.member events.

Messages
========
Users send messages to other users in rooms. These messages may be text, images, 
video, etc. Clients may also want to acknowledge messages by sending feedback, 
in the form of delivery/read receipts.

Server-attached keys
--------------------
The server MAY attach additional keys to messages and feedback. If a client 
submits keys with the same name, they will be clobbered by
the server.

Required keys:
from : "string [user_id]"
  The user_id of the user who sent the message/feedback.

Optional keys:
hsob_ts : integer
  A timestamp (ms resolution) representing when the message/feedback got to the 
  sender's home server ("home server outbound timestamp").

hsib_ts : integer
  A timestamp (ms resolution) representing when the 
  message/feedback got to the receiver's home server ("home server inbound 
  timestamp"). This may be the same as hsob_ts if the sender/receiver are on the 
  same home server.

Sending messages
----------------
  Event Type: m.room.message
  REST Path: /rooms/$room_id/messages/$from/$msg_id
  Valid methods: GET/PUT
  URL parameters:
    $from : user_id - The sender's user_id. This value will be clobbered by the 
    server before sending.
  Required keys: 
    msgtype: [m.text|m.emote|m.image|m.audio|m.video|m.location|m.file] - 
             The type of message. Not to be confused with the Event 'type'.
  Optional keys:
    sender_ts : integer - A timestamp (ms resolution) representing the 
                wall-clock time when the message was sent from the client.
  Reserved keys:
    body : "string" - The human readable string for compatibility with clients 
           which cannot process a given msgtype. This key is optional, but
           if it is included, it MUST be human readable text 
           describing the message. See individual msgtypes for more 
           info on what this means in practice.

Each msgtype may have required fields of their own.

msgtype: m.text
----------------
Required keys:
  body : "string" - The body of the message.
Optional keys:
  None.

msgtype: m.emote
-----------------
Required keys:
  body : "string" - *tries to come up with a witty explanation*.
Optional keys:
  None.

msgtype: m.image
-----------------
Required keys:
  url : "string" - The URL to the image.
Optional keys:
  body : "string" - info : JSON object (ImageInfo) - The image info for image 
         referred to in 'url'.
  thumbnail_url : "string" - The URL to the thumbnail.
  thumbnail_info : JSON object (ImageInfo) - The image info for the image 
                   referred to in 'thumbnail_url'.

ImageInfo: Information about an image.
{
  "size" : integer (size of image in bytes),
  "w" : integer (width of image in pixels),
  "h" : integer (height of image in pixels),
  "mimetype" : "string (e.g. image/jpeg)"
}

Interpretation of 'body' key: The alt text of the image, or some kind of content 
description for accessibility e.g. "image attachment".

msgtype: m.audio
-----------------
Required keys:
  url : "string" - The URL to the audio.
Optional keys:
  info : JSON object (AudioInfo) - The audio info for the audio referred to in 
         'url'.

AudioInfo: Information about a piece of audio. 
{
  "mimetype" : "string (e.g. audio/aac)",
  "size" : integer (size of audio in bytes),
  "duration" : integer (duration of audio in milliseconds)
}

Interpretation of 'body' key: A description of the audio e.g. "Bee Gees - 
Stayin' Alive", or some kind of content description for accessibility e.g. 
"audio attachment".

msgtype: m.video
-----------------
Required keys:
  url : "string" - The URL to the video.
Optional keys:
  info : JSON object (VideoInfo) - The video info for the video referred to in 
         'url'.

VideoInfo: Information about a video.
{
  "mimetype" : "string (e.g. video/mp4)",
  "size" : integer (size of video in bytes),
  "duration" : integer (duration of video in milliseconds),
  "w" : integer (width of video in pixels),
  "h" : integer (height of video in pixels),
  "thumbnail_url" : "string (URL to image)",
  "thumbanil_info" : JSON object (ImageInfo)
}

Interpretation of 'body' key: A description of the video e.g. "Gangnam style", 
or some kind of content description for accessibility e.g. "video attachment".

msgtype: m.location
--------------------
Required keys:
  geo_uri : "string" - The geo URI representing the location.
Optional keys:
  thumbnail_url : "string" - The URL to a thumnail of the location being 
                  represented.
  thumbnail_info : JSON object (ImageInfo) - The image info for the image 
                   referred to in 'thumbnail_url'.

Interpretation of 'body' key: A description of the location e.g. "Big Ben, 
London, UK", or some kind of content description for accessibility e.g. 
"location attachment".


Sending feedback
----------------
When you receive a message, you may want to send delivery receipt to let the 
sender know that the message arrived. You may also want to send a read receipt 
when the user has read the message. These receipts are collectively known as 
'feedback'.

  Event Type: m.room.message.feedback
  REST Path: /rooms/$room_id/messages/$msgfrom/$msg_id/feedback/$from/$feedback
  Valid methods: GET/PUT
  URL parameters:
    $msgfrom - The sender of the message's user_id.
    $from : user_id - The sender of the feedback's user_id. This value will be 
    clobbered by the server before sending.
    $feedback : [d|r] - Specify if this is a [d]elivery or [r]ead receipt.
  Required keys:
    None.
  Optional keys:
    sender_ts : integer - A timestamp (ms resolution) representing the 
    wall-clock time when the receipt was sent from the client.

Receiving messages (bulk/pagination)
------------------------------------
  Event Type: m.room.message
  REST Path: /rooms/$room_id/messages/list
  Valid methods: GET
  Query Parameters:
    feedback : [true|false] - Specify if feedback should be bundled with each 
    message.
  This API can use pagination query parameters.
  Returns:
    A JSON array of Event Data in "chunk" (see Pagination Response). If the 
    "feedback" parameter was set, the Event Data will also contain a "feedback" 
    key which contains a JSON array of feedback, with each element as Event Data 
    with compressed feedback for this message.

Event Data with compressed feedback is a special type of feedback with 
contextual keys removed. It is designed to limit the amount of redundant data 
being sent for feedback. This removes the type, event_id, room ID, 
message sender ID and message ID keys.

     ORIGINAL (via event streaming)
{
  "event_id":"e1247632487",
  "type":"m.room.message.feedback",
  "from":"string [user_id]",
  "feedback":"string [d|r]",
  "room_id":"$room_id",
  "msg_id":"$msg_id",
  "msgfrom":"$msgfromid",
  "content":{
    "sender_ts":139880943
  }
}

     COMPRESSED (via /messages/list)
{
  "from":"string [user_id]",
  "feedback":"string [d|r]",
  "content":{
    "sender_ts":139880943
  }
}

When you join a room $room_id, you may want the last 10 messages with feedback. 
This is represented as:
  GET 
  /rooms/$room_id/messages/list?from=END&to=START&limit=10&feedback=true

You may want to get 10 messages even earlier than that without feedback. If the 
start stream token from the previous request was stok_019173, this request would 
be:
  GET 
  /rooms/$room_id/messages/list?from=stok_019173&to=START&limit=10&
                               feedback=false
  
NOTE: Care must be taken when using this API in conjunction with event 
      streaming. It is possible that this will return a message which will
      then come down the event stream, resulting in a duplicate message. Clients 
      should clobber based on the global message ID, or event ID.


Get current state for all rooms (aka IM Initial Sync API)
-------------------------------
  REST Path: /im/sync
  Valid methods: GET
  This API can use pagination query parameters. Pagination is applied on a per
  *room* basis. E.g. limit=1 means "get 1 message for each room" and not "get 1
  room's messages". If there is no limit, all messages for all rooms will be
  returned.
  If you want 1 room's messages, see "Receiving messages (bulk/pagination)".
  Additional query parameters: 
    feedback: [true] - Bundles feedback with messages.
  Returns:
    An array of RoomStateInfo.

RoomStateInfo: A snapshot of information about a single room.
  {
    "room_id" : "string",
    "membership" : "string [join|invite]",
    "messages" : {
      "start": "string",
      "end": "string",
      "chunk":
      m.room.message pagination stream events (with feedback if specified),
      this is the same as "Receiving messages (bulk/pagination)".
    }
  }
The "membership" key is the calling user's membership state in the given 
"room_id". The "messages" key may be omitted if the "membership" value is 
"invite". Additional keys may be added to the top-level object, such as:
  "topic" : "string" - The topic for the room in question.
  "room_image_url" : "string" - The URL of the room image if specified.
  "num_members" : integer - The number of members in the room.


Profiles
========

Getting/Setting your own displayname
------------------------------------
  REST Path: /profile/$user_id/displayname
  Valid methods: GET/PUT
  Required keys:
    displayname : The displayname text

Getting/Setting your own avatar image URL
-----------------------------------------
The homeserver does not currently store the avatar image itself, but offers
storage for the user to specify a web URL that points at the required image,
leaving it up to clients to fetch it themselves.
  REST Path: /profile/$user_id/avatar_url
  Valid methods: GET/PUT
  Required keys:
    avatar_url : The URL path to the required image

Getting other user's profile information
----------------------------------------
Either of the above REST methods may be used to fetch other user's profile
information by the client, either on other local users on the same homeserver or
for users from other servers entirely.


Presence
========

In the following messages, the presence state is an integer enumeration of the
following states:
  0 : OFFLINE
  1 : BUSY
  2 : ONLINE
  3 : FREE_TO_CHAT

Aside from OFFLINE, the protocol doesn't assign any special meaning to these
states; they are provided as an approximate signal for users to give to other
users and for clients to present them in some way that may be useful. Clients
could have different behaviours for different states of the user's presence, for
example to decide how much prominence or sound to use for incoming event
notifications.

Getting/Setting your own presence state
---------------------------------------
  REST Path: /presence/$user_id/status
  Valid methods: GET/PUT
  Required keys:
    state : [0|1|2|3] - The user's new presence state
  Optional keys:
    status_msg : text string provided by the user to explain their status

Fetching your presence list
---------------------------
  REST Path: /presence_list/$user_id
  Valid methods: GET/(post)
  Returns:
    An array of presence list entries. Each entry is an object with the
    following keys:
      {
        "user_id" : string giving the observed user's ID
        "state" : int giving their status
        "status_msg" : optional text string
        "displayname" : optional text string from the user's profile
        "avatar_url" : optional text string from the user's profile
      }

Maintaining your presence list
------------------------------
  REST Path: /presence_list/$user_id
  Valid methods: POST/(get)
  With: A JSON object optionally containing either of the following keys:
    "invite" : a list of strings giving user IDs to invite for presence
      subscription
    "drop" : a list of strings giving user IDs to remove from your presence
      list

Receiving presence update events
--------------------------------
  Event Type: m.presence
  Keys of the event's content are the same as those returned by the presence
    list.

Examples
========

The following example is the story of "bob", who signs up at "sy.org" and joins 
the public room "room_beta@sy.org". They get the 2 most recent
messages (with feedback) in that room and then send a message in that room. 

For context, here is the complete chat log for room_beta@sy.org:

Room: "Hello world" (room_beta@sy.org)
Members: (2) alice@randomhost.org, friend_of_alice@randomhost.org
Messages:
  alice@randomhost.org : hi friend!                     
  [friend_of_alice@randomhost.org DELIVERED]
  alice@randomhost.org : you're my only friend          
  [friend_of_alice@randomhost.org DELIVERED]
  alice@randomhost.org : afk                            
  [friend_of_alice@randomhost.org DELIVERED]
  [ bob@sy.org joins ]
  bob@sy.org : Hi everyone
  [ alice@randomhost.org changes the topic to "FRIENDS ONLY" ]
  alice@randomhost.org : Hello!!!!
  alice@randomhost.org : Let's go to another room
  alice@randomhost.org : You're not my friend
  [ alice@randomhost.org invites bob@sy.org to the room 
  commoners@randomhost.org]


REGISTER FOR AN ACCOUNT
POST: /register
Content: {}
Returns: { "user_id" : "bob@sy.org" , "access_token" : "abcdef0123456789" }

GET PUBLIC ROOM LIST
GET: /rooms/list?access_token=abcdef0123456789
Returns: 
{ 
  "total":3,
  "chunk":
  [
    { "room_id":"room_alpha@sy.org", "topic":"I am a fish" },
    { "room_id":"room_beta@sy.org", "topic":"Hello world" },
    { "room_id":"room_xyz@sy.org", "topic":"Goodbye cruel world" }
  ]
}

JOIN ROOM room_beta@sy.org
PUT 
/rooms/room_beta%40sy.org/members/bob%40sy.org/state?
                                    access_token=abcdef0123456789
Content: { "membership" : "join" }
Returns: 200 OK

GET LATEST 2 MESSAGES WITH FEEDBACK
GET 
/rooms/room_beta%40sy.org/messages/list?from=END&to=START&limit=2&
                                    feedback=true&access_token=abcdef0123456789
Returns:
{
  "chunk":
    [
      { 
        "event_id":"01948374", 
        "type":"m.room.message",
        "room_id":"room_beta@sy.org",
        "msg_id":"avefifu",
        "from":"alice@randomhost.org",
        "hs_ts":139985736,
        "content":{
          "msgtype":"m.text",
          "body":"afk"
        }
        "feedback": [
          {
            "from":"friend_of_alice@randomhost.org",
            "feedback":"d",
            "hs_ts":139985850,
            "content":{
              "sender_ts":139985843
            }
          }
        ]
      },
      { 
        "event_id":"028dfe8373", 
        "type":"m.room.message",
        "room_id":"room_beta@sy.org",
        "msg_id":"afhgfff",
        "from":"alice@randomhost.org",
        "hs_ts":139970006,
        "content":{
          "msgtype":"m.text",
          "body":"you're my only friend"
        }
        "feedback": [
          {
            "from":"friend_of_alice@randomhost.org",
            "feedback":"d",
            "hs_ts":139970144,
            "content":{
              "sender_ts":139970122
            }
          }
        ]
      },
    ],
  "start": "stok_04823947",
  "end": "etok_1426425"
}

SEND MESSAGE IN ROOM
PUT 
/rooms/room_beta%40sy.org/messages/bob%40sy.org/m0001?
                            access_token=abcdef0123456789
Content: { "msgtype" : "text" , "body" : "Hi everyone" }
Returns: 200 OK


Checking the event stream for this user:
GET: /events?from=START&access_token=abcdef0123456789
Returns:
{
  "chunk": 
    [
      { 
        "event_id":"e10f3d2b", 
        "type":"m.room.member",
        "room_id":"room_beta@sy.org",
        "user_id":"bob@sy.org",
        "content":{
          "membership":"join"
        }
      },
      { 
        "event_id":"1b352d32", 
        "type":"m.room.message",
        "room_id":"room_beta@sy.org",
        "msg_id":"m0001",
        "from":"bob@sy.org",
        "hs_ts":140193857,
        "content":{
          "msgtype":"m.text",
          "body":"Hi everyone"
        }
      }
    ],
  "start": "stok_9348635",
  "end": "etok_1984723"
}

Client disconnects for a while and the topic is updated in this room, 3 new 
messages arrive whilst offline, and bob is invited to another room.

GET /events?from=etok_1984723&access_token=abcdef0123456789
Returns:
{
  "chunk": 
    [
      { 
        "event_id":"feee0294", 
        "type":"m.room.topic",
        "room_id":"room_beta@sy.org",
        "from":"alice@randomhost.org",
        "content":{
          "topic":"FRIENDS ONLY",
        }
      },
      { 
        "event_id":"a028bd9e", 
        "type":"m.room.message",
        "room_id":"room_beta@sy.org",
        "msg_id":"z839409",
        "from":"alice@randomhost.org",
        "hs_ts":140195000,
        "content":{
          "msgtype":"m.text",
          "body":"Hello!!!"
        }
      },
      { 
        "event_id":"49372d9e", 
        "type":"m.room.message",
        "room_id":"room_beta@sy.org",
        "msg_id":"z839410",
        "from":"alice@randomhost.org",
        "hs_ts":140196000,
        "content":{
          "msgtype":"m.text",
          "body":"Let's go to another room"
        }
      },
      { 
        "event_id":"10abdd01", 
        "type":"m.room.message",
        "room_id":"room_beta@sy.org",
        "msg_id":"z839411",
        "from":"alice@randomhost.org",
        "hs_ts":140197000,
        "content":{
          "msgtype":"m.text",
          "body":"You're not my friend"
        }
      },
      { 
        "event_id":"0018453d", 
        "type":"m.room.member",
        "room_id":"commoners@randomhost.org",
        "from":"alice@randomhost.org",
        "user_id":"bob@sy.org",
        "content":{
          "membership":"invite"
        }
      },
    ],
  "start": "stok_0184288",
  "end": "etok_1348723"
}