summary refs log tree commit diff
diff options
context:
space:
mode:
authorMark Haines <mark.haines@matrix.org>2014-10-27 11:14:11 +0000
committerMark Haines <mark.haines@matrix.org>2014-10-27 11:14:11 +0000
commitacb2d171e88f7e5a60f6468e5b4f39fdb1ce94a3 (patch)
tree98b171bd5303e03ee123708f6ed96932f29db558
parentAdd script to hash exisitng history (diff)
parentTest pyflakes jenikns integration (diff)
downloadsynapse-acb2d171e88f7e5a60f6468e5b4f39fdb1ce94a3.tar.xz
Merge branch 'develop' into event_signing
-rw-r--r--CHANGES.rst22
-rw-r--r--UPGRADE.rst14
-rw-r--r--VERSION2
-rwxr-xr-xdemo/clean.sh1
-rwxr-xr-xdemo/start.sh11
-rw-r--r--docs/README.rst6
-rw-r--r--docs/client-server/howto.rst637
-rw-r--r--docs/client-server/swagger_matrix/api-docs46
-rw-r--r--docs/client-server/swagger_matrix/api-docs-directory101
-rw-r--r--docs/client-server/swagger_matrix/api-docs-events247
-rw-r--r--docs/client-server/swagger_matrix/api-docs-login120
-rw-r--r--docs/client-server/swagger_matrix/api-docs-presence164
-rw-r--r--docs/client-server/swagger_matrix/api-docs-profile122
-rw-r--r--docs/client-server/swagger_matrix/api-docs-registration120
-rw-r--r--docs/client-server/swagger_matrix/api-docs-rooms977
-rw-r--r--docs/definitions.rst53
-rw-r--r--docs/human-id-rules.rst79
-rw-r--r--docs/implementation-notes/documentation_style.rst43
-rw-r--r--docs/specification-NOTHAVE.rst30
-rw-r--r--docs/specification.rst2741
-rw-r--r--docs/state_resolution.rst51
-rw-r--r--pylint.cfg280
-rwxr-xr-xsetup.py8
-rw-r--r--synapse/__init__.py2
-rw-r--r--synapse/config/server.py4
-rw-r--r--synapse/crypto/context_factory.py5
-rw-r--r--synapse/crypto/keyclient.py1
-rw-r--r--synapse/crypto/keyring.py1
-rw-r--r--synapse/federation/transport.py27
-rw-r--r--synapse/handlers/register.py3
-rw-r--r--synapse/http/client.py40
-rw-r--r--synapse/rest/room.py2
-rw-r--r--synapse/rest/voip.py2
-rw-r--r--synapse/storage/__init__.py9
-rw-r--r--synapse/storage/keys.py1
-rw-r--r--synapse/storage/schema/delta/v6.sql31
-rw-r--r--synapse/test_pyflakes.py1
-rw-r--r--webclient/components/matrix/event-handler-service.js6
-rw-r--r--webclient/recents/recents-filter.js4
-rw-r--r--webclient/recents/recents.html10
-rw-r--r--webclient/room/room-controller.js4
-rw-r--r--webclient/room/room.html11
42 files changed, 458 insertions, 5581 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 5b05900daf..08efbbf244 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,22 @@
-Changes in latest
-=================
-This breaks federation becuase of signing
+Changes in synapse 0.4.1 (2014-10-17)
+=====================================
+Webclient:
+ * Fix bug with display of timestamps.
+
+Changes in synpase 0.4.0 (2014-10-17)
+=====================================
+This release includes changes to the federation protocol and client-server API
+that is not backwards compatible.
+
+The Matrix specification has been moved to a separate git repository:
+http://github.com/matrix-org/matrix-doc
+
+You will also need an updated syutil and config. See UPGRADES.rst.
+
+Homeserver:
+ * Sign federation transactions to assert strong identity over federation.
+ * Rename timestamp keys in PDUs and events from 'ts' and 'hsob_ts' to 'origin_server_ts'.
+
 
 Changes in synapse 0.3.4 (2014-09-25)
 =====================================
diff --git a/UPGRADE.rst b/UPGRADE.rst
index 2ae9254ecf..99ce1a2d3d 100644
--- a/UPGRADE.rst
+++ b/UPGRADE.rst
@@ -1,7 +1,15 @@
-Upgrading to latest
+Upgrading to v0.4.0
 ===================
-This breaks federation between old and new servers due to signing of
-transactions.
+
+This release needs an updated syutil version. Run::
+
+    python setup.py develop
+
+You will also need to upgrade your configuration as the signing key format has
+changed. Run::
+
+    python -m synapse.app.homeserver --config-path <CONFIG> --generate-config
+
 
 Upgrading to v0.3.0
 ===================
diff --git a/VERSION b/VERSION
index 42045acae2..267577d47e 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.3.4
+0.4.1
diff --git a/demo/clean.sh b/demo/clean.sh
index d5649fee83..c5dabd4767 100755
--- a/demo/clean.sh
+++ b/demo/clean.sh
@@ -14,3 +14,4 @@ fi
 find "$DIR" -name "*.log" -delete
 find "$DIR" -name "*.db" -delete
 
+rm -rf $DIR/etc
diff --git a/demo/start.sh b/demo/start.sh
index 7e33feca8d..fc6cd6303f 100755
--- a/demo/start.sh
+++ b/demo/start.sh
@@ -8,6 +8,14 @@ cd "$DIR/.."
 
 mkdir -p demo/etc
 
