diff --git a/docs/specification.rst b/docs/specification.rst
index c1559c886c..2b47009187 100644
--- a/docs/specification.rst
+++ b/docs/specification.rst
@@ -35,8 +35,8 @@ 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.
-
+The ``localpart`` of a user ID may be a user name, or an opaque ID identifying this user. They are
+case-insensitive.
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
@@ -60,7 +60,8 @@ identified via a "Room ID", which look like::
There is exactly one room ID for each room. Whilst the room ID does contain a
domain, it is simply for namespacing room IDs. The room does NOT reside on the
-domain specified. Room IDs are not meant to be human readable.
+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``::
@@ -102,10 +103,10 @@ Each room can also have multiple "Room Aliases", which looks like::
A room alias "points" to a room ID. The room ID the alias is pointing to can be obtained
by visiting the domain specified. Room aliases are designed to be human readable strings
-which can be used to publicise rooms. 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.
+which can be used to publicise rooms. 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.
::
@@ -132,49 +133,567 @@ Identity
API Standards
-------------
-- All HTTP[S]
-- Uses JSON as HTTP bodies
-- Standard error response format { errcode: M_WHATEVER, error: "some message" }
-- C-S API provides POST for operations, or PUT with txn IDs. Explain txn IDs.
+All communication in Matrix is performed over HTTP[S] using a Content-Type of ``application/json``.
+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
+client-generated transaction ID which identifies the request. 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, it could be a monotonically
+increasing integer, etc). 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
+ {
+ "key": "This is a post."
+ }
+
+ PUT /some/path/here/11
+ {
+ "key": "This is a put with a txnId of 11."
+ }
+
+In contrast, these are invalid requests::
+
+ POST /some/path/here/11
+ {
+ "key": "This is a post, but it has a txnId."
+ }
+
+ PUT /some/path/here
+ {
+ "key": "This is a put but it is missing a txnId."
+ }
+
+
+
+- TODO: All strings everywhere are UTF-8
+
+
Receiving live updates on a client
----------------------------------
-- C-S longpoll event stream
-- Concept of start/end tokens.
-- Mention /initialSync to get token.
+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 the client is authorised
+to view will appear in the event stream. When the stream is closed, an ``end`` token is
+returned. This token can be used in the next request to continue where the client left off.
+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.
Rooms
=====
-- How are they created? PDU anchor point: "root of the tree".
-- Adding / removing aliases.
-- Invite/join dance
-- State and non-state data (+extensibility)
-TODO : Room permissions / config / power levels.
+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
+ ``public`` 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``.
+
+Example::
-Messages
-========
+ {
+ "visibility": "public",
+ "room_alias_name": "the pub",
+ "name": "The Grand Duke Pub",
+ "topic": "All about happy hour"
+ }
-This specification outlines several standard event types, all of which are
-prefixed with ``m.``
+- TODO: This creates a room creation event which serves as the root of the PDU graph for this room.
+- TODO: Keys for speccing a room name / room topic / invite these users?
+
+Modifying aliases
+-----------------
+- path to edit aliases
+- format when retrieving list of aliases. NOT complete list.
+- format for adding aliases.
+
+Permissions
+-----------
+- TODO: What is a power level? How do they work? Defaults / required levels for X. How do they change
+ as people join and leave rooms? What do you do if you get a clash? Examples.
+- TODO: List all actions which use power levels (sending msgs, inviting users, banning people, etc...)
+- TODO: Room config - what is the event and what are the keys/values and explanations for them.
+ Link through to respective sections where necessary. How does this tie in with permissions, e.g.
+ give example of creating a read-only room.
+
+
+Joining rooms
+-------------
+- TODO: What does the home server have to do to join a user to a room?
-State messages
+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
--------------
-- m.room.name
-- m.room.topic
-- m.room.member
-- m.room.config
-- m.room.invite_join
+- Can invite users to a room if the room config key TODO is set to TODO. Must have required power level.
+- 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 ``TODO``. It can have one of the following values:
+
+ - TODO Room config invite only value explanation
+ - TODO Room config free-to-join value explanation
+
+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::
-What are they, when are they used, what do they contain, how should they be used
+ {
+ "user_id": "<user id to invite>"
+ }
-Non-state messages
-------------------
-- m.room.message
-- m.room.message.feedback (and compressed format)
+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``.
+
+- TODO: In what circumstances will this NOT be equivalent to ``/invite``?
+
+Leaving rooms
+-------------
+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::
+
+ {}
-What are they, when are they used, what do they contain, how should they be used
+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. Be aware that leaving a room is not equivalent to have never been
+in that room. A user who has previously left a room still maintains some residual state in
+that room. Their membership state will be marked as ``leave``. This contrasts with
+a user who has *never been invited or joined to that room* who will not have any
+membership state for that room.
+
+If all members in a room leave, that room becomes eligible for deletion.
+ - TODO: Grace period before deletion?
+ - TODO: Under what conditions should a room NOT be purged?
+
+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
+-------------
+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. There are two APIs provided:
+
+ - ``/initialSync`` : A global sync which will present room and event information for all rooms
+ the user has joined.
+
+ - ``/rooms/<room id>/initialSync`` : A sync scoped to a single room. Presents room and event
+ information for this room only.
+
+- TODO: JSON response format for both types
+- TODO: when would you use global? when would you use scoped?
+
+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
+
+
+``/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
+
+``/rooms/<room id>/messages``
+ Description:
+ Get all ``m.room.message`` events.
+ Response format:
+ ``{ TODO }``
+ Example:
+ TODO
+
+``/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 } ``
+ Example:
+ TODO
+
+
+Room Events
+===========
+- voip events?
+
+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.
+
+``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.config``
+ Summary:
+ The room config.
+ Type:
+ State event
+ JSON format:
+ TODO
+ Example:
+ TODO
+ Description:
+ TODO
+
+``m.room.invite_join``
+ Summary:
+ TODO.
+ Type:
+ State event
+ JSON format:
+ TODO
+ Example:
+ TODO
+ Description:
+ TODO
+
+``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.message msgtypes
-----------------------
@@ -285,7 +804,7 @@ 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 ``state`` key, which is an enum of one of the following:
+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.
@@ -295,18 +814,26 @@ is represented by the ``state`` key, which is an enum of one of the following:
- ``hidden`` : TODO. Behaves as offline, but allows the user to see the client
state anyway and generally interact with client features.
-This basic ``state`` field applies to the user as a whole, regardless of how many
+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.
+
Idle Time
---------
-As well as the basic ``state`` 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.
+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.
Transmission
------------
@@ -336,11 +863,23 @@ presence information in a user list for a room.
Typing notifications
====================
+- what is the event type. Are they bundled with other event types? If so, which.
+- what are the valid keys / values. What do they represent. Any gotchas?
+- Timeouts. How do they work, who sets them and how do they expire. Does one
+ have priority over another? Give examples.
TODO : Leo
Voice over IP
=============
+- what are the event types.
+- what are the valid keys/values. What do they represent. Any gotchas?
+- In what sequence should the events be sent?
+- How do you accept / decline inbound calls? How do you make outbound calls?
+ Give examples.
+- How does negotiation work? Give examples.
+- How do you hang up?
+- What does call log information look like e.g. duration of call?
TODO : Dave
@@ -358,8 +897,12 @@ numbers, website URLs, etc...). This specification puts no requirements on the
display name other than it being a valid unicode string.
- Metadata extensibility
-- Bundled with which events? e.g. m.room.member
-- Generate own events? What type?
+- Changing profile info generates m.presence events ("presencelike")
+- keys on m.presence are optional, except presence which is required
+- m.room.member is populated with the current displayname at that point in time.
+- That is added by the HS, not you.
+- Display name changes also generates m.room.member with displayname key f.e. room
+ the user is in.
Registration and login
======================
@@ -823,11 +1366,16 @@ TODO
Content repository
==================
-- thumbnail paths
+- path to upload
+- format for thumbnail paths, mention what it is protecting against.
+- content size limit and associated M_ERROR.
Address book repository
=======================
-- format
+- 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
|