summary refs log tree commit diff
diff options
context:
space:
mode:
authorErik Johnston <erik@matrix.org>2014-10-27 10:20:44 +0000
committerErik Johnston <erik@matrix.org>2014-10-27 10:20:44 +0000
commitbb4a20174cb9128a1d9d5b7bd3d11d98b070edb6 (patch)
treef7ea571aedd4f1c8ae74a7c9ba3356a6c2107351
parentIt doesn't want a dict (diff)
parentAdd log message if we can't enable ECC. Require pyopenssl>=0.14 since 0.13 do... (diff)
downloadsynapse-bb4a20174cb9128a1d9d5b7bd3d11d98b070edb6.tar.xz
Merge branch 'develop' of github.com:matrix-org/synapse into federation_authorization
Conflicts:
	synapse/federation/transport.py
	synapse/handlers/message.py
-rw-r--r--CHANGES.rst20
-rw-r--r--UPGRADE.rst13
-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
-rw-r--r--scripts/basic.css510
-rwxr-xr-xscripts/gendoc.sh14
-rw-r--r--scripts/nature.css270
-rwxr-xr-xsetup.py8
-rw-r--r--synapse/__init__.py2
-rw-r--r--synapse/api/events/factory.py4
-rw-r--r--synapse/config/server.py2
-rw-r--r--synapse/crypto/context_factory.py5
-rw-r--r--synapse/crypto/keyring.py1
-rw-r--r--synapse/federation/pdu_codec.py4
-rw-r--r--synapse/federation/persistence.py2
-rw-r--r--synapse/federation/replication.py6
-rw-r--r--synapse/federation/transport.py19
-rw-r--r--synapse/federation/units.py25
-rw-r--r--synapse/handlers/message.py16
-rw-r--r--synapse/handlers/register.py3
-rw-r--r--synapse/http/client.py44
-rw-r--r--synapse/rest/presence.py4
-rw-r--r--synapse/rest/voip.py2
-rw-r--r--synapse/storage/__init__.py11
-rw-r--r--synapse/storage/_base.py3
-rw-r--r--synapse/storage/schema/delta/v6.sql31
-rw-r--r--synapse/storage/transactions.py13
-rw-r--r--tests/federation/test_federation.py11
-rw-r--r--tests/federation/test_pdu_codec.py4
-rw-r--r--tests/handlers/test_federation.py6
-rw-r--r--tests/handlers/test_presence.py3
-rw-r--r--tests/handlers/test_typing.py3
-rw-r--r--tests/test_state.py2
-rw-r--r--webclient/app-filter.js57
-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.html15
57 files changed, 563 insertions, 6425 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 1690490f66..08efbbf244 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,23 @@
+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)
 =====================================
 This version adds support for using a TURN server. See docs/turn-howto.rst on