+# Check the --no-rate-limit param
+PARAMS=""
+if [ $# -eq 1 ]; then
+    if [ $1 = "--no-rate-limit" ]; then
+	    PARAMS="--rc-messages-per-second 1000 --rc-message-burst-count 1000"
+    fi
+fi
+
 for port in 8080 8081 8082; do
     echo "Starting server on port $port... "
 
@@ -23,7 +31,8 @@ for port in 8080 8081 8082; do
         -d "$DIR/$port.db" \
         -D --pid-file "$DIR/$port.pid" \
         --manhole $((port + 1000)) \
-        --tls-dh-params-path "demo/demo.tls.dh"
+        --tls-dh-params-path "demo/demo.tls.dh" \
+		$PARAMS
 
     python -m synapse.app.homeserver \
         --config-path "demo/etc/$port.config" \
diff --git a/docs/README.rst b/docs/README.rst
new file mode 100644
index 0000000000..3012da8b19
--- /dev/null
+++ b/docs/README.rst
@@ -0,0 +1,6 @@
+All matrix-generic documentation now lives in its own project at
+
+github.com/matrix-org/matrix-doc.git
+
+Only Synapse implementation-specific documentation lives here now
+(together with some older stuff will be shortly migrated over to matrix-doc)
diff --git a/docs/client-server/howto.rst b/docs/client-server/howto.rst
deleted file mode 100644
index 37346224af..0000000000
--- a/docs/client-server/howto.rst
+++ /dev/null
@@ -1,637 +0,0 @@
-.. TODO kegan
-  Room config (specifically: message history,
-  public rooms). /register seems super simplistic compared to /login, maybe it
-  would be better if /register used the same technique as /login? /register should
-  be "user" not "user_id".
-
-
-How to use the client-server API
-================================
-
-This guide focuses on how the client-server APIs *provided by the reference 
-home server* can be used. Since this is specific to a home server 
-implementation, there may be variations in relation to registering/logging in
-which are not covered in extensive detail in this guide.
-
-If you haven't already, get a home server up and running on 
-``http://localhost:8008``.
-
-
-Accounts
-========
-Before you can send and receive messages, you must **register** for an account. 
-If you already have an account, you must **login** into it.
-
-`Try out the fiddle`__
-
-.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/synapse/tree/master/jsfiddles/register_login
-
-Registration
-------------
-The aim of registration is to get a user ID and access token which you will need
-when accessing other APIs::
-
-    curl -XPOST -d '{"user":"example", "password":"wordpass", "type":"m.login.password"}' "http://localhost:8008/_matrix/client/api/v1/register"
-
-    {
-        "access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc", 
-        "home_server": "localhost", 
-        "user_id": "@example:localhost"
-    }
-
-NB: If a ``user`` is not specified, one will be randomly generated for you. 
-If you do not specify a ``password``, you will be unable to login to the account
-if you forget the ``access_token``.
-
-Implementation note: The matrix specification does not enforce how users 
-register with a server. It just specifies the URL path and absolute minimum 
-keys. The reference home server uses a username/password to authenticate user,
-but other home servers may use different methods. This is why you need to
-specify the ``type`` of method.
-
-Login
------
-The aim when logging in is to get an access token for your existing user ID::
-
-    curl -XGET "http://localhost:8008/_matrix/client/api/v1/login"
-
-    {
-        "flows": [
-            {
-                "type": "m.login.password"
-            }
-        ]
-    }
-
-    curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login"
-
-    {
-        "access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd", 
-        "home_server": "localhost", 
-        "user_id": "@example:localhost"
-    }
-    
-Implementation note: Different home servers may implement different methods for 
-logging in to an existing account. In order to check that you know how to login 
-to this home server, you must perform a ``GET`` first and make sure you 
-recognise the login type. If you do not know how to login, you can 
-``GET /login/fallback`` which will return a basic webpage which you can use to 
-login. The reference home server implementation support username/password login,
-but other home servers may support different login methods (e.g. OAuth2).
-
-
-Communicating
-=============
-
-In order to communicate with another user, you must **create a room** with that 
-user and **send a message** to that room. 
-
-`Try out the fiddle`__
-
-.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/synapse/tree/master/jsfiddles/create_room_send_msg
-
-Creating a room
----------------
-If you want to send a message to someone, you have to be in a room with them. To
-create a room::
-
-    curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token=YOUR_ACCESS_TOKEN"
-
-    {
-        "room_alias": "#tutorial:localhost", 
-        "room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
-    }
-    
-The "room alias" is a human-readable string which can be shared with other users
-so they can join a room, rather than the room ID which is a randomly generated
-string. You can have multiple room aliases per room.
-
-.. TODO(kegan)
-  How to add/remove aliases from an existing room.
-    
-
-Sending messages
-----------------
-You can now send messages to this room::
-
-    curl -XPOST -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/send/m.room.message?access_token=YOUR_ACCESS_TOKEN"
-    
-    {
-        "event_id": "YUwRidLecu"
-    }
-    
-The event ID returned is a unique ID which identifies this message.
-    
-NB: There are no limitations to the types of messages which can be exchanged.
-The only requirement is that ``"msgtype"`` is specified. The Matrix 
-specification outlines the following standard types: ``m.text``, ``m.image``,
-``m.audio``, ``m.video``, ``m.location``, ``m.emote``. See the specification for
-more information on these types.
-
-Users and rooms
-===============
-
-Each room can be configured to allow or disallow certain rules. In particular,
-these rules may specify if you require an **invitation** from someone already in
-the room in order to **join the room**. In addition, you may also be able to 
-join a room **via a room alias** if one was set up.
-
-`Try out the fiddle`__
-
-.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/synapse/tree/master/jsfiddles/room_memberships
-
-Inviting a user to a room
--------------------------
-You can directly invite a user to a room like so::
-
-    curl -XPOST -d '{"user_id":"@myfriend:localhost"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/invite?access_token=YOUR_ACCESS_TOKEN"
-    
-This informs ``@myfriend:localhost`` of the room ID 
-``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room.
-
-Joining a room via an invite
-----------------------------
-If you receive an invite, you can join the room::
-
-    curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/join?access_token=YOUR_ACCESS_TOKEN"
-    
-NB: Only the person invited (``@myfriend:localhost``) can change the membership
-state to ``"join"``. Repeatedly joining a room does nothing.
-
-Joining a room via an alias
----------------------------
-Alternatively, if you know the room alias for this room and the room config 
-allows it, you can directly join a room via the alias::
-
-    curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=YOUR_ACCESS_TOKEN"
-    
-    {
-        "room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
-    }
-    
-You will need to use the room ID when sending messages, not the room alias.
-
-NB: If the room is configured to be an invite-only room, you will still require
-an invite in order to join the room even though you know the room alias. As a
-result, it is more common to see a room alias in relation to a public room, 
-which do not require invitations.
-
-Getting events
-==============
-An event is some interesting piece of data that a client may be interested in. 
-It can be a message in a room, a room invite, etc. There are many different ways
-of getting events, depending on what the client already knows.
-
-`Try out the fiddle`__
-
-.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/synapse/tree/master/jsfiddles/event_stream
-
-Getting all state
------------------
-If the client doesn't know any information on the rooms the user is 
-invited/joined on, they can get all the user's state for all rooms::
-
-    curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=YOUR_ACCESS_TOKEN"
-    
-    {
-        "end": "s39_18_0", 
-        "presence": [
-            {
-                "content": {
-                    "last_active_ago": 1061436, 
-                    "user_id": "@example:localhost"
-                }, 
-                "type": "m.presence"
-            }
-        ], 
-        "rooms": [
-            {
-                "membership": "join", 
-                "messages": {
-                    "chunk": [
-                        {
-                            "content": {
-                                "@example:localhost": 10, 
-                                "default": 0
-                            }, 
-                            "event_id": "wAumPSTsWF", 
-                            "required_power_level": 10, 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "state_key": "", 
-                            "ts": 1409665585188, 
-                            "type": "m.room.power_levels", 
-                            "user_id": "@example:localhost"
-                        }, 
-                        {
-                            "content": {
-                                "join_rule": "public"
-                            }, 
-                            "event_id": "jrLVqKHKiI", 
-                            "required_power_level": 10, 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "state_key": "", 
-                            "ts": 1409665585188, 
-                            "type": "m.room.join_rules", 
-                            "user_id": "@example:localhost"
-                        }, 
-                        {
-                            "content": {
-                                "level": 10
-                            }, 
-                            "event_id": "WpmTgsNWUZ", 
-                            "required_power_level": 10, 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "state_key": "", 
-                            "ts": 1409665585188, 
-                            "type": "m.room.add_state_level", 
-                            "user_id": "@example:localhost"
-                        }, 
-                        {
-                            "content": {
-                                "level": 0
-                            }, 
-                            "event_id": "qUMBJyKsTQ", 
-                            "required_power_level": 10, 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "state_key": "", 
-                            "ts": 1409665585188, 
-                            "type": "m.room.send_event_level", 
-                            "user_id": "@example:localhost"
-                        }, 
-                        {
-                            "content": {
-                                "ban_level": 5, 
-                                "kick_level": 5
-                            }, 
-                            "event_id": "YAaDmKvoUW", 
-                            "required_power_level": 10, 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "state_key": "", 
-                            "ts": 1409665585188, 
-                            "type": "m.room.ops_levels", 
-                            "user_id": "@example:localhost"
-                        }, 
-                        {
-                            "content": {
-                                "avatar_url": null, 
-                                "displayname": null, 
-                                "membership": "join"
-                            }, 
-                            "event_id": "RJbPMtCutf", 
-                            "membership": "join", 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "state_key": "@example:localhost", 
-                            "ts": 1409665586730, 
-                            "type": "m.room.member", 
-                            "user_id": "@example:localhost"
-                        }, 
-                        {
-                            "content": {
-                                "body": "hello", 
-                                "hsob_ts": 1409665660439, 
-                                "msgtype": "m.text"
-                            }, 
-                            "event_id": "YUwRidLecu", 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "ts": 1409665660439, 
-                            "type": "m.room.message", 
-                            "user_id": "@example:localhost"
-                        }, 
-                        {
-                            "content": {
-                                "membership": "invite"
-                            }, 
-                            "event_id": "YjNuBKnPsb", 
-                            "membership": "invite", 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "state_key": "@myfriend:localhost", 
-                            "ts": 1409666426819, 
-                            "type": "m.room.member", 
-                            "user_id": "@example:localhost"
-                        }, 
-                        {
-                            "content": {
-                                "avatar_url": null, 
-                                "displayname": null, 
-                                "membership": "join", 
-                                "prev": "join"
-                            }, 
-                            "event_id": "KWwdDjNZnm", 
-                            "membership": "join", 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "state_key": "@example:localhost", 
-                            "ts": 1409666551582, 
-                            "type": "m.room.member", 
-                            "user_id": "@example:localhost"
-                        }, 
-                        {
-                            "content": {
-                                "avatar_url": null, 
-                                "displayname": null, 
-                                "membership": "join"
-                            }, 
-                            "event_id": "JFLVteSvQc", 
-                            "membership": "join", 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "state_key": "@example:localhost", 
-                            "ts": 1409666587265, 
-                            "type": "m.room.member", 
-                            "user_id": "@example:localhost"
-                        }
-                    ], 
-                    "end": "s39_18_0", 
-                    "start": "t1-11_18_0"
-                }, 
-                "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                "state": [
-                    {
-                        "content": {
-                            "creator": "@example:localhost"
-                        }, 
-                        "event_id": "dMUoqVTZca", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.create", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "@example:localhost": 10, 
-                            "default": 0
-                        }, 
-                        "event_id": "wAumPSTsWF", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.power_levels", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "join_rule": "public"
-                        }, 
-                        "event_id": "jrLVqKHKiI", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.join_rules", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "level": 10
-                        }, 
-                        "event_id": "WpmTgsNWUZ", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.add_state_level", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "level": 0
-                        }, 
-                        "event_id": "qUMBJyKsTQ", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.send_event_level", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "ban_level": 5, 
-                            "kick_level": 5
-                        }, 
-                        "event_id": "YAaDmKvoUW", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.ops_levels", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "membership": "invite"
-                        }, 
-                        "event_id": "YjNuBKnPsb", 
-                        "membership": "invite", 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "@myfriend:localhost", 
-                        "ts": 1409666426819, 
-                        "type": "m.room.member", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "avatar_url": null, 
-                            "displayname": null, 
-                            "membership": "join"
-                        }, 
-                        "event_id": "JFLVteSvQc", 
-                        "membership": "join", 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "@example:localhost", 
-                        "ts": 1409666587265, 
-                        "type": "m.room.member", 
-                        "user_id": "@example:localhost"
-                    }
-                ]
-            }
-        ]
-    }
-    
-This returns all the room information the user is invited/joined on, as well as
-all of the presences relevant for these rooms. This can be a LOT of data. You
-may just want the most recent event for each room. This can be achieved by 
-applying query parameters to ``limit`` this request::
-
-    curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?limit=1&access_token=YOUR_ACCESS_TOKEN"
-    
-    {
-        "end": "s39_18_0", 
-        "presence": [
-            {
-                "content": {
-                    "last_active_ago": 1279484, 
-                    "user_id": "@example:localhost"
-                }, 
-                "type": "m.presence"
-            }
-        ], 
-        "rooms": [
-            {
-                "membership": "join", 
-                "messages": {
-                    "chunk": [
-                        {
-                            "content": {
-                                "avatar_url": null, 
-                                "displayname": null, 
-                                "membership": "join"
-                            }, 
-                            "event_id": "JFLVteSvQc", 
-                            "membership": "join", 
-                            "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                            "state_key": "@example:localhost", 
-                            "ts": 1409666587265, 
-                            "type": "m.room.member", 
-                            "user_id": "@example:localhost"
-                        }
-                    ], 
-                    "end": "s39_18_0", 
-                    "start": "t10-30_18_0"
-                }, 
-                "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                "state": [
-                    {
-                        "content": {
-                            "creator": "@example:localhost"
-                        }, 
-                        "event_id": "dMUoqVTZca", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.create", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "@example:localhost": 10, 
-                            "default": 0
-                        }, 
-                        "event_id": "wAumPSTsWF", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.power_levels", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "join_rule": "public"
-                        }, 
-                        "event_id": "jrLVqKHKiI", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.join_rules", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "level": 10
-                        }, 
-                        "event_id": "WpmTgsNWUZ", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.add_state_level", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "level": 0
-                        }, 
-                        "event_id": "qUMBJyKsTQ", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.send_event_level", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "ban_level": 5, 
-                            "kick_level": 5
-                        }, 
-                        "event_id": "YAaDmKvoUW", 
-                        "required_power_level": 10, 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "", 
-                        "ts": 1409665585188, 
-                        "type": "m.room.ops_levels", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "membership": "invite"
-                        }, 
-                        "event_id": "YjNuBKnPsb", 
-                        "membership": "invite", 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "@myfriend:localhost", 
-                        "ts": 1409666426819, 
-                        "type": "m.room.member", 
-                        "user_id": "@example:localhost"
-                    }, 
-                    {
-                        "content": {
-                            "avatar_url": null, 
-                            "displayname": null, 
-                            "membership": "join"
-                        }, 
-                        "event_id": "JFLVteSvQc", 
-                        "membership": "join", 
-                        "room_id": "!MkDbyRqnvTYnoxjLYx:localhost", 
-                        "state_key": "@example:localhost", 
-                        "ts": 1409666587265, 
-                        "type": "m.room.member", 
-                        "user_id": "@example:localhost"
-                    }
-                ]
-            }
-        ]
-    }
-
-Getting live state
-------------------
-Once you know which rooms the client has previously interacted with, you need to
-listen for incoming events. This can be done like so::
-
-    curl -XGET "http://localhost:8008/_matrix/client/api/v1/events?access_token=YOUR_ACCESS_TOKEN"
-    
-    {
-        "chunk": [], 
-        "end": "s39_18_0", 
-        "start": "s39_18_0"
-    }
-    
-This will block waiting for an incoming event, timing out after several seconds.
-Even if there are no new events (as in the example above), there will be some
-pagination stream response keys. The client should make subsequent requests 
-using the value of the ``"end"`` key (in this case ``s39_18_0``) as the ``from`` 
-query parameter e.g. ``http://localhost:8008/_matrix/client/api/v1/events?access
-_token=YOUR_ACCESS_TOKEN&from=s39_18_0``. This value should be stored so when the 
-client reopens your app after a period of inactivity, you can resume from where 
-you got up to in the event stream. If it has been a long period of inactivity, 
-there may be LOTS of events waiting for the user. In this case, you may wish to 
-get all state instead and then resume getting live state from a newer end token.
-
-NB: The timeout can be changed by adding a ``timeout`` query parameter, which is
-in milliseconds. A timeout of 0 will not block.
-
-
-Example application
--------------------
-The following example demonstrates registration and login, live event streaming,
-creating and joining rooms, sending messages, getting member lists and getting 
-historical messages for a room. This covers most functionality of a messaging
-application.
-
-`Try out the fiddle`__
-
-.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/synapse/tree/master/jsfiddles/example_app
diff --git a/docs/client-server/swagger_matrix/api-docs b/docs/client-server/swagger_matrix/api-docs
deleted file mode 100644
index a80b3bb342..0000000000
--- a/docs/client-server/swagger_matrix/api-docs
+++ /dev/null
@@ -1,46 +0,0 @@
-{
-  "apiVersion": "1.0.0",
-  "swaggerVersion": "1.2",
-  "apis": [
-    {
-      "path": "-login",
-      "description": "Login operations"
-    },
-    {
-      "path": "-registration",
-      "description": "Registration operations"
-    },
-    {
-      "path": "-rooms",
-      "description": "Room operations"
-    },
-    {
-      "path": "-profile",
-      "description": "Profile operations"
-    },
-    {
-      "path": "-presence",
-      "description": "Presence operations"
-    },
-    {
-      "path": "-events",
-      "description": "Event operations"
-    },
-    {
-      "path": "-directory",
-      "description": "Directory operations"
-    }
-  ],
-  "authorizations": {
-    "token": {
-      "scopes": []
-    }
-  },
-  "info": {
-    "title": "Matrix Client-Server API Reference",
-    "description": "This contains the client-server API for the reference implementation of the home server",
-    "termsOfServiceUrl": "http://matrix.org",
-    "license": "Apache 2.0",
-    "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.html"
-  }
-}
diff --git a/docs/client-server/swagger_matrix/api-docs-directory b/docs/client-server/swagger_matrix/api-docs-directory
deleted file mode 100644
index 5dda580658..0000000000
--- a/docs/client-server/swagger_matrix/api-docs-directory
+++ /dev/null
@@ -1,101 +0,0 @@
-{
-  "apiVersion": "1.0.0",
-  "swaggerVersion": "1.2",
-  "basePath": "http://localhost:8008/_matrix/client/api/v1",
-  "resourcePath": "/directory",
-  "produces": [
-    "application/json"
-  ],
-  "apis": [
-    {
-      "path": "/directory/room/{roomAlias}",
-      "operations": [
-        {
-          "method": "GET",
-          "summary": "Get the room ID corresponding to this room alias.",
-          "notes": "Volatile: This API is likely to change.",
-          "type": "DirectoryResponse",
-          "nickname": "get_room_id_for_alias",
-          "parameters": [
-            {
-              "name": "roomAlias",
-              "description": "The room alias.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        },
-        {
-          "method": "PUT",
-          "summary": "Create a new mapping from room alias to room ID.",
-          "notes": "Volatile: This API is likely to change.",
-          "type": "void",
-          "nickname": "add_room_alias",
-          "parameters": [
-            {
-              "name": "roomAlias",
-              "description": "The room alias to set.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "body",
-              "description": "The room ID to set.",
-              "required": true,
-              "type": "RoomAliasRequest",
-              "paramType": "body"
-            }
-          ]
-        },
-        {
-          "method": "DELETE",
-          "summary": "Removes a mapping of room alias to room ID.",
-          "notes": "Only privileged users can perform this action.",
-          "type": "void",
-          "nickname": "remove_room_alias",
-          "parameters": [
-            {
-              "name": "roomAlias",
-              "description": "The room alias to remove.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        }
-      ]
-    }
-  ],
-  "models": {
-    "DirectoryResponse": {
-      "id": "DirectoryResponse",
-      "properties": {
-        "room_id": {
-          "type": "string",
-          "description": "The fully-qualified room ID.",
-          "required": true
-        },
-        "servers": {
-          "type": "array",
-          "items": {
-            "$ref": "string"
-          },
-          "description": "A list of servers that know about this room.",
-          "required": true
-        }
-      }
-    },
-    "RoomAliasRequest": {
-      "id": "RoomAliasRequest",
-      "properties": {
-        "room_id": {
-          "type": "string",
-          "description": "The room ID to map the alias to.",
-          "required": true
-        }
-      }
-    }
-  }
-}
diff --git a/docs/client-server/swagger_matrix/api-docs-events b/docs/client-server/swagger_matrix/api-docs-events
deleted file mode 100644
index 1bdb9b034a..0000000000
--- a/docs/client-server/swagger_matrix/api-docs-events
+++ /dev/null
@@ -1,247 +0,0 @@
-{
-  "apiVersion": "1.0.0",
-  "swaggerVersion": "1.2",
-  "basePath": "http://localhost:8008/_matrix/client/api/v1",
-  "resourcePath": "/events",
-  "produces": [
-    "application/json"
-  ],
-  "apis": [
-    {
-      "path": "/events",
-      "operations": [
-        {
-          "method": "GET",
-          "summary": "Listen on the event stream",
-          "notes": "This can only be done by the logged in user. This will block until an event is received, or until the timeout is reached.",
-          "type": "PaginationChunk",
-          "nickname": "get_event_stream",
-          "parameters": [
-            {
-              "name": "from",
-              "description": "The token to stream from.",
-              "required": false,
-              "type": "string",
-              "paramType": "query"
-            },
-            {
-              "name": "timeout",
-              "description": "The maximum time in milliseconds to wait for an event.",
-              "required": false,
-              "type": "integer",
-              "paramType": "query"
-            }
-          ]
-        }
-      ],
-      
-      "responseMessages": [
-        {
-          "code": 400,
-          "message": "Bad pagination token."
-        }
-      ]
-    },
-    {
-      "path": "/events/{eventId}",
-      "operations": [
-        {
-          "method": "GET",
-          "summary": "Get information about a single event.",
-          "notes": "Get information about a single event.",
-          "type": "Event",
-          "nickname": "get_event",
-          "parameters": [
-            {
-              "name": "eventId",
-              "description": "The event ID to get.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ],
-          "responseMessages": [
-            {
-              "code": 404,
-              "message": "Event not found."
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/initialSync",
-      "operations": [
-        {
-          "method": "GET",
-          "summary": "Get this user's current state.",
-          "notes": "Get this user's current state.",
-          "type": "InitialSyncResponse",
-          "nickname": "initial_sync",
-          "parameters": [
-            {
-              "name": "limit",
-              "description": "The maximum number of messages to return for each room.",
-              "type": "integer",
-              "paramType": "query",
-              "required": false
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/publicRooms",
-      "operations": [
-        {
-          "method": "GET",
-          "summary": "Get a list of publicly visible rooms.",
-          "type": "PublicRoomsPaginationChunk",
-          "nickname": "get_public_room_list"
-        }
-      ]
-    }
-  ],
-  "models": {
-    "PaginationChunk": {
-      "id": "PaginationChunk",
-      "properties": {
-        "start": {
-          "type": "string",
-          "description": "A token which correlates to the first value in \"chunk\" for paginating.",
-          "required": true
-        },
-        "end": {
-          "type": "string",
-          "description": "A token which correlates to the last value in \"chunk\" for paginating.",
-          "required": true
-        },
-        "chunk": {
-          "type": "array",
-          "description": "An array of events.",
-          "required": true,
-          "items": {
-            "$ref": "Event"
-          }
-        }
-      }
-    },
-    "Event": {
-      "id": "Event",
-      "properties": {
-        "event_id": {
-          "type": "string",
-          "description": "An ID which uniquely identifies this event.",
-          "required": true
-        },
-        "room_id": {
-          "type": "string",
-          "description": "The room in which this event occurred.",
-          "required": true
-        }
-      }
-    },
-    "PublicRoomInfo": {
-      "id": "PublicRoomInfo",
-      "properties": {
-        "aliases": {
-          "type": "array",
-          "description": "A list of room aliases for this room.",
-          "items": {
-            "$ref": "string"
-          }
-        },
-        "name": {
-          "type": "string",
-          "description": "The name of the room, as given by the m.room.name state event."
-        },
-        "room_id": {
-          "type": "string",
-          "description": "The room ID for this public room.",
-          "required": true
-        },
-        "topic": {
-          "type": "string",
-          "description": "The topic of this room, as given by the m.room.topic state event."
-        }
-      }
-    },
-    "PublicRoomsPaginationChunk": {
-      "id": "PublicRoomsPaginationChunk",
-      "properties": {
-        "start": {
-          "type": "string",
-          "description": "A token which correlates to the first value in \"chunk\" for paginating.",
-          "required": true
-        },
-        "end": {
-          "type": "string",
-          "description": "A token which correlates to the last value in \"chunk\" for paginating.",
-          "required": true
-        },
-        "chunk": {
-          "type": "array",
-          "description": "A list of public room data.",
-          "required": true,
-          "items": {
-            "$ref": "PublicRoomInfo"
-          }
-        }
-      }
-    },
-    "InitialSyncResponse": {
-      "id": "InitialSyncResponse",
-      "properties": {
-        "end": {
-          "type": "string",
-          "description": "A streaming token which can be used with /events to continue from this snapshot of data.",
-          "required": true
-        },
-        "presence": {
-          "type": "array",
-          "description": "A list of presence events.",
-          "items": {
-            "$ref": "Event"
-          },
-          "required": false
-        },
-        "rooms": {
-          "type": "array",
-          "description": "A list of initial sync room data.",
-          "required": false,
-          "items": {
-            "$ref": "InitialSyncRoomData"
-          }
-        }
-      }
-    },
-    "InitialSyncRoomData": {
-      "id": "InitialSyncRoomData",
-      "properties": {
-        "membership": {
-          "type": "string",
-          "description": "This user's membership state in this room.",
-          "required": true
-        },
-        "room_id": {
-          "type": "string",
-          "description": "The ID of this room.",
-          "required": true
-        },
-        "messages": {
-          "type": "PaginationChunk",
-          "description": "The most recent messages for this room, governed by the limit parameter.",
-          "required": false
-        },
-        "state": {
-          "type": "array",
-          "description": "A list of state events representing the current state of the room.",
-          "required": false,
-          "items": {
-            "$ref": "Event"
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/docs/client-server/swagger_matrix/api-docs-login b/docs/client-server/swagger_matrix/api-docs-login
deleted file mode 100644
index d6f8d84f29..0000000000
--- a/docs/client-server/swagger_matrix/api-docs-login
+++ /dev/null
@@ -1,120 +0,0 @@
-{
-  "apiVersion": "1.0.0", 
-  "apis": [
-    {
-      "operations": [
-        {
-          "method": "GET", 
-          "nickname": "get_login_info", 
-          "notes": "All login stages MUST be mentioned if there is >1 login type.", 
-          "summary": "Get the login mechanism to use when logging in.", 
-          "type": "LoginFlows"
-        }, 
-        {
-          "method": "POST", 
-          "nickname": "submit_login", 
-          "notes": "If this is part of a multi-stage login, there MUST be a 'session' key.", 
-          "parameters": [
-            {
-              "description": "A login submission", 
-              "name": "body", 
-              "paramType": "body", 
-              "required": true, 
-              "type": "LoginSubmission"
-            }
-          ], 
-          "responseMessages": [
-            {
-              "code": 400, 
-              "message": "Bad login type"
-            }, 
-            {
-              "code": 400, 
-              "message": "Missing JSON keys"
-            }
-          ], 
-          "summary": "Submit a login action.", 
-          "type": "LoginResult"
-        }
-      ], 
-      "path": "/login"
-    }
-  ], 
-  "basePath": "http://localhost:8008/_matrix/client/api/v1", 
-  "consumes": [
-    "application/json"
-  ], 
-  "models": {
-    "LoginFlows": {
-      "id": "LoginFlows",
-      "properties": {
-        "flows": {
-          "description": "A list of valid login flows.",
-          "type": "array",
-          "items": {
-            "$ref": "LoginInfo"
-          }
-        }
-      }
-    },
-    "LoginInfo": {
-      "id": "LoginInfo", 
-      "properties": {
-        "stages": {
-          "description": "Multi-stage login only: An array of all the login types required to login.", 
-          "items": {
-            "$ref": "string"
-          }, 
-          "type": "array"
-        }, 
-        "type": {
-          "description": "The login type that must be used when logging in.", 
-          "type": "string"
-        }
-      }
-    }, 
-    "LoginResult": {
-      "id": "LoginResult", 
-      "properties": {
-        "access_token": {
-          "description": "The access token for this user's login if this is the final stage of the login process.", 
-          "type": "string"
-        },
-        "user_id": {
-          "description": "The user's fully-qualified user ID.",
-          "type": "string"
-        }, 
-        "next": {
-          "description": "Multi-stage login only: The next login type to submit.", 
-          "type": "string"
-        },
-        "session": {
-          "description": "Multi-stage login only: The session token to send when submitting the next login type.",
-          "type": "string"
-        }
-      }
-    }, 
-    "LoginSubmission": {
-      "id": "LoginSubmission", 
-      "properties": {
-        "type": {
-          "description": "The type of login being submitted.", 
-          "type": "string"
-        },
-        "session": {
-          "description": "Multi-stage login only: The session token from an earlier login stage.",
-          "type": "string"
-        },
-        "_login_type_defined_keys_": {
-          "description": "Keys as defined by the specified login type, e.g. \"user\", \"password\""
-        }
-      }
-    }
-  }, 
-  "produces": [
-    "application/json"
-  ], 
-  "resourcePath": "/login", 
-  "swaggerVersion": "1.2"
-}
-
diff --git a/docs/client-server/swagger_matrix/api-docs-presence b/docs/client-server/swagger_matrix/api-docs-presence
deleted file mode 100644
index 6b22446024..0000000000
--- a/docs/client-server/swagger_matrix/api-docs-presence
+++ /dev/null
@@ -1,164 +0,0 @@
-{
-  "apiVersion": "1.0.0",
-  "swaggerVersion": "1.2",
-  "basePath": "http://localhost:8008/_matrix/client/api/v1",
-  "resourcePath": "/presence",
-  "produces": [
-    "application/json"
-  ],
-  "consumes": [
-    "application/json"
-  ],
-  "apis": [
-    {
-      "path": "/presence/{userId}/status",
-      "operations": [
-        {
-          "method": "PUT",
-          "summary": "Update this user's presence state.",
-          "notes": "This can only be done by the logged in user.",
-          "type": "void",
-          "nickname": "update_presence",
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The new presence state",
-              "required": true,
-              "type": "PresenceUpdate",
-              "paramType": "body"
-            },
-            {
-              "name": "userId",
-              "description": "The user whose presence to set.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        },
-        {
-          "method": "GET",
-          "summary": "Get this user's presence state.",
-          "notes": "Get this user's presence state.",
-          "type": "PresenceUpdate",
-          "nickname": "get_presence",
-          "parameters": [
-            {
-              "name": "userId",
-              "description": "The user whose presence to get.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/presence/list/{userId}",
-      "operations": [
-        {
-          "method": "GET",
-          "summary": "Retrieve a list of presences for all of this user's friends.",
-          "notes": "",
-          "type": "array",
-          "items": {
-            "$ref": "Presence"
-          },
-          "nickname": "get_presence_list",
-          "parameters": [
-            {
-              "name": "userId",
-              "description": "The user whose presence list to get.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        },
-        {
-          "method": "POST",
-          "summary": "Add or remove users from this presence list.",
-          "notes": "Add or remove users from this presence list.",
-          "type": "void",
-          "nickname": "modify_presence_list",
-          "parameters": [
-            {
-              "name": "userId",
-              "description": "The user whose presence list is being modified.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "body",
-              "description": "The modifications to make to this presence list.",
-              "required": true,
-              "type": "PresenceListModifications",
-              "paramType": "body"
-            }
-          ]
-        }
-      ]
-    }
-  ],
-  "models": {
-    "PresenceUpdate": {
-      "id": "PresenceUpdate",
-      "properties": {
-        "presence": {
-          "type": "string",
-          "description": "Enum: The presence state.",
-          "enum": [
-            "offline",
-            "unavailable",
-            "online",
-            "free_for_chat"
-          ]
-        },
-        "status_msg": {
-          "type": "string",
-          "description": "The user-defined message associated with this presence state."
-        }
-      },
-      "subTypes": [
-        "Presence"
-      ]
-    },
-    "Presence": {
-      "id": "Presence",
-      "properties": {
-        "last_active_ago": {
-          "type": "integer",
-          "format": "int64",
-          "description": "The last time this user performed an action on their home server."
-        },
-        "user_id": {
-          "type": "string",
-          "description": "The fully qualified user ID"
-        }
-      }
-    },
-    "PresenceListModifications": {
-      "id": "PresenceListModifications",
-      "properties": {
-        "invite": {
-          "type": "array",
-          "description": "A list of user IDs to add to the list.",
-          "items": {
-            "type": "string",
-            "description": "A fully qualified user ID."
-          }
-        },
-        "drop": {
-          "type": "array",
-          "description": "A list of user IDs to remove from the list.",
-          "items": {
-            "type": "string",
-            "description": "A fully qualified user ID."
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/docs/client-server/swagger_matrix/api-docs-profile b/docs/client-server/swagger_matrix/api-docs-profile
deleted file mode 100644
index d2fccaa67d..0000000000
--- a/docs/client-server/swagger_matrix/api-docs-profile
+++ /dev/null
@@ -1,122 +0,0 @@
-{
-  "apiVersion": "1.0.0",
-  "swaggerVersion": "1.2",
-  "basePath": "http://localhost:8008/_matrix/client/api/v1",
-  "resourcePath": "/profile",
-  "produces": [
-    "application/json"
-  ],
-  "consumes": [
-    "application/json"
-  ],
-  "apis": [
-    {
-      "path": "/profile/{userId}/displayname",
-      "operations": [
-        {
-          "method": "PUT",
-          "summary": "Set a display name.",
-          "notes": "This can only be done by the logged in user.",
-          "type": "void",
-          "nickname": "set_display_name",
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The new display name for this user.",
-              "required": true,
-              "type": "DisplayName",
-              "paramType": "body"
-            },
-            {
-              "name": "userId",
-              "description": "The user whose display name to set.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        },
-        {
-          "method": "GET",
-          "summary": "Get a display name.",
-          "notes": "This can be done by anyone.",
-          "type": "DisplayName",
-          "nickname": "get_display_name",
-          "parameters": [
-            {
-              "name": "userId",
-              "description": "The user whose display name to get.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/profile/{userId}/avatar_url",
-      "operations": [
-        {
-          "method": "PUT",
-          "summary": "Set an avatar URL.",
-          "notes": "This can only be done by the logged in user.",
-          "type": "void",
-          "nickname": "set_avatar_url",
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The new avatar url for this user.",
-              "required": true,
-              "type": "AvatarUrl",
-              "paramType": "body"
-            },
-            {
-              "name": "userId",
-              "description": "The user whose avatar url to set.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        },
-        {
-          "method": "GET",
-          "summary": "Get an avatar url.",
-          "notes": "This can be done by anyone.",
-          "type": "AvatarUrl",
-          "nickname": "get_avatar_url",
-          "parameters": [
-            {
-              "name": "userId",
-              "description": "The user whose avatar url to get.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        }
-      ]
-    }
-  ],
-  "models": {
-    "DisplayName": {
-      "id": "DisplayName",
-      "properties": {
-        "displayname": {
-          "type": "string",
-          "description": "The textual display name"
-        }
-      }
-    },
-    "AvatarUrl": {
-      "id": "AvatarUrl",
-      "properties": {
-        "avatar_url": {
-          "type": "string",
-          "description": "A url to an image representing an avatar."
-        }
-      }
-    }
-  }
-}
diff --git a/docs/client-server/swagger_matrix/api-docs-registration b/docs/client-server/swagger_matrix/api-docs-registration
deleted file mode 100644
index 11c170c3ec..0000000000
--- a/docs/client-server/swagger_matrix/api-docs-registration
+++ /dev/null
@@ -1,120 +0,0 @@
-{
-  "apiVersion": "1.0.0", 
-  "apis": [
-    {
-      "operations": [
-        {
-          "method": "GET", 
-          "nickname": "get_registration_info", 
-          "notes": "All login stages MUST be mentioned if there is >1 login type.", 
-          "summary": "Get the login mechanism to use when registering.", 
-          "type": "RegistrationFlows"
-        }, 
-        {
-          "method": "POST", 
-          "nickname": "submit_registration", 
-          "notes": "If this is part of a multi-stage registration, there MUST be a 'session' key.", 
-          "parameters": [
-            {
-              "description": "A registration submission", 
-              "name": "body", 
-              "paramType": "body", 
-              "required": true, 
-              "type": "RegistrationSubmission"
-            }
-          ], 
-          "responseMessages": [
-            {
-              "code": 400, 
-              "message": "Bad login type"
-            }, 
-            {
-              "code": 400, 
-              "message": "Missing JSON keys"
-            }
-          ], 
-          "summary": "Submit a registration action.", 
-          "type": "RegistrationResult"
-        }
-      ], 
-      "path": "/register"
-    }
-  ], 
-  "basePath": "http://localhost:8008/_matrix/client/api/v1", 
-  "consumes": [
-    "application/json"
-  ], 
-  "models": {
-    "RegistrationFlows": {
-      "id": "RegistrationFlows",
-      "properties": {
-        "flows": {
-          "description": "A list of valid registration flows.",
-          "type": "array",
-          "items": {
-            "$ref": "RegistrationInfo"
-          }
-        }
-      }
-    },
-    "RegistrationInfo": {
-      "id": "RegistrationInfo", 
-      "properties": {
-        "stages": {
-          "description": "Multi-stage registration only: An array of all the login types required to registration.", 
-          "items": {
-            "$ref": "string"
-          }, 
-          "type": "array"
-        }, 
-        "type": {
-          "description": "The first login type that must be used when logging in.", 
-          "type": "string"
-        }
-      }
-    }, 
-    "RegistrationResult": {
-      "id": "RegistrationResult", 
-      "properties": {
-        "access_token": {
-          "description": "The access token for this user's registration if this is the final stage of the registration process.", 
-          "type": "string"
-        },
-        "user_id": {
-          "description": "The user's fully-qualified user ID.",
-          "type": "string"
-        }, 
-        "next": {
-          "description": "Multi-stage registration only: The next registration type to submit.", 
-          "type": "string"
-        },
-        "session": {
-          "description": "Multi-stage registration only: The session token to send when submitting the next registration type.",
-          "type": "string"
-        }
-      }
-    }, 
-    "RegistrationSubmission": {
-      "id": "RegistrationSubmission", 
-      "properties": {
-        "type": {
-          "description": "The type of registration being submitted.", 
-          "type": "string"
-        },
-        "session": {
-          "description": "Multi-stage registration only: The session token from an earlier registration stage.",
-          "type": "string"
-        },
-        "_registration_type_defined_keys_": {
-          "description": "Keys as defined by the specified registration type, e.g. \"user\", \"password\""
-        }
-      }
-    }
-  }, 
-  "produces": [
-    "application/json"
-  ], 
-  "resourcePath": "/register", 
-  "swaggerVersion": "1.2"
-}
-
diff --git a/docs/client-server/swagger_matrix/api-docs-rooms b/docs/client-server/swagger_matrix/api-docs-rooms
deleted file mode 100644
index b941e58139..0000000000
--- a/docs/client-server/swagger_matrix/api-docs-rooms
+++ /dev/null
@@ -1,977 +0,0 @@
-{
-  "apiVersion": "1.0.0",
-  "swaggerVersion": "1.2",
-  "basePath": "http://localhost:8008/_matrix/client/api/v1", 
-  "resourcePath": "/rooms",
-  "produces": [
-    "application/json"
-  ],
-  "consumes": [
-    "application/json"
-  ],
-  "authorizations": {
-    "token": []
-  },
-  "apis": [
-    {
-      "path": "/rooms/{roomId}/send/{eventType}",
-      "operations": [
-        {
-          "method": "POST",
-          "summary": "Send a generic non-state event to this room.",
-          "notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
-          "type": "EventId",
-          "nickname": "send_non_state_event",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The event contents",
-              "required": true,
-              "type": "EventContent",
-              "paramType": "body"
-            },
-            {
-              "name": "roomId",
-              "description": "The room to send the message in.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "eventType",
-              "description": "The type of event to send.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/state/{eventType}/{stateKey}",
-      "operations": [
-        {
-          "method": "PUT",
-          "summary": "Send a generic state event to this room.",
-          "notes": "The state key can be omitted, such that you can PUT to /rooms/{roomId}/state/{eventType}. The state key defaults to a 0 length string in this case.",
-          "type": "void",
-          "nickname": "send_state_event",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The event contents",
-              "required": true,
-              "type": "EventContent",
-              "paramType": "body"
-            },
-            {
-              "name": "roomId",
-              "description": "The room to send the message in.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "eventType",
-              "description": "The type of event to send.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "stateKey",
-              "description": "An identifier used to specify clobbering semantics. State events with the same (roomId, eventType, stateKey) will be replaced.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/send/m.room.message",
-      "operations": [
-        {
-          "method": "POST",
-          "summary": "Send a message in this room.",
-          "notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
-          "type": "EventId",
-          "nickname": "send_message",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The message contents",
-              "required": true,
-              "type": "Message",
-              "paramType": "body"
-            },
-            {
-              "name": "roomId",
-              "description": "The room to send the message in.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/state/m.room.topic",
-      "operations": [
-        {
-          "method": "PUT",
-          "summary": "Set the topic for this room.",
-          "notes": "Set the topic for this room.",
-          "type": "void",
-          "nickname": "set_topic",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The topic contents",
-              "required": true,
-              "type": "Topic",
-              "paramType": "body"
-            },
-            {
-              "name": "roomId",
-              "description": "The room to set the topic in.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        },
-        {
-          "method": "GET",
-          "summary": "Get the topic for this room.",
-          "notes": "Get the topic for this room.",
-          "type": "Topic",
-          "nickname": "get_topic",
-          "parameters": [
-            {
-              "name": "roomId",
-              "description": "The room to get topic in.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ],
-          "responseMessages": [
-            {
-              "code": 404,
-              "message": "Topic not found."
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/state/m.room.name",
-      "operations": [
-        {
-          "method": "PUT",
-          "summary": "Set the name of this room.",
-          "notes": "Set the name of this room.",
-          "type": "void",
-          "nickname": "set_room_name",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The name contents",
-              "required": true,
-              "type": "RoomName",
-              "paramType": "body"
-            },
-            {
-              "name": "roomId",
-              "description": "The room to set the name of.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        },
-        {
-          "method": "GET",
-          "summary": "Get the room's name.",
-          "notes": "",
-          "type": "RoomName",
-          "nickname": "get_room_name",
-          "parameters": [
-            {
-              "name": "roomId",
-              "description": "The room to get the name of.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ],
-          "responseMessages": [
-            {
-              "code": 404,
-              "message": "Name not found."
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/send/m.room.message.feedback",
-      "operations": [
-        {
-          "method": "POST",
-          "summary": "Send feedback to a message.",
-          "notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
-          "type": "EventId",
-          "nickname": "send_feedback",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The feedback contents",
-              "required": true,
-              "type": "Feedback",
-              "paramType": "body"
-            },
-            {
-              "name": "roomId",
-              "description": "The room to send the feedback in.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ],
-          "responseMessages": [
-            {
-              "code": 400,
-              "message": "Bad feedback type."
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/invite",
-      "operations": [
-        {
-          "method": "POST",
-          "summary": "Invite a user to this room.",
-          "notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
-          "type": "void",
-          "nickname": "invite",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "roomId",
-              "description": "The room which has this user.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "body",
-              "description": "The user to invite.",
-              "required": true,
-              "type": "InviteRequest",
-              "paramType": "body"
-            }
-          ]
-        }  
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/join",
-      "operations": [
-        {
-          "method": "POST",
-          "summary": "Join this room.",
-          "notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
-          "type": "void",
-          "nickname": "join_room",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "roomId",
-              "description": "The room to join.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "body",
-              "required": true,
-              "type": "JoinRequest",
-              "paramType": "body"
-            }
-          ]
-        }  
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/leave",
-      "operations": [
-        {
-          "method": "POST",
-          "summary": "Leave this room.",
-          "notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
-          "type": "void",
-          "nickname": "leave",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "roomId",
-              "description": "The room to leave.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "body",
-              "required": true,
-              "type": "LeaveRequest",
-              "paramType": "body"
-            }
-          ]
-        }  
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/ban",
-      "operations": [
-        {
-          "method": "POST",
-          "summary": "Ban a user in the room.",
-          "notes": "This operation can also be done as a PUT by suffixing /{txnId}. The caller must have the required power level to do this operation.",
-          "type": "void",
-          "nickname": "ban",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "roomId",
-              "description": "The room which has the user to ban.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "body",
-              "description": "The user to ban.",
-              "required": true,
-              "type": "BanRequest",
-              "paramType": "body"
-            }
-          ]
-        }  
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/state/m.room.member/{userId}",
-      "operations": [
-        {
-          "method": "PUT",
-          "summary": "Change the membership state for a user in a room.",
-          "notes": "Change the membership state for a user in a room.",
-          "type": "void",
-          "nickname": "set_membership",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The new membership state",
-              "required": true,
-              "type": "Member",
-              "paramType": "body"
-            },
-            {
-              "name": "userId",
-              "description": "The user whose membership is being changed.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "roomId",
-              "description": "The room which has this user.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ],
-          "responseMessages": [
-            {
-              "code": 400,
-              "message": "No membership key."
-            },
-            {
-              "code": 400,
-              "message": "Bad membership value."
-            },
-            {
-              "code": 403,
-              "message": "When inviting: You are not in the room."
-            },
-            {
-              "code": 403,
-              "message": "When inviting: <target> is already in the room."
-            },
-            {
-              "code": 403,
-              "message": "When joining: Cannot force another user to join."
-            },
-            {
-              "code": 403,
-              "message": "When joining: You are not invited to this room."
-            }
-          ]
-        },
-        {
-          "method": "GET",
-          "summary": "Get the membership state of a user in a room.",
-          "notes": "Get the membership state of a user in a room.",
-          "type": "Member",
-          "nickname": "get_membership",
-          "parameters": [
-            {
-              "name": "userId",
-              "description": "The user whose membership state you want to get.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "roomId",
-              "description": "The room which has this user.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ],
-          "responseMessages": [
-            {
-              "code": 404,
-              "message": "Member not found."
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/join/{roomAliasOrId}",
-      "operations": [
-        {
-          "method": "POST",
-          "summary": "Join a room via a room alias or room ID.",
-          "notes": "Join a room via a room alias or room ID.",
-          "type": "JoinRoomInfo",
-          "nickname": "join",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "roomAliasOrId",
-              "description": "The room alias or room ID to join.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ],
-          "responseMessages": [
-            {
-              "code": 400,
-              "message": "Bad room alias."
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/createRoom",
-      "operations": [
-        {
-          "method": "POST",
-          "summary": "Create a room.",
-          "notes": "Create a room.",
-          "type": "RoomInfo",
-          "nickname": "create_room",
-          "consumes": [
-            "application/json"
-          ],
-          "parameters": [
-            {
-              "name": "body",
-              "description": "The desired configuration for the room. This operation can also be done as a PUT by suffixing /{txnId}.",
-              "required": true,
-              "type": "RoomConfig",
-              "paramType": "body"
-            }
-          ],
-          "responseMessages": [
-            {
-              "code": 400,
-              "message": "Body must be JSON."
-            },
-            {
-              "code": 400,
-              "message": "Room alias already taken."
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/messages",
-      "operations": [
-        {
-          "method": "GET",
-          "summary": "Get a list of messages for this room.",
-          "notes": "Get a list of messages for this room.",
-          "type": "MessagePaginationChunk",
-          "nickname": "get_messages",
-          "parameters": [
-            {
-              "name": "roomId",
-              "description": "The room to get messages in.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "from",
-              "description": "The token to start getting results from.",
-              "required": false,
-              "type": "string",
-              "paramType": "query"
-            },
-            {
-              "name": "to",
-              "description": "The token to stop getting results at.",
-              "required": false,
-              "type": "string",
-              "paramType": "query"
-            },
-            {
-              "name": "limit",
-              "description": "The maximum number of messages to return.",
-              "required": false,
-              "type": "integer",
-              "paramType": "query"
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/members",
-      "operations": [
-        {
-          "method": "GET",
-          "summary": "Get a list of members for this room.",
-          "notes": "Get a list of members for this room.",
-          "type": "MemberPaginationChunk",
-          "nickname": "get_members",
-          "parameters": [
-            {
-              "name": "roomId",
-              "description": "The room to get a list of members from.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            },
-            {
-              "name": "from",
-              "description": "The token to start getting results from.",
-              "required": false,
-              "type": "string",
-              "paramType": "query"
-            },
-            {
-              "name": "to",
-              "description": "The token to stop getting results at.",
-              "required": false,
-              "type": "string",
-              "paramType": "query"
-            },
-            {
-              "name": "limit",
-              "description": "The maximum number of members to return.",
-              "required": false,
-              "type": "integer",
-              "paramType": "query"
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/state",
-      "operations": [
-        {
-          "method": "GET",
-          "summary": "Get a list of all the current state events for this room.",
-          "notes": "This is equivalent to the events returned under the 'state' key for this room in /initialSync.",
-          "type": "array",
-          "items": {
-            "$ref": "Event"
-          },
-          "nickname": "get_state_events",
-          "parameters": [
-            {
-              "name": "roomId",
-              "description": "The room to get a list of current state events from.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        }
-      ]
-    },
-    {
-      "path": "/rooms/{roomId}/initialSync",
-      "operations": [
-        {
-          "method": "GET",
-          "summary": "Get all the current information for this room, including messages and state events.",
-          "notes": "NOT YET IMPLEMENTED.",
-          "type": "InitialSyncRoomData",
-          "nickname": "get_room_sync_data",
-          "parameters": [
-            {
-              "name": "roomId",
-              "description": "The room to get information for.",
-              "required": true,
-              "type": "string",
-              "paramType": "path"
-            }
-          ]
-        }
-      ]
-    }
-  ],
-  "models": {
-    "Topic": {
-      "id": "Topic",
-      "properties": {
-        "topic": {
-          "type": "string",
-          "description": "The topic text"
-        }
-      }
-    },
-    "RoomName": {
-      "id": "RoomName",
-      "properties": {
-        "name": {
-          "type": "string",
-          "description": "The human-readable name for the room. Can contain spaces."
-        }
-      }
-    },
-    "Message": {
-      "id": "Message",
-      "properties": {
-        "msgtype": {
-          "type": "string",
-          "description": "The type of message being sent, e.g. \"m.text\"",
-          "required": true
-        },
-        "_msgtype_defined_keys_": {
-          "description": "Additional keys as defined by the msgtype, e.g. \"body\""
-        }
-      }
-    },
-    "Feedback": {
-      "id": "Feedback",
-      "properties": {
-        "target_event_id": {
-          "type": "string",
-          "description": "The event ID being acknowledged.",
-          "required": true
-        },
-        "type": {
-          "type": "string",
-          "description": "The type of feedback. Either 'delivered' or 'read'.",
-          "required": true
-        }
-      }
-    },
-    "Member": {
-      "id": "Member",
-      "properties": {
-        "membership": {
-          "type": "string",
-          "description": "Enum: The membership state of this member.",
-          "enum": [
-            "invite",
-            "join",
-            "leave",
-            "ban"
-          ]
-        }
-      }
-    },
-    "RoomInfo": {
-      "id": "RoomInfo",
-      "properties": {
-        "room_id": {
-          "type": "string",
-          "description": "The allocated room ID.",
-          "required": true
-        },
-        "room_alias": {
-          "type": "string",
-          "description": "The alias for the room.",
-          "required": false
-        }
-      }
-    },
-    "JoinRoomInfo": {
-      "id": "JoinRoomInfo",
-      "properties": {
-        "room_id": {
-          "type": "string",
-          "description": "The room ID joined, if joined via a room alias only.",
-          "required": true
-        }
-      }
-    },
-    "RoomConfig": {
-      "id": "RoomConfig",
-      "properties": {
-        "visibility": {
-          "type": "string",
-          "description": "Enum: The room visibility.",
-          "required": false,
-          "enum": [
-            "public",
-            "private"
-          ]
-        },
-        "room_alias_name": {
-          "type": "string",
-          "description": "The alias to give the new room.",
-          "required": false
-        },
-        "name": {
-          "type": "string",
-          "description": "Sets the name of the room. Send a m.room.name event after creating the room with the 'name' key specified.",
-          "required": false
-        },
-        "topic": {
-          "type": "string",
-          "description": "Sets the topic for the room. Send a m.room.topic event after creating the room with the 'topic' key specified.",
-          "required": false
-        }
-      }
-    },
-    "PaginationRequest": {
-      "id": "PaginationRequest",
-      "properties": {
-        "from": {
-          "type": "string",
-          "description": "The token to start getting results from."
-        },
-        "to": {
-          "type": "string",
-          "description": "The token to stop getting results at."
-        },
-        "limit": {
-          "type": "integer",
-          "description": "The maximum number of entries to return."
-        }
-      }
-    },
-    "PaginationChunk": {
-      "id": "PaginationChunk",
-      "properties": {
-        "start": {
-          "type": "string",
-          "description": "A token which correlates to the first value in \"chunk\" for paginating.",
-          "required": true
-        },
-        "end": {
-          "type": "string",
-          "description": "A token which correlates to the last value in \"chunk\" for paginating.",
-          "required": true
-        }
-      },
-      "subTypes": [
-        "MessagePaginationChunk"
-      ]
-    },
-    "MessagePaginationChunk": {
-      "id": "MessagePaginationChunk",
-      "properties": {
-        "chunk": {
-          "type": "array",
-          "description": "A list of message events.",
-          "items": {
-            "$ref": "MessageEvent"
-          },
-          "required": true
-        }
-      }
-    },
-    "MemberPaginationChunk": {
-      "id": "MemberPaginationChunk",
-      "properties": {
-        "chunk": {
-          "type": "array",
-          "description": "A list of member events.",
-          "items": {
-            "$ref": "MemberEvent"
-          },
-          "required": true
-        }
-      }
-    },
-    "Event": {
-      "id": "Event",
-      "properties": {
-        "event_id": {
-          "type": "string",
-          "description": "An ID which uniquely identifies this event. This is automatically set by the server.",
-          "required": true
-        },
-        "room_id": {
-          "type": "string",
-          "description": "The room in which this event occurred. This is automatically set by the server.",
-          "required": true
-        },
-        "type": {
-          "type": "string",
-          "description": "The event type.",
-          "required": true
-        }
-      },
-      "subTypes": [
-        "MessageEvent"
-      ]
-    },
-    "EventId": {
-      "id": "EventId",
-      "properties": {
-        "event_id": {
-          "type": "string",
-          "description": "The allocated event ID for this event.",
-          "required": true
-        }
-      }
-    },
-    "EventContent": {
-      "id": "EventContent",
-      "properties": {
-        "__event_content_keys__": {
-          "type": "string",
-          "description": "Event-specific content keys and values.",
-          "required": false
-        }
-      }
-    },
-    "MessageEvent": {
-      "id": "MessageEvent",
-      "properties": {
-        "content": {
-          "type": "Message"
-        }
-      }
-    },
-    "MemberEvent": {
-      "id": "MemberEvent",
-      "properties": {
-        "content": {
-          "type": "Member"
-        }
-      }
-    },
-    "InviteRequest": {
-      "id": "InviteRequest",
-      "properties": {
-        "user_id": {
-          "type": "string",
-          "description": "The fully-qualified user ID."
-        }
-      }
-    },
-    "JoinRequest": {
-      "id": "JoinRequest",
-      "properties": {}
-    },
-    "LeaveRequest": {
-      "id": "LeaveRequest",
-      "properties": {}
-    },
-    "BanRequest": {
-      "id": "BanRequest",
-      "properties": {
-        "user_id": {
-          "type": "string",
-          "description": "The fully-qualified user ID."
-        },
-        "reason": {
-          "type": "string",
-          "description": "The reason for the ban."
-        }
-      }
-    },
-    "InitialSyncRoomData": {
-      "id": "InitialSyncRoomData",
-      "properties": {
-        "membership": {
-          "type": "string",
-          "description": "This user's membership state in this room.",
-          "required": true
-        },
-        "room_id": {
-          "type": "string",
-          "description": "The ID of this room.",
-          "required": true
-        },
-        "messages": {
-          "type": "MessagePaginationChunk",
-          "description": "The most recent messages for this room, governed by the limit parameter.",
-          "required": false
-        },
-        "state": {
-          "type": "array",
-          "description": "A list of state events representing the current state of the room.",
-          "required": false,
-          "items": {
-            "$ref": "Event"
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/docs/definitions.rst b/docs/definitions.rst
deleted file mode 100644
index b0f95ae9d7..0000000000
--- a/docs/definitions.rst
+++ /dev/null
@@ -1,53 +0,0 @@
-Definitions
-===========
-
-# *Event* -- A JSON object that represents a piece of information to be
-distributed to the the room. The object includes a payload and metadata,
-including a `type` used to indicate what the payload is for and how to process
-them. It also includes one or more references to previous events.
-
-# *Event graph* -- Events and their references to previous events form a
-directed acyclic graph. All events must be a descendant of the first event in a
-room, except for a few special circumstances.
-
-# *State event* -- A state event is an event that has a non-null string valued
-`state_key` field. It may also include a `prev_state` key referencing exactly
-one state event with the same type and state key, in the same event graph.
-
-# *State tree* -- A state tree is a tree formed by a collection of state events
-that have the same type and state key (all in the same event graph.
-
-# *State resolution algorithm* -- An algorithm that takes a state tree as input
-and selects a single leaf node.
-
-# *Current state event* -- The leaf node of a given state tree that has been
-selected by the state resolution algorithm.
-
-# *Room state* / *state dictionary* / *current state* -- A mapping of the pair
-(event type, state key) to the current state event for that pair.
-
-# *Room* -- An event graph and its associated state dictionary. An event is in
-the room if it is part of the event graph.
-
-# *Topological ordering* -- The partial ordering that can be extracted from the
-event graph due to it being a DAG.
-
-(The state definitions are purposely slightly ill-defined, since if we allow
-deleting events we might end up with multiple state trees for a given event
-type and state key pair.)
-
-Federation specific
--------------------
-# *(Persistent data unit) PDU* -- An encoding of an event for distribution of
-the server to server protocol.
-
-# *(Ephemeral data unit) EDU* -- A piece of information that is sent between
-servers and doesn't encode an event.
-
-Client specific
----------------
-# *Child events* -- Events that reference a single event in the same room
-independently of the event graph.
-
-# *Collapsed events* -- Events that have all child events that reference it
-included in the JSON object.
diff --git a/docs/human-id-rules.rst b/docs/human-id-rules.rst
deleted file mode 100644
index 3a1ff39892..0000000000
--- a/docs/human-id-rules.rst
+++ /dev/null
@@ -1,79 +0,0 @@
-This document outlines the format for human-readable IDs within matrix.
-
-Overview
---------
-UTF-8 is quickly becoming the standard character encoding set on the web. As
-such, Matrix requires that all strings MUST be encoded as UTF-8. However,
-using Unicode as the character set for human-readable IDs is troublesome. There
-are many different characters which appear identical to each other, but would
-identify different users. In addition, there are non-printable characters which
-cannot be rendered by the end-user. This opens up a security vulnerability with
-phishing/spoofing of IDs, commonly known as a homograph attack.
-
-Web browers encountered this problem when International Domain Names were
-introduced. A variety of checks were put in place in order to protect users. If
-an address failed the check, the raw punycode would be displayed to disambiguate
-the address. Similar checks are performed by home servers in Matrix. However, 
-Matrix does not use punycode representations, and so does not show raw punycode 
-on a failed check. Instead, home servers must outright reject these misleading 
-IDs.
-
-Types of human-readable IDs
----------------------------
-There are two main human-readable IDs in question:
-
-- Room aliases
-- User IDs
- 
-Room aliases look like ``#localpart:domain``. These aliases point to opaque
-non human-readable room IDs. These pointers can change, so there is already an
-issue present with the same ID pointing to a different destination at a later
-date.
-
-User IDs look like ``@localpart:domain``. These represent actual end-users, and
-unlike room aliases, there is no layer of indirection. This presents a much
-greater concern with homograph attacks. 
-
-Checks
-------
-- Similar to web browsers.
-- blacklisted chars (e.g. non-printable characters)
-- mix of language sets from 'preferred' language not allowed. 
-- Language sets from CLDR dataset.
-- Treated in segments (localpart, domain)
-- Additional restrictions for ease of processing IDs.
-   - Room alias localparts MUST NOT have ``#`` or ``:``.
-   - User ID localparts MUST NOT have ``@`` or ``:``.
-
-Rejecting
----------
-- Home servers MUST reject room aliases which do not pass the check, both on 
-  GETs and PUTs.
-- Home servers MUST reject user ID localparts which do not pass the check, both
-  on creation and on events.
-- Any home server whose domain does not pass this check, MUST use their punycode
-  domain name instead of the IDN, to prevent other home servers rejecting you.
-- Error code is ``M_FAILED_HUMAN_ID_CHECK``. (generic enough for both failing 
-  due to homograph attacks, and failing due to including ``:`` s, etc)
-- Error message MAY go into further information about which characters were
-  rejected and why.
-- Error message SHOULD contain a ``failed_keys`` key which contains an array
-  of strings which represent the keys which failed the check e.g::
-  
-    failed_keys: [ user_id, room_alias ]
-  
-Other considerations
---------------------
-- Basic security: Informational key on the event attached by HS to say "unsafe 
-  ID". Problem: clients can just ignore it, and since it will appear only very
-  rarely, easy to forget when implementing clients.
-- Moderate security: Requires client handshake. Forces clients to implement
-  a check, else they cannot communicate with the misleading ID. However, this is
-  extra overhead in both client implementations and round-trips.
-- High security: Outright rejection of the ID at the point of creation / 
-  receiving event. Point of creation rejection is preferable to avoid the ID
-  entering the system in the first place. However, malicious HSes can just allow
-  the ID. Hence, other home servers must reject them if they see them in events.
-  Client never sees the problem ID, provided the HS is correctly implemented.
-- High security decided; client doesn't need to worry about it, no additional
-  protocol complexity aside from rejection of an event.
\ No newline at end of file
diff --git a/docs/implementation-notes/documentation_style.rst b/docs/implementation-notes/documentation_style.rst
deleted file mode 100644
index c365d09dff..0000000000
--- a/docs/implementation-notes/documentation_style.rst
+++ /dev/null
@@ -1,43 +0,0 @@
-===================
-Documentation Style
-===================
-
-A brief single sentence to describe what this file contains; in this case a
-description of the style to write documentation in.
-
-
-Sections
-========
-
-Each section should be separated from the others by two blank lines. Headings
-should be underlined using a row of equals signs (===). Paragraphs should be
-separated by a single blank line, and wrap to no further than 80 columns.
-
-[[TODO(username): if you want to leave some unanswered questions, notes for
-further consideration, or other kinds of comment, use a TODO section. Make sure
-to notate it with your name so we know who to ask about it!]]
-
-Subsections
------------
-
-If required, subsections can use a row of dashes to underline their header. A
-single blank line between subsections of a single section.
-
-
-Bullet Lists
-============
-
- * Bullet lists can use asterisks with a single space either side.
- 
- * Another blank line between list elements.
-
-
-Definition Lists
-================
-
-Terms:
-  Start in the first column, ending with a colon
-
-Definitions:
-  Take a two space indent, following immediately from the term without a blank
-  line before it, but having a blank line afterwards.
diff --git a/docs/specification-NOTHAVE.rst b/docs/specification-NOTHAVE.rst
deleted file mode 100644
index 369594f6ae..0000000000
--- a/docs/specification-NOTHAVE.rst
+++ /dev/null
@@ -1,30 +0,0 @@
-Matrix Specification NOTHAVEs
-=============================
-
-This document contains sections of the main specification that have been
-temporarily removed, because they specify intentions or aspirations that have
-in no way yet been implemented. Rather than outright-deleting them, they have
-been moved here so as to stand as an initial version for such time as they
-become extant.
-
-
-Presence
-========
-
-Idle Time
----------
-As well as the basic ``presence`` field, the presence information can also show
-a sense of an "idle timer". This should be maintained individually by the
-user's clients, and the home server can take the highest reported time as that
-to report. When a user is offline, the home server can still report when the
-user was last seen online.
-
-Device Type
------------
-
-Client devices that may limit the user experience somewhat (such as "mobile"
-devices with limited ability to type on a real keyboard or read large amounts of
-text) should report this to the home server, as this is also useful information
-to report as "presence" if the user cannot be expected to provide a good typed
-response to messages.
-
diff --git a/docs/specification.rst b/docs/specification.rst
deleted file mode 100644
index 84722aa281..0000000000
--- a/docs/specification.rst
+++ /dev/null
@@ -1,2741 +0,0 @@
-Matrix Specification
-====================
-
-WARNING
-=======
-
-.. WARNING::
-  The Matrix specification is still very much evolving: the API is not yet frozen
-  and this document is in places incomplete, stale, and may contain security
-  issues. Needless to say, we have made every effort to highlight the problem
-  areas that we're aware of.
-
-  We're publishing it at this point because it's complete enough to be more than
-  useful and provide a canonical reference to how Matrix is evolving. Our end
-  goal is to mirror WHATWG's `Living Standard <http://wiki.whatwg.org/wiki/FAQ#What_does_.22Living_Standard.22_mean.3F>`_   
-  approach except right now Matrix is more in the process of being born than actually being
-  living!
-
-.. contents:: Table of Contents
-.. sectnum::
-
-Introduction
-============
-
-Matrix is a new set of open APIs for open-federated Instant Messaging and VoIP
-functionality, designed to create and support a new global real-time
-communication ecosystem on the internet. This specification is the ongoing
-result of standardising the APIs used by the various components of the Matrix
-ecosystem to communicate with one another.
-
-The principles that Matrix attempts to follow are:
-
-- Pragmatic Web-friendly APIs (i.e. JSON over REST)
-- Keep It Simple & Stupid
-
-  + provide a simple architecture with minimal third-party dependencies.
-
-- Fully open:
-
-  + Fully open federation - anyone should be able to participate in the global
-    Matrix network
-  + Fully open standard - publicly documented standard with no IP or patent
-    licensing encumbrances
-  + Fully open source reference implementation - liberally-licensed example
-    implementations with no IP or patent licensing encumbrances
-
-- Empowering the end-user
-
-  + The user should be able to choose the server and clients they use
-  + The user should be control how private their communication is
-  + The user should know precisely where their data is stored
-
-- Fully decentralised - no single points of control over conversations or the
-  network as a whole
-- Learning from history to avoid repeating it
-
-  + Trying to take the best aspects of XMPP, SIP, IRC, SMTP, IMAP and NNTP
-    whilst trying to avoid their failings
-
-The functionality that Matrix provides includes:
-
-- Creation and management of fully distributed chat rooms with no
-  single points of control or failure
-- Eventually-consistent cryptographically secure synchronisation of room
-  state across a global open network of federated servers and services
-- Sending and receiving extensible messages in a room with (optional)
-  end-to-end encryption
-- Extensible user management (inviting, joining, leaving, kicking, banning)
-  mediated by a power-level based user privilege system.
-- Extensible room state management (room naming, aliasing, topics, bans)
-- Extensible user profile management (avatars, displaynames, etc)
-- Managing user accounts (registration, login, logout)
-- Use of 3rd Party IDs (3PIDs) such as email addresses, phone numbers,
-  Facebook accounts to authenticate, identify and discover users on Matrix.
-- Trusted federation of Identity servers for:
-
-  + Publishing user public keys for PKI
-  + Mapping of 3PIDs to Matrix IDs
-
-The end goal of Matrix is to be a ubiquitous messaging layer for synchronising
-arbitrary data between sets of people, devices and services - be that for
-instant messages, VoIP call setups, or any other objects that need to be
-reliably and persistently pushed from A to B in an interoperable and federated
-manner.
-
-
-Architecture
-============
-
-Clients transmit data to other clients through home servers (HSes). Clients do
-not communicate with each other directly.
-
-::
-
-                         How data flows between clients
-                         ==============================
-
-       { Matrix client A }                             { Matrix client B }
-           ^          |                                    ^          |
-           |  events  |                                    |  events  |
-           |          V                                    |          V
-       +------------------+                            +------------------+
-       |                  |---------( HTTP )---------->|                  |
-       |   Home Server    |                            |   Home Server    |
-       |                  |<--------( HTTP )-----------|                  |
-       +------------------+        Federation          +------------------+
-
-A "Client" typically represents a human using a web application or mobile app.
-Clients use the "Client-to-Server" (C-S) API to communicate with their home
-server, which stores their profile data and their record of the conversations
-in which they participate. Each client is associated with a user account (and
-may optionally support multiple user accounts). A user account is represented
-by a unique "User ID". This ID is namespaced to the home server which allocated
-the account and looks like::
-
-  @localpart:domain
-
-The ``localpart`` of a user ID may be a user name, or an opaque ID identifying
-this user. They are case-insensitive.
-
-.. TODO-spec
-    - Need to specify precise grammar for Matrix IDs
-
-A "Home Server" is a server which provides C-S APIs and has the ability to
-federate with other HSes.  It is typically responsible for multiple clients.
-"Federation" is the term used to describe the sharing of data between two or
-more home servers.
-
-Data in Matrix is encapsulated in an "event". An event is an action within the
-system. Typically each action (e.g. sending a message) correlates with exactly
-one event. Each event has a ``type`` which is used to differentiate different
-kinds of data. ``type`` values MUST be uniquely globally namespaced following
-Java's `package naming conventions
-<http://docs.oracle.com/javase/specs/jls/se5.0/html/packages.html#7.7>`, e.g.
-``com.example.myapp.event``. The special top-level namespace ``m.`` is reserved
-for events defined in the Matrix specification. Events are usually sent in the
-context of a "Room".
-
-Room structure
---------------
-
-A room is a conceptual place where users can send and receive events. Rooms can
-be created, joined and left. Events are sent to a room, and all participants in
-that room with sufficient access will receive the event. Rooms are uniquely
-identified internally via a "Room ID", which look like::
-
-  !opaque_id:domain
-
-There is exactly one room ID for each room. Whilst the room ID does contain a
-domain, it is simply for globally namespacing room IDs. The room does NOT
-reside on the domain specified. Room IDs are not meant to be human readable.
-They ARE case-sensitive.
-
-The following diagram shows an ``m.room.message`` event being sent in the room 
-``!qporfwt:matrix.org``::
-
-       { @alice:matrix.org }                             { @bob:domain.com }
-               |                                                 ^
-               |                                                 |
-      Room ID: !qporfwt:matrix.org                 Room ID: !qporfwt:matrix.org
-      Event type: m.room.message                   Event type: m.room.message
-      Content: { JSON object }                     Content: { JSON object }
-               |                                                 |
-               V                                                 |
-       +------------------+                          +------------------+
-       |   Home Server    |                          |   Home Server    |
-       |   matrix.org     |<-------Federation------->|   domain.com     |
-       +------------------+                          +------------------+
-                |       .................................        |
-                |______|           Shared State          |_______|
-                       | Room ID: !qporfwt:matrix.org    |
-                       | Servers: matrix.org, domain.com |
-                       | Members:                        |
-                       |  - @alice:matrix.org            |
-                       |  - @bob:domain.com              |
-                       |.................................|
-
-Federation maintains shared state between multiple home servers, such that when
-an event is sent to a room, the home server knows where to forward the event on
-to, and how to process the event. State is scoped to a single room, and
-federation ensures that all home servers have the information they need, even
-if that means the home server has to request more information from another home
-server before processing the event.
-
-Room Aliases
-------------
-
-Each room can also have multiple "Room Aliases", which looks like::
-
-  #room_alias:domain
-
-  .. TODO
-      - Need to specify precise grammar for Room Aliases
-
-A room alias "points" to a room ID and is the human-readable label by which
-rooms are publicised and discovered.  The room ID the alias is pointing to can
-be obtained by visiting the domain specified. They are case-insensitive. Note
-that the mapping from a room alias to a room ID is not fixed, and may change
-over time to point to a different room ID. For this reason, Clients SHOULD
-resolve the room alias to a room ID once and then use that ID on subsequent
-requests.
-
-When resolving a room alias the server will also respond with a list of servers
-that are in the room that can be used to join via.
-
-::
-
-          GET    
-   #matrix:domain.com      !aaabaa:matrix.org
-           |                    ^
-           |                    |
-    _______V____________________|____
-   |          domain.com            |
-   | Mappings:                      |
-   | #matrix >> !aaabaa:matrix.org  |
-   | #golf   >> !wfeiofh:sport.com  |
-   | #bike   >> !4rguxf:matrix.org  |
-   |________________________________|
-       
-Identity
---------
-
-Users in Matrix are identified via their user ID. However, existing ID
-namespaces can also be used in order to identify Matrix users. A Matrix
-"Identity" describes both the user ID and any other existing IDs from third
-party namespaces *linked* to their account.
-
-Matrix users can *link* third-party IDs (3PIDs) such as email addresses, social
-network accounts and phone numbers to their user ID. Linking 3PIDs creates a
-mapping from a 3PID to a user ID. This mapping can then be used by other Matrix
-users in order to discover other users, according to a strict set of privacy
-permissions.
-
-In order to ensure that the mapping from 3PID to user ID is genuine, a globally
-federated cluster of trusted "Identity Servers" (IS) are used to perform
-authentication of the 3PID.  Identity servers are also used to preserve the
-mapping indefinitely, by replicating the mappings across multiple ISes.
-
-Usage of an IS is not required in order for a client application to be part of
-the Matrix ecosystem. However, without one clients will not be able to look up
-user IDs using 3PIDs.
-
-API Standards
--------------
-
-The mandatory baseline for communication in Matrix is exchanging JSON objects
-over RESTful HTTP APIs. HTTPS is mandated as the baseline for server-server
-(federation) communication.  HTTPS is recommended for client-server
-communication, although HTTP may be supported as a fallback to support basic
-HTTP clients. More efficient optional transports for client-server
-communication will in future be supported as optional extensions - e.g. a
-packed binary encoding over stream-cipher encrypted TCP socket for
-low-bandwidth/low-roundtrip mobile usage.
-
-.. TODO
-  We need to specify capability negotiation for extensible transports
-
-For the default HTTP transport, all API calls use a Content-Type of
-``application/json``.  In addition, all strings MUST be encoded as UTF-8.
-
-Clients are authenticated using opaque ``access_token`` strings (see
-`Registration and Login`_ for details), passed as a query string parameter on
-all requests.
-
-.. TODO
-  Need to specify any HMAC or access_token lifetime/ratcheting tricks
-
-Any errors which occur on the Matrix API level MUST return a "standard error
-response". This is a JSON object which looks like::
-
-  {
-    "errcode": "<error code>",
-    "error": "<error message>"
-  }
-
-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`` MUST always be present. 
-
-Some standard error codes are below:
-
-:``M_FORBIDDEN``:
-  Forbidden access, e.g. joining a room without permission, failed login.
-
-:``M_UNKNOWN_TOKEN``:
-  The access token specified was not recognised.
-
-:``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.
-
-:``M_LIMIT_EXCEEDED``:
-  Too many requests have been sent in a short period of time. Wait a while then
-  try again.
-
-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 query parameters.
-
-:``M_LOGIN_EMAIL_URL_NOT_YET``:
-  Encountered when polling for an email link which has not been clicked yet.
-
-The C-S API typically uses ``HTTP POST`` to submit requests. This means these
-requests are not idempotent. The C-S API also allows ``HTTP PUT`` to make
-requests idempotent. In order to use a ``PUT``, paths should be suffixed with
-``/{txnId}``. ``{txnId}`` is a unique client-generated transaction ID which
-identifies the request, and is scoped to a given Client (identified by that
-client's ``access_token``). Crucially, it **only** serves to identify new
-requests from retransmits. After the request has finished, the ``{txnId}``
-value should be changed (how is not specified; a monotonically increasing
-integer is recommended). It is preferable to use ``HTTP PUT`` to make sure
-requests to send messages do not get sent more than once should clients need to
-retransmit requests.
-
-Valid requests look like::
-
-    POST /some/path/here?access_token=secret
-    {
-      "key": "This is a post."
-    }
-
-    PUT /some/path/here/11?access_token=secret
-    {
-      "key": "This is a put with a txnId of 11."
-    }
-
-In contrast, these are invalid requests::
-
-    POST /some/path/here/11?access_token=secret
-    {
-      "key": "This is a post, but it has a txnId."
-    }
-
-    PUT /some/path/here?access_token=secret
-    {
-      "key": "This is a put but it is missing a txnId."
-    }
-
-Receiving live updates on a client
-----------------------------------
-
-Clients can receive new events by long-polling the home server. This will hold
-open the HTTP connection for a short period of time waiting for new events,
-returning early if an event occurs. This is called the `Event Stream`_. All
-events which are visible to the client will appear in the event stream. When
-the request returns, an ``end`` token is included in the response. This token
-can be used in the next request to continue where the client left off.
-
-.. TODO-spec
-  How do we filter the event stream?
-  Do we ever return multiple events in a single request?  Don't we get lots of request
-  setup RTT latency if we only do one event per request? Do we ever support streaming
-  requests? Why not websockets?
-
-When the client first logs in, they will need to initially synchronise with
-their home server. This is achieved via the |initialSync|_ API. This API also
-returns an ``end`` token which can be used with the event stream.
-
-
-Registration and Login
-======================
-
-Clients must register with a home server in order to use Matrix. After
-registering, the client will be given an access token which must be used in ALL
-requests to that home server as a query parameter 'access_token'.
-
-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 (OAuth2), login by confirming a
-token sent to their email address, etc. This specification does not define how
-home servers should authorise their users who want to login to their existing
-accounts, but instead defines the standard interface which implementations
-should follow so that ANY client can login to ANY home server. Clients login
-using the |login|_ API. Clients register using the |register|_ API.
-Registration follows the same general procedure as login, but the path requests
-are sent to and the details contained in them are different.
-
-In both registration and login cases, the process takes the form of one or more
-stages, where at each stage the client submits a set of data for a given stage
-type and awaits a response from the server, which will either be a final
-success or a request to perform an additional stage. This exchange continues
-until the final success.
-
-In order to determine up-front what the server's requirements are, the client
-can request from the server a complete description of all of its acceptable
-flows of the registration or login process. It can then inspect the list of
-returned flows looking for one for which it believes it can complete all of the
-required stages, and perform it. As each home server may have different ways of
-logging in, the client needs to know how they should login. All distinct login
-stages MUST have a corresponding ``type``. A ``type`` is a namespaced string
-which details the mechanism for logging in.
-
-A client may be able to login via multiple valid login flows, and should choose
-a single flow when logging in. A flow is a series of login stages. The home
-server MUST respond with all the valid login flows when requested by a simple
-``GET`` request directly to the ``/login`` or ``/register`` paths::
-
-  {
-    "flows": [
-      {
-        "type": "<login type1a>",
-        "stages": [ "<login type 1a>", "<login type 1b>" ]
-      },
-      {
-        "type": "<login type2a>",
-        "stages": [ "<login type 2a>", "<login type 2b>" ]
-      },
-      {
-        "type": "<login type3>"
-      }
-    ]
-  }
-
-The client can now select which flow it wishes to use, and begin making
-``POST`` requests to the ``/login`` or ``/register`` paths with JSON body
-content containing the name of the stage as the ``type`` key, along with
-whatever additional parameters are required for that login or registration type
-(see below). After the flow is completed, the client's fully-qualified user
-ID and a new access token MUST be returned::
-
-  {
-    "user_id": "@user:matrix.org",
-    "access_token": "abcdef0123456789"
-  }
-
-The ``user_id`` key is particularly useful if the home server wishes to support
-localpart entry of usernames (e.g. "user" rather than "@user:matrix.org"), as
-the client may not be able to determine its ``user_id`` in this case.
-
-If the flow has multiple stages to it, the home server may wish to create a
-session to store context between requests. If a home server responds with a
-``session`` key to a request, clients MUST submit it in subsequent requests
-until the flow is completed::
-
-  {
-    "session": "<session id>"
-  }
-
-This specification defines the following login types:
- - ``m.login.password``
- - ``m.login.oauth2``
- - ``m.login.email.code``
- - ``m.login.email.url``
- - ``m.login.email.identity``
-
-Password-based
---------------
-:Type: 
-  ``m.login.password``
-:Description: 
-  Login is supported via a username and password.
-
-To respond to this type, reply with::
-
-  {
-    "type": "m.login.password",
-    "user": "<user_id or user localpart>",
-    "password": "<password>"
-  }
-
-The home server MUST respond with either new credentials, the next stage of the
-login process, or a standard error response.
-
-OAuth2-based
-------------
-:Type: 
-  ``m.login.oauth2``
-:Description:
-  Login is supported via OAuth2 URLs. This login consists of multiple requests.
-
-To respond to this type, reply with::
-
-  {
-    "type": "m.login.oauth2",
-    "user": "<user_id or user localpart>"
-  }
-
-The server MUST respond with::
-
-  {
-    "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 MUST point to a webpage which prompts
-the user to choose which service to authorize with. On selection of a service,
-this MUST link through to an ``Authorization Request URI``. 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 then 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 verifies that the cilent has
-authorised with the 3rd party, and can now complete the login. The OAuth2
-``redirect URI`` (with auth code) MUST respond with either new credentials, the
-next stage of the login process, or a standard error response.
-    
-For example, if a home server accepts OAuth2 from Google, it would return 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::
-
-  {
-    "user_id": "@user:matrix.org",
-    "access_token": "0123456789abcdef"
-  }
-
-Email-based (code)
-------------------
-:Type: 
-  ``m.login.email.code``
-:Description:
-  Login is supported by typing in a code which is sent in an email. This login 
-  consists of multiple requests.
-
-To respond to this type, reply with::
-
-  {
-    "type": "m.login.email.code",
-    "user": "<user_id or user localpart>",
-    "email": "<email address>"
-  }
-
-After validating the email address, the home server MUST send an email
-containing an authentication code and return::
-
-  {
-    "type": "m.login.email.code",
-    "session": "<session id>"
-  }
-
-The second request in this login stage involves sending this authentication
-code::
-
-  {
-    "type": "m.login.email.code",
-    "session": "<session id>",
-    "code": "<code in email sent>"
-  }
-
-The home server MUST respond to this with either new credentials, the next
-stage of the login process, or a standard error response.
-
-Email-based (url)
------------------
-:Type: 
-  ``m.login.email.url``
-:Description:
-  Login is supported by clicking on a URL in an email. This login consists of 
-  multiple requests.
-
-To respond to this type, reply with::
-
-  {
-    "type": "m.login.email.url",
-    "user": "<user_id or user localpart>",
-    "email": "<email address>"
-  }
-
-After validating the email address, the home server MUST send an email
-containing an authentication URL and return::
-
-  {
-    "type": "m.login.email.url",
-    "session": "<session id>"
-  }
-
-The email contains a URL which must be clicked. After it has been clicked, the
-client should perform another request::
-
-  {
-    "type": "m.login.email.url",
-    "session": "<session id>"
-  }
-
-The home server MUST respond to this with either new credentials, the next
-stage of the login process, or a standard error response. 
-
-A common client implementation will be to periodically poll until the link is
-clicked.  If the link has not been visited yet, a standard error response with
-an errcode of ``M_LOGIN_EMAIL_URL_NOT_YET`` should be returned.
-
-
-Email-based (identity server)
------------------------------
-:Type:
-  ``m.login.email.identity``
-:Description:
-  Login is supported by authorising an email address with an identity server.
-
-Prior to submitting this, the client should authenticate with an identity
-server.  After authenticating, the session information should be submitted to
-the home server.
-
-To respond to this type, reply with::
-
-  {
-    "type": "m.login.email.identity",
-    "threepidCreds": [
-      {
-        "sid": "<identity server session id>",
-        "clientSecret": "<identity server client secret>",
-        "idServer": "<url of identity server authed with, e.g. 'matrix.org:8090'>"
-      }
-    ]
-  }
-
-
-
-N-Factor Authentication
------------------------
-Multiple login stages can be combined to create N-factor authentication during
-login.
-
-This can be achieved by responding with the ``next`` login type on completion
-of a previous login stage::
-
-  {
-    "next": "<next login type>"
-  }
-
-If a home server implements N-factor authentication, it MUST respond with all 
-``stages`` when initially queried for their login requirements::
-
-  {
-    "type": "<1st login type>",
-    "stages": [ <1st login type>, <2nd login type>, ... , <Nth login type> ]
-  }
-
-This can be represented conceptually as::
-
-   _______________________
-  |    Login Stage 1      |
-  | type: "<login type1>" |
-  |  ___________________  |
-  | |_Request_1_________| | <-- Returns "session" key which is used throughout.
-  |  ___________________  |     
-  | |_Request_2_________| | <-- Returns a "next" value of "login type2"
-  |_______________________|
-            |
-            |
-   _________V_____________
-  |    Login Stage 2      |
-  | type: "<login type2>" |
-  |  ___________________  |
-  | |_Request_1_________| |
-  |  ___________________  |
-  | |_Request_2_________| |
-  |  ___________________  |
-  | |_Request_3_________| | <-- Returns a "next" value of "login type3"
-  |_______________________|
-            |
-            |
-   _________V_____________
-  |    Login Stage 3      |
-  | type: "<login type3>" |
-  |  ___________________  |
-  | |_Request_1_________| | <-- Returns user credentials
-  |_______________________|
-
-Fallback
---------
-Clients cannot be expected to be able to know how to process every single login
-type. If a client determines it does not know how to handle a given login type,
-it should request a login fallback page::
-
-  GET matrix/client/api/v1/login/fallback
-
-This MUST return an HTML page which can perform the entire login process.
-
-
-Rooms
-=====
-
-Creation
---------
-To create a room, a client has to use the |createRoom|_ API. There are various
-options which can be set when creating a room:
-
-``visibility``
-  Type: 
-    String
-  Optional: 
-    Yes
-  Value:
-    Either ``public`` or ``private``.
-  Description:
-    A ``public`` visibility indicates that the room will be shown in the public
-    room list. A ``private`` visibility will hide the room from the public room
-    list. Rooms default to ``private`` visibility if this key is not included.
-
-``room_alias_name``
-  Type: 
-    String
-  Optional: 
-    Yes
-  Value:
-    The room alias localpart.
-  Description:
-    If this is included, a room alias will be created and mapped to the newly
-    created room.  The alias will belong on the same home server which created
-    the room, e.g.  ``!qadnasoi:domain.com >>> #room_alias_name:domain.com``
-
-``name``
-  Type: 
-    String
-  Optional: 
-    Yes
-  Value:
-    The ``name`` value for the ``m.room.name`` state event.
-  Description:
-    If this is included, an ``m.room.name`` event will be sent into the room to
-    indicate the name of the room. See `Room Events`_ for more information on
-    ``m.room.name``.
-
-``topic``
-  Type: 
-    String
-  Optional: 
-    Yes
-  Value:
-    The ``topic`` value for the ``m.room.topic`` state event.
-  Description:
-    If this is included, an ``m.room.topic`` event will be sent into the room
-    to indicate the topic for the room. See `Room Events`_ for more information
-    on ``m.room.topic``.
-
-``invite``
-  Type:
-    List
-  Optional:
-    Yes
-  Value:
-    A list of user ids to invite.
-  Description:
-    This will tell the server to invite everyone in the list to the newly
-    created room.
-
-Example::
-
-  {
-    "visibility": "public", 
-    "room_alias_name": "thepub",
-    "name": "The Grand Duke Pub",
-    "topic": "All about happy hour"
-  }
-
-The home server will create a ``m.room.create`` event when the room is created,
-which serves as the root of the PDU graph for this room. This event also has a
-``creator`` key which contains the user ID of the room creator. It will also
-generate several other events in order to manage permissions in this room. This
-includes:
-
- - ``m.room.power_levels`` : Sets the power levels of users.
- - ``m.room.join_rules`` : Whether the room is "invite-only" or not.
- - ``m.room.add_state_level``: The power level required in order to add new
-   state to the room (as opposed to updating exisiting state)
- - ``m.room.send_event_level`` : The power level required in order to send a
-   message in this room.
- - ``m.room.ops_level`` : The power level required in order to kick or ban a
-   user from the room or redact an event in the room.
-
-See `Room Events`_ for more information on these events.
-
-Room aliases
-------------
-.. NOTE::
-  This section is a work in progress.
-
-Room aliases can be created by sending a ``PUT /directory/room/<room alias>``::
-
-  {
-    "room_id": <room id>
-  }
-
-They can be deleted by sending a ``DELETE /directory/room/<room alias>`` with
-no content. Only some privileged users may be able to delete room aliases, e.g.
-server admins, the creator of the room alias, etc. This specification does not
-outline the privilege level required for deleting room aliases.
-
-As room aliases are scoped to a particular home server domain name, it is
-likely that a home server will reject attempts to maintain aliases on other
-domain names. This specification does not provide a way for home servers to
-send update requests to other servers.
-
-Rooms store a *partial* list of room aliases via the ``m.room.aliases`` state
-event. This alias list is partial because it cannot guarantee that the alias
-list is in any way accurate or up-to-date, as room aliases can point to 
-different room IDs over time. Crucially, the aliases in this event are
-**purely informational** and SHOULD NOT be treated as accurate. They SHOULD
-be checked before they are used or shared with another user. If a room
-appears to have a room alias of ``#alias:example.com``, this SHOULD be checked
-to make sure that the room's ID matches the ``room_id`` returned from the
-request.
-
-Room aliases can be checked in the same way they are resolved; by sending a 
-``GET /directory/room/<room alias>``::
-
-  {
-    "room_id": <room id>,
-    "servers": [ <domain>, <domain2>, <domain3> ]
-  }
-
-Home servers can respond to resolve requests for aliases on other domains than
-their own by using the federation API to ask other domain name home servers.
-
-
-Permissions
------------
-.. NOTE::
-  This section is a work in progress.
-
-Permissions for rooms are done via the concept of power levels - to do any
-action in a room a user must have a suitable power level. Power levels are
-stored as state events in a given room. 
-
-Power levels for users are defined in ``m.room.power_levels``, where both a
-default and specific users' power levels can be set::
-
-  {
-    "<user id 1>": <power level int>,
-    "<user id 2>": <power level int>,
-    "default": 0
-  }
-
-By default all users have a power level of 0, other than the room creator whose
-power level defaults to 100. Users can grant other users increased power levels
-up to their own power level. For example, user A with a power level of 50 could
-increase the power level of user B to a maximum of level 50. Power levels for 
-users are tracked per-room even if the user is not present in the room.
-
-State events may contain a ``required_power_level`` key, which indicates the
-minimum power a user must have before they can update that state key. The only
-exception to this is when a user leaves a room, which revokes the user's right
-to update state events in that room.
-
-To perform certain actions there are additional power level requirements
-defined in the following state events:
-
-- ``m.room.send_event_level`` defines the minimum ``level`` for sending 
-  non-state events. Defaults to 50.
-- ``m.room.add_state_level`` defines the minimum ``level`` for adding new 
-  state, rather than updating existing state. Defaults to 50.
-- ``m.room.ops_level`` defines the minimum ``ban_level`` and ``kick_level`` to 
-  ban and kick other users respectively. This defaults to a kick and ban levels
-  of 50 each.
-
-
-Joining rooms
--------------
-.. TODO-doc What does the home server have to do to join a user to a room?
-   -  See SPEC-30.
-
-Users need to join a room in order to send and receive events in that room. A
-user can join a room by making a request to |/join/<room_alias_or_id>|_ with::
-
-  {}
-
-Alternatively, a user can make a request to |/rooms/<room_id>/join|_ with the
-same request content.  This is only provided for symmetry with the other
-membership APIs: ``/rooms/<room id>/invite`` and ``/rooms/<room id>/leave``. If
-a room alias was specified, it will be automatically resolved to a room ID,
-which will then be joined. The room ID that was joined will be returned in
-response::
-
-  {
-    "room_id": "!roomid:domain"
-  }
-
-The membership state for the joining user can also be modified directly to be
-``join`` by sending the following request to
-``/rooms/<room id>/state/m.room.member/<url encoded user id>``::
-
-  {
-    "membership": "join"
-  }
-
-See the `Room events`_ section for more information on ``m.room.member``.
-
-After the user has joined a room, they will receive subsequent events in that
-room. This room will now appear as an entry in the |initialSync|_ API.
-
-Some rooms enforce that a user is *invited* to a room before they can join that
-room. Other rooms will allow anyone to join the room even if they have not
-received an invite.
-
-Inviting users
---------------
-.. TODO-doc Invite-join dance 
-  - Outline invite join dance. What is it? Why is it required? How does it work?
-  - What does the home server have to do?
-
-The purpose of inviting users to a room is to notify them that the room exists
-so they can choose to become a member of that room. Some rooms require that all
-users who join a room are previously invited to it (an "invite-only" room).
-Whether a given room is an "invite-only" room is determined by the room config
-key ``m.room.join_rules``. It can have one of the following values:
-
-``public``
-  This room is free for anyone to join without an invite.
-
-``invite``
-  This room can only be joined if you were invited.
-
-Only users who have a membership state of ``join`` in a room can invite new
-users to said room. The person being invited must not be in the ``join`` state
-in the room. The fully-qualified user ID must be specified when inviting a
-user, as the user may reside on a different home server. To invite a user, send
-the following request to |/rooms/<room_id>/invite|_, which will manage the
-entire invitation process::
-
-  {
-    "user_id": "<user id to invite>"
-  }
-
-Alternatively, the membership state for this user in this room can be modified 
-directly by sending the following request to 
-``/rooms/<room id>/state/m.room.member/<url encoded user id>``::
-
-  {
-    "membership": "invite"
-  }
-
-See the `Room events`_ section for more information on ``m.room.member``.
-
-Leaving rooms
--------------
-.. TODO-spec - HS deleting rooms they are no longer a part of. Not implemented.
-  - This is actually Very Tricky. If all clients a HS is serving leave a room,
-  the HS will no longer get any new events for that room, because the servers
-  who get the events are determined on the *membership list*. There should
-  probably be a way for a HS to lurk on a room even if there are 0 of their
-  members in the room.
-  - Grace period before deletion?
-  - Under what conditions should a room NOT be purged?
-
-
-A user can leave a room to stop receiving events for that room. A user must
-have joined the room before they are eligible to leave the room. If the room is
-an "invite-only" room, they will need to be re-invited before they can re-join
-the room.  To leave a room, a request should be made to
-|/rooms/<room_id>/leave|_ with::
-
-  {}
-
-Alternatively, the membership state for this user in this room can be modified 
-directly by sending the following request to 
-``/rooms/<room id>/state/m.room.member/<url encoded user id>``::
-
-  {
-    "membership": "leave"
-  }
-
-See the `Room events`_ section for more information on ``m.room.member``.
-
-Once a user has left a room, that room will no longer appear on the
-|initialSync|_ API.
-
-If all members in a room leave, that room becomes eligible for deletion. 
-
-Banning users in a room
------------------------
-A user may decide to ban another user in a room. 'Banning' forces the target
-user to leave the room and prevents them from re-joining the room. A banned
-user will not be treated as a joined user, and so will not be able to send or
-receive events in the room. In order to ban someone, the user performing the
-ban MUST have the required power level. To ban a user, a request should be made
-to |/rooms/<room_id>/ban|_ with::
-
-  {
-    "user_id": "<user id to ban"
-    "reason": "string: <reason for the ban>"
-  }
-  
-Banning a user adjusts the banned member's membership state to ``ban`` and
-adjusts the power level of this event to a level higher than the banned person.
-Like with other membership changes, a user can directly adjust the target
-member's state, by making a request to
-``/rooms/<room id>/state/m.room.member/<user id>``::
-
-  {
-    "membership": "ban"
-  }
-
-Events in a room
-----------------
-Room events can be split into two categories:
-
-:State Events:
-  These are events which replace events that came before it, depending on a set
-  of unique keys.  These keys are the event ``type`` and a ``state_key``.
-  Events with the same set of keys will be overwritten. Typically, state events
-  are used to store state, hence their name.
-
-:Non-state events:
-  These are events which cannot be overwritten after sending. The list of
-  events continues to grow as more events are sent. As this list grows, it
-  becomes necessary to provide a mechanism for navigating this list. Pagination
-  APIs are used to view the list of historical non-state events. Typically,
-  non-state events are used to send messages.
-
-This specification outlines several events, all with the event type prefix
-``m.``. However, applications may wish to add their own type of event, and this
-can be achieved using the REST API detailed in the following sections. If new
-events are added, the event ``type`` key SHOULD follow the Java package naming
-convention, e.g. ``com.example.myapp.event``.  This ensures event types are
-suitably namespaced for each application and reduces the risk of clashes.
-
-State events
-------------
-State events can be sent by ``PUT`` ing to
-|/rooms/<room_id>/state/<event_type>/<state_key>|_.  These events will be
-overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` all match.
-If the state event has no ``state_key``, it can be omitted from the path. These
-requests **cannot use transaction IDs** like other ``PUT`` paths because they
-cannot be differentiated from the ``state_key``. Furthermore, ``POST`` is
-unsupported on state paths. Valid requests look like::
-
-  PUT /rooms/!roomid:domain/state/m.example.event
-  { "key" : "without a state key" }
-
-  PUT /rooms/!roomid:domain/state/m.another.example.event/foo
-  { "key" : "with 'foo' as the state key" }
-
-In contrast, these requests are invalid::
-
-  POST /rooms/!roomid:domain/state/m.example.event/
-  { "key" : "cannot use POST here" }
-
-  PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11
-  { "key" : "txnIds are not supported" }
-
-Care should be taken to avoid setting the wrong ``state key``::
-
-  PUT /rooms/!roomid:domain/state/m.another.example.event/11
-  { "key" : "with '11' as the state key, but was probably intended to be a txnId" }
-
-The ``state_key`` is often used to store state about individual users, by using
-the user ID as the ``state_key`` value. For example::
-
-  PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Adomain.com
-  { "animal" : "cat", "reason": "fluffy" }
-
-In some cases, there may be no need for a ``state_key``, so it can be omitted::
-
-  PUT /rooms/!roomid:domain/state/m.room.bgd.color
-  { "color": "red", "hex": "#ff0000" }
-
-See `Room Events`_ for the ``m.`` event specification.
-
-Non-state events
-----------------
-Non-state events can be sent by sending a request to
-|/rooms/<room_id>/send/<event_type>|_.  These requests *can* use transaction
-IDs and ``PUT``/``POST`` methods. Non-state events allow access to historical
-events and pagination, making it best suited for sending messages.  For
-example::
-
-  POST /rooms/!roomid:domain/send/m.custom.example.message
-  { "text": "Hello world!" }
-
-  PUT /rooms/!roomid:domain/send/m.custom.example.message/11
-  { "text": "Goodbye world!" }
-
-See `Room Events`_ for the ``m.`` event specification.
-
-Syncing rooms
--------------
-.. NOTE::
-  This section is a work in progress.
-
-When a client logs in, they may have a list of rooms which they have already
-joined. These rooms may also have a list of events associated with them. The
-purpose of 'syncing' is to present the current room and event information in a
-convenient, compact manner. The events returned are not limited to room events;
-presence events will also be returned. A single syncing API is provided:
-
- - |initialSync|_ : A global sync which will present room and event information
-   for all rooms the user has joined.
-
-.. TODO-spec room-scoped initial sync
- - |/rooms/<room_id>/initialSync|_ : A sync scoped to a single room. Presents
-   room and event information for this room only.
- - Room-scoped initial sync is Very Tricky because typically people would
-   want to sync the room then listen for any new content from that point
-   onwards. The event stream cannot do this for a single room currently.
-   As a result, commenting room-scoped initial sync at this time.
-
-The |initialSync|_ API contains the following keys:
-
-``presence``
-  Description:
-    Contains a list of presence information for users the client is interested
-    in.
-  Format:
-    A JSON array of ``m.presence`` events.
-
-``end``
-  Description:
-    Contains an event stream token which can be used with the `Event Stream`_.
-  Format:
-    A string containing the event stream token.
-
-``rooms``
-  Description:
-    Contains a list of room information for all rooms the client has joined,
-    and limited room information on rooms the client has been invited to.
-  Format:
-    A JSON array containing Room Information JSON objects.
-
-Room Information:
-  Description:
-    Contains all state events for the room, along with a limited amount of
-    the most recent non-state events, configured via the ``limit`` query
-    parameter. Also contains additional keys with room metadata, such as the
-    ``room_id`` and the client's ``membership`` to the room.
-  Format:
-    A JSON object with the following keys:
-      ``room_id``
-        A string containing the ID of the room being described.
-      ``membership``
-        A string representing the client's membership status in this room.
-      ``messages``
-        An event stream JSON object containing a ``chunk`` of recent non-state
-        events, along with an ``end`` token. *NB: The name of this key will be
-        changed in a later version.*
-      ``state``
-        A JSON array containing all the current state events for this room.
-
-Getting events for a room
--------------------------
-There are several APIs provided to ``GET`` events for a room:
-
-``/rooms/<room id>/state/<event type>/<state key>``
-  Description:
-    Get the state event identified.
-  Response format:
-    A JSON object representing the state event **content**.
-  Example:
-    ``/rooms/!room:domain.com/state/m.room.name`` returns ``{ "name": "Room name" }``
-
-|/rooms/<room_id>/state|_
-  Description:
-    Get all state events for a room.
-  Response format:
-    ``[ { state event }, { state event }, ... ]``
-  Example:
-    TODO-doc
-
-
-|/rooms/<room_id>/members|_
-  Description:
-    Get all ``m.room.member`` state events.
-  Response format:
-    ``{ "start": "<token>", "end": "<token>", "chunk": [ { m.room.member event }, ... ] }``
-  Example:
-    TODO-doc
-
-|/rooms/<room_id>/messages|_
-  Description:
-    Get all ``m.room.message`` and ``m.room.member`` events. This API supports
-    pagination using ``from`` and ``to`` query parameters, coupled with the
-    ``start`` and ``end`` tokens from an |initialSync|_ API.
-  Response format:
-    ``{ "start": "<token>", "end": "<token>" }``
-  Example:
-    TODO-doc
-    
-|/rooms/<room_id>/initialSync|_
-  Description:
-    Get all relevant events for a room. This includes state events, paginated
-    non-state events and presence events.
-  Response format:
-    `` { TODO-doc } ``
-  Example:
-    TODO-doc
-
-Redactions
-----------
-Since events are extensible it is possible for malicious users and/or servers
-to add keys that are, for example offensive or illegal. Since some events
-cannot be simply deleted, e.g. membership events, we instead 'redact' events.
-This involves removing all keys from an event that are not required by the
-protocol. This stripped down event is thereafter returned anytime a client or
-remote server requests it.
-
-Events that have been redacted include a ``redacted_because`` key whose value
-is the event that caused it to be redacted, which may include a reason.
-
-Redacting an event cannot be undone, allowing server owners to delete the
-offending content from the databases.
-
-Currently, only room admins can redact events by sending a ``m.room.redaction``
-event, but server admins also need to be able to redact events by a similar
-mechanism.
-
-Upon receipt of a redaction event, the server should strip off any keys not in
-the following list:
-
- - ``event_id``
- - ``type``
- - ``room_id``
- - ``user_id``
- - ``state_key``
- - ``prev_state``
- - ``content``
-
-The content object should also be stripped of all keys, unless it is one of
-one of the following event types:
-
- - ``m.room.member`` allows key ``membership``
- - ``m.room.create`` allows key ``creator``
- - ``m.room.join_rules`` allows key ``join_rule``
- - ``m.room.power_levels`` allows keys that are user ids or ``default``
- - ``m.room.add_state_level`` allows key ``level``
- - ``m.room.send_event_level`` allows key ``level``
- - ``m.room.ops_levels`` allows keys ``kick_level``, ``ban_level``
-   and ``redact_level``
- - ``m.room.aliases`` allows key ``aliases``
-
-The redaction event should be added under the key ``redacted_because``.
-
-
-When a client receives a redaction event it should change the redacted event
-in the same way a server does.
-
-
-Room Events
-===========
-.. NOTE::
-  This section is a work in progress.
-
-This specification outlines several standard event types, all of which are
-prefixed with ``m.``
-
-``m.room.name``
-  Summary:
-    Set the human-readable name for the room.
-  Type: 
-    State event
-  JSON format:
-    ``{ "name" : "string" }``
-  Example:
-    ``{ "name" : "My Room" }``
-  Description:
-    A room has an opaque room ID which is not human-friendly to read. A room
-    alias is human-friendly, but not all rooms have room aliases. The room name
-    is a human-friendly string designed to be displayed to the end-user. The
-    room name is not *unique*, as multiple rooms can have the same room name
-    set. The room name can also be set when creating a room using |createRoom|_
-    with the ``name`` key.
-
-``m.room.topic``
-  Summary:
-    Set a topic for the room.
-  Type: 
-    State event
-  JSON format:
-    ``{ "topic" : "string" }``
-  Example:
-    ``{ "topic" : "Welcome to the real world." }``
-  Description:
-    A topic is a short message detailing what is currently being discussed in
-    the room.  It can also be used as a way to display extra information about
-    the room, which may not be suitable for the room name. The room topic can
-    also be set when creating a room using |createRoom|_ with the ``topic``
-    key.
-
-``m.room.member``
-  Summary:
-    The current membership state of a user in the room.
-  Type: 
-    State event
-  JSON format:
-    ``{ "membership" : "enum[ invite|join|leave|ban ]" }``
-  Example:
-    ``{ "membership" : "join" }``
-  Description:
-    Adjusts the membership state for a user in a room. It is preferable to use
-    the membership APIs (``/rooms/<room id>/invite`` etc) when performing
-    membership actions rather than adjusting the state directly as there are a
-    restricted set of valid transformations. For example, user A cannot force
-    user B to join a room, and trying to force this state change directly will
-    fail. See the `Rooms`_ section for how to use the membership APIs.
-
-``m.room.create``
-  Summary:
-    The first event in the room.
-  Type: 
-    State event
-  JSON format:
-    ``{ "creator": "string"}``
-  Example:
-    ``{ "creator": "@user:example.com" }``
-  Description:
-    This is the first event in a room and cannot be changed. It acts as the 
-    root of all other events.
-
-``m.room.join_rules``
-  Summary:
-    Descripes how/if people are allowed to join.
-  Type: 
-    State event
-  JSON format:
-    ``{ "join_rule": "enum [ public|knock|invite|private ]" }``
-  Example:
-    ``{ "join_rule": "public" }``
-  Description:
-    TODO-doc : Use docs/models/rooms.rst
-   
-``m.room.power_levels``
-  Summary:
-    Defines the power levels of users in the room.
-  Type: 
-    State event
-  JSON format:
-    ``{ "<user_id>": <int>, ..., "default": <int>}``
-  Example:
-    ``{ "@user:example.com": 5, "@user2:example.com": 10, "default": 0 }`` 
-  Description:
-    If a user is in the list, then they have the associated power level. 
-    Otherwise they have the default level. If not ``default`` key is supplied,
-    it is assumed to be 0.
-
-``m.room.add_state_level``
-  Summary:
-    Defines the minimum power level a user needs to add state.
-  Type: 
-    State event
-  JSON format:
-    ``{ "level": <int> }``
-  Example:
-    ``{ "level": 5 }``
-  Description:
-    To add a new piece of state to the room a user must have the given power 
-    level. This does not apply to updating current state, which is goverened
-    by the ``required_power_level`` event key.
-    
-``m.room.send_event_level``
-  Summary:
-    Defines the minimum power level a user needs to send an event.
-  Type: 
-    State event
-  JSON format:
-    ``{ "level": <int> }``
-  Example:
-    ``{ "level": 0 }``
-  Description:
-    To send a new event into the room a user must have at least this power 
-    level. This allows ops to make the room read only by increasing this level,
-    or muting individual users by lowering their power level below this
-    threshold.
-
-``m.room.ops_levels``
-  Summary:
-    Defines the minimum power levels that a user must have before they can 
-    kick and/or ban other users.
-  Type: 
-    State event
-  JSON format:
-    ``{ "ban_level": <int>, "kick_level": <int>, "redact_level": <int> }``
-  Example:
-    ``{ "ban_level": 5, "kick_level": 5 }``
-  Description:
-    This defines who can ban and/or kick people in the room. Most of the time
-    ``ban_level`` will be greater than or equal to ``kick_level`` since 
-    banning is more severe than kicking.
-
-``m.room.aliases``
-  Summary:
-    These state events are used to inform the room about what room aliases it
-    has.
-  Type:
-    State event
-  JSON format:
-    ``{ "aliases": ["string", ...] }``
-  Example:
-    ``{ "aliases": ["#foo:example.com"] }``
-  Description:
-    This event is sent by a homeserver directly to inform of changes to the
-    list of aliases it knows about for that room. As a special-case, the
-    ``state_key`` of the event is the homeserver which owns the room alias.
-    For example, an event might look like::
-
-      {
-        "type": "m.room.aliases",
-        "event_id": "012345678ab",
-        "room_id": "!xAbCdEfG:example.com",
-        "state_key": "example.com",
-        "content": {
-          "aliases": ["#foo:example.com"]
-        }
-      }
-
-    The event contains the full list of aliases now stored by the home server
-    that emitted it; additions or deletions are not explicitly mentioned as
-    being such. The entire set of known aliases for the room is then the union
-    of the individual lists declared by all such keys, one from each home
-    server holding at least one alias.
-
-    Clients `should` check the validity of any room alias given in this list
-    before presenting it to the user as trusted fact. The lists given by this
-    event should be considered simply as advice on which aliases might exist,
-    for which the client can perform the lookup to confirm whether it receives
-    the correct room ID.
-
-``m.room.message``
-  Summary:
-    A message.
-  Type: 
-    Non-state event
-  JSON format:
-    ``{ "msgtype": "string" }``
-  Example:
-    ``{ "msgtype": "m.text", "body": "Testing" }``
-  Description:
-    This event is used when sending messages in a room. Messages are not
-    limited to be text.  The ``msgtype`` key outlines the type of message, e.g.
-    text, audio, image, video, etc.  Whilst not required, the ``body`` key
-    SHOULD be used with every kind of ``msgtype`` as a fallback mechanism when
-    a client cannot render the message. For more information on the types of
-    messages which can be sent, see `m.room.message msgtypes`_.
-
-``m.room.message.feedback``
-  Summary:
-    A receipt for a message.
-  Type: 
-    Non-state event
-  JSON format:
-    ``{ "type": "enum [ delivered|read ]", "target_event_id": "string" }``
-  Example:
-    ``{ "type": "delivered", "target_event_id": "e3b2icys" }``
-  Description:
-    Feedback events are events sent to acknowledge a message in some way. There
-    are two supported acknowledgements: ``delivered`` (sent when the event has
-    been received) and ``read`` (sent when the event has been observed by the
-    end-user). The ``target_event_id`` should reference the ``m.room.message``
-    event being acknowledged. 
-
-``m.room.redaction``
-  Summary:
-    Indicates a previous event has been redacted.
-  Type:
-    Non-state event
-  JSON format:
-    ``{ "reason": "string" }``
-  Description:
-    Events can be redacted by either room or server admins. Redacting an event
-    means that all keys not required by the protocol are stripped off, allowing
-    admins to remove offensive or illegal content that may have been attached
-    to any event. This cannot be undone, allowing server owners to physically
-    delete the offending data.  There is also a concept of a moderator hiding a
-    non-state event, which can be undone, but cannot be applied to state
-    events.
-    The event that has been redacted is specified in the ``redacts`` event
-    level key.
-
-m.room.message msgtypes
------------------------
-
-.. TODO-spec
-   How a client should handle unknown message types.
-
-Each ``m.room.message`` MUST have a ``msgtype`` key which identifies the type
-of message being sent. Each type has their own required and optional keys, as
-outlined below:
-
-``m.text``
-  Required keys:
-    - ``body`` : "string" - The body of the message.
-  Optional keys:
-    None.
-  Example:
-    ``{ "msgtype": "m.text", "body": "I am a fish" }``
-
-``m.emote``
-  Required keys:
-    - ``body`` : "string" - The emote action to perform.
-  Optional keys:
-    None.
-  Example:
-    ``{ "msgtype": "m.emote", "body": "tries to come up with a witty explanation" }``
-
-``m.image``
-  Required keys:
-    - ``url`` : "string" - The URL to the image.
-  Optional keys:
-    - ``info`` : "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``.
-    - ``body`` : "string" - The alt text of the image, or some kind of content
-      description for accessibility e.g. "image attachment".
-
-  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)",
-      }
-
-``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``.
-    - ``body`` : "string" - A description of the audio e.g. "Bee Gees - Stayin'
-      Alive", or some kind of content description for accessibility e.g.
-      "audio attachment".
-  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),
-      }
-
-``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``.
-    - ``body`` : "string" - A description of the video e.g. "Gangnam style", or
-      some kind of content description for accessibility e.g. "video
-      attachment".
-
-  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)
-      }
-
-``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``.
-    - ``body`` : "string" - A description of the location e.g. "Big Ben,
-      London, UK", or some kind of content description for accessibility e.g.
-      "location attachment".
-
-The following keys can be attached to any ``m.room.message``:
-
-  Optional keys:
-    - ``sender_ts`` : integer - A timestamp (ms resolution) representing the
-      wall-clock time when the message was sent from the client.
-
-Presence
-========
-.. NOTE::
-  This section is a work in progress.
-
-Each user has the concept of presence information. This encodes the
-"availability" of that user, suitable for display on other user's clients. This
-is transmitted as an ``m.presence`` event and is one of the few events which
-are sent *outside the context of a room*. The basic piece of presence
-information is represented by the ``presence`` key, which is an enum of one of
-the following:
-
-  - ``online`` : The default state when the user is connected to an event
-    stream.
-  - ``unavailable`` : The user is not reachable at this time.
-  - ``offline`` : The user is not connected to an event stream.
-  - ``free_for_chat`` : The user is generally willing to receive messages
-    moreso than default.
-  - ``hidden`` : Behaves as offline, but allows the user to see the client
-    state anyway and generally interact with client features. (Not yet
-    implemented in synapse).
-
-This basic ``presence`` field applies to the user as a whole, regardless of how
-many client devices they have connected. The home server should synchronise
-this status choice among multiple devices to ensure the user gets a consistent
-experience.
-
-In addition, the server maintains a timestamp of the last time it saw an active
-action from the user; either sending a message to a room, or changing presence
-state from a lower to a higher level of availability (thus: changing state from
-``unavailable`` to ``online`` will count as an action for being active, whereas
-in the other direction will not). This timestamp is presented via a key called
-``last_active_ago``, which gives the relative number of miliseconds since the
-message is generated/emitted, that the user was last seen active.
-
-Home servers can also use the user's choice of presence state as a signal for
-how to handle new private one-to-one chat message requests. For example, it
-might decide:
-
-  - ``free_for_chat`` : accept anything
-  - ``online`` : accept from anyone in my addres book list
-  - ``busy`` : accept from anyone in this "important people" group in my
-    address book list
-
-Presence List
--------------
-Each user's home server stores a "presence list" for that user. This stores a
-list of other user IDs the user has chosen to add to it. To be added to this
-list, the user being added must receive permission from the list owner. Once
-granted, both user's HS(es) store this information. Since such subscriptions
-are likely to be bidirectional, HSes may wish to automatically accept requests
-when a reverse subscription already exists.
-
-As a convenience, presence lists should support the ability to collect users
-into groups, which could allow things like inviting the entire group to a new
-("ad-hoc") chat room, or easy interaction with the profile information ACL
-implementation of the HS.
-
-Presence and Permissions
-------------------------
-For a viewing user to be allowed to see the presence information of a target
-user, either:
-
- - The target user has allowed the viewing user to add them to their presence
-   list, or
- - The two users share at least one room in common
-
-In the latter case, this allows for clients to display some minimal sense of
-presence information in a user list for a room.
-
-Client API
-----------
-The client API for presence is on the following set of REST calls.
-
-Fetching basic status::
-
-  GET $PREFIX/presence/:user_id/status
-
-  Returned content: JSON object containing the following keys:
-    presence: "offline"|"unavailable"|"online"|"free_for_chat"
-    status_msg: (optional) string of freeform text
-    last_active_ago: miliseconds since the last activity by the user
-
-Setting basic status::
-
-  PUT $PREFIX/presence/:user_id/status
-
-  Content: JSON object containing the following keys:
-    presence and status_msg: as above
-
-When setting the status, the activity time is updated to reflect that activity;
-the client does not need to specify the ``last_active_ago`` field.
-
-Fetching the presence list::
-
-  GET $PREFIX/presence/list
-
-  Returned content: JSON array containing objects; each object containing the
-    following keys:
-    user_id: observed user ID
-    presence: "offline"|"unavailable"|"online"|"free_for_chat"
-    status_msg: (optional) string of freeform text
-    last_active_ago: miliseconds since the last activity by the user
-
-Maintaining the presence list::
-
-  POST $PREFIX/presence/list
-
-  Content: JSON object containing either or both of the following keys:
-    invite: JSON array of strings giving user IDs to send invites to
-    drop: JSON array of strings giving user IDs to remove from the list
-
-.. TODO-spec
-  - Define how users receive presence invites, and how they accept/decline them
-
-Server API
-----------
-The server API for presence is based entirely on exchange of the following
-EDUs. There are no PDUs or Federation Queries involved.
-
-Performing a presence update and poll subscription request::
-
-  EDU type: m.presence
-
-  Content keys:
-    push: (optional): list of push operations.
-      Each should be an object with the following keys:
-        user_id: string containing a User ID
-        presence: "offline"|"unavailable"|"online"|"free_for_chat"
-        status_msg: (optional) string of freeform text
-        last_active_ago: miliseconds since the last activity by the user
-
-    poll: (optional): list of strings giving User IDs
-
-    unpoll: (optional): list of strings giving User IDs
-
-The presence of this combined message is two-fold: it informs the recipient
-server of the current status of one or more users on the sending server (by the
-``push`` key), and it maintains the list of users on the recipient server that
-the sending server is interested in receiving updates for, by adding (by the
-``poll`` key) or removing them (by the ``unpoll`` key). The ``poll`` and
-``unpoll`` lists apply *changes* to the implied list of users; any existing IDs
-that the server sent as ``poll`` operations in a previous message are not
-removed until explicitly requested by a later ``unpoll``.
-
-On receipt of a message containing a non-empty ``poll`` list, the receiving
-server should immediately send the sending server a presence update EDU of its
-own, containing in a ``push`` list the current state of every user that was in
-the orginal EDU's ``poll`` list.
-
-Sending a presence invite::
-
-  EDU type: m.presence_invite
-
-  Content keys:
-    observed_user: string giving the User ID of the user whose presence is
-      requested (i.e. the recipient of the invite)
-    observer_user: string giving the User ID of the user who is requesting to
-      observe the presence (i.e. the sender of the invite)
-
-Accepting a presence invite::
-
-  EDU type: m.presence_accept
-
-  Content keys - as for m.presence_invite
-
-Rejecting a presence invite::
-
-  EDU type: m.presence_deny
-
-  Content keys - as for m.presence_invite
-
-.. TODO-doc
-  - Explain the timing-based roundtrip reduction mechanism for presence
-    messages
-  - Explain the zero-byte presence inference logic
-  See also: docs/client-server/model/presence
-
-
-Voice over IP
-=============
-Matrix can also be used to set up VoIP calls. This is part of the core
-specification, although is still in a very early stage. Voice (and video) over
-Matrix is based on the WebRTC standards.
-
-Call events are sent to a room, like any other event. This means that clients
-must only send call events to rooms with exactly two participants as currently
-the WebRTC standard is based around two-party communication.
-
-Events
-------
-``m.call.invite``
-This event is sent by the caller when they wish to establish a call.
-
-  Required keys:
-    - ``call_id`` : "string" - A unique identifier for the call
-    - ``offer`` : "offer object" - The session description
-    - ``version`` : "integer" - The version of the VoIP specification this
-      message adheres to. This specification is version 0.
-    - ``lifetime`` : "integer" - The time in milliseconds that the invite is
-      valid for. Once the invite age exceeds this value, clients should discard
-      it. They should also no longer show the call as awaiting an answer in the
-      UI.
-      
-  Optional keys:
-    None.
-  Example:
-    ``{ "version" : 0, "call_id": "12345", "offer": { "type" : "offer", "sdp" : "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]" } }``
-
-``Offer Object``
-  Required keys:
-    - ``type`` : "string" - The type of session description, in this case
-      'offer'
-    - ``sdp`` : "string" - The SDP text of the session description
-
-``m.call.candidates``
-This event is sent by callers after sending an invite and by the callee after
-answering.  Its purpose is to give the other party additional ICE candidates to
-try using to communicate.
-
-  Required keys:
-    - ``call_id`` : "string" - The ID of the call this event relates to
-    - ``version`` : "integer" - The version of the VoIP specification this
-      messages adheres to. his specification is version 0.
-    - ``candidates`` : "array of candidate objects" - Array of object
-      describing the candidates.
-
-``Candidate Object``
-
-  Required Keys:
-    - ``sdpMid`` : "string" - The SDP media type this candidate is intended
-      for.
-    - ``sdpMLineIndex`` : "integer" - The index of the SDP 'm' line this
-      candidate is intended for
-    - ``candidate`` : "string" - The SDP 'a' line of the candidate
-
-``m.call.answer``
-
-  Required keys:
-    - ``call_id`` : "string" - The ID of the call this event relates to
-    - ``version`` : "integer" - The version of the VoIP specification this
-      messages
-    - ``answer`` : "answer object" - Object giving the SDK answer
-
-``Answer Object``
-
-  Required keys:
-    - ``type`` : "string" - The type of session description. 'answer' in this
-      case.
-    - ``sdp`` : "string" - The SDP text of the session description
-
-``m.call.hangup``
-Sent by either party to signal their termination of the call. This can be sent
-either once the call has has been established or before to abort the call.
-
-  Required keys:
-    - ``call_id`` : "string" - The ID of the call this event relates to
-    - ``version`` : "integer" - The version of the VoIP specification this
-      messages
-
-Message Exchange
-----------------
-A call is set up with messages exchanged as follows:
-
-::
-
-   Caller                   Callee
- m.call.invite ----------->
- m.call.candidate -------->
- [more candidates events]
-                         User answers call
-                  <------ m.call.answer
-               [...]
-                  <------ m.call.hangup
-                  
-Or a rejected call:
-
-::
-
-   Caller                   Callee
- m.call.invite ----------->
- m.call.candidate -------->
- [more candidates events]
-                        User rejects call
-                 <------- m.call.hangup
-
-Calls are negotiated according to the WebRTC specification.
-
-
-Glare
------
-This specification aims to address the problem of two users calling each other
-at roughly the same time and their invites crossing on the wire. It is a far
-better experience for the users if their calls are connected if it is clear
-that their intention is to set up a call with one another.
-
-In Matrix, calls are to rooms rather than users (even if those rooms may only
-contain one other user) so we consider calls which are to the same room.
-
-The rules for dealing with such a situation are as follows:
-
- - If an invite to a room is received whilst the client is preparing to send an
-   invite to the same room, the client should cancel its outgoing call and
-   instead automatically accept the incoming call on behalf of the user.
- - If an invite to a room is received after the client has sent an invite to
-   the same room and is waiting for a response, the client should perform a
-   lexicographical comparison of the call IDs of the two calls and use the
-   lesser of the two calls, aborting the greater. If the incoming call is the
-   lesser, the client should accept this call on behalf of the user.
-
-The call setup should appear seamless to the user as if they had simply placed
-a call and the other party had accepted. Thusly, any media stream that had been
-setup for use on a call should be transferred and used for the call that
-replaces it.
- 
-
-Profiles
-========
-.. NOTE::
-  This section is a work in progress.
-
-.. TODO-spec
-  - Metadata extensibility
-
-Internally within Matrix users are referred to by their user ID, which is
-typically a compact unique identifier. Profiles grant users the ability to see
-human-readable names for other users that are in some way meaningful to them.
-Additionally, profiles can publish additional information, such as the user's
-age or location.
-
-A Profile consists of a display name, an avatar picture, and a set of other
-metadata fields that the user may wish to publish (email address, phone
-numbers, website URLs, etc...). This specification puts no requirements on the
-display name other than it being a valid unicode string. Avatar images are not
-stored directly; instead the home server stores an ``http``-scheme URL where
-clients may fetch it from.
-
-Client API
-----------
-The client API for profile management consists of the following REST calls.
-
-Fetching a user account displayname::
-
-  GET $PREFIX/profile/:user_id/displayname
-
-  Returned content: JSON object containing the following keys:
-    displayname: string of freeform text
-
-This call may be used to fetch the user's own displayname or to query the name
-of other users; either locally or on remote systems hosted on other home
-servers.
-
-Setting a new displayname::
-
-  PUT $PREFIX/profile/:user_id/displayname
-
-  Content: JSON object containing the following keys:
-    displayname: string of freeform text
-
-Fetching a user account avatar URL::
-
-  GET $PREFIX/profile/:user_id/avatar_url
-
-  Returned content: JSON object containing the following keys:
-    avatar_url: string containing an http-scheme URL
-
-As with displayname, this call may be used to fetch either the user's own, or
-other users' avatar URL.
-
-Setting a new avatar URL::
-
-  PUT $PREFIX/profile/:user_id/avatar_url
-
-  Content: JSON object containing the following keys:
-    avatar_url: string containing an http-scheme URL
-
-Fetching combined account profile information::
-
-  GET $PREFIX/profile/:user_id
-
-  Returned content: JSON object containing the following keys:
-    displayname: string of freeform text
-    avatar_url: string containing an http-scheme URL
-
-At the current time, this API simply returns the displayname and avatar URL
-information, though it is intended to return more fields about the user's
-profile once they are defined. Client implementations should take care not to
-expect that these are the only two keys returned as future versions of this
-specification may yield more keys here.
-
-Server API
-----------
-The server API for profiles is based entirely on the following Federation
-Queries. There are no additional EDU or PDU types involved, other than the
-implicit ``m.presence`` and ``m.room.member`` events (see section below).
-
-Querying profile information::
-
-  Query type: profile
-
-  Arguments:
-    user_id: the ID of the user whose profile to return
-    field: (optional) string giving a field name
-
-  Returns: JSON object containing the following keys:
-    displayname: string of freeform text
-    avatar_url: string containing an http-scheme URL
-
-If the query contains the optional ``field`` key, it should give the name of a
-result field. If such is present, then the result should contain only a field
-of that name, with no others present. If not, the result should contain as much
-of the user's profile as the home server has available and can make public.
-
-Events on Change of Profile Information
----------------------------------------
-Because the profile displayname and avatar information are likely to be used in
-many places of a client's display, changes to these fields cause an automatic
-propagation event to occur, informing likely-interested parties of the new
-values. This change is conveyed using two separate mechanisms:
-
- - a ``m.room.member`` event is sent to every room the user is a member of,
-   to update the ``displayname`` and ``avatar_url``.
- - a presence status update is sent, again containing the new values of the
-   ``displayname`` and ``avatar_url`` keys, in addition to the required
-   ``presence`` key containing the current presence state of the user.
-
-Both of these should be done automatically by the home server when a user
-successfully changes their displayname or avatar URL fields.
-
-Additionally, when home servers emit room membership events for their own
-users, they should include the displayname and avatar URL fields in these
-events so that clients already have these details to hand, and do not have to
-perform extra roundtrips to query it.
-
-
-Identity
-========
-.. NOTE::
-  This section is a work in progress.
-
-.. TODO-doc Dave
-  - 3PIDs and identity server, functions
-
-
-Federation
-==========
-
-Federation is the term used to describe how to communicate between Matrix home
-servers. Federation is a mechanism by which two home servers can exchange
-Matrix event messages, both as a real-time push of current events, and as a
-historic fetching mechanism to synchronise past history for clients to view. It
-uses HTTPS connections between each pair of servers involved as the underlying
-transport. Messages are exchanged between servers in real-time by active
-pushing from each server's HTTP client into the server of the other. Queries to
-fetch historic data for the purpose of back-filling scrollback buffers and the
-like can also be performed. Currently routing of messages between homeservers
-is full mesh (like email) - however, fan-out refinements to this design are
-currently under consideration.
-
-There are three main kinds of communication that occur between home servers:
-
-:Queries:
-   These are single request/response interactions between a given pair of
-   servers, initiated by one side sending an HTTPS GET request to obtain some
-   information, and responded by the other. They are not persisted and contain
-   no long-term significant history. They simply request a snapshot state at
-   the instant the query is made.
-
-:Ephemeral Data Units (EDUs):
-   These are notifications of events that are pushed from one home server to
-   another. They are not persisted and contain no long-term significant
-   history, nor does the receiving home server have to reply to them.
-
-:Persisted Data Units (PDUs):
-   These are notifications of events that are broadcast from one home server to
-   any others that are interested in the same "context" (namely, a Room ID).
-   They are persisted to long-term storage and form the record of history for
-   that context.
-
-EDUs and PDUs are further wrapped in an envelope called a Transaction, which is
-transferred from the origin to the destination home server using an HTTP PUT
-request.
-
-
-Transactions
-------------
-.. WARNING::
-  This section may be misleading or inaccurate.
-
-The transfer of EDUs and PDUs between home servers is performed by an exchange
-of Transaction messages, which are encoded as JSON objects, passed over an HTTP
-PUT request. A Transaction is meaningful only to the pair of home servers that
-exchanged it; they are not globally-meaningful.
-
-Each transaction has:
- - An opaque transaction ID.
- - A timestamp (UNIX epoch time in milliseconds) generated by its origin
-   server.
- - An origin and destination server name.
- - A list of "previous IDs".
- - A list of PDUs and EDUs - the actual message payload that the Transaction
-   carries.
- 
-``origin``
-  Type: 
-    String
-  Description:
-    DNS name of homeserver making this transaction.
-    
-``ts``
-  Type: 
-    Integer
-  Description:
-    Timestamp in milliseconds on originating homeserver when this transaction 
-    started.
-    
-``previous_ids``
-  Type:
-    List of strings
-  Description:
-    List of transactions that were sent immediately prior to this transaction.
-    
-``pdus``
-  Type:
-    List of Objects.
-  Description:
-    List of updates contained in this transaction.
-
-::
-
- {
-  "transaction_id":"916d630ea616342b42e98a3be0b74113",
-  "ts":1404835423000,
-  "origin":"red",
-  "destination":"blue",
-  "prev_ids":["e1da392e61898be4d2009b9fecce5325"],
-  "pdus":[...],
-  "edus":[...]
- }
-
-The ``prev_ids`` field contains a list of previous transaction IDs that the
-``origin`` server has sent to this ``destination``. Its purpose is to act as a
-sequence checking mechanism - the destination server can check whether it has
-successfully received that Transaction, or ask for a retransmission if not.
-
-The ``pdus`` field of a transaction is a list, containing zero or more PDUs.[*]
-Each PDU is itself a JSON object containing a number of keys, the exact details
-of which will vary depending on the type of PDU. Similarly, the ``edus`` field
-is another list containing the EDUs. This key may be entirely absent if there
-are no EDUs to transfer.
-
-(* Normally the PDU list will be non-empty, but the server should cope with
-receiving an "empty" transaction.)
-
-PDUs and EDUs
--------------
-.. WARNING::
-  This section may be misleading or inaccurate.
-
-All PDUs have:
- - An ID
- - A context
- - A declaration of their type
- - A list of other PDU IDs that have been seen recently on that context
-   (regardless of which origin sent them)
-
-``context``
-  Type:
-    String
-  Description:
-    Event context identifier
-    
-``origin``
-  Type:
-    String
-  Description:
-    DNS name of homeserver that created this PDU.
-    
-``pdu_id``
-  Type:
-    String
-  Description:
-    Unique identifier for PDU within the context for the originating homeserver
-
-``ts``
-  Type:
-    Integer
-  Description:
-    Timestamp in milliseconds on originating homeserver when this PDU was
-    created.
-
-``pdu_type``
-  Type:
-    String
-  Description:
-    PDU event type.
-
-``prev_pdus``
-  Type:
-    List of pairs of strings
-  Description:
-    The originating homeserver and PDU ids of the most recent PDUs the
-    homeserver was aware of for this context when it made this PDU.
-
-``depth``
-  Type:
-    Integer
-  Description:
-    The maximum depth of the previous PDUs plus one.
-
-
-.. TODO-spec paul
-  - Update this structure so that 'pdu_id' is a two-element [origin,ref] pair
-    like the prev_pdus are
-
-For state updates:
-
-``is_state``
-  Type:
-    Boolean
-  Description:
-    True if this PDU is updating state.
-    
-``state_key``
-  Type:
-    String
-  Description:
-    Optional key identifying the updated state within the context.
-    
-``power_level``
-  Type:
-    Integer
-  Description:
-    The asserted power level of the user performing the update.
-    
-``required_power_level``
-  Type:
-    Integer
-  Description:
-    The required power level needed to replace this update.
-
-``prev_state_id``
-  Type:
-    String
-  Description:
-    PDU event type.
-    
-``prev_state_origin``
-  Type:
-    String
-  Description:
-    The PDU id of the update this replaces.
-    
-``user_id``
-  Type:
-    String
-  Description:
-    The user updating the state.
-
-::
-
- {
-  "pdu_id":"a4ecee13e2accdadf56c1025af232176",
-  "context":"#example.green",
-  "origin":"green",
-  "ts":1404838188000,
-  "pdu_type":"m.text",
-  "prev_pdus":[["blue","99d16afbc857975916f1d73e49e52b65"]],
-  "content":...
-  "is_state":false
- }
-
-In contrast to Transactions, it is important to note that the ``prev_pdus``
-field of a PDU refers to PDUs that any origin server has sent, rather than
-previous IDs that this ``origin`` has sent. This list may refer to other PDUs
-sent by the same origin as the current one, or other origins.
-
-Because of the distributed nature of participants in a Matrix conversation, it
-is impossible to establish a globally-consistent total ordering on the events.
-However, by annotating each outbound PDU at its origin with IDs of other PDUs
-it has received, a partial ordering can be constructed allowing causality
-relationships to be preserved. A client can then display these messages to the
-end-user in some order consistent with their content and ensure that no message
-that is semantically in reply of an earlier one is ever displayed before it.
-
-PDUs fall into two main categories: those that deliver Events, and those that
-synchronise State. For PDUs that relate to State synchronisation, additional
-keys exist to support this:
-
-::
-
- {...,
-  "is_state":true,
-  "state_key":TODO-doc
-  "power_level":TODO-doc
-  "prev_state_id":TODO-doc
-  "prev_state_origin":TODO-doc}
-
-EDUs, by comparison to PDUs, do not have an ID, a context, or a list of
-"previous" IDs. The only mandatory fields for these are the type, origin and
-destination home server names, and the actual nested content.
-
-::
-
- {"edu_type":"m.presence",
-  "origin":"blue",
-  "destination":"orange",
-  "content":...}
-  
-  
-Protocol URLs
--------------
-.. WARNING::
-  This section may be misleading or inaccurate.
-
-All these URLs are namespaced within a prefix of::
-
-  /_matrix/federation/v1/...
-
-For active pushing of messages representing live activity "as it happens"::
-
-  PUT .../send/:transaction_id/
-    Body: JSON encoding of a single Transaction
-    Response: TODO-doc
-
-The transaction_id path argument will override any ID given in the JSON body.
-The destination name will be set to that of the receiving server itself. Each
-embedded PDU in the transaction body will be processed.
-
-
-To fetch a particular PDU::
-
-  GET .../pdu/:origin/:pdu_id/
-    Response: JSON encoding of a single Transaction containing one PDU
-
-Retrieves a given PDU from the server. The response will contain a single new
-Transaction, inside which will be the requested PDU.
-  
-
-To fetch all the state of a given context::
-
-  GET .../state/:context/
-    Response: JSON encoding of a single Transaction containing multiple PDUs
-
-Retrieves a snapshot of the entire current state of the given context. The
-response will contain a single Transaction, inside which will be a list of PDUs
-that encode the state.
-
-To backfill events on a given context::
-
-  GET .../backfill/:context/
-    Query args: v, limit
-    Response: JSON encoding of a single Transaction containing multiple PDUs
-
-Retrieves a sliding-window history of previous PDUs that occurred on the given
-context. Starting from the PDU ID(s) given in the "v" argument, the PDUs that
-preceeded it are retrieved, up to a total number given by the "limit" argument.
-These are then returned in a new Transaction containing all of the PDUs.
-
-
-To stream events all the events::
-
-  GET .../pull/
-    Query args: origin, v
-    Response: JSON encoding of a single Transaction consisting of multiple PDUs
-
-Retrieves all of the transactions later than any version given by the "v"
-arguments.
-
-
-To make a query::
-
-  GET .../query/:query_type
-    Query args: as specified by the individual query types
-    Response: JSON encoding of a response object
-
-Performs a single query request on the receiving home server. The Query Type
-part of the path specifies the kind of query being made, and its query
-arguments have a meaning specific to that kind of query. The response is a
-JSON-encoded object whose meaning also depends on the kind of query.
-
-Backfilling
------------
-.. NOTE::
-  This section is a work in progress.
-
-.. TODO-doc
-  - What it is, when is it used, how is it done
-
-SRV Records
------------
-.. NOTE::
-  This section is a work in progress.
-
-.. TODO-doc
-  - Why it is needed
-
-State Conflict Resolution
--------------------------
-.. NOTE::
-  This section is a work in progress.
-
-.. TODO-doc
-  - How do conflicts arise (diagrams?)
-  - How are they resolved (incl tie breaks)
-  - How does this work with deleting current state
-
-Security
-========
-
-.. NOTE::
-  This section is a work in progress.
-
-Server-Server Authentication
-----------------------------
-
-.. TODO-doc
-  - Why is this needed.
-  - High level overview of process.
-  - Transaction/PDU signing
-  - How does this work with redactions? (eg hashing required keys only)
-
-End-to-End Encryption
----------------------
-
-.. TODO-doc
-  - Why is this needed.
-  - Overview of process
-  - Implementation
-
-Lawful Interception
--------------------
-
-Key Escrow Servers
-~~~~~~~~~~~~~~~~~~
-
-Threat Model
-------------
-
-Denial of Service
-~~~~~~~~~~~~~~~~~
-
-The attacker could attempt to prevent delivery of messages to or from the
-victim in order to:
-
-* Disrupt service or marketing campaign of a commercial competitor.
-* Censor a discussion or censor a participant in a discussion.
-* Perform general vandalism.
-
-Threat: Resource Exhaustion
-+++++++++++++++++++++++++++
-
-An attacker could cause the victims server to exhaust a particular resource
-(e.g. open TCP connections, CPU, memory, disk storage)
-
-Threat: Unrecoverable Consistency Violations
-++++++++++++++++++++++++++++++++++++++++++++
-
-An attacker could send messages which created an unrecoverable "split-brain"
-state in the cluster such that the victim's servers could no longer dervive a
-consistent view of the chatroom state.
-
-Threat: Bad History
-+++++++++++++++++++
-
-An attacker could convince the victim to accept invalid messages which the
-victim would then include in their view of the chatroom history. Other servers
-in the chatroom would reject the invalid messages and potentially reject the
-victims messages as well since they depended on the invalid messages.
-
-.. TODO-spec
-  Track trustworthiness of HS or users based on if they try to pretend they
-  haven't seen recent events, and fake a splitbrain... --M
-
-Threat: Block Network Traffic
-+++++++++++++++++++++++++++++
-
-An attacker could try to firewall traffic between the victim's server and some
-or all of the other servers in the chatroom.
-
-Threat: High Volume of Messages
-+++++++++++++++++++++++++++++++
-
-An attacker could send large volumes of messages to a chatroom with the victim
-making the chatroom unusable.
-
-Threat: Banning users without necessary authorisation
-+++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-An attacker could attempt to ban a user from a chatroom with the necessary
-authorisation.
-
-Spoofing
-~~~~~~~~
-
-An attacker could try to send a message claiming to be from the victim without
-the victim having sent the message in order to:
-
-* Impersonate the victim while performing illict activity.
-* Obtain privileges of the victim.
-
-Threat: Altering Message Contents
-+++++++++++++++++++++++++++++++++
-
-An attacker could try to alter the contents of an existing message from the
-victim.
-
-Threat: Fake Message "origin" Field
-+++++++++++++++++++++++++++++++++++
-
-An attacker could try to send a new message purporting to be from the victim
-with a phony "origin" field.
-
-Spamming
-~~~~~~~~
-
-The attacker could try to send a high volume of solicicted or unsolicted
-messages to the victim in order to:
-
-* Find victims for scams.
-* Market unwanted products.
-
-Threat: Unsoliticted Messages
-+++++++++++++++++++++++++++++
-
-An attacker could try to send messages to victims who do not wish to receive
-them.
-
-Threat: Abusive Messages
-++++++++++++++++++++++++
-
-An attacker could send abusive or threatening messages to the victim
-
-Spying
-~~~~~~
-
-The attacker could try to access message contents or metadata for messages sent
-by the victim or to the victim that were not intended to reach the attacker in
-order to:
-
-* Gain sensitive personal or commercial information.
-* Impersonate the victim using credentials contained in the messages.
-  (e.g. password reset messages)
-* Discover who the victim was talking to and when.
-
-Threat: Disclosure during Transmission
-++++++++++++++++++++++++++++++++++++++
-
-An attacker could try to expose the message contents or metadata during
-transmission between the servers.
-
-Threat: Disclosure to Servers Outside Chatroom
-++++++++++++++++++++++++++++++++++++++++++++++
-
-An attacker could try to convince servers within a chatroom to send messages to
-a server it controls that was not authorised to be within the chatroom.
-
-Threat: Disclosure to Servers Within Chatroom
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-An attacker could take control of a server within a chatroom to expose message
-contents or metadata for messages in that room.
-
-Rate limiting
--------------
-Home servers SHOULD implement rate limiting to reduce the risk of being
-overloaded. If a request is refused due to rate limiting, it should return a
-standard error response of the form::
-
-  {
-    "errcode": "M_LIMIT_EXCEEDED",
-    "error": "string",
-    "retry_after_ms": integer (optional)
-  }
-
-The ``retry_after_ms`` key SHOULD be included to tell the client how long they
-have to wait in milliseconds before they can try again.
-
-.. TODO-spec
-  - Surely we should recommend an algorithm for the rate limiting, rather than letting every
-    homeserver come up with their own idea, causing totally unpredictable performance over
-    federated rooms?
-
-
-Policy Servers
-==============
-.. NOTE::
-  This section is a work in progress.
-
-.. TODO-spec
-  We should mention them in the Architecture section at least: how they fit
-  into the picture.
-
-Enforcing policies
-------------------
-
-
-Content repository
-==================
-.. NOTE::
-  This section is a work in progress.
-
-.. TODO-spec
-  - path to upload
-  - format for thumbnail paths, mention what it is protecting against.
-  - content size limit and associated M_ERROR.
-
-
-Address book repository
-=======================
-.. NOTE::
-  This section is a work in progress.
-
-.. TODO-spec
-  - format: POST(?) wodges of json, some possible processing, then return wodges of json on GET.
-  - processing may remove dupes, merge contacts, pepper with extra info (e.g. matrix-ability of
-    contacts), etc.
-  - Standard json format for contacts? Piggy back off vcards?
-
-
-Glossary
-========
-.. NOTE::
-  This section is a work in progress.
-
-Backfilling:
-  The process of synchronising historic state from one home server to another,
-  to backfill the event storage so that scrollback can be presented to the
-  client(s). Not to be confused with pagination.
-
-Context:
-  A single human-level entity of interest (currently, a chat room)
-
-EDU (Ephemeral Data Unit):
-  A message that relates directly to a given pair of home servers that are
-  exchanging it. EDUs are short-lived messages that related only to one single
-  pair of servers; they are not persisted for a long time and are not forwarded
-  on to other servers. Because of this, they have no internal ID nor previous
-  EDUs reference chain.
-
-Event:
-  A record of activity that records a single thing that happened on to a context
-  (currently, a chat room). These are the "chat messages" that Synapse makes
-  available.
-
-PDU (Persistent Data Unit):
-  A message that relates to a single context, irrespective of the server that
-  is communicating it. PDUs either encode a single Event, or a single State
-  change. A PDU is referred to by its PDU ID; the pair of its origin server
-  and local reference from that server.
-
-PDU ID:
-  The pair of PDU Origin and PDU Reference, that together globally uniquely
-  refers to a specific PDU.
-
-PDU Origin:
-  The name of the origin server that generated a given PDU. This may not be the
-  server from which it has been received, due to the way they are copied around
-  from server to server. The origin always records the original server that
-  created it.
-
-PDU Reference:
-  A local ID used to refer to a specific PDU from a given origin server. These
-  references are opaque at the protocol level, but may optionally have some
-  structured meaning within a given origin server or implementation.
-
-Presence:
-  The concept of whether a user is currently online, how available they declare
-  they are, and so on. See also: doc/model/presence
-
-Profile:
-  A set of metadata about a user, such as a display name, provided for the
-  benefit of other users. See also: doc/model/profiles
-
-Room ID:
-  An opaque string (of as-yet undecided format) that identifies a particular
-  room and used in PDUs referring to it.
-
-Room Alias:
-  A human-readable string of the form #name:some.domain that users can use as a
-  pointer to identify a room; a Directory Server will map this to its Room ID
-
-State:
-  A set of metadata maintained about a Context, which is replicated among the
-  servers in addition to the history of Events.
-
-User ID:
-  A string of the form @localpart:domain.name that identifies a user for
-  wire-protocol purposes. The localpart is meaningless outside of a particular
-  home server. This takes a human-readable form that end-users can use directly
-  if they so wish, avoiding the 3PIDs.
-
-Transaction:
-  A message which relates to the communication between a given pair of servers.
-  A transaction contains possibly-empty lists of PDUs and EDUs.
-
-.. TODO
-  This glossary contradicts the terms used above - especially on State Events v. "State"
-  and Non-State Events v. "Events".  We need better consistent names.
-
-.. Links through the external API docs are below
-.. =============================================
-
-.. |createRoom| replace:: ``/createRoom``
-.. _createRoom: /docs/api/client-server/#!/-rooms/create_room
-
-.. |initialSync| replace:: ``/initialSync``
-.. _initialSync: /docs/api/client-server/#!/-events/initial_sync
-
-.. |/rooms/<room_id>/initialSync| replace:: ``/rooms/<room_id>/initialSync``
-.. _/rooms/<room_id>/initialSync: /docs/api/client-server/#!/-rooms/get_room_sync_data
-
-.. |login| replace:: ``/login``
-.. _login: /docs/api/client-server/#!/-login
-
-.. |register| replace:: ``/register``
-.. _register: /docs/api/client-server/#!/-registration
-
-.. |/rooms/<room_id>/messages| replace:: ``/rooms/<room_id>/messages``
-.. _/rooms/<room_id>/messages: /docs/api/client-server/#!/-rooms/get_messages
-
-.. |/rooms/<room_id>/members| replace:: ``/rooms/<room_id>/members``
-.. _/rooms/<room_id>/members: /docs/api/client-server/#!/-rooms/get_members
-
-.. |/rooms/<room_id>/state| replace:: ``/rooms/<room_id>/state``
-.. _/rooms/<room_id>/state: /docs/api/client-server/#!/-rooms/get_state_events
-
-.. |/rooms/<room_id>/send/<event_type>| replace:: ``/rooms/<room_id>/send/<event_type>``
-.. _/rooms/<room_id>/send/<event_type>: /docs/api/client-server/#!/-rooms/send_non_state_event
-
-.. |/rooms/<room_id>/state/<event_type>/<state_key>| replace:: ``/rooms/<room_id>/state/<event_type>/<state_key>``
-.. _/rooms/<room_id>/state/<event_type>/<state_key>: /docs/api/client-server/#!/-rooms/send_state_event
-
-.. |/rooms/<room_id>/invite| replace:: ``/rooms/<room_id>/invite``
-.. _/rooms/<room_id>/invite: /docs/api/client-server/#!/-rooms/invite
-
-.. |/rooms/<room_id>/join| replace:: ``/rooms/<room_id>/join``
-.. _/rooms/<room_id>/join: /docs/api/client-server/#!/-rooms/join_room
-
-.. |/rooms/<room_id>/leave| replace:: ``/rooms/<room_id>/leave``
-.. _/rooms/<room_id>/leave: /docs/api/client-server/#!/-rooms/leave
-
-.. |/rooms/<room_id>/ban| replace:: ``/rooms/<room_id>/ban``
-.. _/rooms/<room_id>/ban: /docs/api/client-server/#!/-rooms/ban
-
-.. |/join/<room_alias_or_id>| replace:: ``/join/<room_alias_or_id>``
-.. _/join/<room_alias_or_id>: /docs/api/client-server/#!/-rooms/join
-
-.. _`Event Stream`: /docs/api/client-server/#!/-events/get_event_stream
-
diff --git a/docs/state_resolution.rst b/docs/state_resolution.rst
deleted file mode 100644
index fec290dd79..0000000000
--- a/docs/state_resolution.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-State Resolution
-================
-This section describes why we need state resolution and how it works.
-
-
-Motivation
------------
-We want to be able to associate some shared state with rooms, e.g. a room name
-or members list. This is done by having a current state dictionary that maps
-from the pair event type and state key to an event.
-
-However, since the servers involved in the room are distributed we need to be
-able to handle the case when two (or more) servers try and update the state at
-the same time. This is done via the state resolution algorithm.
-
-
-State Tree
-------------
-State events contain a reference to the state it is trying to replace. These
-relations form a tree where the current state is one of the leaf nodes.
-
-Note that state events are events, and so are part of the PDU graph. Thus we
-can be sure that (modulo the internet being particularly broken) we will see
-all state events eventually.
-
-
-Algorithm requirements
-----------------------
-We want the algorithm to have the following properties:
-- Since we aren't guaranteed what order we receive state events in, except that
-  we see parents before children, the state resolution algorithm must not depend
-  on the order and must always come to the same result. 
-- If we receive a state event whose parent is the current state, then the
-  algorithm will select it.
-- The algorithm does not depend on internal state, ensuring all servers should
-  come to the same decision.
-
-These three properties mean it is enough to keep track of the current state and
-compare it with any new proposed state, rather than having to keep track of all
-the leafs of the tree and recomputing across the entire state tree.
-
-
-Current Implementation
-----------------------
-The current implementation works as follows: Upon receipt of a newly proposed
-state change we first find the common ancestor. Then we take the maximum
-across each branch of the users' power levels, if one is higher then it is
-selected as the current state. Otherwise, we check if one chain is longer than
-the other, if so we choose that one. If that also fails, then we concatenate
-all the pdu ids and take a SHA1 hash and compare them to select a common
-ancestor.
diff --git a/pylint.cfg b/pylint.cfg
new file mode 100644
index 0000000000..2368997112
--- /dev/null
+++ b/pylint.cfg
@@ -0,0 +1,280 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time. See also the "--disable" option for examples.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=missing-docstring
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=yes
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the beginning of the name of dummy variables
+# (i.e. not used).
+dummy-variables-rgx=_$|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct attribute names in class
+# bodies
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=__.*__
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+# List of optional constructs for which whitespace checking is disabled
+no-space-check=trailing-comma,dict-separator
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/setup.py b/setup.py
index a9470b4c9e..660efd5b89 100755
--- a/setup.py
+++ b/setup.py
@@ -31,9 +31,10 @@ setup(
     packages=find_packages(exclude=["tests"]),
     description="Reference Synapse Home Server",
     install_requires=[
-        "syutil==0.0.1",
+        "syutil==0.0.2",
         "Twisted>=14.0.0",
         "service_identity>=1.0.0",
+        "pyopenssl>=0.14",
         "pyyaml",
         "pyasn1",
         "pynacl",
@@ -41,11 +42,12 @@ setup(
         "py-bcrypt",
     ],
     dependency_links=[
-        "git+ssh://git@github.com/matrix-org/syutil.git#egg=syutil-0.0.1",
+        "https://github.com/matrix-org/syutil/tarball/v0.0.2#egg=syutil-0.0.2",
     ],
     setup_requires=[
         "setuptools_trial",
-        "setuptools>=1.0.0", # Needs setuptools that supports git+ssh. It's not obvious when support for this was introduced.
+        "setuptools>=1.0.0", # Needs setuptools that supports git+ssh.
+                             # TODO: Do we need this now? we don't use git+ssh.
         "mock"
     ],
     include_package_data=True,
diff --git a/synapse/__init__.py b/synapse/__init__.py
index a340a5db66..7067188c5b 100644
--- a/synapse/__init__.py
+++ b/synapse/__init__.py
@@ -16,4 +16,4 @@
 """ This is a reference implementation of a synapse home server.
 """
 
-__version__ = "0.3.4"
+__version__ = "0.4.1"
diff --git a/synapse/config/server.py b/synapse/config/server.py
index d9d8d0e14e..086937044f 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -74,7 +74,7 @@ class ServerConfig(Config):
             return syutil.crypto.signing_key.read_signing_keys(
                 signing_keys.splitlines(True)
             )
-        except Exception as e:
+        except Exception:
             raise ConfigError(
                 "Error reading signing_key."
                 " Try running again with --generate-config"
@@ -94,7 +94,7 @@ class ServerConfig(Config):
             with open(args.signing_key_path, "w") as signing_key_file:
                 syutil.crypto.signing_key.write_signing_keys(
                     signing_key_file,
-                    (syutil.crypto.SigningKey.generate("auto"),),
+                    (syutil.crypto.signing_key.generate_singing_key("auto"),),
                 )
         else:
             signing_keys = cls.read_file(args.signing_key_path, "signing_key")
diff --git a/synapse/crypto/context_factory.py b/synapse/crypto/context_factory.py
index f86bd19255..f402c795bb 100644
--- a/synapse/crypto/context_factory.py
+++ b/synapse/crypto/context_factory.py
@@ -16,6 +16,9 @@ from twisted.internet import ssl
 from OpenSSL import SSL
 from twisted.internet._sslverify import _OpenSSLECCurve, _defaultCurveName
 
+import logging
+
+logger = logging.getLogger(__name__)
 
 class ServerContextFactory(ssl.ContextFactory):
     """Factory for PyOpenSSL SSL contexts that are used to handle incoming
@@ -31,7 +34,7 @@ class ServerContextFactory(ssl.ContextFactory):
             _ecCurve = _OpenSSLECCurve(_defaultCurveName)
             _ecCurve.addECKeyToContext(context)
         except:
-            pass
+            logger.exception("Failed to enable eliptic curve for TLS")
         context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
         context.use_certificate(config.tls_certificate)
         context.use_privatekey(config.tls_private_key)
diff --git a/synapse/crypto/keyclient.py b/synapse/crypto/keyclient.py
index 5949ea0573..7cfec5148e 100644
--- a/synapse/crypto/keyclient.py
+++ b/synapse/crypto/keyclient.py
@@ -17,7 +17,6 @@
 from twisted.web.http import HTTPClient
 from twisted.internet.protocol import Factory
 from twisted.internet import defer, reactor
-from twisted.internet.endpoints import connectProtocol
 from synapse.http.endpoint import matrix_endpoint
 import json
 import logging
diff --git a/synapse/crypto/keyring.py b/synapse/crypto/keyring.py
index 015f76ebe3..2440d604c3 100644
--- a/synapse/crypto/keyring.py
+++ b/synapse/crypto/keyring.py
@@ -38,6 +38,7 @@ class Keyring(object):
 
     @defer.inlineCallbacks
     def verify_json_for_server(self, server_name, json_object):
+        logger.debug("Verifying for %s", server_name)
         key_ids = signature_ids(json_object, server_name)
         if not key_ids:
             raise SynapseError(
diff --git a/synapse/federation/transport.py b/synapse/federation/transport.py
index 755eee8cf6..e7517cac4d 100644
--- a/synapse/federation/transport.py
+++ b/synapse/federation/transport.py
@@ -238,6 +238,11 @@ class TransportLayer(object):
 
         auth_headers = request.requestHeaders.getRawHeaders(b"Authorization")
 
+        if not auth_headers:
+            raise SynapseError(
+                401, "Missing Authorization headers", Codes.UNAUTHORIZED,
+            )
+
         for auth in auth_headers:
             if auth.startswith("X-Matrix"):
                 (origin, key, sig) = parse_auth_header(auth)
@@ -256,10 +261,14 @@ class TransportLayer(object):
     def _with_authentication(self, handler):
         @defer.inlineCallbacks
         def new_handler(request, *args, **kwargs):
-            (origin, content) = yield self._authenticate_request(request)
-            response = yield handler(
-                origin, content, request.args, *args, **kwargs
-            )
+            try:
+                (origin, content) = yield self._authenticate_request(request)
+                response = yield handler(
+                    origin, content, request.args, *args, **kwargs
+                )
+            except:
+                logger.exception("_authenticate_request failed")
+                raise
             defer.returnValue(response)
         return new_handler
 
@@ -392,9 +401,13 @@ class TransportLayer(object):
             defer.returnValue((400, {"error": "Invalid transaction"}))
             return
 
-        code, response = yield self.received_handler.on_incoming_transaction(
-            transaction_data
-        )
+        try:
+            code, response = yield self.received_handler.on_incoming_transaction(
+                transaction_data
+            )
+        except:
+            logger.exception("on_incoming_transaction failed")
+            raise
 
         defer.returnValue((code, response))
 
diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py
index df562aa762..94b7890b5e 100644
--- a/synapse/handlers/register.py
+++ b/synapse/handlers/register.py
@@ -15,6 +15,7 @@
 
 """Contains functions for registering clients."""
 from twisted.internet import defer
+from twisted.python import log
 
 from synapse.types import UserID
 from synapse.api.errors import (
@@ -126,7 +127,7 @@ class RegistrationHandler(BaseHandler):
             try:
                 threepid = yield self._threepid_from_creds(c)
             except:
-                logger.err()
+                log.err()
                 raise RegistrationError(400, "Couldn't validate 3pid")
 
             if not threepid:
diff --git a/synapse/http/client.py b/synapse/http/client.py
index 316ca1ccb9..46c90dbb76 100644
--- a/synapse/http/client.py
+++ b/synapse/http/client.py
@@ -101,7 +101,9 @@ class BaseHttpClient(object):
 
         while True:
 
-            producer = body_callback(method, url_bytes, headers_dict)
+            producer = None
+            if body_callback:
+                producer = body_callback(method, url_bytes, headers_dict)
 
             try:
                 response = yield self.agent.request(
@@ -312,6 +314,42 @@ class IdentityServerHttpClient(BaseHttpClient):
 
         defer.returnValue(json.loads(body))
 
+    @defer.inlineCallbacks
+    def get_json(self, destination, path, args={}, retry_on_dns_fail=True):
+        """ Get's some json from the given host homeserver and path
+
+        Args:
+            destination (str): The remote server to send the HTTP request
+                to.
+            path (str): The HTTP path.
+            args (dict): A dictionary used to create query strings, defaults to
+                None.
+                **Note**: The value of each key is assumed to be an iterable
+                and *not* a string.
+
+        Returns:
+            Deferred: Succeeds when we get *any* HTTP response.
+
+            The result of the deferred is a tuple of `(code, response)`,
+            where `response` is a dict representing the decoded JSON body.
+        """
+        logger.debug("get_json args: %s", args)
+
+        query_bytes = urllib.urlencode(args, True)
+        logger.debug("Query bytes: %s Retry DNS: %s", args, retry_on_dns_fail)
+
+        response = yield self._create_request(
+            destination.encode("ascii"),
+            "GET",
+            path.encode("ascii"),
+            query_bytes=query_bytes,
+            retry_on_dns_fail=retry_on_dns_fail,
+            body_callback=None
+        )
+
+        body = yield readBody(response)
+
+        defer.returnValue(json.loads(body))
 
 class CaptchaServerHttpClient(MatrixHttpClient):
     """Separate HTTP client for talking to google's captcha servers"""
diff --git a/synapse/rest/room.py b/synapse/rest/room.py
index a01dab1b8e..c72bdc2c34 100644
--- a/synapse/rest/room.py
+++ b/synapse/rest/room.py
@@ -344,7 +344,7 @@ class RoomInitialSyncRestServlet(RestServlet):
 
     @defer.inlineCallbacks
     def on_GET(self, request, room_id):
-        user = yield self.auth.get_user_by_req(request)
+        yield self.auth.get_user_by_req(request)
         # TODO: Get all the initial sync data for this room and return in the
         # same format as initial sync, that is:
         # {
diff --git a/synapse/rest/voip.py b/synapse/rest/voip.py
index 2e4627606f..0d0243a249 100644
--- a/synapse/rest/voip.py
+++ b/synapse/rest/voip.py
@@ -36,7 +36,7 @@ class VoipRestServlet(RestServlet):
         if not turnUris or not turnSecret or not userLifetime:
             defer.returnValue( (200, {}) )
 
-        expiry = self.hs.get_clock().time_msec() + userLifetime
+        expiry = (self.hs.get_clock().time_msec() + userLifetime) / 1000
         username = "%d:%s" % (expiry, auth_user.to_string())
          
         mac = hmac.new(turnSecret, msg=username, digestmod=hashlib.sha1)
diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py
index e4f708b6ad..1639e2c973 100644
--- a/synapse/storage/__init__.py
+++ b/synapse/storage/__init__.py
@@ -70,7 +70,7 @@ SCHEMAS = [
 
 # Remember to update this number every time an incompatible change is made to
 # database schema files, so the users will be informed on server restarts.
-SCHEMA_VERSION = 5
+SCHEMA_VERSION = 6
 
 
 class _RollbackButIsFineException(Exception):
@@ -487,10 +487,11 @@ def prepare_database(db_conn):
             db_conn.commit()
 
     else:
+        sql_script = "BEGIN TRANSACTION;"
         for sql_loc in SCHEMAS:
-            sql_script = read_schema(sql_loc)
-
-            c.executescript(sql_script)
+            sql_script += read_schema(sql_loc)
+        sql_script += "COMMIT TRANSACTION;"
+        c.executescript(sql_script)
         db_conn.commit()
         c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)
 
diff --git a/synapse/storage/keys.py b/synapse/storage/keys.py
index 8189e071a3..4feb8335ba 100644
--- a/synapse/storage/keys.py
+++ b/synapse/storage/keys.py
@@ -104,7 +104,6 @@ class KeyStore(SQLBaseStore):
             ts_now_ms (int): The time now in milliseconds
             verification_key (VerifyKey): The NACL verify key.
         """
-        verify_key_bytes = verify_key.encode()
         return self._simple_insert(
             table="server_signature_keys",
             values={
diff --git a/synapse/storage/schema/delta/v6.sql b/synapse/storage/schema/delta/v6.sql
new file mode 100644
index 0000000000..9bf2068d84
--- /dev/null
+++ b/synapse/storage/schema/delta/v6.sql
@@ -0,0 +1,31 @@
+/* Copyright 2014 OpenMarket Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+CREATE TABLE IF NOT EXISTS server_tls_certificates(
+  server_name TEXT, -- Server name.
+  fingerprint TEXT, -- Certificate fingerprint.
+  from_server TEXT, -- Which key server the certificate was fetched from.
+  ts_added_ms INTEGER, -- When the certifcate was added.
+  tls_certificate BLOB, -- DER encoded x509 certificate.
+  CONSTRAINT uniqueness UNIQUE (server_name, fingerprint)
+);
+
+CREATE TABLE IF NOT EXISTS server_signature_keys(
+  server_name TEXT, -- Server name.
+  key_id TEXT, -- Key version.
+  from_server TEXT, -- Which key server the key was fetched form.
+  ts_added_ms INTEGER, -- When the key was added.
+  verify_key BLOB, -- NACL verification key.
+  CONSTRAINT uniqueness UNIQUE (server_name, key_id)
+);
diff --git a/synapse/test_pyflakes.py b/synapse/test_pyflakes.py
new file mode 100644
index 0000000000..7b5b1a0858
--- /dev/null
+++ b/synapse/test_pyflakes.py
@@ -0,0 +1 @@
+import an_unused_module
diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js
index e990d42d05..112b3ad96c 100644
--- a/webclient/components/matrix/event-handler-service.js
+++ b/webclient/components/matrix/event-handler-service.js
@@ -148,10 +148,10 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
         // ts is later.
         var latestData = true;
         if (!isLiveEvent) {
-            var eventTs = event.ts;
+            var eventTs = event.origin_server_ts;
             var storedEvent = $rootScope.events.rooms[event.room_id][event.type];
             if (storedEvent) {
-                if (storedEvent.ts > eventTs) {
+                if (storedEvent.origin_server_ts > eventTs) {
                     // ignore it, we have a newer one already.
                     latestData = false;
                 }
@@ -256,7 +256,7 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
             // could be a membership change, display name change, etc.
             // Find out which one.
             var memberChanges = undefined;
-            if (event.content.prev !== event.content.membership) {
+            if (event.prev_content && (event.prev_content.membership !== event.content.membership)) {
                 memberChanges = "membership";
             }
             else if (event.prev_content && (event.prev_content.displayname !== event.content.displayname)) {
diff --git a/webclient/recents/recents-filter.js b/webclient/recents/recents-filter.js
index 468a746b18..ef8d9897f7 100644
--- a/webclient/recents/recents-filter.js
+++ b/webclient/recents/recents-filter.js
@@ -59,9 +59,9 @@ angular.module('RecentsController')
                 return 1;
             }
             else {
-                return lastMsgRoomB.ts - lastMsgRoomA.ts;
+                return lastMsgRoomB.origin_server_ts - lastMsgRoomA.origin_server_ts;
             }
         });
         return filtered;
     };
-}]);
\ No newline at end of file
+}]);
diff --git a/webclient/recents/recents.html b/webclient/recents/recents.html
index 9cbdcd357a..a52b215c7e 100644
--- a/webclient/recents/recents.html
+++ b/webclient/recents/recents.html
@@ -18,7 +18,7 @@
                          Declaring it in this way ensures the data-binding -->
                     {{ lastMsg = eventHandlerService.getLastMessage(room.room_id, true);"" }}
 
-                    {{ (lastMsg.ts) | date:'MMM d HH:mm' }}
+                    {{ (lastMsg.origin_server_ts) | date:'MMM d HH:mm' }}
                     
                     <img ng-click="leave(room.room_id); $event.stopPropagation();" src="img/close.png" width="10" height="10" style="margin-bottom: -1px; margin-left: 2px;" alt="close"/>
                 </td>
@@ -42,12 +42,12 @@
                                         <span ng-if="lastMsg.user_id === lastMsg.state_key">
                                             {{lastMsg.state_key | mUserDisplayName: room.room_id }} left
                                         </span>
-                                        <span ng-if="lastMsg.user_id !== lastMsg.state_key">
+                                        <span ng-if="lastMsg.user_id !== lastMsg.state_key && lastMsg.prev_content">
                                             {{ lastMsg.user_id | mUserDisplayName: room.room_id }}
-                                            {{ {"join": "kicked", "ban": "unbanned"}[lastMsg.content.prev] }}
+                                            {{ {"invite": "kicked", "join": "kicked", "ban": "unbanned"}[lastMsg.prev_content.membership] }}
                                             {{ lastMsg.state_key | mUserDisplayName: room.room_id }}
                                         </span>
-                                        <span ng-if="'join' === lastMsg.content.prev && lastMsg.content.reason">
+                                        <span ng-if="lastMsg.prev_content && 'join' === lastMsg.prev_content.membership && lastMsg.content.reason">
                                             : {{ lastMsg.content.reason }}
                                         </span>
                                     </span>
@@ -55,7 +55,7 @@
                                         {{ lastMsg.user_id | mUserDisplayName: room.room_id }}
                                         {{ {"invite": "invited", "ban": "banned"}[lastMsg.content.membership] }}
                                         {{ lastMsg.state_key | mUserDisplayName: room.room_id }}
-                                        <span ng-if="'ban' === lastMsg.content.prev && lastMsg.content.reason">
+                                        <span ng-if="lastMsg.prev_content && 'ban' === lastMsg.prev_content.membership && lastMsg.content.reason">
                                             : {{ lastMsg.content.reason }}
                                         </span>
                                     </span>         
diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js
index d8c62c231e..a1d2e87039 100644
--- a/webclient/room/room-controller.js
+++ b/webclient/room/room-controller.js
@@ -603,9 +603,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
             var echoMessage = {
                 content: {
                     body: (cmd === "/me" ? args : input),
-                    hsob_ts: new Date().getTime(), // fake a timestamp
                     msgtype: (cmd === "/me" ? "m.emote" : "m.text"),
                 },
+                origin_server_ts: new Date().getTime(), // fake a timestamp
                 room_id: $scope.room_id,
                 type: "m.room.message",
                 user_id: $scope.state.user_id,
@@ -640,7 +640,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
 
                     if (echoMessage) {
                         // Mark the message as unsent for the rest of the page life
-                        echoMessage.content.hsob_ts = "Unsent";
+                        echoMessage.origin_server_ts = "Unsent";
                         echoMessage.echo_msg_state = "messageUnSent";
                     }
                 });
diff --git a/webclient/room/room.html b/webclient/room/room.html
index 79a60585a5..ce2c581903 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -68,7 +68,6 @@
          ng-hide="state.permission_denied" 
          ng-style="{ 'visibility': state.messages_visibility }"
          keep-scroll>
-        <!-- FIXME: need to have better timestamp semantics than the (msg.content.hsob_ts || msg.ts) hack below -->
         <table id="messageTable" infinite-scroll="paginateMore()">
             <tr ng-repeat="msg in events.rooms[room_id].messages"
                 ng-class="(events.rooms[room_id].messages[$index + 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item>
@@ -76,7 +75,7 @@
                     <div class="sender" ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id"> {{ msg.user_id | mUserDisplayName: room_id }}</div>
                     <div class="timestamp"
                          ng-class="msg.echo_msg_state">
-                        {{ (msg.content.hsob_ts || msg.ts) | date:'MMM d HH:mm' }}
+                        {{ (msg.origin_server_ts) | date:'MMM d HH:mm' }}
                     </div>
                 </td>
                 <td class="avatar">
@@ -92,11 +91,11 @@
                             <span ng-if="msg.user_id === msg.state_key">
                                 {{ members[msg.state_key].displayname || msg.state_key }} left
                             </span>
-                            <span ng-if="msg.user_id !== msg.state_key">
+                            <span ng-if="msg.user_id !== msg.state_key && msg.prev_content">
                                 {{ members[msg.user_id].displayname || msg.user_id }}
-                                {{ {"join": "kicked", "ban": "unbanned"}[msg.content.prev] }}
+                                {{ {"invite": "kicked", "join": "kicked", "ban": "unbanned"}[msg.prev_content.membership] }}
                                 {{ members[msg.state_key].displayname || msg.state_key }}
-                                <span ng-if="'join' === msg.content.prev && msg.content.reason">
+                                <span ng-if="'join' === msg.prev_content.membership && msg.content.reason">
                                     : {{ msg.content.reason }}
                                 </span>
                             </span>
@@ -106,7 +105,7 @@
                             {{ members[msg.user_id].displayname || msg.user_id }}
                             {{ {"invite": "invited", "ban": "banned"}[msg.content.membership] }}
                             {{ members[msg.state_key].displayname || msg.state_key }}
-                            <span ng-if="'ban' === msg.content.prev && msg.content.reason">
+                            <span ng-if="msg.prev_content && 'ban' === msg.prev_content.membership && msg.content.reason">
                                 : {{ msg.content.reason }}
                             </span>
                         </span>