summary refs log tree commit diff
path: root/docs/client-server/specification.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/client-server/specification.rst')
-rw-r--r--docs/client-server/specification.rst1264
1 files changed, 1264 insertions, 0 deletions
diff --git a/docs/client-server/specification.rst b/docs/client-server/specification.rst
new file mode 100644
index 0000000000..7df2bb14c5
--- /dev/null
+++ b/docs/client-server/specification.rst
@@ -0,0 +1,1264 @@
+=========================
+Synapse Client-Server API
+=========================
+
+The following specification outlines how a client can send and receive data from 
+a home server.
+
+[[TODO(kegan): 4/7/14 Grilling
+- Mechanism for getting historical state changes (e.g. topic updates) - add 
+  query param flag?
+- Generic mechanism for linking first class events (e.g. feedback) with other s
+  first class events (e.g. messages)?
+- Generic mechanism for updating 'stuff about the room' (e.g. favourite coffee) 
+  AND specifying clobbering rules (clobber/add to list/etc)?
+- How to ensure a consistent view for clients paginating through room lists? 
+  They aren't really ordered in any way, and if you're paginating
+  through them, how can you show them a consistent result set? Temporary 'room 
+  list versions' akin to event version? How does that work?
+]]
+
+[[TODO(kegan):
+Outstanding problems / missing spec:
+- Push
+- Typing notifications
+]]
+
+Terminology
+-----------
+Stream Tokens: 
+An opaque token used to make further streaming requests. When using any 
+pagination streaming API, responses will contain a start and end stream token. 
+When reconnecting to the stream, these tokens can be used to tell the server 
+where the client got up to in the stream.
+
+Event ID:
+Every event that comes down the event stream or that is returned from the REST
+API has an associated event ID (event_id). This ID will be the same between the 
+REST API and the event stream, so any duplicate events can be clobbered 
+correctly without knowing anything else about the event.
+
+Message ID:
+The ID of a message sent by a client in a room. Clients send IMs to each other 
+in rooms. Each IM sent by a client must have a unique message ID which is unique
+for that particular client.
+
+User ID:
+The @username:host style ID of the client. When registering for an account, the 
+client specifies their username. The user_id is this username along with the 
+home server's unique hostname. When federating between home servers, the user_id
+is used to uniquely identify users across multiple home servers.
+
+Room ID:
+The room_id@host style ID for the room. When rooms are created, the client either
+specifies or is allocated a room ID. This room ID must be used to send messages 
+in that room. Like with clients, there may be multiple rooms with the same ID 
+across multiple home servers. The room_id is used to uniquely identify a room 
+when federating.
+
+Global message ID:
+The globally unique ID for a message. This ID is formed from the msg_id, the 
+client's user_id and the room_id. This uniquely identifies any 
+message. It is represented with '-' as the delimeter between IDs. The 
+global_msg_id is of the form: room_id-user_id-msg_id
+
+
+REST API and the Event Stream
+-----------------------------
+Clients send data to the server via a RESTful API. They can receive data via 
+this API or from an event stream. An event stream is a special path which 
+streams all events the client may be interested in. This makes it easy to 
+immediately receive updates from the REST API. All data is represented as JSON.
+
+Pagination streaming API
+========================
+Clients are often interested in very large datasets. The data itself could
+be 1000s of messages in a given room, 1000s of rooms in a public room list, or
+1000s of events (presence, typing, messages, etc) in the system. It is not
+practical to send vast quantities of data to the client every time they
+request a list of public rooms for example. There needs to be a way to show a
+subset of this data, and apply various filters to it. This is what the pagination
+streaming API is. This API defines standard request/response parameters which 
+can be used when navigating this stream of data.
+
+Pagination Request Query Parameters
+-----------------------------------
+Clients may wish to paginate results from the event stream, or other sources of 
+information where the amount of information may be a problem,
+e.g. in a room with 10,000s messages. The pagination query parameters provide a 
+way to navigate a 'window' around a large set of data. These
+parameters are only valid for GET requests.
+       
+        S e r v e r - s i d e   d a t a
+ |-------------------------------------------------|
+START      ^               ^                      END
+           |_______________|
+                   |
+            Client-extraction
+
+'START' and 'END' are magic token values which specify the start and end of the 
+dataset respectively.
+
+Query parameters:
+  from : $streamtoken - The opaque token to start streaming from.
+  to : $streamtoken - The opaque token to end streaming at. Typically,
+       clients will not know the item of data to end at, so this will usually be 
+       START or END.
+  limit : integer - An integer representing the maximum number of items to 
+          return.
+
+For example, the event stream has events E1 -> E15. The client wants the last 5 
+events and doesn't know any previous events:
+
+S                                                    E
+|-E1-E2-E3-E4-E5-E6-E7-E8-E9-E10-E11-E12-E13-E14-E15-|
+|                               |                    |
+|                          _____|                    |
+|__________________       |       ___________________|
+                   |      |      |
+ GET /events?to=START&limit=5&from=END
+ Returns:
+   E15,E14,E13,E12,E11
+
+
+Another example: a public room list has rooms R1 -> R17. The client is showing 5 
+rooms at a time on screen, and is on page 2. They want to
+now show page 3 (rooms R11 -> 15):
+
+S                                                           E
+|  0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16 | stream token
+|-R1-R2-R3-R4-R5-R6-R7-R8-R9-R10-R11-R12-R13-R14-R15-R16-R17| room
+                  |____________| |________________|
+                        |                |
+                    Currently            |
+                    viewing              |
+                                         |
+                         GET /rooms/list?from=9&to=END&limit=5
+                         Returns: R11,R12,R13,R14,R15
+                         
+Note that tokens are treated in an *exclusive*, not inclusive, manner. The end 
+token from the intial request was '9' which corresponded to R10. When the 2nd
+request was made, R10 did not appear again, even though from=9 was specified. If
+you know the token, you already have the data.
+
+Pagination Response
+-------------------
+Responses to pagination requests MUST follow the format:
+{
+  "chunk": [ ... , Responses , ... ],
+  "start" : $streamtoken,
+  "end" : $streamtoken
+}
+Where $streamtoken is an opaque token which can be used in another query to
+get the next set of results. The "start" and "end" keys can only be omitted if
+the complete dataset is provided in "chunk".
+
+If the client wants earlier results, they should use from=$start_streamtoken,
+to=START. Likewise, if the client wants later results, they should use
+from=$end_streamtoken, to=END.
+
+Unless specified, the default pagination parameters are from=START, to=END, 
+without a limit set. This allows you to hit an API like
+/events without any query parameters to get everything.
+
+The Event Stream
+----------------
+The event stream returns events using the pagination streaming API. When the 
+client disconnects for a while and wants to reconnect to the event stream, they 
+should specify from=$end_streamtoken. This lets the server know where in the 
+event stream the client is. These tokens are completely opaque, and the client 
+cannot infer anything from them.
+
+  GET /events?from=$LAST_STREAM_TOKEN
+  REST Path: /events
+  Returns (success): A JSON array of Event Data.
+  Returns (failure): An Error Response
+
+LAST_STREAM_TOKEN is the last stream token obtained from the event stream. If the 
+client is connecting for the first time and does not know any stream tokens,
+they can use "START" to request all events from the start. For more information 
+on this, see "Pagination Request Query Parameters".
+
+The event stream supports shortpoll and longpoll with the "timeout" query
+parameter. This parameter specifies the number of milliseconds the server should
+hold onto the connection waiting for incoming events. If no events occur in this
+period, the connection will be closed and an empty chunk will be returned. To
+use shortpoll, specify "timeout=0".
+
+Event Data
+----------
+This is a JSON object which looks like:
+{
+  "event_id" : $EVENT_ID,
+  "type" : $EVENT_TYPE,
+  $URL_ARGS,
+  "content" : {
+    $EVENT_CONTENT
+  }
+}
+
+EVENT_ID
+  An ID identifying this event. This is so duplicate events can be suppressed on
+  the client.
+
+EVENT_TYPE
+  The namespaced event type (m.*)
+
+URL_ARGS
+  Path specific data from the REST API.
+
+EVENT_CONTENT
+  The event content, matching the REST content PUT previously.
+
+Events are differentiated via the event type "type" key. This is the type of 
+event being received. This can be expanded upon by using different namespaces. 
+Every event MUST have a 'type' key.
+
+Most events will have a corresponding REST URL. This URL will generally have 
+data in it to represent the resource being modified,
+e.g. /rooms/$room_id. The event data will contain extra top-level keys to expose 
+this information to clients listening on an event
+stream. The event content maps directly to the contents submitted via the REST 
+API.
+
+For example:
+  Event Type: m.example.room.members
+  REST Path: /examples/room/$room_id/members/$user_id
+  REST Content: { "membership" : "invited" }
+  
+is represented in the event stream as:
+
+{
+  "event_id" : "e_some_event_id",
+  "type" : "m.example.room.members",
+  "room_id" : $room_id,
+  "user_id" : $user_id,
+  "content" : {
+    "membership" : "invited"
+  }
+}
+
+As convention, the URL variable "$varname" will map directly onto the name 
+of the JSON key "varname".
+
+Error Responses
+---------------
+If the client sends an invalid request, the server MAY respond with an error 
+response. This is of the form:
+{
+  "error" : "string",
+  "errcode" : "string"
+}
+The 'error' string will be a human-readable error message, usually a sentence
+explaining what went wrong. 
+
+The 'errcode' string will be a unique string which can be used to handle an 
+error message e.g. "M_FORBIDDEN". These error codes should have their namespace 
+first in ALL CAPS, followed by a single _. For example, if there was a custom
+namespace com.mydomain.here, and a "FORBIDDEN" code, the error code should look
+like "COM.MYDOMAIN.HERE_FORBIDDEN". There may be additional keys depending on 
+the error, but the keys 'error' and 'errcode' will always be present. 
+
+Some standard error codes are below:
+
+M_FORBIDDEN:
+Forbidden access, e.g. bad access token, failed login.
+
+M_BAD_JSON:
+Request contained valid JSON, but it was malformed in some way, e.g. missing
+required keys, invalid values for keys.
+
+M_NOT_JSON:
+Request did not contain valid JSON.
+
+M_NOT_FOUND:
+No resource was found for this request.
+
+Some requests have unique error codes:
+
+M_USER_IN_USE:
+Encountered when trying to register a user ID which has been taken.
+
+M_ROOM_IN_USE:
+Encountered when trying to create a room which has been taken.
+
+M_BAD_PAGINATION:
+Encountered when specifying bad pagination values to a Pagination Streaming API.
+
+
+========
+REST API
+========
+
+All content must be application/json. Some keys are required, while others are 
+optional. Unless otherwise specified,
+all HTTP PUT/POST/DELETEs will return a 200 OK with an empty response body on 
+success, and a 4xx/5xx with an optional Error Response on failure. When sending 
+data, if there are no keys to send, an empty JSON object should be sent.
+
+All POST/PUT/GET/DELETE requests MUST have an 'access_token' query parameter to 
+allow the server to authenticate the client. All
+POST requests MUST be submitted as application/json. 
+
+All paths MUST be namespaced by the version of the API being used. This should
+be:
+
+/matrix/client/api/v1
+
+All REST paths in this section MUST be prefixed with this. E.g.
+  REST Path: /rooms/$room_id
+  Absolute Path: /matrix/client/api/v1/rooms/$room_id
+
+Registration
+============
+Clients must register with the server in order to use the service. After 
+registering, the client will be given an
+access token which must be used in ALL requests as a query parameter 
+'access_token'.
+
+Registering for an account
+--------------------------
+  POST /register
+  With: A JSON object containing the key "user_id" which contains the desired 
+        user_id, or an empty JSON object to have the server allocate a user_id 
+        automatically.
+  Returns (success): 200 OK with a JSON object:
+                     {
+                       "user_id" : "string [user_id]",
+                       "access_token" : "string"
+                     }
+  Returns (failure): An Error Response. M_USER_IN_USE if the user ID is taken.
+                     
+
+Unregistering an account
+------------------------
+  POST /unregister
+  With query parameters: access_token=$ACCESS_TOKEN
+  Returns (success): 200 OK
+  Returns (failure): An Error Response.
+  
+  
+Logging in to an existing account
+=================================
+If the client has already registered, they need to be able to login to their
+account. The home server may provide many different ways of logging in, such
+as user/password auth, login via a social network (OAuth), login by confirming 
+a token sent to their email address, etc. This section does NOT define how home 
+servers should authorise their users who want to login to their existing 
+accounts. This section defines the standard interface which implementations 
+should follow so that ANY client can login to ANY home server.
+
+The login process breaks down into the following:
+  1: Get login process info.
+  2: Submit the login stage credentials.
+  3: Get access token or be told the next stage in the login process and repeat 
+     step 2.
+     
+Getting login process info:
+  GET /login
+  Returns (success): 200 OK with LoginInfo.
+  Returns (failure): An Error Response.
+  
+Submitting the login stage credentials:
+  POST /login
+  With: LoginSubmission
+  Returns (success): 200 OK with LoginResult
+  Returns (failure): An Error Response
+  
+Where LoginInfo is a JSON object which MUST have a "type" key which denotes the 
+login type. If there are multiple login stages, this object MUST also contain a 
+"stages" key, which has a JSON array of login types denoting all the steps in 
+order to login, including the first stage which is in "type". This allows the 
+client to make an informed decision as to whether or not they can natively
+handle the entire login process, or whether they should fallback (see below).
+
+Where LoginSubmission is a JSON object which MUST have a "type" key.
+
+Where LoginResult is a JSON object which MUST have either a "next" key OR an
+"access_token" key, depending if the login process is over or not. This object
+MUST have a "session" key if multiple POSTs need to be sent to /login.
+
+Fallback
+--------
+If the client does NOT know how to handle the given type, they should:
+  GET /login/fallback
+This MUST return an HTML page which can perform the entire login process.
+
+Password-based
+--------------
+Type: "m.login.password"
+LoginSubmission:
+{
+  "type": "m.login.password",
+  "user": <user_id>,
+  "password": <password>
+}
+
+Example:
+Assume you are @bob:matrix.org and you wish to login on another mobile device.
+First, you GET /login which returns:
+{
+  "type": "m.login.password"
+}
+Your client knows how to handle this, so your client prompts the user to enter
+their username and password. This is then submitted:
+{
+  "type": "m.login.password",
+  "user": "@bob:matrix.org",
+  "password": "monkey"
+}
+The server checks this, finds it is valid, and returns:
+{
+  "access_token": "abcdef0123456789"
+}
+
+OAuth2-based
+------------
+Type: "m.login.oauth2"
+This is a multi-stage login.
+
+LoginSubmission:
+{
+  "type": "m.login.oauth2",
+  "user": <user_id>
+}
+Returns:
+{
+  "uri": <Authorization Request uri OR service selection uri>
+}
+
+The home server acts as a 'confidential' Client for the purposes of OAuth2.
+
+If the uri is a "sevice selection uri", it is a simple page which prompts the 
+user to choose which service to authorize with. On selection of a service, they
+link through to Authorization Request URIs. If there is only 1 service which the
+home server accepts when logging in, this indirection can be skipped and the
+"uri" key can be the Authorization Request URI. 
+
+The client visits the Authorization Request URI, which then shows the OAuth2 
+Allow/Deny prompt. Hitting 'Allow' returns the redirect URI with the auth code. 
+Home servers can choose any path for the redirect URI. The client should visit 
+the redirect URI, which will then finish the OAuth2 login process, granting the 
+home server an access token for the chosen service. When the home server gets 
+this access token, it knows that the cilent has authed with the 3rd party, and 
+so can return a LoginResult.
+
+The OAuth redirect URI (with auth code) MUST return a LoginResult.
+    
+Example:
+Assume you are @bob:matrix.org and you wish to login on another mobile device.
+First, you GET /login which returns:
+{
+  "type": "m.login.oauth2"
+}
+Your client knows how to handle this, so your client prompts the user to enter
+their username. This is then submitted:
+{
+  "type": "m.login.oauth2",
+  "user": "@bob:matrix.org"
+}
+The server only accepts auth from Google, so returns the Authorization Request
+URI for Google:
+{
+  "uri": "https://accounts.google.com/o/oauth2/auth?response_type=code&
+  client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos"
+}
+The client then visits this URI and authorizes the home server. The client then
+visits the REDIRECT_URI with the auth code= query parameter which returns:
+{
+  "access_token": "0123456789abcdef"
+}
+
+Email-based (code)
+------------------
+Type: "m.login.email.code"
+This is a multi-stage login.
+
+First LoginSubmission:
+{
+  "type": "m.login.email.code",
+  "user": <user_id>
+  "email": <email address>
+}
+Returns:
+{
+  "session": <session id>
+}
+
+The email contains a code which must be sent in the next LoginSubmission:
+{
+  "type": "m.login.email.code",
+  "session": <session id>,
+  "code": <code in email sent>
+}
+Returns:
+{
+  "access_token": <access token>
+}
+
+Example:
+Assume you are @bob:matrix.org and you wish to login on another mobile device.
+First, you GET /login which returns:
+{
+  "type": "m.login.email.code"
+}
+Your client knows how to handle this, so your client prompts the user to enter
+their email address. This is then submitted:
+{
+  "type": "m.login.email.code",
+  "user": "@bob:matrix.org",
+  "email": "bob@mydomain.com"
+}
+The server confirms that bob@mydomain.com is linked to @bob:matrix.org, then 
+sends an email to this address and returns:
+{
+  "session": "ewuigf7462"
+}
+The client's screen changes to a code submission page. The email arrives and it 
+says something to the effect of "please enter 2348623 into the app". This is
+the submitted along with the session:
+{
+  "type": "m.login.email.code",
+  "session": "ewuigf7462",
+  "code": "2348623"
+}
+The server accepts this and returns:
+{
+  "access_token": "abcdef0123456789"
+}
+
+Email-based (url)
+-----------------
+Type: "m.login.email.url"
+This is a multi-stage login.
+
+First LoginSubmission:
+{
+  "type": "m.login.email.url",
+  "user": <user_id>
+  "email": <email address>
+}
+Returns:
+{
+  "session": <session id>
+}
+
+The email contains a URL which must be clicked. After it has been clicked, the
+client should perform a request:
+{
+  "type": "m.login.email.code",
+  "session": <session id>
+}
+Returns:
+{
+  "access_token": <access token>
+}
+
+Example:
+Assume you are @bob:matrix.org and you wish to login on another mobile device.
+First, you GET /login which returns:
+{
+  "type": "m.login.email.url"
+}
+Your client knows how to handle this, so your client prompts the user to enter
+their email address. This is then submitted:
+{
+  "type": "m.login.email.url",
+  "user": "@bob:matrix.org",
+  "email": "bob@mydomain.com"
+}
+The server confirms that bob@mydomain.com is linked to @bob:matrix.org, then 
+sends an email to this address and returns:
+{
+  "session": "ewuigf7462"
+}
+The client then starts polling the server with the following:
+{
+  "type": "m.login.email.url",
+  "session": "ewuigf7462"
+}
+(Alternatively, the server could send the device a push notification when the
+email has been validated). The email arrives and it contains a URL to click on.
+The user clicks on the which completes the login process with the server. The
+next time the client polls, it returns:
+{
+  "access_token": "abcdef0123456789"
+}
+
+N-Factor auth
+-------------
+Multiple login stages can be combined with the "next" key in the LoginResult.
+
+Example:
+A server demands an email.code then password auth before logging in. First, the
+client performs a GET /login which returns:
+{
+  "type": "m.login.email.code",
+  "stages": ["m.login.email.code", "m.login.password"]
+}
+The client performs the email login (See "Email-based (code)"), but instead of
+returning an access_token, it returns:
+{
+  "next": "m.login.password"
+}
+The client then presents a user/password screen and the login continues until
+this is complete (See "Password-based"), which then returns the "access_token".
+
+Rooms
+=====
+A room is a conceptual place where users can send and receive messages. Rooms 
+can be created, joined and left. Messages are sent
+to a room, and all participants in that room will receive the message. Rooms are 
+uniquely identified via the room_id.
+
+Creating a room (with a room ID)
+--------------------------------
+  Event Type: m.room.create [TODO(kegan): Do we generate events for this?]
+  REST Path: /rooms/$room_id
+  Valid methods: PUT
+  Required keys: None.
+  Optional keys:
+    visibility : [public|private] - Set whether this room shows up in the public 
+    room list.
+  Returns:
+    On Failure: MAY return a suggested alternative room ID if this room ID is 
+    taken.
+    {
+      suggested_room_id : $new_room_id
+      error : "Room already in use."
+      errcode : "M_ROOM_IN_USE"
+    }
+    
+
+Creating a room (without a room ID)
+-----------------------------------
+  Event Type: m.room.create [TODO(kegan): Do we generate events for this?]
+  REST Path: /rooms
+  Valid methods: POST
+  Required keys: None.
+  Optional keys:
+    visibility : [public|private] - Set whether this room shows up in the public 
+    room list.
+  Returns:
+    On Success: The allocated room ID. Additional information about the room
+    such as the visibility MAY be included as extra keys in this response.
+    {
+      room_id : $room_id
+    }
+
+Setting the topic for a room
+----------------------------
+  Event Type: m.room.topic
+  REST Path: /rooms/$room_id/topic
+  Valid methods: GET/PUT
+  Required keys: 
+    topic : $topicname - Set the topic to $topicname in room $room_id.
+
+
+See a list of public rooms
+--------------------------
+  REST Path: /public/rooms?pagination_query_parameters
+  Valid methods: GET
+  This API can use pagination query parameters.
+  Returns:
+    {
+      "chunk" : JSON array of RoomInfo JSON objects - Required.
+      "start" : "string (start token)" - See Pagination Response.
+      "end" : "string (end token)" - See Pagination Response.
+      "total" : integer - Optional. The total number of rooms.
+    }
+
+RoomInfo: Information about a single room.
+  Servers MUST send the key: room_id
+  Servers MAY send the keys: topic, num_members
+  {
+    "room_id" : "string",
+    "topic" : "string",
+    "num_members" : integer
+  }
+
+Room Members
+============
+
+Invite/Joining/Leaving a room
+-----------------------------
+  Event Type: m.room.member
+  REST Path: /rooms/$room_id/members/$user_id/state
+  Valid methods: PUT/GET/DELETE
+  Required keys:
+    membership : [join|invite] - The membership state of $user_id in room 
+                                 $room_id.
+
+Where:
+  join - Indicate you ($user_id) are joining the room $room_id.
+  invite - Indicate that $user_id has been invited to room $room_id.
+
+User $user_id can leave room $room_id by DELETEing this path.
+
+Checking the user list of a room
+--------------------------------
+  REST Path: /rooms/$room_id/members/list
+  This API can use pagination query parameters.
+  Valid methods: GET
+  Returns:
+    A pagination response with chunk data as m.room.member events.
+
+Messages
+========
+Users send messages to other users in rooms. These messages may be text, images, 
+video, etc. Clients may also want to acknowledge messages by sending feedback, 
+in the form of delivery/read receipts.
+
+Server-attached keys
+--------------------
+The server MAY attach additional keys to messages and feedback. If a client 
+submits keys with the same name, they will be clobbered by
+the server.
+
+Required keys:
+from : "string [user_id]"
+  The user_id of the user who sent the message/feedback.
+
+Optional keys:
+hsob_ts : integer
+  A timestamp (ms resolution) representing when the message/feedback got to the 
+  sender's home server ("home server outbound timestamp").
+
+hsib_ts : integer
+  A timestamp (ms resolution) representing when the 
+  message/feedback got to the receiver's home server ("home server inbound 
+  timestamp"). This may be the same as hsob_ts if the sender/receiver are on the 
+  same home server.
+
+Sending messages
+----------------
+  Event Type: m.room.message
+  REST Path: /rooms/$room_id/messages/$from/$msg_id
+  Valid methods: GET/PUT
+  URL parameters:
+    $from : user_id - The sender's user_id. This value will be clobbered by the 
+    server before sending.
+  Required keys: 
+    msgtype: [m.text|m.emote|m.image|m.audio|m.video|m.location|m.file] - 
+             The type of message. Not to be confused with the Event 'type'.
+  Optional keys:
+    sender_ts : integer - A timestamp (ms resolution) representing the 
+                wall-clock time when the message was sent from the client.
+  Reserved keys:
+    body : "string" - The human readable string for compatibility with clients 
+           which cannot process a given msgtype. This key is optional, but
+           if it is included, it MUST be human readable text 
+           describing the message. See individual msgtypes for more 
+           info on what this means in practice.
+
+Each msgtype may have required fields of their own.
+
+msgtype: m.text
+----------------
+Required keys:
+  body : "string" - The body of the message.
+Optional keys:
+  None.
+
+msgtype: m.emote
+-----------------
+Required keys:
+  body : "string" - *tries to come up with a witty explanation*.
+Optional keys:
+  None.
+
+msgtype: m.image
+-----------------
+Required keys:
+  url : "string" - The URL to the image.
+Optional keys:
+  body : "string" - info : JSON object (ImageInfo) - The image info for image 
+         referred to in 'url'.
+  thumbnail_url : "string" - The URL to the thumbnail.
+  thumbnail_info : JSON object (ImageInfo) - The image info for the image 
+                   referred to in 'thumbnail_url'.
+
+ImageInfo: Information about an image.
+{
+  "size" : integer (size of image in bytes),
+  "w" : integer (width of image in pixels),
+  "h" : integer (height of image in pixels),
+  "mimetype" : "string (e.g. image/jpeg)"
+}
+
+Interpretation of 'body' key: The alt text of the image, or some kind of content 
+description for accessibility e.g. "image attachment".
+
+msgtype: m.audio
+-----------------
+Required keys:
+  url : "string" - The URL to the audio.
+Optional keys:
+  info : JSON object (AudioInfo) - The audio info for the audio referred to in 
+         'url'.
+
+AudioInfo: Information about a piece of audio. 
+{
+  "mimetype" : "string (e.g. audio/aac)",
+  "size" : integer (size of audio in bytes),
+  "duration" : integer (duration of audio in milliseconds)
+}
+
+Interpretation of 'body' key: A description of the audio e.g. "Bee Gees - 
+Stayin' Alive", or some kind of content description for accessibility e.g. 
+"audio attachment".
+
+msgtype: m.video
+-----------------
+Required keys:
+  url : "string" - The URL to the video.
+Optional keys:
+  info : JSON object (VideoInfo) - The video info for the video referred to in 
+         'url'.
+
+VideoInfo: Information about a video.
+{
+  "mimetype" : "string (e.g. video/mp4)",
+  "size" : integer (size of video in bytes),
+  "duration" : integer (duration of video in milliseconds),
+  "w" : integer (width of video in pixels),
+  "h" : integer (height of video in pixels),
+  "thumbnail_url" : "string (URL to image)",
+  "thumbanil_info" : JSON object (ImageInfo)
+}
+
+Interpretation of 'body' key: A description of the video e.g. "Gangnam style", 
+or some kind of content description for accessibility e.g. "video attachment".
+
+msgtype: m.location
+--------------------
+Required keys:
+  geo_uri : "string" - The geo URI representing the location.
+Optional keys:
+  thumbnail_url : "string" - The URL to a thumnail of the location being 
+                  represented.
+  thumbnail_info : JSON object (ImageInfo) - The image info for the image 
+                   referred to in 'thumbnail_url'.
+
+Interpretation of 'body' key: A description of the location e.g. "Big Ben, 
+London, UK", or some kind of content description for accessibility e.g. 
+"location attachment".
+
+
+Sending feedback
+----------------
+When you receive a message, you may want to send delivery receipt to let the 
+sender know that the message arrived. You may also want to send a read receipt 
+when the user has read the message. These receipts are collectively known as 
+'feedback'.
+
+  Event Type: m.room.message.feedback
+  REST Path: /rooms/$room_id/messages/$msgfrom/$msg_id/feedback/$from/$feedback
+  Valid methods: GET/PUT
+  URL parameters:
+    $msgfrom - The sender of the message's user_id.
+    $from : user_id - The sender of the feedback's user_id. This value will be 
+    clobbered by the server before sending.
+    $feedback : [d|r] - Specify if this is a [d]elivery or [r]ead receipt.
+  Required keys:
+    None.
+  Optional keys:
+    sender_ts : integer - A timestamp (ms resolution) representing the 
+    wall-clock time when the receipt was sent from the client.
+
+Receiving messages (bulk/pagination)
+------------------------------------
+  Event Type: m.room.message
+  REST Path: /rooms/$room_id/messages/list
+  Valid methods: GET
+  Query Parameters:
+    feedback : [true|false] - Specify if feedback should be bundled with each 
+    message.
+  This API can use pagination query parameters.
+  Returns:
+    A JSON array of Event Data in "chunk" (see Pagination Response). If the 
+    "feedback" parameter was set, the Event Data will also contain a "feedback" 
+    key which contains a JSON array of feedback, with each element as Event Data 
+    with compressed feedback for this message.
+
+Event Data with compressed feedback is a special type of feedback with 
+contextual keys removed. It is designed to limit the amount of redundant data 
+being sent for feedback. This removes the type, event_id, room ID, 
+message sender ID and message ID keys.
+
+     ORIGINAL (via event streaming)
+{
+  "event_id":"e1247632487",
+  "type":"m.room.message.feedback",
+  "from":"string [user_id]",
+  "feedback":"string [d|r]",
+  "room_id":"$room_id",
+  "msg_id":"$msg_id",
+  "msgfrom":"$msgfromid",
+  "content":{
+    "sender_ts":139880943
+  }
+}
+
+     COMPRESSED (via /messages/list)
+{
+  "from":"string [user_id]",
+  "feedback":"string [d|r]",
+  "content":{
+    "sender_ts":139880943
+  }
+}
+
+When you join a room $room_id, you may want the last 10 messages with feedback. 
+This is represented as:
+  GET 
+  /rooms/$room_id/messages/list?from=END&to=START&limit=10&feedback=true
+
+You may want to get 10 messages even earlier than that without feedback. If the 
+start stream token from the previous request was stok_019173, this request would 
+be:
+  GET 
+  /rooms/$room_id/messages/list?from=stok_019173&to=START&limit=10&
+                               feedback=false
+  
+NOTE: Care must be taken when using this API in conjunction with event 
+      streaming. It is possible that this will return a message which will
+      then come down the event stream, resulting in a duplicate message. Clients 
+      should clobber based on the global message ID, or event ID.
+
+
+Get current state for all rooms (aka IM Initial Sync API)
+-------------------------------
+  REST Path: /im/sync
+  Valid methods: GET
+  This API can use pagination query parameters. Pagination is applied on a per
+  *room* basis. E.g. limit=1 means "get 1 message for each room" and not "get 1
+  room's messages". If there is no limit, all messages for all rooms will be
+  returned.
+  If you want 1 room's messages, see "Receiving messages (bulk/pagination)".
+  Additional query parameters: 
+    feedback: [true] - Bundles feedback with messages.
+  Returns:
+    An array of RoomStateInfo.
+
+RoomStateInfo: A snapshot of information about a single room.
+  {
+    "room_id" : "string",
+    "membership" : "string [join|invite]",
+    "messages" : {
+      "start": "string",
+      "end": "string",
+      "chunk":
+      m.room.message pagination stream events (with feedback if specified),
+      this is the same as "Receiving messages (bulk/pagination)".
+    }
+  }
+The "membership" key is the calling user's membership state in the given 
+"room_id". The "messages" key may be omitted if the "membership" value is 
+"invite". Additional keys may be added to the top-level object, such as:
+  "topic" : "string" - The topic for the room in question.
+  "room_image_url" : "string" - The URL of the room image if specified.
+  "num_members" : integer - The number of members in the room.
+
+
+Profiles
+========
+
+Getting/Setting your own displayname
+------------------------------------
+  REST Path: /profile/$user_id/displayname
+  Valid methods: GET/PUT
+  Required keys:
+    displayname : The displayname text
+
+Getting/Setting your own avatar image URL
+-----------------------------------------
+The homeserver does not currently store the avatar image itself, but offers
+storage for the user to specify a web URL that points at the required image,
+leaving it up to clients to fetch it themselves.
+  REST Path: /profile/$user_id/avatar_url
+  Valid methods: GET/PUT
+  Required keys:
+    avatar_url : The URL path to the required image
+
+Getting other user's profile information
+----------------------------------------
+Either of the above REST methods may be used to fetch other user's profile
+information by the client, either on other local users on the same homeserver or
+for users from other servers entirely.
+
+
+Presence
+========
+
+In the following messages, the presence state is an integer enumeration of the
+following states:
+  0 : OFFLINE
+  1 : BUSY
+  2 : ONLINE
+  3 : FREE_TO_CHAT
+
+Aside from OFFLINE, the protocol doesn't assign any special meaning to these
+states; they are provided as an approximate signal for users to give to other
+users and for clients to present them in some way that may be useful. Clients
+could have different behaviours for different states of the user's presence, for
+example to decide how much prominence or sound to use for incoming event
+notifications.
+
+Getting/Setting your own presence state
+---------------------------------------
+  REST Path: /presence/$user_id/status
+  Valid methods: GET/PUT
+  Required keys:
+    state : [0|1|2|3] - The user's new presence state
+  Optional keys:
+    status_msg : text string provided by the user to explain their status
+
+Fetching your presence list
+---------------------------
+  REST Path: /presence_list/$user_id
+  Valid methods: GET/(post)
+  Returns:
+    An array of presence list entries. Each entry is an object with the
+    following keys:
+      {
+        "user_id" : string giving the observed user's ID
+        "state" : int giving their status
+        "status_msg" : optional text string
+        "displayname" : optional text string from the user's profile
+        "avatar_url" : optional text string from the user's profile
+      }
+
+Maintaining your presence list
+------------------------------
+  REST Path: /presence_list/$user_id
+  Valid methods: POST/(get)
+  With: A JSON object optionally containing either of the following keys:
+    "invite" : a list of strings giving user IDs to invite for presence
+      subscription
+    "drop" : a list of strings giving user IDs to remove from your presence
+      list
+
+Receiving presence update events
+--------------------------------
+  Event Type: m.presence
+  Keys of the event's content are the same as those returned by the presence
+    list.
+
+Examples
+========
+
+The following example is the story of "bob", who signs up at "sy.org" and joins 
+the public room "room_beta@sy.org". They get the 2 most recent
+messages (with feedback) in that room and then send a message in that room. 
+
+For context, here is the complete chat log for room_beta@sy.org:
+
+Room: "Hello world" (room_beta@sy.org)
+Members: (2) alice@randomhost.org, friend_of_alice@randomhost.org
+Messages:
+  alice@randomhost.org : hi friend!                     
+  [friend_of_alice@randomhost.org DELIVERED]
+  alice@randomhost.org : you're my only friend          
+  [friend_of_alice@randomhost.org DELIVERED]
+  alice@randomhost.org : afk                            
+  [friend_of_alice@randomhost.org DELIVERED]
+  [ bob@sy.org joins ]
+  bob@sy.org : Hi everyone
+  [ alice@randomhost.org changes the topic to "FRIENDS ONLY" ]
+  alice@randomhost.org : Hello!!!!
+  alice@randomhost.org : Let's go to another room
+  alice@randomhost.org : You're not my friend
+  [ alice@randomhost.org invites bob@sy.org to the room 
+  commoners@randomhost.org]
+
+
+REGISTER FOR AN ACCOUNT
+POST: /register
+Content: {}
+Returns: { "user_id" : "bob@sy.org" , "access_token" : "abcdef0123456789" }
+
+GET PUBLIC ROOM LIST
+GET: /rooms/list?access_token=abcdef0123456789
+Returns: 
+{ 
+  "total":3,
+  "chunk":
+  [
+    { "room_id":"room_alpha@sy.org", "topic":"I am a fish" },
+    { "room_id":"room_beta@sy.org", "topic":"Hello world" },
+    { "room_id":"room_xyz@sy.org", "topic":"Goodbye cruel world" }
+  ]
+}
+
+JOIN ROOM room_beta@sy.org
+PUT 
+/rooms/room_beta%40sy.org/members/bob%40sy.org/state?
+                                    access_token=abcdef0123456789
+Content: { "membership" : "join" }
+Returns: 200 OK
+
+GET LATEST 2 MESSAGES WITH FEEDBACK
+GET 
+/rooms/room_beta%40sy.org/messages/list?from=END&to=START&limit=2&
+                                    feedback=true&access_token=abcdef0123456789
+Returns:
+{
+  "chunk":
+    [
+      { 
+        "event_id":"01948374", 
+        "type":"m.room.message",
+        "room_id":"room_beta@sy.org",
+        "msg_id":"avefifu",
+        "from":"alice@randomhost.org",
+        "hs_ts":139985736,
+        "content":{
+          "msgtype":"m.text",
+          "body":"afk"
+        }
+        "feedback": [
+          {
+            "from":"friend_of_alice@randomhost.org",
+            "feedback":"d",
+            "hs_ts":139985850,
+            "content":{
+              "sender_ts":139985843
+            }
+          }
+        ]
+      },
+      { 
+        "event_id":"028dfe8373", 
+        "type":"m.room.message",
+        "room_id":"room_beta@sy.org",
+        "msg_id":"afhgfff",
+        "from":"alice@randomhost.org",
+        "hs_ts":139970006,
+        "content":{
+          "msgtype":"m.text",
+          "body":"you're my only friend"
+        }
+        "feedback": [
+          {
+            "from":"friend_of_alice@randomhost.org",
+            "feedback":"d",
+            "hs_ts":139970144,
+            "content":{
+              "sender_ts":139970122
+            }
+          }
+        ]
+      },
+    ],
+  "start": "stok_04823947",
+  "end": "etok_1426425"
+}
+
+SEND MESSAGE IN ROOM
+PUT 
+/rooms/room_beta%40sy.org/messages/bob%40sy.org/m0001?
+                            access_token=abcdef0123456789
+Content: { "msgtype" : "text" , "body" : "Hi everyone" }
+Returns: 200 OK
+
+
+Checking the event stream for this user:
+GET: /events?from=START&access_token=abcdef0123456789
+Returns:
+{
+  "chunk": 
+    [
+      { 
+        "event_id":"e10f3d2b", 
+        "type":"m.room.member",
+        "room_id":"room_beta@sy.org",
+        "user_id":"bob@sy.org",
+        "content":{
+          "membership":"join"
+        }
+      },
+      { 
+        "event_id":"1b352d32", 
+        "type":"m.room.message",
+        "room_id":"room_beta@sy.org",
+        "msg_id":"m0001",
+        "from":"bob@sy.org",
+        "hs_ts":140193857,
+        "content":{
+          "msgtype":"m.text",
+          "body":"Hi everyone"
+        }
+      }
+    ],
+  "start": "stok_9348635",
+  "end": "etok_1984723"
+}
+
+Client disconnects for a while and the topic is updated in this room, 3 new 
+messages arrive whilst offline, and bob is invited to another room.
+
+GET /events?from=etok_1984723&access_token=abcdef0123456789
+Returns:
+{
+  "chunk": 
+    [
+      { 
+        "event_id":"feee0294", 
+        "type":"m.room.topic",
+        "room_id":"room_beta@sy.org",
+        "from":"alice@randomhost.org",
+        "content":{
+          "topic":"FRIENDS ONLY",
+        }
+      },
+      { 
+        "event_id":"a028bd9e", 
+        "type":"m.room.message",
+        "room_id":"room_beta@sy.org",
+        "msg_id":"z839409",
+        "from":"alice@randomhost.org",
+        "hs_ts":140195000,
+        "content":{
+          "msgtype":"m.text",
+          "body":"Hello!!!"
+        }
+      },
+      { 
+        "event_id":"49372d9e", 
+        "type":"m.room.message",
+        "room_id":"room_beta@sy.org",
+        "msg_id":"z839410",
+        "from":"alice@randomhost.org",
+        "hs_ts":140196000,
+        "content":{
+          "msgtype":"m.text",
+          "body":"Let's go to another room"
+        }
+      },
+      { 
+        "event_id":"10abdd01", 
+        "type":"m.room.message",
+        "room_id":"room_beta@sy.org",
+        "msg_id":"z839411",
+        "from":"alice@randomhost.org",
+        "hs_ts":140197000,
+        "content":{
+          "msgtype":"m.text",
+          "body":"You're not my friend"
+        }
+      },
+      { 
+        "event_id":"0018453d", 
+        "type":"m.room.member",
+        "room_id":"commoners@randomhost.org",
+        "from":"alice@randomhost.org",
+        "user_id":"bob@sy.org",
+        "content":{
+          "membership":"invite"
+        }
+      },
+    ],
+  "start": "stok_0184288",
+  "end": "etok_1348723"
+}