diff --git a/UPGRADE.rst b/UPGRADE.rst
index 713fb9ae83..99ce1a2d3d 100644
--- a/UPGRADE.rst
+++ b/UPGRADE.rst
@@ -1,3 +1,16 @@
+Upgrading to v0.4.0
+===================
+
+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/scripts/basic.css b/scripts/basic.css
deleted file mode 100644
index 6411570ee6..0000000000
--- a/scripts/basic.css
+++ /dev/null
@@ -1,510 +0,0 @@
-/*
- * basic.css
- * ~~~~~~~~~
- *
- * Sphinx stylesheet -- basic theme.
- *
- * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
- * :license: BSD, see LICENSE for details.
- *
- */
-
-/* -- main layout ----------------------------------------------------------- */
-
-div.clearer {
-    clear: both;
-}
-
-/* -- relbar ---------------------------------------------------------------- */
-
-div.related {
-    width: 100%;
-    font-size: 90%;
-}
-
-div.related h3 {
-    display: none;
-}
-
-div.related ul {
-    margin: 0;
-    padding: 0 0 0 10px;
-    list-style: none;
-}
-
-div.related li {
-    display: inline;
-}
-
-div.related li.right {
-    float: right;
-    margin-right: 5px;
-}
-
-/* -- sidebar --------------------------------------------------------------- */
-
-div.sphinxsidebarwrapper {
-    padding: 10px 5px 0 10px;
-}
-
-div.sphinxsidebar {
-    float: left;
-    width: 230px;
-    margin-left: -100%;
-    font-size: 90%;
-}
-
-div.sphinxsidebar ul {
-    list-style: none;
-}
-
-div.sphinxsidebar ul ul,
-div.sphinxsidebar ul.want-points {
-    margin-left: 20px;
-    list-style: square;
-}
-
-div.sphinxsidebar ul ul {
-    margin-top: 0;
-    margin-bottom: 0;
-}
-
-div.sphinxsidebar form {
-    margin-top: 10px;
-}
-
-div.sphinxsidebar input {
-    border: 1px solid #98dbcc;
-    font-family: sans-serif;
-    font-size: 1em;
-}
-
-img {
-    border: 0;
-}
-
-/* -- search page ----------------------------------------------------------- */
-
-ul.search {
-    margin: 10px 0 0 20px;
-    padding: 0;
-}
-
-ul.search li {
-    padding: 5px 0 5px 20px;
-    background-image: url(file.png);
-    background-repeat: no-repeat;
-    background-position: 0 7px;
-}
-
-ul.search li a {
-    font-weight: bold;
-}
-
-ul.search li div.context {
-    color: #888;
-    margin: 2px 0 0 30px;
-    text-align: left;
-}
-
-ul.keywordmatches li.goodmatch a {
-    font-weight: bold;
-}
-
-/* -- index page ------------------------------------------------------------ */
-
-table.contentstable {
-    width: 90%;
-}
-
-table.contentstable p.biglink {
-    line-height: 150%;
-}
-
-a.biglink {
-    font-size: 1.3em;
-}
-
-span.linkdescr {
-    font-style: italic;
-    padding-top: 5px;
-    font-size: 90%;
-}
-
-/* -- general index --------------------------------------------------------- */
-
-table.indextable {
-    width: 100%;
-}
-
-table.indextable td {
-    text-align: left;
-    vertical-align: top;
-}
-
-table.indextable dl, table.indextable dd {
-    margin-top: 0;
-    margin-bottom: 0;
-}
-
-table.indextable tr.pcap {
-    height: 10px;
-}
-
-table.indextable tr.cap {
-    margin-top: 10px;
-    background-color: #f2f2f2;
-}
-
-img.toggler {
-    margin-right: 3px;
-    margin-top: 3px;
-    cursor: pointer;
-}
-
-div.modindex-jumpbox {
-    border-top: 1px solid #ddd;
-    border-bottom: 1px solid #ddd;
-    margin: 1em 0 1em 0;
-    padding: 0.4em;
-}
-
-div.genindex-jumpbox {
-    border-top: 1px solid #ddd;
-    border-bottom: 1px solid #ddd;
-    margin: 1em 0 1em 0;
-    padding: 0.4em;
-}
-
-/* -- general body styles --------------------------------------------------- */
-
-a.headerlink {
-    visibility: hidden;
-}
-
-h1:hover > a.headerlink,
-h2:hover > a.headerlink,
-h3:hover > a.headerlink,
-h4:hover > a.headerlink,
-h5:hover > a.headerlink,
-h6:hover > a.headerlink,
-dt:hover > a.headerlink {
-    visibility: visible;
-}
-
-div.document p.caption {
-    text-align: inherit;
-}
-
-div.document td {
-    text-align: left;
-}
-
-.field-list ul {
-    padding-left: 1em;
-}
-
-.first {
-    margin-top: 0 !important;
-}
-
-p.rubric {
-    margin-top: 30px;
-    font-weight: bold;
-}
-
-.align-left {
-    text-align: left;
-}
-
-.align-center {
-    clear: both;
-    text-align: center;
-}
-
-.align-right {
-    text-align: right;
-}
-
-/* -- sidebars -------------------------------------------------------------- */
-
-div.sidebar {
-    margin: 0 0 0.5em 1em;
-    border: 1px solid #ddb;
-    padding: 7px 7px 0 7px;
-    background-color: #ffe;
-    width: 40%;
-    float: right;
-}
-
-p.sidebar-title {
-    font-weight: bold;
-}
-
-/* -- topics ---------------------------------------------------------------- */
-
-div.topic {
-    border: 1px solid #ccc;
-    padding: 7px 7px 0 7px;
-    margin: 10px 0 10px 0;
-}
-
-p.topic-title {
-    font-size: 1.1em;
-    font-weight: bold;
-    margin-top: 10px;
-}
-
-/* -- admonitions ----------------------------------------------------------- */
-
-div.admonition {
-    margin-top: 10px;
-    margin-bottom: 10px;
-    padding: 7px;
-}
-
-div.admonition dt {
-    font-weight: bold;
-}
-
-div.admonition dl {
-    margin-bottom: 0;
-}
-
-p.admonition-title {
-    margin: 0px 10px 5px 0px;
-    font-weight: bold;
-}
-
-div.document p.centered {
-    text-align: center;
-    margin-top: 25px;
-}
-
-/* -- tables ---------------------------------------------------------------- */
-
-table.docutils {
-    border: 0;
-    border-collapse: collapse;
-}
-
-table.docutils td, table.docutils th {
-    padding: 1px 8px 1px 5px;
-    border-top: 0;
-    border-left: 0;
-    border-right: 0;
-    border-bottom: 1px solid #aaa;
-}
-
-table.field-list td, table.field-list th {
-    border: 0 !important;
-}
-
-table.footnote td, table.footnote th {
-    border: 0 !important;
-}
-
-th {
-    text-align: left;
-    padding-right: 5px;
-}
-
-table.citation {
-    border-left: solid 1px gray;
-    margin-left: 1px;
-}
-
-table.citation td {
-    border-bottom: none;
-}
-
-/* -- other body styles ----------------------------------------------------- */
-
-ol.arabic {
-    list-style: decimal;
-}
-
-ol.loweralpha {
-    list-style: lower-alpha;
-}
-
-ol.upperalpha {
-    list-style: upper-alpha;
-}
-
-ol.lowerroman {
-    list-style: lower-roman;
-}
-
-ol.upperroman {
-    list-style: upper-roman;
-}
-
-dl {
-    margin-bottom: 15px;
-}
-
-dd p {
-    margin-top: 0px;
-}
-
-dd ul, dd table {
-    margin-bottom: 10px;
-}
-
-dd {
-    margin-top: 3px;
-    margin-bottom: 10px;
-    margin-left: 30px;
-}
-
-dt:target, .highlighted {
-    background-color: #fbe54e;
-}
-
-dl.glossary dt {
-    font-weight: bold;
-    font-size: 1.1em;
-}
-
-.field-list ul {
-    margin: 0;
-    padding-left: 1em;
-}
-
-.field-list p {
-    margin: 0;
-}
-
-.refcount {
-    color: #060;
-}
-
-.optional {
-    font-size: 1.3em;
-}
-
-.versionmodified {
-    font-style: italic;
-}
-
-.system-message {
-    background-color: #fda;
-    padding: 5px;
-    border: 3px solid red;
-}
-
-.footnote:target  {
-    background-color: #ffa
-}
-
-.line-block {
-    display: block;
-    margin-top: 1em;
-    margin-bottom: 1em;
-}
-
-.line-block .line-block {
-    margin-top: 0;
-    margin-bottom: 0;
-    margin-left: 1.5em;
-}
-
-.guilabel, .menuselection {
-    font-family: sans-serif;
-}
-
-.accelerator {
-    text-decoration: underline;
-}
-
-.classifier {
-    font-style: oblique;
-}
-
-/* -- code displays --------------------------------------------------------- */
-
-pre {
-    overflow: auto;
-}
-
-td.linenos pre {
-    padding: 5px 0px;
-    border: 0;
-    background-color: transparent;
-    color: #aaa;
-}
-
-table.highlighttable {
-    margin-left: 0.5em;
-}
-
-table.highlighttable td {
-    padding: 0 0.5em 0 0.5em;
-}
-
-tt.descname {
-    background-color: transparent;
-    font-weight: bold;
-    font-size: 1.2em;
-}
-
-tt.descclassname {
-    background-color: transparent;
-}
-
-tt.xref, a tt {
-    background-color: transparent;
-    font-weight: bold;
-}
-
-h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
-    background-color: transparent;
-}
-
-.viewcode-link {
-    float: right;
-}
-
-.viewcode-back {
-    float: right;
-    font-family: sans-serif;
-}
-
-div.viewcode-block:target {
-    margin: -1px -10px;
-    padding: 0 10px;
-}
-
-/* -- math display ---------------------------------------------------------- */
-
-img.math {
-    vertical-align: middle;
-}
-
-div.document div.math p {
-    text-align: center;
-}
-
-span.eqno {
-    float: right;
-}
-
-/* -- printout stylesheet --------------------------------------------------- */
-
-@media print {
-    div.document,
-    div.documentwrapper,
-    div.bodywrapper {
-        margin: 0 !important;
-        width: 100%;
-    }
-
-    div.sphinxsidebar,
-    div.related,
-    div.footer,
-    #top-link {
-        display: none;
-    }
-}
-
diff --git a/scripts/gendoc.sh b/scripts/gendoc.sh
deleted file mode 100755
index 64aff3155e..0000000000
--- a/scripts/gendoc.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-
-MATRIXDOTORG=$HOME/workspace/matrix.org
-
-rst2html-2.7.py --stylesheet=basic.css,nature.css ../docs/specification.rst > $MATRIXDOTORG/docs/spec/index.html
-rst2html-2.7.py --stylesheet=basic.css,nature.css ../docs/client-server/howto.rst > $MATRIXDOTORG/docs/howtos/client-server.html
-
-perl -pi -e 's#<head>#<head><link rel="stylesheet" href="/site.css">#' $MATRIXDOTORG/docs/spec/index.html $MATRIXDOTORG/docs/howtos/client-server.html
-
-perl -pi -e 's#<body>#<body><div id="header"><div id="headerContent">&nbsp;</div></div><div id="page"><div id="wrapper"><div style="text-align: center; padding: 40px;"><a href="/"><img src="/matrix.png" width="305" height="130" alt="[matrix]"/></a></div>#' $MATRIXDOTORG/docs/spec/index.html $MATRIXDOTORG/docs/howtos/client-server.html
-
-perl -pi -e 's#</body>#</div></div><div id="footer"><div id="footerContent">&copy 2014 Matrix.org</div></div></body>#' $MATRIXDOTORG/docs/spec/index.html $MATRIXDOTORG/docs/howtos/client-server.html
-
-scp -r $MATRIXDOTORG/docs matrix@ldc-prd-matrix-001:/sites/matrix
\ No newline at end of file
diff --git a/scripts/nature.css b/scripts/nature.css
deleted file mode 100644
index b8147f10ee..0000000000
--- a/scripts/nature.css
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * nature.css_t
- * ~~~~~~~~~~~~
- *
- * Sphinx stylesheet -- nature theme.
- *
- * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
- * :license: BSD, see LICENSE for details.
- *
- */
- 
-/* -- page layout ----------------------------------------------------------- */
- 
-body {
-    font-family: Arial, sans-serif;
-    font-size: 100%;
-    /*background-color: #111;*/
-    color: #555;
-    margin: 0;
-    padding: 0;
-}
-
-div.documentwrapper {
-    float: left;
-    width: 100%;
-}
-
-div.bodywrapper {
-    margin: 0 0 0 230px;
-}
-
-hr {
-    border: 1px solid #B1B4B6;
-}
- 
-/*
-div.document {
-    background-color: #eee;
-}
-*/
- 
-div.document {
-    background-color: #ffffff;
-    color: #3E4349;
-    padding: 0 30px 30px 30px;
-    font-size: 0.9em;
-}
- 
-div.footer {
-    color: #555;
-    width: 100%;
-    padding: 13px 0;
-    text-align: center;
-    font-size: 75%;
-}
- 
-div.footer a {
-    color: #444;
-    text-decoration: underline;
-}
- 
-div.related {
-    background-color: #6BA81E;
-    line-height: 32px;
-    color: #fff;
-    text-shadow: 0px 1px 0 #444;
-    font-size: 0.9em;
-}
- 
-div.related a {
-    color: #E2F3CC;
-}
- 
-div.sphinxsidebar {
-    font-size: 0.75em;
-    line-height: 1.5em;
-}
-
-div.sphinxsidebarwrapper{
-    padding: 20px 0;
-}
- 
-div.sphinxsidebar h3,
-div.sphinxsidebar h4 {
-    font-family: Arial, sans-serif;
-    color: #222;
-    font-size: 1.2em;
-    font-weight: normal;
-    margin: 0;
-    padding: 5px 10px;
-    background-color: #ddd;
-    text-shadow: 1px 1px 0 white
-}
-
-div.sphinxsidebar h4{
-    font-size: 1.1em;
-}
- 
-div.sphinxsidebar h3 a {
-    color: #444;
-}
- 
- 
-div.sphinxsidebar p {
-    color: #888;
-    padding: 5px 20px;
-}
- 
-div.sphinxsidebar p.topless {
-}
- 
-div.sphinxsidebar ul {
-    margin: 10px 20px;
-    padding: 0;
-    color: #000;
-}
- 
-div.sphinxsidebar a {
-    color: #444;
-}
- 
-div.sphinxsidebar input {
-    border: 1px solid #ccc;
-    font-family: sans-serif;
-    font-size: 1em;
-}
-
-div.sphinxsidebar input[type=text]{
-    margin-left: 20px;
-}
- 
-/* -- body styles ----------------------------------------------------------- */
- 
-a {
-    color: #005B81;
-    text-decoration: none;
-}
- 
-a:hover {
-    color: #E32E00;
-    text-decoration: underline;
-}
- 
-div.document h1,
-div.document h2,
-div.document h3,
-div.document h4,
-div.document h5,
-div.document h6 {
-    font-family: Arial, sans-serif;
-    background-color: #BED4EB;
-    font-weight: normal;
-    color: #212224;
-    margin: 30px 0px 10px 0px;
-    padding: 5px 0 5px 10px;
-    text-shadow: 0px 1px 0 white
-}
- 
-div.document h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
-div.document h2 { font-size: 150%; background-color: #C8D5E3; }
-div.document h3 { font-size: 120%; background-color: #D8DEE3; }
-div.document h4 { font-size: 110%; background-color: #D8DEE3; }
-div.document h5 { font-size: 100%; background-color: #D8DEE3; }
-div.document h6 { font-size: 100%; background-color: #D8DEE3; }
- 
-a.headerlink {
-    color: #c60f0f;
-    font-size: 0.8em;
-    padding: 0 4px 0 4px;
-    text-decoration: none;
-}
- 
-a.headerlink:hover {
-    background-color: #c60f0f;
-    color: white;
-}
- 
-div.document p, div.document dd, div.document li {
-    line-height: 1.5em;
-}
- 
-div.admonition p.admonition-title + p {
-    display: inline;
-}
-
-div.highlight{
-    background-color: white;
-}
-
-div.note {
-    background-color: #eee;
-    border: 1px solid #ccc;
-}
- 
-div.seealso {
-    background-color: #ffc;
-    border: 1px solid #ff6;
-}
- 
-div.topic {
-    background-color: #eee;
-}
- 
-div.warning {
-    background-color: #ffe4e4;
-    border: 1px solid #f66;
-}
- 
-p.admonition-title {
-    display: inline;
-}
- 
-p.admonition-title:after {
-    content: ":";
-}
- 
-pre {
-    padding: 10px;
-    background-color: White;
-    color: #222;
-    line-height: 1.2em;
-    border: 1px solid #C6C9CB;
-    font-size: 1.1em;
-    margin: 1.5em 0 1.5em 0;
-    -webkit-box-shadow: 1px 1px 1px #d8d8d8;
-    -moz-box-shadow: 1px 1px 1px #d8d8d8;
-}
- 
-tt {
-    background-color: #ecf0f3;
-    color: #222;
-    /* padding: 1px 2px; */
-    font-size: 1.1em;
-    font-family: monospace;
-}
-
-.viewcode-back {
-    font-family: Arial, sans-serif;
-}
-
-div.viewcode-block:target {
-    background-color: #f4debf;
-    border-top: 1px solid #ac9;
-    border-bottom: 1px solid #ac9;
-}
-
-p {
-	margin: 0;
-}
-
-ul li dd {
-	margin-top: 0;
-}
-
-ul li dl {
-	margin-bottom: 0;
-}
-
-li dl dd {
-	margin-bottom: 0;
-}
-
-dd ul {
-	padding-left: 0;
-}
-
-li dd ul {
-	margin-bottom: 0;
-}
-
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/api/events/factory.py b/synapse/api/events/factory.py
index c6d1151cac..06f3bf232b 100644
--- a/synapse/api/events/factory.py
+++ b/synapse/api/events/factory.py
@@ -66,8 +66,8 @@ class EventFactory(object):
         if "event_id" not in kwargs:
             kwargs["event_id"] = self.create_event_id()
 
-        if "ts" not in kwargs:
-            kwargs["ts"] = int(self.clock.time_msec())
+        if "origin_server_ts" not in kwargs:
+            kwargs["origin_server_ts"] = int(self.clock.time_msec())
 
         # The "age" key is a delta timestamp that should be converted into an
         # absolute timestamp the minute we see it.
diff --git a/synapse/config/server.py b/synapse/config/server.py
index d9d8d0e14e..9332e4acd7 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -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/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/pdu_codec.py b/synapse/federation/pdu_codec.py
index cef61108dd..e8180d94fd 100644
--- a/synapse/federation/pdu_codec.py
+++ b/synapse/federation/pdu_codec.py
@@ -96,7 +96,7 @@ class PduCodec(object):
             if k not in ["event_id", "room_id", "type", "prev_events"]
         })
 
-        if "ts" not in kwargs:
-            kwargs["ts"] = int(self.clock.time_msec())
+        if "origin_server_ts" not in kwargs:
+            kwargs["origin_server_ts"] = int(self.clock.time_msec())
 
         return Pdu(**kwargs)
diff --git a/synapse/federation/persistence.py b/synapse/federation/persistence.py
index de36a80e41..7043fcc504 100644
--- a/synapse/federation/persistence.py
+++ b/synapse/federation/persistence.py
@@ -157,7 +157,7 @@ class TransactionActions(object):
         transaction.prev_ids = yield self.store.prep_send_transaction(
             transaction.transaction_id,
             transaction.destination,
-            transaction.ts,
+            transaction.origin_server_ts,
             [(p["pdu_id"], p["origin"]) for p in transaction.pdus]
         )
 
diff --git a/synapse/federation/replication.py b/synapse/federation/replication.py
index 8c7d510ef6..d901837d0a 100644
--- a/synapse/federation/replication.py
+++ b/synapse/federation/replication.py
@@ -321,7 +321,7 @@ class ReplicationLayer(object):
 
         if hasattr(transaction, "edus"):
             for edu in [Edu(**x) for x in transaction.edus]:
-                self.received_edu(edu.origin, edu.edu_type, edu.content)
+                self.received_edu(transaction.origin, edu.edu_type, edu.content)
 
         results = yield defer.DeferredList(dl)
 
@@ -474,7 +474,7 @@ class ReplicationLayer(object):
         return Transaction(
             origin=self.server_name,
             pdus=pdus,
-            ts=int(self._clock.time_msec()),
+            origin_server_ts=int(self._clock.time_msec()),
             destination=None,
         )
 
@@ -654,7 +654,7 @@ class _TransactionQueue(object):
             logger.debug("TX [%s] Persisting transaction...", destination)
 
             transaction = Transaction.create_new(
-                ts=self._clock.time_msec(),
+                origin_server_ts=self._clock.time_msec(),
                 transaction_id=str(self._next_txn_id),
                 origin=self.server_name,
                 destination=destination,
diff --git a/synapse/federation/transport.py b/synapse/federation/transport.py
index de64702e2f..7f01b4faaf 100644
--- a/synapse/federation/transport.py
+++ b/synapse/federation/transport.py
@@ -301,6 +301,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)
@@ -319,13 +324,13 @@ class TransportLayer(object):
     def _with_authentication(self, handler):
         @defer.inlineCallbacks
         def new_handler(request, *args, **kwargs):
-            (origin, content) = yield self._authenticate_request(request)
             try:
+                (origin, content) = yield self._authenticate_request(request)
                 response = yield handler(
                     origin, content, request.args, *args, **kwargs
                 )
             except:
-                logger.exception("Callback failed")
+                logger.exception("_authenticate_request failed")
                 raise
             defer.returnValue(response)
         return new_handler
@@ -496,9 +501,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/federation/units.py b/synapse/federation/units.py
index ecca35ac43..b2fb964180 100644
--- a/synapse/federation/units.py
+++ b/synapse/federation/units.py
@@ -40,7 +40,7 @@ class Pdu(JsonEncodedObject):
 
         {
             "pdu_id": "78c",
-            "ts": 1404835423000,
+            "origin_server_ts": 1404835423000,
             "origin": "bar",
             "prev_ids": [
                 ["23b", "foo"],
@@ -55,7 +55,7 @@ class Pdu(JsonEncodedObject):
         "pdu_id",
         "context",
         "origin",
-        "ts",
+        "origin_server_ts",
         "pdu_type",
         "destinations",
         "transaction_id",
@@ -82,7 +82,7 @@ class Pdu(JsonEncodedObject):
         "pdu_id",
         "context",
         "origin",
-        "ts",
+        "origin_server_ts",
         "pdu_type",
         "content",
     ]
@@ -118,6 +118,7 @@ class Pdu(JsonEncodedObject):
         """
         if pdu_tuple:
             d = copy.copy(pdu_tuple.pdu_entry._asdict())
+            d["origin_server_ts"] = d.pop("ts")
 
             d["content"] = json.loads(d["content_json"])
             del d["content_json"]
@@ -156,11 +157,15 @@ class Edu(JsonEncodedObject):
     ]
 
     required_keys = [
-        "origin",
-        "destination",
         "edu_type",
     ]
 
+#    TODO: SYN-103: Remove "origin" and "destination" keys.
+#    internal_keys = [
+#        "origin",
+#        "destination",
+#    ]
+
 
 class Transaction(JsonEncodedObject):
     """ A transaction is a list of Pdus and Edus to be sent to a remote home
@@ -182,7 +187,7 @@ class Transaction(JsonEncodedObject):
         "transaction_id",
         "origin",
         "destination",
-        "ts",
+        "origin_server_ts",
         "previous_ids",
         "pdus",
         "edus",
@@ -199,7 +204,7 @@ class Transaction(JsonEncodedObject):
         "transaction_id",
         "origin",
         "destination",
-        "ts",
+        "origin_server_ts",
         "pdus",
     ]
 
@@ -221,10 +226,10 @@ class Transaction(JsonEncodedObject):
     @staticmethod
     def create_new(pdus, **kwargs):
         """ Used to create a new transaction. Will auto fill out
-        transaction_id and ts keys.
+        transaction_id and origin_server_ts keys.
         """
-        if "ts" not in kwargs:
-            raise KeyError("Require 'ts' to construct a Transaction")
+        if "origin_server_ts" not in kwargs:
+            raise KeyError("Require 'origin_server_ts' to construct a Transaction")
         if "transaction_id" not in kwargs:
             raise KeyError(
                 "Require 'transaction_id' to construct a Transaction"
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index 4aaf97a83e..65861033e9 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -64,7 +64,7 @@ class MessageHandler(BaseHandler):
         defer.returnValue(None)
 
     @defer.inlineCallbacks
-    def send_message(self, event=None, suppress_auth=False, stamp_event=True):
+    def send_message(self, event=None, suppress_auth=False):
         """ Send a message.
 
         Args:
@@ -72,7 +72,6 @@ class MessageHandler(BaseHandler):
             suppress_auth (bool) : True to suppress auth for this message. This
             is primarily so the home server can inject messages into rooms at
             will.
-            stamp_event (bool) : True to stamp event content with server keys.
         Raises:
             SynapseError if something went wrong.
         """
@@ -82,9 +81,6 @@ class MessageHandler(BaseHandler):
         user = self.hs.parse_userid(event.user_id)
         assert user.is_mine, "User must be our own: %s" % (user,)
 
-        if stamp_event:
-            event.content["hsob_ts"] = int(self.clock.time_msec())
-
         snapshot = yield self.store.snapshot_room(event.room_id, event.user_id)
 
         yield self._on_new_room_event(
@@ -131,7 +127,7 @@ class MessageHandler(BaseHandler):
         defer.returnValue(chunk)
 
     @defer.inlineCallbacks
-    def store_room_data(self, event=None, stamp_event=True):
+    def store_room_data(self, event=None):
         """ Stores data for a room.
 
         Args:
@@ -148,9 +144,6 @@ class MessageHandler(BaseHandler):
             state_key=event.state_key,
         )
 
-        if stamp_event:
-            event.content["hsob_ts"] = int(self.clock.time_msec())
-
         yield self._on_new_room_event(event, snapshot)
 
     @defer.inlineCallbacks
@@ -216,10 +209,7 @@ class MessageHandler(BaseHandler):
         defer.returnValue(None)
 
     @defer.inlineCallbacks
-    def send_feedback(self, event, stamp_event=True):
-        if stamp_event:
-            event.content["hsob_ts"] = int(self.clock.time_msec())
-
+    def send_feedback(self, event):
         snapshot = yield self.store.snapshot_room(event.room_id, event.user_id)
 
         # store message in db
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 9f54b74e3a..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(
@@ -177,10 +179,6 @@ class MatrixHttpClient(BaseHttpClient):
 
         request = sign_json(request, self.server_name, self.signing_key)
 
-        from syutil.jsonutil import encode_canonical_json
-        logger.debug("Signing " + " " * 11 + "%s %s",
-            self.server_name, encode_canonical_json(request))
-
         auth_headers = []
 
         for key,sig in request["signatures"][self.server_name].items():
@@ -316,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/presence.py b/synapse/rest/presence.py
index 7fc8ce4404..138cc88a05 100644
--- a/synapse/rest/presence.py
+++ b/synapse/rest/presence.py
@@ -68,7 +68,7 @@ class PresenceStatusRestServlet(RestServlet):
         yield self.handlers.presence_handler.set_state(
             target_user=user, auth_user=auth_user, state=state)
 
-        defer.returnValue((200, ""))
+        defer.returnValue((200, {}))
 
     def on_OPTIONS(self, request):
         return (200, {})
@@ -141,7 +141,7 @@ class PresenceListRestServlet(RestServlet):
 
         yield defer.DeferredList(deferreds)
 
-        defer.returnValue((200, ""))
+        defer.returnValue((200, {}))
 
     def on_OPTIONS(self, request):
         return (200, {})
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 10456688ef..b848630c0b 100644
--- a/synapse/storage/__init__.py
+++ b/synapse/storage/__init__.py
@@ -66,7 +66,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):
@@ -157,6 +157,8 @@ class DataStore(RoomMemberStore, RoomStore,
 
         cols["unrecognized_keys"] = json.dumps(unrec_keys)
 
+        cols["ts"] = cols.pop("origin_server_ts")
+
         logger.debug("Persisting: %s", repr(cols))
 
         if pdu.is_state:
@@ -454,10 +456,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/_base.py b/synapse/storage/_base.py
index dba50f1213..65a86e9056 100644
--- a/synapse/storage/_base.py
+++ b/synapse/storage/_base.py
@@ -354,6 +354,7 @@ class SQLBaseStore(object):
         d.pop("stream_ordering", None)
         d.pop("topological_ordering", None)
         d.pop("processed", None)
+        d["origin_server_ts"] = d.pop("ts", 0)
 
         d.update(json.loads(row_dict["unrecognized_keys"]))
         d["content"] = json.loads(d["content"])
@@ -361,7 +362,7 @@ class SQLBaseStore(object):
 
         if "age_ts" not in d:
             # For compatibility
-            d["age_ts"] = d["ts"] if "ts" in d else 0
+            d["age_ts"] = d.get("origin_server_ts", 0)
 
         return self.event_factory.create_event(
             etype=d["type"],
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/storage/transactions.py b/synapse/storage/transactions.py
index ab4599b468..2ba8e30efe 100644
--- a/synapse/storage/transactions.py
+++ b/synapse/storage/transactions.py
@@ -87,7 +87,8 @@ class TransactionStore(SQLBaseStore):
 
         txn.execute(query, (code, response_json, transaction_id, origin))
 
-    def prep_send_transaction(self, transaction_id, destination, ts, pdu_list):
+    def prep_send_transaction(self, transaction_id, destination,
+                              origin_server_ts, pdu_list):
         """Persists an outgoing transaction and calculates the values for the
         previous transaction id list.
 
@@ -97,7 +98,7 @@ class TransactionStore(SQLBaseStore):
         Args:
             transaction_id (str)
             destination (str)
-            ts (int)
+            origin_server_ts (int)
             pdu_list (list)
 
         Returns:
@@ -106,11 +107,11 @@ class TransactionStore(SQLBaseStore):
 
         return self.runInteraction(
             self._prep_send_transaction,
-            transaction_id, destination, ts, pdu_list
+            transaction_id, destination, origin_server_ts, pdu_list
         )
 
-    def _prep_send_transaction(self, txn, transaction_id, destination, ts,
-                               pdu_list):
+    def _prep_send_transaction(self, txn, transaction_id, destination,
+                               origin_server_ts, pdu_list):
 
         # First we find out what the prev_txs should be.
         # Since we know that we are only sending one transaction at a time,
@@ -131,7 +132,7 @@ class TransactionStore(SQLBaseStore):
             None,
             transaction_id=transaction_id,
             destination=destination,
-            ts=ts,
+            ts=origin_server_ts,
             response_code=0,
             response_json=None
         ))
diff --git a/tests/federation/test_federation.py b/tests/federation/test_federation.py
index 8d277d6612..933aa61c77 100644
--- a/tests/federation/test_federation.py
+++ b/tests/federation/test_federation.py
@@ -158,7 +158,7 @@ class FederationTestCase(unittest.TestCase):
                 origin="red",
                 destinations=["remote"],
                 context="my-context",
-                ts=123456789002,
+                origin_server_ts=123456789002,
                 pdu_type="m.test",
                 content={"testing": "content here"},
                 depth=1,
@@ -170,14 +170,14 @@ class FederationTestCase(unittest.TestCase):
                 "remote",
                 path="/_matrix/federation/v1/send/1000000/",
                 data={
-                    "ts": 1000000,
+                    "origin_server_ts": 1000000,
                     "origin": "test",
                     "pdus": [
                         {
                             "origin": "red",
                             "pdu_id": "abc123def456",
                             "prev_pdus": [],
-                            "ts": 123456789002,
+                            "origin_server_ts": 123456789002,
                             "context": "my-context",
                             "pdu_type": "m.test",
                             "is_state": False,
@@ -207,10 +207,11 @@ class FederationTestCase(unittest.TestCase):
                 path="/_matrix/federation/v1/send/1000000/",
                 data={
                     "origin": "test",
-                    "ts": 1000000,
+                    "origin_server_ts": 1000000,
                     "pdus": [],
                     "edus": [
                         {
+                            # TODO: SYN-103: Remove "origin" and "destination"
                             "origin": "test",
                             "destination": "remote",
                             "edu_type": "m.test",
@@ -233,7 +234,7 @@ class FederationTestCase(unittest.TestCase):
                 "/_matrix/federation/v1/send/1001000/",
                 """{
                     "origin": "remote",
-                    "ts": 1001000,
+                    "origin_server_ts": 1001000,
                     "pdus": [],
                     "edus": [
                         {
diff --git a/tests/federation/test_pdu_codec.py b/tests/federation/test_pdu_codec.py
index 344e1baf60..0754ef92e8 100644
--- a/tests/federation/test_pdu_codec.py
+++ b/tests/federation/test_pdu_codec.py
@@ -68,7 +68,7 @@ class PduCodecTestCase(unittest.TestCase):
             context="rooooom",
             pdu_type="m.room.message",
             origin="bar.com",
-            ts=12345,
+            origin_server_ts=12345,
             depth=5,
             prev_pdus=[("alice", "bob.com")],
             is_state=False,
@@ -123,7 +123,7 @@ class PduCodecTestCase(unittest.TestCase):
             context="rooooom",
             pdu_type="m.room.topic",
             origin="bar.com",
-            ts=12345,
+            origin_server_ts=12345,
             depth=5,
             prev_pdus=[("alice", "bob.com")],
             is_state=True,
diff --git a/tests/handlers/test_federation.py b/tests/handlers/test_federation.py
index 35c3a4df7b..219b2c4c5e 100644
--- a/tests/handlers/test_federation.py
+++ b/tests/handlers/test_federation.py
@@ -68,7 +68,7 @@ class FederationTestCase(unittest.TestCase):
             pdu_type=MessageEvent.TYPE,
             context="foo",
             content={"msgtype": u"fooo"},
-            ts=0,
+            origin_server_ts=0,
             pdu_id="a",
             origin="b",
         )
@@ -95,7 +95,7 @@ class FederationTestCase(unittest.TestCase):
             target_host=self.hostname,
             context=room_id,
             content={},
-            ts=0,
+            origin_server_ts=0,
             pdu_id="a",
             origin="b",
         )
@@ -127,7 +127,7 @@ class FederationTestCase(unittest.TestCase):
             state_key="@red:not%s" % self.hostname,
             context=room_id,
             content={},
-            ts=0,
+            origin_server_ts=0,
             pdu_id="a",
             origin="b",
         )
diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py
index 15022b8d05..1850deacf5 100644
--- a/tests/handlers/test_presence.py
+++ b/tests/handlers/test_presence.py
@@ -39,10 +39,11 @@ ONLINE = PresenceState.ONLINE
 def _expect_edu(destination, edu_type, content, origin="test"):
     return {
         "origin": origin,
-        "ts": 1000000,
+        "origin_server_ts": 1000000,
         "pdus": [],
         "edus": [
             {
+                # TODO: SYN-103: Remove "origin" and "destination" keys.
                 "origin": origin,
                 "destination": destination,
                 "edu_type": edu_type,
diff --git a/tests/handlers/test_typing.py b/tests/handlers/test_typing.py
index 064b04c217..f1d3b27f74 100644
--- a/tests/handlers/test_typing.py
+++ b/tests/handlers/test_typing.py
@@ -29,10 +29,11 @@ from synapse.handlers.typing import TypingNotificationHandler
 def _expect_edu(destination, edu_type, content, origin="test"):
     return {
         "origin": origin,
-        "ts": 1000000,
+        "origin_server_ts": 1000000,
         "pdus": [],
         "edus": [
             {
+                # TODO: SYN-103: Remove "origin" and "destination" keys.
                 "origin": origin,
                 "destination": destination,
                 "edu_type": edu_type,
diff --git a/tests/test_state.py b/tests/test_state.py
index b1624f0b25..4b1feaf410 100644
--- a/tests/test_state.py
+++ b/tests/test_state.py
@@ -599,7 +599,7 @@ def new_fake_pdu(pdu_id, context, pdu_type, state_key, prev_state_id,
         prev_state_id=prev_state_id,
         origin="example.com",
         context="context",
-        ts=1405353060021,
+        origin_server_ts=1405353060021,
         depth=depth,
         content_json="{}",
         unrecognized_keys="{}",
diff --git a/webclient/app-filter.js b/webclient/app-filter.js
index fc16492ef3..39ea1d637d 100644
--- a/webclient/app-filter.js
+++ b/webclient/app-filter.js
@@ -1,12 +1,12 @@
 /*
  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.
@@ -80,4 +80,53 @@ angular.module('matrixWebClient')
     return function(text) {
         return $sce.trustAsHtml(text);
     };
-}]);
\ No newline at end of file
+}])
+// Exactly the same as ngSanitize's linky but instead of pushing sanitized
+// text in the addText function, we just push the raw text.
+.filter('unsanitizedLinky', ['$sanitize', function($sanitize) {
+  var LINKY_URL_REGEXP =
+        /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,
+      MAILTO_REGEXP = /^mailto:/;
+
+  return function(text, target) {
+    if (!text) return text;
+    var match;
+    var raw = text;
+    var html = [];
+    var url;
+    var i;
+    while ((match = raw.match(LINKY_URL_REGEXP))) {
+      // We can not end in these as they are sometimes found at the end of the sentence
+      url = match[0];
+      // if we did not match ftp/http/mailto then assume mailto
+      if (match[2] == match[3]) url = 'mailto:' + url;
+      i = match.index;
+      addText(raw.substr(0, i));
+      addLink(url, match[0].replace(MAILTO_REGEXP, ''));
+      raw = raw.substring(i + match[0].length);
+    }
+    addText(raw);
+    return $sanitize(html.join(''));
+
+    function addText(text) {
+      if (!text) {
+        return;
+      }
+      html.push(text);
+    }
+
+    function addLink(url, text) {
+      html.push('<a ');
+      if (angular.isDefined(target)) {
+        html.push('target="');
+        html.push(target);
+        html.push('" ');
+      }
+      html.push('href="');
+      html.push(url);
+      html.push('">');
+      addText(text);
+      html.push('</a>');
+    }
+  };
+}]);
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 b99413cbbf..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>                        
@@ -121,7 +120,9 @@
                         <span ng-show='msg.content.msgtype === "m.text"' 
                               class="message"
                               ng-class="containsBingWord(msg.content.body) && msg.user_id != state.user_id ? msg.echo_msg_state + ' messageBing' : msg.echo_msg_state"
-                              ng-bind-html="((msg.content.msgtype === 'm.text') ? msg.content.body : '') | linky:'_blank'"/>
+                              ng-bind-html="(msg.content.msgtype === 'm.text' && msg.type === 'm.room.message' && msg.content.format === 'org.matrix.custom.html') ? 
+                                                                                        (msg.content.formatted_body | unsanitizedLinky) :
+                                             (msg.content.msgtype === 'm.text' && msg.type === 'm.room.message') ? (msg.content.body | linky:'_blank') : '' "/>
 
                         <span ng-show='msg.type === "m.call.invite" && msg.user_id == state.user_id'>Outgoing Call{{ isWebRTCSupported ? '' : ' (But your browser does not support VoIP)' }}</span>
                         <span ng-show='msg.type === "m.call.invite" && msg.user_id != state.user_id'>Incoming Call{{ isWebRTCSupported ? '' : ' (But your browser does not support VoIP)' }}</span>